mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
split window WIP
This commit is contained in:
parent
a3847842cb
commit
1a13132a69
@ -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'))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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'));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user