mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-28 23:24:05 +00:00
feat: add functionality to create new HTTP requests from the welcome modal and collections section (#7350)
This commit is contained in:
@@ -16,11 +16,13 @@ import {
|
||||
IconTerminal2
|
||||
} from '@tabler/icons';
|
||||
|
||||
import { importCollection, openCollection, importCollectionFromZip } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { importCollection, openCollection, importCollectionFromZip, newHttpRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { sortCollections } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { savePreferences } from 'providers/ReduxStore/slices/app';
|
||||
import { normalizePath } from 'utils/common/path';
|
||||
import { isScratchCollection } from 'utils/collections';
|
||||
import { isScratchCollection, flattenItems, isItemTransientRequest } from 'utils/collections';
|
||||
import { sanitizeName } from 'utils/common/regex';
|
||||
import filter from 'lodash/filter';
|
||||
|
||||
import MenuDropdown from 'ui/MenuDropdown';
|
||||
import ActionIcon from 'ui/ActionIcon';
|
||||
@@ -179,6 +181,50 @@ const CollectionsSection = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleStartRequest = () => {
|
||||
const scratchCollectionUid = activeWorkspace?.scratchCollectionUid;
|
||||
if (!scratchCollectionUid) {
|
||||
toast.error('Unable to create request');
|
||||
return;
|
||||
}
|
||||
|
||||
const scratchCollection = collections.find((c) => c.uid === scratchCollectionUid);
|
||||
if (!scratchCollection) {
|
||||
toast.error('Unable to create request');
|
||||
return;
|
||||
}
|
||||
|
||||
const allItems = flattenItems(scratchCollection.items || []);
|
||||
const transientRequests = filter(allItems, (item) => isItemTransientRequest(item));
|
||||
let maxNumber = 0;
|
||||
transientRequests.forEach((item) => {
|
||||
const match = item.name?.match(/^Untitled (\d+)$/);
|
||||
if (match) {
|
||||
const number = parseInt(match[1], 10);
|
||||
if (number > maxNumber) {
|
||||
maxNumber = number;
|
||||
}
|
||||
}
|
||||
});
|
||||
const requestName = `Untitled ${maxNumber + 1}`;
|
||||
const filename = sanitizeName(requestName);
|
||||
|
||||
dispatch(
|
||||
newHttpRequest({
|
||||
requestName,
|
||||
filename,
|
||||
requestType: 'http-request',
|
||||
requestUrl: '',
|
||||
requestMethod: 'GET',
|
||||
collectionUid: scratchCollectionUid,
|
||||
itemUid: null,
|
||||
isTransient: true
|
||||
})
|
||||
).catch((err) => {
|
||||
toast.error('An error occurred while creating the request');
|
||||
});
|
||||
};
|
||||
|
||||
const addDropdownItems = [
|
||||
{
|
||||
id: 'create',
|
||||
@@ -289,6 +335,10 @@ const CollectionsSection = () => {
|
||||
handleDismissWelcomeModal();
|
||||
handleOpenCollection();
|
||||
}}
|
||||
onStartRequest={() => {
|
||||
handleDismissWelcomeModal();
|
||||
handleStartRequest();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{createCollectionModalOpen && (
|
||||
|
||||
@@ -76,8 +76,12 @@ const StyledWrapper = styled.div`
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
|
||||
border-color: ${(props) => props.theme.border.border1};
|
||||
border-color: ${(props) => props.theme.primary.subtle};
|
||||
background: ${(props) => rgba(props.theme.primary.solid, 0.06)};
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.secondary-icon {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { IconPlus, IconDownload, IconFileImport } from '@tabler/icons';
|
||||
import { IconPlus, IconDownload, IconFileImport, IconSend } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const GetStartedStep = ({ onCreateCollection, onImportCollection, onOpenCollection }) => (
|
||||
const GetStartedStep = ({ onCreateCollection, onImportCollection, onOpenCollection, onStartRequest }) => (
|
||||
<StyledWrapper className="step-body">
|
||||
<div className="step-label">Your first collection</div>
|
||||
<div className="step-title">You're all set! What's next?</div>
|
||||
@@ -38,6 +38,15 @@ const GetStartedStep = ({ onCreateCollection, onImportCollection, onOpenCollecti
|
||||
<div className="secondary-desc">Open a Bruno collection from your filesystem</div>
|
||||
</div>
|
||||
</button>
|
||||
<button className="secondary-action" onClick={onStartRequest}>
|
||||
<span className="secondary-icon">
|
||||
<IconSend size={16} stroke={1.5} />
|
||||
</span>
|
||||
<div>
|
||||
<div className="secondary-label">Get started with a request</div>
|
||||
<div className="secondary-desc">Jump right in with a new HTTP request</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ const StorageStep = ({ collectionLocation, onBrowse }) => (
|
||||
<div className="step-label">Storage</div>
|
||||
<div className="step-title">Where should we store your collections?</div>
|
||||
<div className="step-description">
|
||||
Bruno saves collections as plain files on your filesystem — perfect for version control with Git.
|
||||
Bruno saves collections as plain files on your filesystem, perfect for version control with Git.
|
||||
</div>
|
||||
|
||||
<div className="location-input-group">
|
||||
@@ -31,7 +31,7 @@ const StorageStep = ({ collectionLocation, onBrowse }) => (
|
||||
</div>
|
||||
</div>
|
||||
<div className="location-hint">
|
||||
Each collection gets its own folder inside this directory. You can change this per-collection later.
|
||||
Each collection and workspace gets its own folder inside this directory. You can change this later.
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -10,8 +10,8 @@ import StyledWrapper from './StyledWrapper';
|
||||
const highlights = [
|
||||
{
|
||||
icon: IconFolderTabler,
|
||||
title: 'Filesystem-first',
|
||||
desc: 'Collections are plain files on your disk. No cloud sync, no proprietary lock-in. Your data stays yours.'
|
||||
title: 'Filesystem only',
|
||||
desc: 'Collections are plain files on your disk. No cloud sync, no proprietary lock-in.'
|
||||
},
|
||||
{
|
||||
icon: IconGitFork,
|
||||
@@ -21,12 +21,12 @@ const highlights = [
|
||||
{
|
||||
icon: IconLock,
|
||||
title: 'Privacy-focused',
|
||||
desc: 'No accounts required. No telemetry. Bruno works entirely offline — your API keys never leave your machine.'
|
||||
desc: 'No account, no login. Bruno works entirely offline, your API keys never leave your machine.'
|
||||
},
|
||||
{
|
||||
icon: IconRocket,
|
||||
title: 'Fast and lightweight',
|
||||
desc: 'Built to be snappy. No bloated runtimes — just a fast, focused tool for exploring and testing APIs.'
|
||||
desc: 'Built to be snappy. No bloated runtimes, just a fast, focused tool for exploring and testing APIs.'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const TOTAL_STEPS = 4;
|
||||
|
||||
const WelcomeModal = ({ onDismiss, onImportCollection, onCreateCollection, onOpenCollection }) => {
|
||||
const WelcomeModal = ({ onDismiss, onImportCollection, onCreateCollection, onOpenCollection, onStartRequest }) => {
|
||||
const dispatch = useDispatch();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const defaultLocation = get(preferences, 'general.defaultLocation', '');
|
||||
@@ -93,6 +93,7 @@ const WelcomeModal = ({ onDismiss, onImportCollection, onCreateCollection, onOpe
|
||||
onCreateCollection={handleActionAndDismiss(onCreateCollection)}
|
||||
onImportCollection={handleActionAndDismiss(onImportCollection)}
|
||||
onOpenCollection={handleActionAndDismiss(onOpenCollection)}
|
||||
onStartRequest={handleActionAndDismiss(onStartRequest)}
|
||||
/>
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user