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.
*/
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<T extends {}> {
}
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<T extends {}> {
}
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();

View File

@ -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;

View File

@ -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);
}

View File

@ -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<Props> {
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 (
<div
className={cssNames("Dock", className, { isOpen, fullSize })}
className={cssNames("Dock", className, { isOpen, isClosed: !isOpen, fullSize })}
onKeyDown={this.onKeydown}
tabIndex={-1}
>
@ -121,8 +120,8 @@ export class Dock extends React.Component<Props> {
)}
</div>
</div>
<div className="tab-content" style={{ flexBasis: height }}>
<TabContent tab={selectedTab}/>
<div className="tab-content" style={{ flexBasis: isOpen ? height : 0 }}>
{DockTabContent && <DockTabContent tab={selectedTab}/>}
</div>
</div>
);

View File

@ -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<TabId, Terminal>();
protected terminals = observable.map<TabId, Terminal>();
protected connections = observable.map<TabId, TerminalApi>();
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);
};
}