diff --git a/src/renderer/components/dock/dock-tab.store.ts b/src/renderer/components/dock/dock-tab.store.ts index a19a93388e..d6d38ac8c9 100644 --- a/src/renderer/components/dock/dock-tab.store.ts +++ b/src/renderer/components/dock/dock-tab.store.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { comparer, makeObservable, observable, reaction } from "mobx"; +import { action, makeObservable, observable } from "mobx"; import { autoBind, createStorage, disposer, StorageHelper } from "../../utils"; import { dockStore, TabId } from "./dock.store"; @@ -52,8 +52,8 @@ export class DockTabStore { } protected constructor(protected options: DockTabStoreOptions = {}) { + makeObservable(this); // must be called *before* autoBind() when used with mobx's method decorators autoBind(this); - makeObservable(this); this.options = { autoInit: true, @@ -78,44 +78,34 @@ export class DockTabStore { } protected init() { - this.dispose.push( - reaction(() => ({ - dockTabs: dockStore.tabs.map(tab => tab.id) as TabId[], - dataTabs: Object.keys(this.data) as TabId[], - }), ({ dataTabs, dockTabs }) => { - - // clear data for closed or non-existing dock tabs - if (dockTabs.length < dataTabs.length) { - const closedDockTabs = dataTabs.filter(id => !dockTabs.includes(id)); - - closedDockTabs.forEach(tabId => this.clearData(tabId)); - } - }, { - fireImmediately: true, - equals: comparer.structural, - }) - ); - this.dataReady = true; + + this.dispose.push( + dockStore.onTabClose(({ tabId }) => this.clearData(tabId)), + ); } getData(tabId: TabId): T { return this.data[tabId]; } + @action setData(tabId: TabId, data: T) { this.data[tabId] = data; } + @action clearData(tabId: TabId) { delete this.data[tabId]; } + @action reset() { this.data = {}; this.dataReady = false; } + @action destroy() { this.reset(); this.dispose(); diff --git a/src/renderer/components/dock/dock.scss b/src/renderer/components/dock/dock.scss index e1cf52e116..4f76ae5530 100644 --- a/src/renderer/components/dock/dock.scss +++ b/src/renderer/components/dock/dock.scss @@ -38,7 +38,7 @@ } } - &:not(.isOpen) { + &.isClosed { height: auto !important; .Tab { @@ -71,7 +71,7 @@ background: $terminalBackground; flex: 1; overflow: hidden; - transition: flex 60ms ease-in-out; + transition: flex-basis 25ms ease-in; > *:not(.Spinner) { position: absolute; diff --git a/src/renderer/components/dock/dock.store.ts b/src/renderer/components/dock/dock.store.ts index feb8471ace..17a9b2b140 100644 --- a/src/renderer/components/dock/dock.store.ts +++ b/src/renderer/components/dock/dock.store.ts @@ -20,8 +20,8 @@ */ import * as uuid from "uuid"; -import { action, comparer, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx"; -import { autoBind, createStorage, Disposer } from "../../utils"; +import { action, comparer, computed, IReactionOptions, makeObservable, observable, reaction, runInAction } from "mobx"; +import { autoBind, createStorage } from "../../utils"; import throttle from "lodash/throttle"; export type TabId = string; @@ -89,16 +89,20 @@ export interface DockStorageState { } export interface DockTabChangeEvent { - selectedTabId?: TabId; + tabId?: TabId; tab?: DockTab; prevTab?: DockTab; } export interface DockTabChangeEventOptions extends IReactionOptions { - kind?: TabKind; // filter: matching by dockStore.selectedTab.kind + tabKind?: TabKind; // filter: by dockStore.selectedTab.kind == tabKind dockIsVisible?: boolean; // filter: dock and selected tab should be visible (default: true) } +export interface DockTabCloseEvent { + tabId: TabId; // closed tab id +} + export class DockStore implements DockStorageState { constructor() { makeObservable(this); @@ -189,37 +193,35 @@ export class DockStore implements DockStorageState { return reaction(() => [this.height, this.fullSize], callback, options); } - onTabClose(tabId: TabId, callback: () => void, options: IReactionOptions = {}): Disposer { - let disposed = false; + onTabClose(callback: (evt: DockTabCloseEvent) => void, options: IReactionOptions = {}) { + return reaction(() => dockStore.tabs.map(tab => tab.id), (tabs: TabId[], prevTabs?: TabId[]) => { + if (!Array.isArray(prevTabs)) { + return; // tabs not yet modified + } - const stopWatcher = reaction(() => this.getTabById(tabId), tab => { - if (!tab) { - disposed = true; - stopWatcher(); - callback(); + const closedTabs: TabId[] = prevTabs.filter(id => !tabs.includes(id)); + + if (closedTabs.length > 0) { + runInAction(() => { + closedTabs.forEach(tabId => callback({ tabId })); + }); } }, { - fireImmediately: true, - equals: comparer.shallow, + equals: comparer.structural, ...options, }); - - return () => { - if (disposed) return; - stopWatcher(); - }; } onTabChange(callback?: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) { - const { kind, dockIsVisible = true, ...reactionOpts } = options; + const { tabKind, dockIsVisible = true, ...reactionOpts } = options; return reaction(() => this.selectedTab, ((tab, prevTab) => { - if (kind && kind !== tab.kind) return; - if (dockIsVisible && !dockStore.isOpen) return; + if (tabKind && tabKind !== tab.kind) return; + if (dockIsVisible && !this.isOpen) return; callback({ tab, prevTab, - selectedTabId: this.selectedTabId, + tabId: this.selectedTabId, }); }), reactionOpts); } diff --git a/src/renderer/components/dock/dock.tsx b/src/renderer/components/dock/dock.tsx index 2599b65de1..1bbf505471 100644 --- a/src/renderer/components/dock/dock.tsx +++ b/src/renderer/components/dock/dock.tsx @@ -20,7 +20,6 @@ */ import "./dock.scss"; - import React from "react"; import { observer } from "mobx-react"; import { cssNames, prevDefault } from "../../utils"; @@ -62,15 +61,15 @@ export class Dock extends React.Component { open(); selectTab(tab.id); }; - + render() { const { className } = this.props; const { isOpen, toggle, tabs, toggleFillSize, selectedTab, hasTabs, fullSize, height } = dockStore; - const TabContent = dockViewsManager.get(selectedTab?.kind)?.tabContent ?? React.Fragment; + const DockTabContent = dockViewsManager.get(selectedTab?.kind)?.tabContent; return (
@@ -121,8 +120,8 @@ export class Dock extends React.Component { )}
-
- +
+ {DockTabContent && }
); diff --git a/src/renderer/components/dock/terminal.store.ts b/src/renderer/components/dock/terminal.store.ts index b5116a94e8..36102828e0 100644 --- a/src/renderer/components/dock/terminal.store.ts +++ b/src/renderer/components/dock/terminal.store.ts @@ -19,8 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { autorun, observable, when } from "mobx"; -import { autoBind, noop, Singleton } from "../../utils"; +import { action, makeObservable, observable, when } from "mobx"; +import { autoBind, disposer, noop, Singleton } from "../../utils"; import { Terminal } from "./terminal"; import { TerminalApi } from "../../api/terminal-api"; import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; @@ -40,29 +40,30 @@ export function createTerminalTab(tabParams: DockTabCreateSpecific = {}) { } export class TerminalStore extends Singleton { - protected terminals = new Map(); + protected terminals = observable.map(); protected connections = observable.map(); + public dispose = disposer(); constructor() { super(); + makeObservable(this); autoBind(this); + this.init(); + } - // connect active tab - autorun(() => { - const { selectedTab, isOpen } = dockStore; + protected init() { + // connect active dock tab + this.dispose.push( + dockStore.onTabChange(({ tabId }) => this.connect(tabId), { + tabKind: TabKind.TERMINAL, + fireImmediately: true, + }), + ); - if (selectedTab?.kind === TabKind.TERMINAL && isOpen) { - this.connect(selectedTab.id); - } - }); - // disconnect closed tabs - autorun(() => { - const currentTabs = dockStore.tabs.map(tab => tab.id); - - for (const [tabId] of this.connections) { - if (!currentTabs.includes(tabId)) this.disconnect(tabId); - } - }); + // disconnect terminals for closed dock tabs (if any) + this.dispose.push( + dockStore.onTabClose(({ tabId }) => this.disconnect(tabId)), + ); } connect(tabId: TabId) { @@ -80,6 +81,7 @@ export class TerminalStore extends Singleton { this.terminals.set(tabId, terminal); } + @action disconnect(tabId: TabId) { if (!this.isConnected(tabId)) { return; @@ -165,7 +167,7 @@ export const terminalStore = new Proxy({}, { const res = (ts as any)?.[p]; if (typeof res === "function") { - return function(...args: any[]) { + return function (...args: any[]) { return res.apply(ts, args); }; }