1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

- fixed animation for open/close <Dock/>'s UI state

- added helper dockStore.onTabClose(): IReactionDisposer for easier clean up

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-10-08 13:55:49 +03:00
parent dfd1f06977
commit f86fc022f3
5 changed files with 62 additions and 69 deletions

View File

@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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 { autoBind, createStorage, disposer, StorageHelper } from "../../utils";
import { dockStore, TabId } from "./dock.store"; import { dockStore, TabId } from "./dock.store";
@ -52,8 +52,8 @@ export class DockTabStore<T extends {}> {
} }
protected constructor(protected options: DockTabStoreOptions = {}) { protected constructor(protected options: DockTabStoreOptions = {}) {
makeObservable(this); // must be called *before* autoBind() when used with mobx's method decorators
autoBind(this); autoBind(this);
makeObservable(this);
this.options = { this.options = {
autoInit: true, autoInit: true,
@ -78,44 +78,34 @@ export class DockTabStore<T extends {}> {
} }
protected init() { 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.dataReady = true;
this.dispose.push(
dockStore.onTabClose(({ tabId }) => this.clearData(tabId)),
);
} }
getData(tabId: TabId): T { getData(tabId: TabId): T {
return this.data[tabId]; return this.data[tabId];
} }
@action
setData(tabId: TabId, data: T) { setData(tabId: TabId, data: T) {
this.data[tabId] = data; this.data[tabId] = data;
} }
@action
clearData(tabId: TabId) { clearData(tabId: TabId) {
delete this.data[tabId]; delete this.data[tabId];
} }
@action
reset() { reset() {
this.data = {}; this.data = {};
this.dataReady = false; this.dataReady = false;
} }
@action
destroy() { destroy() {
this.reset(); this.reset();
this.dispose(); this.dispose();

View File

@ -38,7 +38,7 @@
} }
} }
&:not(.isOpen) { &.isClosed {
height: auto !important; height: auto !important;
.Tab { .Tab {
@ -71,7 +71,7 @@
background: $terminalBackground; background: $terminalBackground;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
transition: flex 60ms ease-in-out; transition: flex-basis 25ms ease-in;
> *:not(.Spinner) { > *:not(.Spinner) {
position: absolute; position: absolute;

View File

@ -20,8 +20,8 @@
*/ */
import * as uuid from "uuid"; import * as uuid from "uuid";
import { action, comparer, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx"; import { action, comparer, computed, IReactionOptions, makeObservable, observable, reaction, runInAction } from "mobx";
import { autoBind, createStorage, Disposer } from "../../utils"; import { autoBind, createStorage } from "../../utils";
import throttle from "lodash/throttle"; import throttle from "lodash/throttle";
export type TabId = string; export type TabId = string;
@ -89,16 +89,20 @@ export interface DockStorageState {
} }
export interface DockTabChangeEvent { export interface DockTabChangeEvent {
selectedTabId?: TabId; tabId?: TabId;
tab?: DockTab; tab?: DockTab;
prevTab?: DockTab; prevTab?: DockTab;
} }
export interface DockTabChangeEventOptions extends IReactionOptions { 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) 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 { export class DockStore implements DockStorageState {
constructor() { constructor() {
makeObservable(this); makeObservable(this);
@ -189,37 +193,35 @@ export class DockStore implements DockStorageState {
return reaction(() => [this.height, this.fullSize], callback, options); return reaction(() => [this.height, this.fullSize], callback, options);
} }
onTabClose(tabId: TabId, callback: () => void, options: IReactionOptions = {}): Disposer { onTabClose(callback: (evt: DockTabCloseEvent) => void, options: IReactionOptions = {}) {
let disposed = false; 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 => { const closedTabs: TabId[] = prevTabs.filter(id => !tabs.includes(id));
if (!tab) {
disposed = true; if (closedTabs.length > 0) {
stopWatcher(); runInAction(() => {
callback(); closedTabs.forEach(tabId => callback({ tabId }));
});
} }
}, { }, {
fireImmediately: true, equals: comparer.structural,
equals: comparer.shallow,
...options, ...options,
}); });
return () => {
if (disposed) return;
stopWatcher();
};
} }
onTabChange(callback?: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) { 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) => { return reaction(() => this.selectedTab, ((tab, prevTab) => {
if (kind && kind !== tab.kind) return; if (tabKind && tabKind !== tab.kind) return;
if (dockIsVisible && !dockStore.isOpen) return; if (dockIsVisible && !this.isOpen) return;
callback({ callback({
tab, prevTab, tab, prevTab,
selectedTabId: this.selectedTabId, tabId: this.selectedTabId,
}); });
}), reactionOpts); }), reactionOpts);
} }

View File

@ -20,7 +20,6 @@
*/ */
import "./dock.scss"; import "./dock.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { cssNames, prevDefault } from "../../utils"; import { cssNames, prevDefault } from "../../utils";
@ -66,11 +65,11 @@ export class Dock extends React.Component<Props> {
render() { render() {
const { className } = this.props; const { className } = this.props;
const { isOpen, toggle, tabs, toggleFillSize, selectedTab, hasTabs, fullSize, height } = dockStore; 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 ( return (
<div <div
className={cssNames("Dock", className, { isOpen, fullSize })} className={cssNames("Dock", className, { isOpen, isClosed: !isOpen, fullSize })}
onKeyDown={this.onKeydown} onKeyDown={this.onKeydown}
tabIndex={-1} tabIndex={-1}
> >
@ -121,8 +120,8 @@ export class Dock extends React.Component<Props> {
)} )}
</div> </div>
</div> </div>
<div className="tab-content" style={{ flexBasis: height }}> <div className="tab-content" style={{ flexBasis: isOpen ? height : 0 }}>
<TabContent tab={selectedTab}/> {DockTabContent && <DockTabContent tab={selectedTab}/>}
</div> </div>
</div> </div>
); );

View File

@ -19,8 +19,8 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { autorun, observable, when } from "mobx"; import { action, makeObservable, observable, when } from "mobx";
import { autoBind, noop, Singleton } from "../../utils"; import { autoBind, disposer, noop, Singleton } from "../../utils";
import { Terminal } from "./terminal"; import { Terminal } from "./terminal";
import { TerminalApi } from "../../api/terminal-api"; import { TerminalApi } from "../../api/terminal-api";
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
@ -40,29 +40,30 @@ export function createTerminalTab(tabParams: DockTabCreateSpecific = {}) {
} }
export class TerminalStore extends Singleton { export class TerminalStore extends Singleton {
protected terminals = new Map<TabId, Terminal>(); protected terminals = observable.map<TabId, Terminal>();
protected connections = observable.map<TabId, TerminalApi>(); protected connections = observable.map<TabId, TerminalApi>();
public dispose = disposer();
constructor() { constructor() {
super(); super();
makeObservable(this);
autoBind(this); autoBind(this);
this.init();
// connect active tab
autorun(() => {
const { selectedTab, isOpen } = dockStore;
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) { protected init() {
if (!currentTabs.includes(tabId)) this.disconnect(tabId); // connect active dock tab
} this.dispose.push(
}); dockStore.onTabChange(({ tabId }) => this.connect(tabId), {
tabKind: TabKind.TERMINAL,
fireImmediately: true,
}),
);
// disconnect terminals for closed dock tabs (if any)
this.dispose.push(
dockStore.onTabClose(({ tabId }) => this.disconnect(tabId)),
);
} }
connect(tabId: TabId) { connect(tabId: TabId) {
@ -80,6 +81,7 @@ export class TerminalStore extends Singleton {
this.terminals.set(tabId, terminal); this.terminals.set(tabId, terminal);
} }
@action
disconnect(tabId: TabId) { disconnect(tabId: TabId) {
if (!this.isConnected(tabId)) { if (!this.isConnected(tabId)) {
return; return;
@ -165,7 +167,7 @@ export const terminalStore = new Proxy({}, {
const res = (ts as any)?.[p]; const res = (ts as any)?.[p];
if (typeof res === "function") { if (typeof res === "function") {
return function(...args: any[]) { return function (...args: any[]) {
return res.apply(ts, args); return res.apply(ts, args);
}; };
} }