Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multiple tabs - [INS-4225] #8151

Open
wants to merge 46 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2af849d
feat: multiple tabs
CurryYangxx Nov 6, 2024
0d456cd
fix: lint error
CurryYangxx Nov 6, 2024
d540af4
fix: type error
CurryYangxx Nov 6, 2024
3aa8229
fix: ui border align
CurryYangxx Nov 7, 2024
9ec6c88
feat: sync with db
CurryYangxx Nov 7, 2024
1b93389
feat: sync with database
CurryYangxx Nov 7, 2024
ba74e6a
ui improvement
CurryYangxx Nov 11, 2024
136ecb1
fix: mock route display
CurryYangxx Nov 11, 2024
7533f5e
add dropdown
CurryYangxx Nov 12, 2024
ba6ebf7
+ button
CurryYangxx Nov 12, 2024
8b72a17
move search box to center
CurryYangxx Nov 14, 2024
631dba1
fix: cannot del request tab
CurryYangxx Nov 14, 2024
ac6990c
fix ui
CurryYangxx Nov 15, 2024
b45dd27
tab background improvement
CurryYangxx Nov 15, 2024
1b746e5
change tab background
CurryYangxx Nov 18, 2024
384e034
feat: add list scroll
CurryYangxx Nov 18, 2024
8d2d984
ui improvement
CurryYangxx Nov 18, 2024
4b15b1f
feat: add tab contextmenu
CurryYangxx Nov 20, 2024
a446a4d
modify menu text
CurryYangxx Nov 20, 2024
c8c6965
fix(ui): tab disappear in panelgroup
CurryYangxx Nov 21, 2024
8025145
feat: optimize tablist scroll button
CurryYangxx Nov 21, 2024
4b127f5
add context menu enum
CurryYangxx Nov 21, 2024
fa8770d
del log
CurryYangxx Nov 25, 2024
02e5bf2
fix: rename workspace
CurryYangxx Nov 26, 2024
0f3d158
feat: scroll into view if needed
CurryYangxx Nov 27, 2024
aa6f02a
fix: runner request list not update after switch tab
CurryYangxx Nov 27, 2024
23c1098
fix: runner not update
CurryYangxx Nov 28, 2024
24824f0
feat: use different tab if for collection runner and folder runner
CurryYangxx Nov 28, 2024
b133698
fix: update tab data after move request or folder
CurryYangxx Dec 2, 2024
6ec2cec
fix: database test
CurryYangxx Dec 2, 2024
cdb2f59
feat: support drag and drop
CurryYangxx Dec 3, 2024
1381956
fix: ui
CurryYangxx Dec 3, 2024
ef84c32
cr feedback
CurryYangxx Dec 5, 2024
50b6e82
fix tab update
CurryYangxx Dec 9, 2024
90a7631
runner improvements (multiple tabs)-[INS-4779] (#8244)
CurryYangxx Dec 27, 2024
89f0712
remove debugger
CurryYangxx Jan 7, 2025
8169045
use own debounce instead lodash
CurryYangxx Jan 7, 2025
d8fe001
feat: move runner keep log to context
CurryYangxx Jan 7, 2025
7dceca0
convert eventbus type enum to union
CurryYangxx Jan 9, 2025
4cadc7b
reduce duplicate useInsomniaTabContext
CurryYangxx Jan 9, 2025
bab624c
fix search bar style
CurryYangxx Jan 9, 2025
57cc613
covert enum to union
CurryYangxx Jan 13, 2025
1e731ca
del log
CurryYangxx Jan 13, 2025
2fc3f88
feat(tabs): keep connection for tabs-[INS-4778] (#8266)
CurryYangxx Jan 13, 2025
cec5994
fix: tab icon
CurryYangxx Jan 14, 2025
4ab08e7
uppercase
CurryYangxx Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/insomnia-inso/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"sourceMap": true,
/* Runs in the DOM NOTE: this is inconsistent with reality */
"lib": [
"ES2020",
"ES2023",
"DOM",
"DOM.Iterable"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/insomnia-sdk/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"jsx": "react",
/* If your code runs in the DOM: */
"lib": [
"es2022",
"es2023",
"dom",
"dom.iterable"
],
Expand Down
24 changes: 12 additions & 12 deletions packages/insomnia/src/common/__tests__/database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ describe('onChange()', () => {
name: 'bar',
});
expect(changesSeen).toEqual([
[['insert', newDoc, false]],
[['update', updatedDoc, false]],
[['insert', newDoc, false, []]],
[['update', updatedDoc, false, [{ name: 'bar' }]]],
]);
db.offChange(callback);
await models.request.create(doc);
Expand Down Expand Up @@ -71,16 +71,16 @@ describe('bufferChanges()', () => {
await db.flushChanges();
expect(changesSeen).toEqual([
[
['insert', newDoc, false],
['update', updatedDoc, false],
['insert', newDoc, false, []],
['update', updatedDoc, false, [true]],
],
]);
// Assert no more changes seen after flush again
await db.flushChanges();
expect(changesSeen).toEqual([
[
['insert', newDoc, false],
['update', updatedDoc, false],
['insert', newDoc, false, []],
['update', updatedDoc, false, [true]],
],
]);
});
Expand All @@ -106,8 +106,8 @@ describe('bufferChanges()', () => {
await new Promise(resolve => setTimeout(resolve, 1500));
expect(changesSeen).toEqual([
[
['insert', newDoc, false],
['update', updatedDoc, false],
['insert', newDoc, false, []],
['update', updatedDoc, false, [true]],
],
]);
});
Expand All @@ -132,8 +132,8 @@ describe('bufferChanges()', () => {
await new Promise(resolve => setTimeout(resolve, 1000));
expect(changesSeen).toEqual([
[
['insert', newDoc, false],
['update', updatedDoc, false],
['insert', newDoc, false, []],
['update', updatedDoc, false, [true]],
],
]);
});
Expand Down Expand Up @@ -166,8 +166,8 @@ describe('bufferChangesIndefinitely()', () => {
await db.flushChanges();
expect(changesSeen).toEqual([
[
['insert', newDoc, false],
['update', updatedDoc, false],
['insert', newDoc, false, []],
['update', updatedDoc, false, [true]],
],
]);
});
Expand Down
14 changes: 8 additions & 6 deletions packages/insomnia/src/common/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const database = {
},
...patches,
);
return database.update<T>(doc);
return database.update<T>(doc, false, patches);
},

/** duplicate doc and its decendents recursively */
Expand Down Expand Up @@ -524,7 +524,7 @@ export const database = {
notifyOfChange('remove', doc, fromSync);
},

update: async function<T extends BaseModel>(doc: T, fromSync = false) {
update: async function <T extends BaseModel>(doc: T, fromSync = false, patches: Patch<T>[] = []) {
if (db._empty) {
return _send<T>('update', ...arguments);
}
Expand All @@ -550,7 +550,7 @@ export const database = {

resolve(docWithDefaults);
// NOTE: This needs to be after we resolve
notifyOfChange('update', docWithDefaults, fromSync);
notifyOfChange('update', docWithDefaults, fromSync, patches);
},
);
});
Expand Down Expand Up @@ -698,7 +698,8 @@ let bufferChangesId = 1;
export type ChangeBufferEvent<T extends BaseModel = BaseModel> = [
event: ChangeType,
doc: T,
fromSync: boolean
fromSync: boolean,
patches: Patch<T>[],
];

let changeBuffer: ChangeBufferEvent[] = [];
Expand All @@ -709,10 +710,11 @@ let changeListeners: ChangeListener[] = [];

/** push changes into the buffer, so that changeListeners can get change contents when database.flushChanges is called,
* this method should be called whenever a document change happens */
async function notifyOfChange<T extends BaseModel>(event: ChangeType, doc: T, fromSync: boolean) {
async function notifyOfChange<T extends BaseModel>(event: ChangeType, doc: T, fromSync: boolean, patches: Patch<T>[] = []) {
const updatedDoc = doc;

changeBuffer.push([event, updatedDoc, fromSync]);
// TODO: Use object is better than array
changeBuffer.push([event, updatedDoc, fromSync, patches]);
Comment on lines +716 to +717
Copy link
Contributor

@jackkav jackkav Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain what behavior a ui tabs feature has which needs a new persistence layer feature. Is there an alternative way to achieve this behavior?
The reason I ask is this code is already hacked to achieve a sync side-effect so this smell like a hack too.

Copy link
Member Author

@CurryYangxx CurryYangxx Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We sync the tab states with the database by listening to the database's onChange event, such as changing a request's name or request method. The onchange event is triggered frequently, we only want to perform the corresponding update operation when certain fields are changed, because some of the update have heavy logic, such as move request or folder to another workspace. So, I add patches to the buffer to help us avoid some unnecessary operations.

if (patchObj.parentId && !patchObj.metaSortKey && (patchObj.parentId as string).startsWith('wrk_')) {


// Flush right away if we're not buffering
if (!bufferingChanges) {
Expand Down
27 changes: 21 additions & 6 deletions packages/insomnia/src/main/ipc/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export type MainOnChannels =
| 'restart'
| 'set-hidden-window-busy-status'
| 'setMenuBarVisibility'
| 'show-context-menu'
| 'show-nunjucks-context-menu'
| 'showContextMenu'
| 'showItemInFolder'
| 'showOpenDialog'
| 'showSaveDialog'
Expand All @@ -73,7 +74,8 @@ export type MainOnChannels =
export type RendererOnChannels =
'clear-all-models'
| 'clear-model'
| 'context-menu-command'
| 'nunjucks-context-menu-command'
| 'contextMenuCommand'
| 'grpc.data'
| 'grpc.end'
| 'grpc.error'
Expand Down Expand Up @@ -114,10 +116,10 @@ const getTemplateValue = (arg: NunjucksParsedTagArg) => {
return arg.defaultValue;
};
export function registerElectronHandlers() {
ipcMainOn('show-context-menu', (event, options: { key: string; nunjucksTag: ReturnType<typeof extractNunjucksTagFromCoords> }) => {
ipcMainOn('show-nunjucks-context-menu', (event, options: { key: string; nunjucksTag: ReturnType<typeof extractNunjucksTagFromCoords> }) => {
const { key, nunjucksTag } = options;
const sendNunjuckTagContextMsg = (type: NunjucksTagContextMenuAction) => {
event.sender.send('context-menu-command', { key, nunjucksTag: { ...nunjucksTag, type } });
event.sender.send('nunjucks-context-menu-command', { key, nunjucksTag: { ...nunjucksTag, type } });
};
try {
const baseTemplate: MenuItemConstructorOptions[] = nunjucksTag ?
Expand Down Expand Up @@ -170,7 +172,7 @@ export function registerElectronHandlers() {
{
click: () => {
const tag = `{% ${l.templateTag.name} ${l.templateTag.args?.map(getTemplateValue).join(', ')} %}`;
event.sender.send('context-menu-command', { key, tag });
event.sender.send('nunjucks-context-menu-command', { key, tag });
},
} :
{
Expand All @@ -179,7 +181,7 @@ export function registerElectronHandlers() {
click: () => {
const additionalTagFields = additionalArgs.length ? ', ' + additionalArgs.map(getTemplateValue).join(', ') : '';
const tag = `{% ${l.templateTag.name} '${action.value}'${additionalTagFields} %}`;
event.sender.send('context-menu-command', { key, tag });
event.sender.send('nunjucks-context-menu-command', { key, tag });
},
})),
}),
Expand Down Expand Up @@ -236,4 +238,17 @@ export function registerElectronHandlers() {
ipcMainOn('getAppPath', event => {
event.returnValue = app.getAppPath();
});

ipcMainOn('showContextMenu', (event, options: { key: string; menuItems: MenuItemConstructorOptions[]; extra?: Record<string, any> }) => {
const menuItems = options.menuItems.map(item => {
return {
...item,
click: () => {
event.sender.send('contextMenuCommand', { key: options.key, label: item.label, extra: options.extra });
},
};
});
const menu = Menu.buildFromTemplate(menuItems);
menu.popup();
});
}
6 changes: 4 additions & 2 deletions packages/insomnia/src/main/ipc/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/electron/main';
import type { MarkerRange } from 'codemirror';
import { app, BrowserWindow, type IpcRendererEvent, shell } from 'electron';
import { app, BrowserWindow, type IpcRendererEvent, type MenuItemConstructorOptions, shell } from 'electron';
import fs from 'fs';

import { APP_START_TIME, LandingPage, SentryMetrics } from '../../common/sentry';
Expand Down Expand Up @@ -39,7 +39,9 @@ export interface RendererToMainBridgeAPI {
curl: CurlBridgeAPI;
trackSegmentEvent: (options: { event: string; properties?: Record<string, unknown> }) => void;
trackPageView: (options: { name: string }) => void;
showContextMenu: (options: { key: string; nunjucksTag?: { template: string; range: MarkerRange } }) => void;
showNunjucksContextMenu: (options: { key: string; nunjucksTag?: { template: string; range: MarkerRange } }) => void;
showContextMenu: (options: { key: string; menuItems: MenuItemConstructorOptions[]; extra?: Record<string, any> }) => void;

database: {
caCertificate: {
create: (options: { parentId: string; path: string }) => Promise<string>;
Expand Down
3 changes: 2 additions & 1 deletion packages/insomnia/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ const main: Window['main'] = {
curl,
trackSegmentEvent: options => ipcRenderer.send('trackSegmentEvent', options),
trackPageView: options => ipcRenderer.send('trackPageView', options),
showContextMenu: options => ipcRenderer.send('show-context-menu', options),
showNunjucksContextMenu: options => ipcRenderer.send('show-nunjucks-context-menu', options),
showContextMenu: options => ipcRenderer.send('showContextMenu', options),
database: {
caCertificate: {
create: options => ipcRenderer.invoke('database.caCertificate.create', options),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ export const CodeEditor = memo(forwardRef<CodeEditorHandle, CodeEditorProps>(({
}
};
useEffect(() => {
const unsubscribe = window.main.on('context-menu-command', (_, { key, tag, nunjucksTag }) => {
const unsubscribe = window.main.on('nunjucks-context-menu-command', (_, { key, tag, nunjucksTag }) => {
if (id === key) {
if (nunjucksTag) {
const { type, template, range } = nunjucksTag as nunjucksTagContextMenuOptions;
Expand Down Expand Up @@ -657,10 +657,10 @@ export const CodeEditor = memo(forwardRef<CodeEditorHandle, CodeEditorProps>(({
const nunjucksTag = extractNunjucksTagFromCoords({ left: clientX, top: clientY }, codeMirror);
if (nunjucksTag) {
// show context menu for nunjucks tag
window.main.showContextMenu({ key: id, nunjucksTag });
window.main.showNunjucksContextMenu({ key: id, nunjucksTag });
}
} else {
window.main.showContextMenu({ key: id });
window.main.showNunjucksContextMenu({ key: id });
}
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
}, [onChange]);

useEffect(() => {
const unsubscribe = window.main.on('context-menu-command', (_, { key, tag, nunjucksTag }) => {
const unsubscribe = window.main.on('nunjucks-context-menu-command', (_, { key, tag, nunjucksTag }) => {
if (id === key) {
if (nunjucksTag) {
const { type, template, range } = nunjucksTag as nunjucksTagContextMenuOptions;
Expand Down Expand Up @@ -320,10 +320,10 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
const nunjucksTag = extractNunjucksTagFromCoords({ left: clientX, top: clientY }, codeMirror);
if (nunjucksTag) {
// show context menu for nunjucks tag
window.main.showContextMenu({ key: id, nunjucksTag });
window.main.showNunjucksContextMenu({ key: id, nunjucksTag });
}
} else {
window.main.showContextMenu({ key: id });
window.main.showNunjucksContextMenu({ key: id });
}
}}
>
Expand Down
8 changes: 5 additions & 3 deletions packages/insomnia/src/ui/components/command-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ export const CommandPalette = memo(function CommandPalette() {
onOpenChange={setIsOpen}
isOpen={isOpen}
>
<Button data-testid='quick-search' className="px-4 py-1 h-[30.5px] flex-shrink-0 flex items-center justify-center gap-2 bg-[--hl-xs] aria-pressed:bg-[--hl-sm] data-[pressed]:bg-[--hl-sm] rounded-md text-[--color-font] hover:bg-[--hl-xs] ring-inset ring-transparent ring-1 focus:ring-[--hl-md] transition-all text-sm">
<Icon icon="search" />
Search..
<Button data-testid='quick-search' className="px-4 py-1 h-[30.5px] flex-shrink-0 flex items-center justify-between gap-2 bg-[--hl-xs] aria-pressed:bg-[--hl-sm] data-[pressed]:bg-[--hl-sm] rounded-md text-[--color-font] hover:bg-[--hl-xs] ring-inset ring-transparent ring-1 focus:ring-[--hl-md] transition-all text-sm">
<div>
<Icon icon="search" className='mr-2' />
Search..
</div>
{requestSwitchKeyCombination && <Keyboard className='space-x-0.5 items-center font-sans font-normal text-center text-sm shadow-sm bg-[--hl-xs] text-[--hl] rounded-md py-0.5 px-2 inline-block'>
{constructKeyCombinationDisplay(requestSwitchKeyCombination, false)}
</Keyboard>}
Expand Down
34 changes: 34 additions & 0 deletions packages/insomnia/src/ui/components/document-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import classnames from 'classnames';
import React from 'react';
import { NavLink } from 'react-router-dom';

interface Props {
organizationId: string;
projectId: string;
workspaceId: string;
className?: string;
}

export const DocumentTab = ({ organizationId, projectId, workspaceId, className }: Props) => {
return (
<nav className={`flex w-full h-[40px] items-center ${className} px-1 justify-around`}>
{[
{ id: 'spec', name: 'Spec' },
{ id: 'debug', name: 'Collection' },
{ id: 'test', name: 'Tests' },
].map(item => (
<NavLink
key={item.id}
to={`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/${item.id}`}
className={({ isActive, isPending }) => classnames('text-center rounded-full px-2', {
'text-[--color-font-surprise] bg-[--color-surprise]': isActive,
'animate-pulse': isPending,
})}
data-testid={`workspace-${item.id}`}
>
{item.name}
</NavLink>
))}
</nav>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const ResponseHistoryDropdown = ({

const handleDeleteResponses = useCallback(async () => {
if (isWebSocketResponse(activeResponse)) {
window.main.webSocket.closeAll();
window.main.webSocket.close({ requestId });
}
fetcher.submit({}, {
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request/${requestId}/response/delete-all`,
Expand Down
3 changes: 2 additions & 1 deletion packages/insomnia/src/ui/components/environment-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useFetcher, useNavigate, useParams, useRouteLoaderData } from 'react-ro

import { fuzzyMatch } from '../../common/misc';
import { isRemoteProject } from '../../models/project';
import uiEventBus from '../eventBus';
import { useOrganizationPermissions } from '../hooks/use-organization-features';
import type { WorkspaceLoaderData } from '../routes/workspace';
import { Icon } from './icon';
Expand Down Expand Up @@ -239,7 +240,6 @@ export const EnvironmentPicker = ({
return;
}
const [environmentId] = keys.values();

setActiveEnvironmentFetcher.submit(
{
environmentId,
Expand All @@ -249,6 +249,7 @@ export const EnvironmentPicker = ({
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/set-active`,
}
);
uiEventBus.emit('CHANGE_ACTIVE_ENV', workspaceId);
}}
className="p-2 select-none text-sm overflow-y-auto focus:outline-none"
>
Expand Down
Loading
Loading