diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 8d4746a5a..c65f04b76 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -182,46 +182,44 @@ export default class DesktopLayout { .child(new TitleBarButtonsWidget()) .css('height', '36px') ) - .child(new TabCachingWidget(() => - new PaneContainer(() => - new FlexContainer('column') - .css("flex-grow", "1") - .child(new FlexContainer('row').class('title-row') - .css('align-items: center;') - .cssBlock('.title-row > * { margin: 5px; }') - .overflowing() - .child(new NoteIconWidget()) - .child(new NoteTitleWidget()) - .child(new NotePathsWidget().hideInZenMode()) - .child(new NoteTypeWidget().hideInZenMode()) - .child(new NoteActionsWidget().hideInZenMode()) - .child(new ButtonWidget() - .icon("bx-window-open bx-rotate-90") - .title("Create new pane") - .command("openNewPane")) - ) - .child( - new CollapsibleSectionContainer() - .child(new SearchDefinitionWidget()) - .child(new NotePropertiesWidget()) - .child(new FilePropertiesWidget()) - .child(new ImagePropertiesWidget()) - .child(new PromotedAttributesWidget()) - .child(new OwnedAttributeListWidget()) - .child(new InheritedAttributesWidget()) - ) - .child(new NoteUpdateStatusWidget()) - .child( - new ScrollingContainer() - .child(new SqlTableSchemasWidget()) - .child(new NoteDetailWidget()) - .child(new NoteListWidget()) - .child(new SearchResultWidget()) - .child(new SqlResultWidget()) - ) - .child(new SimilarNotesWidget()) - .child(...this.customWidgets.get('center-pane')) + .child(new PaneContainer(() => + new FlexContainer('column') + .css("flex-grow", "1") + .child(new FlexContainer('row').class('title-row') + .css('align-items: center;') + .cssBlock('.title-row > * { margin: 5px; }') + .overflowing() + .child(new NoteIconWidget()) + .child(new NoteTitleWidget()) + .child(new NotePathsWidget().hideInZenMode()) + .child(new NoteTypeWidget().hideInZenMode()) + .child(new NoteActionsWidget().hideInZenMode()) + .child(new ButtonWidget() + .icon("bx-window-open bx-rotate-90") + .title("Create new pane") + .command("openNewPane")) ) + .child( + new CollapsibleSectionContainer() + .child(new SearchDefinitionWidget()) + .child(new NotePropertiesWidget()) + .child(new FilePropertiesWidget()) + .child(new ImagePropertiesWidget()) + .child(new PromotedAttributesWidget()) + .child(new OwnedAttributeListWidget()) + .child(new InheritedAttributesWidget()) + ) + .child(new NoteUpdateStatusWidget()) + .child( + new ScrollingContainer() + .child(new SqlTableSchemasWidget()) + .child(new NoteDetailWidget()) + .child(new NoteListWidget()) + .child(new SearchResultWidget()) + .child(new SqlResultWidget()) + ) + .child(new SimilarNotesWidget()) + .child(...this.customWidgets.get('center-pane')) ) ) ); diff --git a/src/public/app/services/tab_context.js b/src/public/app/services/tab_context.js index 6310a3fd1..fb73d3579 100644 --- a/src/public/app/services/tab_context.js +++ b/src/public/app/services/tab_context.js @@ -11,12 +11,12 @@ class TabContext extends Component { /** * @param {string|null} tabId */ - constructor(tabId = null, hoistedNoteId = 'root') { + constructor(tabId = null, hoistedNoteId = 'root', parentTabId = null) { super(); this.tabId = tabId || utils.randomString(4); this.hoistedNoteId = hoistedNoteId; - this.parentTabId = null; + this.parentTabId = parentTabId; } setEmpty() { @@ -60,6 +60,19 @@ class TabContext extends Component { } } + getAllSubTabContexts() { + return appContext.tabManager.tabContexts.filter(tc => tc.tabId === this.tabId || tc.parentTabId === this.tabId); + } + + getMainTabContext() { + if (this.parentTabId) { + return appContext.tabManager.getTabContextById(this.parentTabId); + } + else { + return this; + } + } + saveToRecentNotes(resolvedNotePath) { setTimeout(async () => { // we include the note into recent list only if the user stayed on the note at least 5 seconds @@ -135,6 +148,7 @@ class TabContext extends Component { return { tabId: this.tabId, + parentTabId: this.parentTabId, notePath: this.notePath, hoistedNoteId: this.hoistedNoteId, active: this.isActive() diff --git a/src/public/app/services/tab_manager.js b/src/public/app/services/tab_manager.js index b4116416f..c95d6b323 100644 --- a/src/public/app/services/tab_manager.js +++ b/src/public/app/services/tab_manager.js @@ -36,6 +36,11 @@ export default class TabManager extends Component { return this.children; } + /** @type {TabContext[]} */ + get mainTabContexts() { + return this.tabContexts.filter(tc => !tc.parentTabId) + } + async loadTabs() { const tabsToOpen = appContext.isMainWindow ? (options.getJson('openTabs') || []) @@ -97,7 +102,7 @@ export default class TabManager extends Component { await this.tabsUpdate.allowUpdateWithoutChange(async () => { for (const tab of filteredTabs) { - await this.openTabWithNote(tab.notePath, tab.active, tab.tabId, tab.hoistedNoteId); + await this.openTabWithNote(tab.notePath, tab.active, tab.tabId, tab.hoistedNoteId, tab.parentTabId); } }); } @@ -138,12 +143,20 @@ export default class TabManager extends Component { /** @returns {TabContext} */ getTabContextById(tabId) { - return this.tabContexts.find(tc => tc.tabId === tabId); + const tabContext = this.tabContexts.find(tc => tc.tabId === tabId); + + if (!tabContext) { + throw new Error(`Cannot find tabContext id='${tabId}'`); + } + + return tabContext; } /** @returns {TabContext} */ getActiveTabContext() { - return this.getTabContextById(this.activeTabId); + return this.activeTabId + ? this.getTabContextById(this.activeTabId) + : null; } /** @returns {string|null} */ @@ -188,8 +201,8 @@ export default class TabManager extends Component { await tabContext.setEmpty(); } - async openEmptyTab(tabId, hoistedNoteId) { - const tabContext = new TabContext(tabId, hoistedNoteId); + async openEmptyTab(tabId, hoistedNoteId, parentTabId = null) { + const tabContext = new TabContext(tabId, hoistedNoteId, parentTabId); this.child(tabContext); await this.triggerEvent('newTabOpened', {tabContext}); @@ -215,8 +228,8 @@ export default class TabManager extends Component { return this.openTabWithNote(notePath, false, null, hoistedNoteId); } - async openTabWithNote(notePath, activate, tabId, hoistedNoteId) { - const tabContext = await this.openEmptyTab(tabId, hoistedNoteId); + async openTabWithNote(notePath, activate, tabId, hoistedNoteId, parentTabId = null) { + const tabContext = await this.openEmptyTab(tabId, hoistedNoteId, parentTabId); if (notePath) { await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event @@ -267,24 +280,30 @@ export default class TabManager extends Component { } async removeTab(tabId) { - const tabContextToRemove = this.getTabContextById(tabId); + let mainTabContextToRemove = this.getTabContextById(tabId); - if (!tabContextToRemove) { + if (!mainTabContextToRemove) { return; } + if (mainTabContextToRemove.parentTabId) { + mainTabContextToRemove = this.getTabContextById(mainTabContextToRemove.parentTabId); + } + // close dangling autocompletes after closing the tab $(".aa-input").autocomplete("close"); - await this.triggerEvent('beforeTabRemove', {tabId}); + const tabIdsToRemove = mainTabContextToRemove.getAllSubTabContexts().map(tc => tc.tabId); - if (this.tabContexts.length <= 1) { - this.openAndActivateEmptyTab(); + await this.triggerEvent('beforeTabRemove', { tabIds: tabIdsToRemove }); + + if (this.mainTabContexts.length <= 1) { + await this.openAndActivateEmptyTab(); } - else if (tabContextToRemove.isActive()) { - const idx = this.tabContexts.findIndex(tc => tc.tabId === tabId); + else if (tabIdsToRemove.includes(this.activeTabId)) { + const idx = this.mainTabContexts.findIndex(tc => tc.tabId === mainTabContextToRemove.tabId); - if (idx === this.tabContexts.length - 1) { + if (idx === this.mainTabContexts.length - 1) { this.activatePreviousTabCommand(); } else { @@ -292,9 +311,9 @@ export default class TabManager extends Component { } } - this.children = this.children.filter(tc => tc.tabId !== tabId); + this.children = this.children.filter(tc => !tabIdsToRemove.includes(tc.tabId)); - this.triggerEvent('tabRemoved', {tabId}); + this.triggerEvent('tabRemoved', {tabIds: tabIdsToRemove}); this.tabsUpdate.scheduleUpdate(); } @@ -312,15 +331,15 @@ export default class TabManager extends Component { } activateNextTabCommand() { - const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId); - const newActiveTabId = this.tabContexts[oldIdx === this.tabContexts.length - 1 ? 0 : oldIdx + 1].tabId; + const oldIdx = this.mainTabContexts.findIndex(tc => tc.tabId === this.activeTabId); + const newActiveTabId = this.mainTabContexts[oldIdx === this.tabContexts.length - 1 ? 0 : oldIdx + 1].tabId; this.activateTab(newActiveTabId); } activatePreviousTabCommand() { - const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId); - const newActiveTabId = this.tabContexts[oldIdx === 0 ? this.tabContexts.length - 1 : oldIdx - 1].tabId; + const oldIdx = this.mainTabContexts.findIndex(tc => tc.tabId === this.activeTabId); + const newActiveTabId = this.mainTabContexts[oldIdx === 0 ? this.tabContexts.length - 1 : oldIdx - 1].tabId; this.activateTab(newActiveTabId); } diff --git a/src/public/app/widgets/containers/pane_container.js b/src/public/app/widgets/containers/pane_container.js index f2ee3d037..ea2828e1e 100644 --- a/src/public/app/widgets/containers/pane_container.js +++ b/src/public/app/widgets/containers/pane_container.js @@ -1,6 +1,5 @@ import FlexContainer from "./flex_container.js"; import appContext from "../../services/app_context.js"; -import TabContext from "../../services/tab_context.js"; export default class PaneContainer extends FlexContainer { constructor(widgetFactory) { @@ -9,48 +8,90 @@ export default class PaneContainer extends FlexContainer { this.counter = 0; this.widgetFactory = widgetFactory; + this.widgets = {}; - this.child(this.widgetFactory()); - - this.id('pane-container-widget'); + this.class('pane-container-widget'); this.css('flex-grow', '1'); } - doRender() { - super.doRender(); - - this.$widget.find("div").on("click", () => { - const activeTabContext = appContext.tabManager.getActiveTabContext(); - - const tabId = activeTabContext.parentTabId || activeTabContext.tabId; - - appContext.tabManager.activateTab(tabId); - }); + setTabContextEvent({tabContext}) { + /** @var {TabContext} */ + this.tabContext = tabContext; } - async openNewPaneCommand() { - const newWidget = this.widgetFactory(); + async newTabOpenedEvent({tabContext}) { + const widget = this.widgetFactory(); - const $rendered = newWidget.render(); + const $renderedWidget = widget.render(); - this.$widget.append($rendered); - - const tabContext = new TabContext(); - appContext.tabManager.tabContexts.push(tabContext); - appContext.tabManager.child(tabContext); - - $rendered.on('click', () => { + $renderedWidget.on('click', () => { appContext.tabManager.activateTab(tabContext.tabId); }); - tabContext.parentTabId = appContext.tabManager.getActiveTabContext().tabId; + let $parent; - await newWidget.handleEvent('setTabContext', { tabContext }); + if (!tabContext.parentTabId) { + $parent = $("