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:
parent
dfd1f06977
commit
f86fc022f3
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user