split window WIP

This commit is contained in:
zadam 2021-05-20 23:13:34 +02:00
parent a3847842cb
commit 1a13132a69
9 changed files with 186 additions and 105 deletions

View File

@ -182,8 +182,7 @@ export default class DesktopLayout {
.child(new TitleBarButtonsWidget()) .child(new TitleBarButtonsWidget())
.css('height', '36px') .css('height', '36px')
) )
.child(new TabCachingWidget(() => .child(new PaneContainer(() =>
new PaneContainer(() =>
new FlexContainer('column') new FlexContainer('column')
.css("flex-grow", "1") .css("flex-grow", "1")
.child(new FlexContainer('row').class('title-row') .child(new FlexContainer('row').class('title-row')
@ -223,7 +222,6 @@ export default class DesktopLayout {
.child(...this.customWidgets.get('center-pane')) .child(...this.customWidgets.get('center-pane'))
) )
) )
)
); );
} }
} }

View File

@ -11,12 +11,12 @@ class TabContext extends Component {
/** /**
* @param {string|null} tabId * @param {string|null} tabId
*/ */
constructor(tabId = null, hoistedNoteId = 'root') { constructor(tabId = null, hoistedNoteId = 'root', parentTabId = null) {
super(); super();
this.tabId = tabId || utils.randomString(4); this.tabId = tabId || utils.randomString(4);
this.hoistedNoteId = hoistedNoteId; this.hoistedNoteId = hoistedNoteId;
this.parentTabId = null; this.parentTabId = parentTabId;
} }
setEmpty() { 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) { saveToRecentNotes(resolvedNotePath) {
setTimeout(async () => { setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds // 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 { return {
tabId: this.tabId, tabId: this.tabId,
parentTabId: this.parentTabId,
notePath: this.notePath, notePath: this.notePath,
hoistedNoteId: this.hoistedNoteId, hoistedNoteId: this.hoistedNoteId,
active: this.isActive() active: this.isActive()

View File

@ -36,6 +36,11 @@ export default class TabManager extends Component {
return this.children; return this.children;
} }
/** @type {TabContext[]} */
get mainTabContexts() {
return this.tabContexts.filter(tc => !tc.parentTabId)
}
async loadTabs() { async loadTabs() {
const tabsToOpen = appContext.isMainWindow const tabsToOpen = appContext.isMainWindow
? (options.getJson('openTabs') || []) ? (options.getJson('openTabs') || [])
@ -97,7 +102,7 @@ export default class TabManager extends Component {
await this.tabsUpdate.allowUpdateWithoutChange(async () => { await this.tabsUpdate.allowUpdateWithoutChange(async () => {
for (const tab of filteredTabs) { 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} */ /** @returns {TabContext} */
getTabContextById(tabId) { 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} */ /** @returns {TabContext} */
getActiveTabContext() { getActiveTabContext() {
return this.getTabContextById(this.activeTabId); return this.activeTabId
? this.getTabContextById(this.activeTabId)
: null;
} }
/** @returns {string|null} */ /** @returns {string|null} */
@ -188,8 +201,8 @@ export default class TabManager extends Component {
await tabContext.setEmpty(); await tabContext.setEmpty();
} }
async openEmptyTab(tabId, hoistedNoteId) { async openEmptyTab(tabId, hoistedNoteId, parentTabId = null) {
const tabContext = new TabContext(tabId, hoistedNoteId); const tabContext = new TabContext(tabId, hoistedNoteId, parentTabId);
this.child(tabContext); this.child(tabContext);
await this.triggerEvent('newTabOpened', {tabContext}); await this.triggerEvent('newTabOpened', {tabContext});
@ -215,8 +228,8 @@ export default class TabManager extends Component {
return this.openTabWithNote(notePath, false, null, hoistedNoteId); return this.openTabWithNote(notePath, false, null, hoistedNoteId);
} }
async openTabWithNote(notePath, activate, tabId, hoistedNoteId) { async openTabWithNote(notePath, activate, tabId, hoistedNoteId, parentTabId = null) {
const tabContext = await this.openEmptyTab(tabId, hoistedNoteId); const tabContext = await this.openEmptyTab(tabId, hoistedNoteId, parentTabId);
if (notePath) { if (notePath) {
await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event 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) { async removeTab(tabId) {
const tabContextToRemove = this.getTabContextById(tabId); let mainTabContextToRemove = this.getTabContextById(tabId);
if (!tabContextToRemove) { if (!mainTabContextToRemove) {
return; return;
} }
if (mainTabContextToRemove.parentTabId) {
mainTabContextToRemove = this.getTabContextById(mainTabContextToRemove.parentTabId);
}
// close dangling autocompletes after closing the tab // close dangling autocompletes after closing the tab
$(".aa-input").autocomplete("close"); $(".aa-input").autocomplete("close");
await this.triggerEvent('beforeTabRemove', {tabId}); const tabIdsToRemove = mainTabContextToRemove.getAllSubTabContexts().map(tc => tc.tabId);
if (this.tabContexts.length <= 1) { await this.triggerEvent('beforeTabRemove', { tabIds: tabIdsToRemove });
this.openAndActivateEmptyTab();
if (this.mainTabContexts.length <= 1) {
await this.openAndActivateEmptyTab();
} }
else if (tabContextToRemove.isActive()) { else if (tabIdsToRemove.includes(this.activeTabId)) {
const idx = this.tabContexts.findIndex(tc => tc.tabId === tabId); const idx = this.mainTabContexts.findIndex(tc => tc.tabId === mainTabContextToRemove.tabId);
if (idx === this.tabContexts.length - 1) { if (idx === this.mainTabContexts.length - 1) {
this.activatePreviousTabCommand(); this.activatePreviousTabCommand();
} }
else { 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(); this.tabsUpdate.scheduleUpdate();
} }
@ -312,15 +331,15 @@ export default class TabManager extends Component {
} }
activateNextTabCommand() { activateNextTabCommand() {
const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId); const oldIdx = this.mainTabContexts.findIndex(tc => tc.tabId === this.activeTabId);
const newActiveTabId = this.tabContexts[oldIdx === this.tabContexts.length - 1 ? 0 : oldIdx + 1].tabId; const newActiveTabId = this.mainTabContexts[oldIdx === this.tabContexts.length - 1 ? 0 : oldIdx + 1].tabId;
this.activateTab(newActiveTabId); this.activateTab(newActiveTabId);
} }
activatePreviousTabCommand() { activatePreviousTabCommand() {
const oldIdx = this.tabContexts.findIndex(tc => tc.tabId === this.activeTabId); const oldIdx = this.mainTabContexts.findIndex(tc => tc.tabId === this.activeTabId);
const newActiveTabId = this.tabContexts[oldIdx === 0 ? this.tabContexts.length - 1 : oldIdx - 1].tabId; const newActiveTabId = this.mainTabContexts[oldIdx === 0 ? this.tabContexts.length - 1 : oldIdx - 1].tabId;
this.activateTab(newActiveTabId); this.activateTab(newActiveTabId);
} }

View File

@ -1,6 +1,5 @@
import FlexContainer from "./flex_container.js"; import FlexContainer from "./flex_container.js";
import appContext from "../../services/app_context.js"; import appContext from "../../services/app_context.js";
import TabContext from "../../services/tab_context.js";
export default class PaneContainer extends FlexContainer { export default class PaneContainer extends FlexContainer {
constructor(widgetFactory) { constructor(widgetFactory) {
@ -9,48 +8,90 @@ export default class PaneContainer extends FlexContainer {
this.counter = 0; this.counter = 0;
this.widgetFactory = widgetFactory; this.widgetFactory = widgetFactory;
this.widgets = {};
this.child(this.widgetFactory()); this.class('pane-container-widget');
this.id('pane-container-widget');
this.css('flex-grow', '1'); this.css('flex-grow', '1');
} }
doRender() { setTabContextEvent({tabContext}) {
super.doRender(); /** @var {TabContext} */
this.tabContext = tabContext;
}
this.$widget.find("div").on("click", () => { async newTabOpenedEvent({tabContext}) {
const activeTabContext = appContext.tabManager.getActiveTabContext(); const widget = this.widgetFactory();
const tabId = activeTabContext.parentTabId || activeTabContext.tabId; const $renderedWidget = widget.render();
appContext.tabManager.activateTab(tabId); $renderedWidget.on('click', () => {
appContext.tabManager.activateTab(tabContext.tabId);
}); });
let $parent;
if (!tabContext.parentTabId) {
$parent = $("<div>")
.attr("data-main-tab-id", tabContext.tabId)
.css("display", "flex")
.css("flex-grow", "1");
this.$widget.append($parent);
}
else {
$parent = this.$widget.find(`[data-main-tab-id="${tabContext.parentTabId}"]`);
}
$parent.append($renderedWidget);
this.widgets[tabContext.tabId] = widget;
await widget.handleEvent('setTabContext', { tabContext });
this.child(widget);
} }
async openNewPaneCommand() { async openNewPaneCommand() {
const newWidget = this.widgetFactory(); const tabContext = await appContext.tabManager.openEmptyTab(null, null, appContext.tabManager.getActiveTabContext().tabId);
const $rendered = newWidget.render(); await appContext.tabManager.activateTab(tabContext.tabId);
this.$widget.append($rendered); await tabContext.setEmpty();
}
const tabContext = new TabContext(); /**
appContext.tabManager.tabContexts.push(tabContext); * widget.hasBeenAlreadyShown is intended for lazy loading of cached tabs - initial note switches of new tabs
appContext.tabManager.child(tabContext); * are not executed, we're waiting for the first tab activation and then we update the tab. After this initial
* activation further note switches are always propagated to the tabs.
*/
handleEventInChildren(name, data) {
if (['tabNoteSwitched', 'tabNoteSwitchedAndActivated'].includes(name)) {
// this event is propagated only to the widgets of a particular tab
const widget = this.widgets[data.tabContext.tabId];
$rendered.on('click', () => { if (widget && (widget.hasBeenAlreadyShown || name === 'tabNoteSwitchedAndActivated')) {
appContext.tabManager.activateTab(tabContext.tabId); widget.hasBeenAlreadyShown = true;
});
tabContext.parentTabId = appContext.tabManager.getActiveTabContext().tabId; return widget.handleEvent('tabNoteSwitched', data);
}
await newWidget.handleEvent('setTabContext', { tabContext }); else {
return Promise.resolve();
this.child(newWidget); }
}
tabContext.setEmpty();
if (name === 'activeTabChanged') {
appContext.tabManager.activateTab(tabContext.tabId); const widget = this.widgets[data.tabContext.tabId];
if (widget.hasBeenAlreadyShown) {
return Promise.resolve();
}
else {
widget.hasBeenAlreadyShown = true;
return widget.handleEvent(name, data);
}
} else {
return super.handleEventInChildren(name, data);
}
} }
} }

View File

@ -202,8 +202,8 @@ export default class NoteDetailWidget extends TabAwareWidget {
} }
} }
async beforeTabRemoveEvent({tabId}) { async beforeTabRemoveEvent({tabIds}) {
if (this.isTab(tabId)) { if (this.isTab(tabIds)) {
await this.spacedUpdate.updateNowIfNecessary(); await this.spacedUpdate.updateNowIfNecessary();
} }
} }

View File

@ -72,8 +72,8 @@ export default class NoteTitleWidget extends TabAwareWidget {
} }
} }
async beforeTabRemoveEvent({tabId}) { async beforeTabRemoveEvent({tabIds}) {
if (this.isTab(tabId)) { if (this.isTab(tabIds)) {
await this.spacedUpdate.updateNowIfNecessary(); await this.spacedUpdate.updateNowIfNecessary();
} }
} }

View File

@ -3,8 +3,13 @@ import appContext from "../services/app_context.js";
export default class TabAwareWidget extends BasicWidget { export default class TabAwareWidget extends BasicWidget {
isTab(tabId) { isTab(tabId) {
if (Array.isArray(tabId)) {
return this.tabContext && tabId.includes(this.tabContext.tabId);
}
else {
return this.tabContext && this.tabContext.tabId === tabId; return this.tabContext && this.tabContext.tabId === tabId;
} }
}
isTabOrParent(tabId) { isTabOrParent(tabId) {
return this.tabContext && (this.tabContext.tabId === tabId || this.tabContext.parentTabId === tabId); return this.tabContext && (this.tabContext.tabId === tabId || this.tabContext.parentTabId === tabId);

View File

@ -34,7 +34,8 @@ export default class TabCachingWidget extends TabAwareWidget {
this.child(this.widgets[tabId]); // add as child only once it is ready (rendered with tabContext) this.child(this.widgets[tabId]); // add as child only once it is ready (rendered with tabContext)
} }
tabRemovedEvent({tabId}) { tabRemovedEvent({tabIds}) {
for (const tabId of tabIds) {
const widget = this.widgets[tabId]; const widget = this.widgets[tabId];
if (widget) { if (widget) {
@ -44,6 +45,7 @@ export default class TabCachingWidget extends TabAwareWidget {
this.children = this.children.filter(ch => ch !== widget); this.children = this.children.filter(ch => ch !== widget);
} }
} }
}
async refresh() { async refresh() {
this.toggleExt(true); this.toggleExt(true);

View File

@ -445,7 +445,7 @@ export default class TabRowWidget extends BasicWidget {
} }
newTabOpenedEvent({tabContext}) { newTabOpenedEvent({tabContext}) {
if (!tabContext.parentId) { if (!tabContext.parentTabId) {
this.addTab(tabContext.tabId); this.addTab(tabContext.tabId);
} }
} }
@ -478,9 +478,11 @@ export default class TabRowWidget extends BasicWidget {
return $tab.attr('data-tab-id'); return $tab.attr('data-tab-id');
} }
tabRemovedEvent({tabId}) { tabRemovedEvent({tabIds}) {
for (const tabId of tabIds) {
this.removeTab(tabId); this.removeTab(tabId);
} }
}
cleanUpPreviouslyDraggedTabs() { cleanUpPreviouslyDraggedTabs() {
this.tabEls.forEach((tabEl) => tabEl.classList.remove('note-tab-was-just-dragged')); this.tabEls.forEach((tabEl) => tabEl.classList.remove('note-tab-was-just-dragged'));