-
Notifications
You must be signed in to change notification settings - Fork 228
feat(workspaces): context menu on tabs to duplicate and close all other tabs COMPASS-9397 #7053
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
Conversation
dispatch({ type: WorkspacesActions.CloseTab, atIndex }); | ||
cleanupLocalAppRegistryForTab(tab?.id); | ||
dispatch({ type: WorkspacesActions.CloseAllOtherTabs, atIndex }); | ||
cleanupLocalAppRegistryForTab(tabs[atIndex].id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You want to cleanup for tabs that you closed, not for the only one that stays open. We usually do this through cleanupRemovedTabs
method used in similar cases in this slice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think you caught one too! 😄 The closeTab
method is not doing this full cleanup and it probably should, I don't see necessarily anything breaking because of that, but seems like we might be slowly leaking some uncleaned data, maybe you can drive-by address this? Good if not, we can follow-up quickly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed a change to call cleanupRemovedTabs
in closeTab
as well 👍
const { tabs } = getState(); | ||
const otherTabs = tabs.filter((_, index) => index !== atIndex); | ||
for (const tab of otherTabs) { | ||
if (!canCloseTab(tab) && !(await confirmClosingTabs())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't feel right that canceling close for one tab stops all the others from closing too (or the other way around, you say "yes" for one, then "no" for other, and all stay open), in vscode you'd be asked for evey one separately and they are closed based on your answers. Wouldn't that make more sense for our flow too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also now that we have this case of confirmation being shown for multiple tabs, it would probably make sense to add something to this confirmation model to make identification easier. "Are you sure you want to close the tab?" reads fine if you actively clicked a close button on a certain tab, but not when you are getting this message as a side effect of another action with no context what exactly will be closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can make sure only the tabs which are confirmed will close 👍 Getting to the title of the tab turns out very difficulty for me, as this is controlled by the plugin ultimately rendering the tab. I suggest selecting the tab just before confirming, to make the dialog display in context of the tab being closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good point, makes sense! This seems to match what VSCode is doing also
c0ad255
to
69a787d
Compare
4460015
to
6c4c97a
Compare
@gribnoysup I'm curious if you would add "release notes" or "no release notes" to this? It seems a bit niece but then again it does introduce new functionality that might be hard to discover. |
Ah, a good question! Honestly I feel like it might be worth manually editing release notes at the point we're publishing this to highlight this change in conjunction with other related ones and just have a big mention of all newly available context menus as this is a long requested Compass feature. Having release notes label is already pretty good not to lose track of this in the release notes 👍 |
For just some random context I think generally speaking for "bigger" features we usually have them behind a flag and so a PR that would enable a feature by default will be the one we'd include in the notes (hence the special feature flagged label for everything else), but it's just not the case with this one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two notes, otherwise lgtm
const tabsToClose = await tabs.reduce( | ||
async (prev: Promise<WorkspaceTab[]>, tab, tabIndex) => { | ||
const tabsToClose = await prev; | ||
if (tabIndex === atIndex) { | ||
return tabsToClose; // Skip the tab which is not being closed | ||
} | ||
if (!canCloseTab(tab)) { | ||
// Select the closing tab - to show the confirmation dialog in context | ||
dispatch({ type: WorkspacesActions.SelectTab, atIndex: tabIndex }); | ||
if (!(await confirmClosingTab())) { | ||
return tabsToClose; // Skip this tab | ||
} | ||
} | ||
return [...tabsToClose, tab]; | ||
}, | ||
Promise.resolve([]) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I found it really hard to read through this reducer with the promise being an accumulator that always needs to be awaited, I think a for of loop reads easier, but feel free to ignore this suggestion
const tabsToClose = await tabs.reduce( | |
async (prev: Promise<WorkspaceTab[]>, tab, tabIndex) => { | |
const tabsToClose = await prev; | |
if (tabIndex === atIndex) { | |
return tabsToClose; // Skip the tab which is not being closed | |
} | |
if (!canCloseTab(tab)) { | |
// Select the closing tab - to show the confirmation dialog in context | |
dispatch({ type: WorkspacesActions.SelectTab, atIndex: tabIndex }); | |
if (!(await confirmClosingTab())) { | |
return tabsToClose; // Skip this tab | |
} | |
} | |
return [...tabsToClose, tab]; | |
}, | |
Promise.resolve([]) | |
); | |
const tabsToClose = []; | |
for (const [tabIndex, tab] of tabs.entries()) { | |
if (tabIndex !== atIndex && !canCloseTab(tab)) { | |
// Select the closing tab - to show the confirmation dialog in context | |
dispatch({ type: WorkspacesActions.SelectTab, atIndex: tabIndex }); | |
if (await confirmClosingTab()) { | |
tabsToClose.push(tab); | |
} | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right - a simple loop reads much better. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed an update using a for loop. I needed to change it slightly to accommodate for tabs to which canCloseTab
returned true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, sorry, I sketched the code, but forgot to double-check it when making a suggestion, thanks for refactoring, looks good to me!
} | ||
if (!canCloseTab(tab)) { | ||
// Select the closing tab - to show the confirmation dialog in context | ||
dispatch({ type: WorkspacesActions.SelectTab, atIndex: tabIndex }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI nit: as discussed, it makes sense to switch to the tab that's being closed during the process, but I found it a bit confusing that if you skip closing some of the tabs, you're not being returned back to the tab that you started with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added an optional activeTabId
to the CloseTabs
action.
0822ce8
to
6a77ddf
Compare
The failures are unrelated to these changes. |
Description
Stacked on #6956
Merging this PR will:
Duplicate tab and close all others
duplicate-and-close-other-tabs.mov
Partial closing of tabs
Any tab with unsaved work will confirm the close by getting selected and displaying a confirmation dialog.
partial-closing-tabs.mov
Checklist
Motivation and Context
Open Questions
I feel the tooltip is getting in the way of this interaction and while we could solve this in a bespoke way (ex disabling the tooltip component if the context menu
isOpen
), we could disable the underlying UI (using ex thepointer-events
CSS property) when the menu is in its opened state.For what it's worth, this match the behavior in Google Sheets:
Dependents
Types of changes