mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-29 00:47:01 +00:00
Keep active tab state in one place (manager) (#683)
With this change TabsBase objects will retain a reference to the TabsManager they belong to, so they can ask it if they're the active tab or not. This removes the possibility for bugs like activating an unmanaged tab, or having more than one active tab, etc.
This commit is contained in:
parent
a9fd01f9b4
commit
e0060b12e5
@ -276,7 +276,6 @@ export interface TabOptions {
|
|||||||
tabKind: CollectionTabKind;
|
tabKind: CollectionTabKind;
|
||||||
title: string;
|
title: string;
|
||||||
tabPath: string;
|
tabPath: string;
|
||||||
isActive: ko.Observable<boolean>;
|
|
||||||
hashLocation: string;
|
hashLocation: string;
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
||||||
isTabsContentExpanded?: ko.Observable<boolean>;
|
isTabsContentExpanded?: ko.Observable<boolean>;
|
||||||
|
@ -39,7 +39,6 @@ describe("SettingsComponent", () => {
|
|||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
hashLocation: "settings",
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: undefined,
|
onUpdateTabsButtons: undefined,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -559,6 +559,12 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
||||||
|
this.tabsManager.openedTabs.subscribe((tabs) => {
|
||||||
|
if (tabs.length === 0) {
|
||||||
|
this.selectedNode(undefined);
|
||||||
|
this.onUpdateTabsButtons([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._panes = [
|
this._panes = [
|
||||||
this.addDatabasePane,
|
this.addDatabasePane,
|
||||||
@ -1570,7 +1576,6 @@ export default class Explorer {
|
|||||||
collection: null,
|
collection: null,
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
hashLocation: "notebooks",
|
hashLocation: "notebooks",
|
||||||
isActive: ko.observable(false),
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
||||||
@ -1976,7 +1981,6 @@ export default class Explorer {
|
|||||||
tabPath: title,
|
tabPath: title,
|
||||||
collection: null,
|
collection: null,
|
||||||
hashLocation: hashLocation,
|
hashLocation: hashLocation,
|
||||||
isActive: ko.observable(false),
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
||||||
|
@ -23,8 +23,8 @@ export class CommandBarComponentAdapter implements ReactAdapter {
|
|||||||
constructor(container: Explorer) {
|
constructor(container: Explorer) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.tabsButtons = [];
|
this.tabsButtons = [];
|
||||||
this.isNotebookTabActive = ko.computed(() =>
|
this.isNotebookTabActive = ko.computed(
|
||||||
container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
|
() => container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||||
);
|
);
|
||||||
|
|
||||||
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
|
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
|
||||||
|
@ -16,8 +16,6 @@ describe("Documents tab", () => {
|
|||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,8 +87,6 @@ describe("Documents tab", () => {
|
|||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,8 +102,6 @@ describe("Documents tab", () => {
|
|||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -123,8 +117,6 @@ describe("Documents tab", () => {
|
|||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -140,8 +132,6 @@ describe("Documents tab", () => {
|
|||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -157,8 +147,6 @@ describe("Documents tab", () => {
|
|||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,7 +88,6 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
public onCloseTabButtonClick(): Q.Promise<any> {
|
public onCloseTabButtonClick(): Q.Promise<any> {
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
this.notebookComponentAdapter.notebookShutdown();
|
this.notebookComponentAdapter.notebookShutdown();
|
||||||
this.isActive(false);
|
|
||||||
super.onCloseTabButtonClick();
|
super.onCloseTabButtonClick();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ describe("Query Tab", () => {
|
|||||||
database: database,
|
database: database,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { RouteHandler } from "../../RouteHandlers/RouteHandler";
|
|
||||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as ThemeUtility from "../../Common/ThemeUtility";
|
import * as ThemeUtility from "../../Common/ThemeUtility";
|
||||||
import Explorer from "../Explorer";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { RouteHandler } from "../../RouteHandlers/RouteHandler";
|
||||||
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||||
|
import { TabsManager } from "./TabsManager";
|
||||||
|
|
||||||
// TODO: Use specific actions for logging telemetry data
|
// TODO: Use specific actions for logging telemetry data
|
||||||
export default class TabsBase extends WaitsForTemplateViewModel {
|
export default class TabsBase extends WaitsForTemplateViewModel {
|
||||||
@ -20,18 +21,16 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
public database: ViewModels.Database;
|
public database: ViewModels.Database;
|
||||||
public rid: string;
|
public rid: string;
|
||||||
public hasFocus: ko.Observable<boolean>;
|
public hasFocus: ko.Observable<boolean>;
|
||||||
public isActive: ko.Observable<boolean>;
|
|
||||||
public isMouseOver: ko.Observable<boolean>;
|
public isMouseOver: ko.Observable<boolean>;
|
||||||
public tabId: string;
|
public tabId: string;
|
||||||
public tabKind: ViewModels.CollectionTabKind;
|
public tabKind: ViewModels.CollectionTabKind;
|
||||||
public tabTitle: ko.Observable<string>;
|
public tabTitle: ko.Observable<string>;
|
||||||
public tabPath: ko.Observable<string>;
|
public tabPath: ko.Observable<string>;
|
||||||
public closeButtonTabIndex: ko.Computed<number>;
|
|
||||||
public errorDetailsTabIndex: ko.Computed<number>;
|
|
||||||
public hashLocation: ko.Observable<string>;
|
public hashLocation: ko.Observable<string>;
|
||||||
public isExecutionError: ko.Observable<boolean>;
|
public isExecutionError: ko.Observable<boolean>;
|
||||||
public isExecuting: ko.Observable<boolean>;
|
public isExecuting: ko.Observable<boolean>;
|
||||||
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
||||||
|
public manager?: TabsManager;
|
||||||
|
|
||||||
protected _theme: string;
|
protected _theme: string;
|
||||||
public onLoadStartKey: number;
|
public onLoadStartKey: number;
|
||||||
@ -46,7 +45,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
this.database = options.database;
|
this.database = options.database;
|
||||||
this.rid = options.rid || (this.collection && this.collection.rid) || "";
|
this.rid = options.rid || (this.collection && this.collection.rid) || "";
|
||||||
this.hasFocus = ko.observable<boolean>(false);
|
this.hasFocus = ko.observable<boolean>(false);
|
||||||
this.isActive = options.isActive || ko.observable<boolean>(false);
|
|
||||||
this.isMouseOver = ko.observable<boolean>(false);
|
this.isMouseOver = ko.observable<boolean>(false);
|
||||||
this.tabId = `tab${id}`;
|
this.tabId = `tab${id}`;
|
||||||
this.tabKind = options.tabKind;
|
this.tabKind = options.tabKind;
|
||||||
@ -55,21 +53,12 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
(options.tabPath && ko.observable<string>(options.tabPath)) ||
|
(options.tabPath && ko.observable<string>(options.tabPath)) ||
|
||||||
(this.collection &&
|
(this.collection &&
|
||||||
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
|
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
|
||||||
this.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
|
|
||||||
this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
|
|
||||||
this.isExecutionError = ko.observable<boolean>(false);
|
this.isExecutionError = ko.observable<boolean>(false);
|
||||||
this.isExecuting = ko.observable<boolean>(false);
|
this.isExecuting = ko.observable<boolean>(false);
|
||||||
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
|
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
|
||||||
this.onLoadStartKey = options.onLoadStartKey;
|
this.onLoadStartKey = options.onLoadStartKey;
|
||||||
this.hashLocation = ko.observable<string>(options.hashLocation || "");
|
this.hashLocation = ko.observable<string>(options.hashLocation || "");
|
||||||
this.hashLocation.subscribe((newLocation: string) => this.updateGlobalHash(newLocation));
|
this.hashLocation.subscribe((newLocation: string) => this.updateGlobalHash(newLocation));
|
||||||
|
|
||||||
this.isActive.subscribe((isActive: boolean) => {
|
|
||||||
if (isActive) {
|
|
||||||
this.onActivate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.closeTabButton = {
|
this.closeTabButton = {
|
||||||
enabled: ko.computed<boolean>(() => {
|
enabled: ko.computed<boolean>(() => {
|
||||||
return true;
|
return true;
|
||||||
@ -82,12 +71,9 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
const explorer = this.getContainer();
|
this.manager?.closeTab(this);
|
||||||
explorer.tabsManager.closeTab(this.tabId, explorer);
|
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
||||||
tabName: this.constructor.name,
|
tabName: this.constructor.name,
|
||||||
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.tabTitle(),
|
tabTitle: this.tabTitle(),
|
||||||
tabId: this.tabId,
|
tabId: this.tabId,
|
||||||
@ -95,7 +81,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.getContainer().tabsManager.activateTab(this);
|
this.manager?.activateTab(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateSelectedNode(): void {
|
protected updateSelectedNode(): void {
|
||||||
@ -127,6 +113,11 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
|
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */
|
||||||
|
public isActive() {
|
||||||
|
return this === this.manager?.activeTab();
|
||||||
|
}
|
||||||
|
|
||||||
public onActivate(): void {
|
public onActivate(): void {
|
||||||
this.updateSelectedNode();
|
this.updateSelectedNode();
|
||||||
if (!!this.collection) {
|
if (!!this.collection) {
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
<div id="content" class="flexContainer hideOverflows" data-bind="visible: openedTabs && openedTabs().length > 0">
|
<div
|
||||||
|
id="content"
|
||||||
|
class="flexContainer hideOverflows"
|
||||||
|
data-bind="visible: activeTab() && openedTabs && openedTabs().length > 0"
|
||||||
|
>
|
||||||
<!-- Tabs - Start -->
|
<!-- Tabs - Start -->
|
||||||
<div class="nav-tabs-margin">
|
<div class="nav-tabs-margin">
|
||||||
<ul class="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
<ul class="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||||
@ -8,12 +12,12 @@
|
|||||||
data-bind="
|
data-bind="
|
||||||
attr: {
|
attr: {
|
||||||
title: $data.tabPath,
|
title: $data.tabPath,
|
||||||
'aria-selected': $data.isActive,
|
'aria-selected': $parent.activeTab() === $data,
|
||||||
'aria-expanded': $data.isActive,
|
'aria-expanded': $parent.activeTab() === $data,
|
||||||
'aria-controls': $data.tabId
|
'aria-controls': $data.tabId
|
||||||
},
|
},
|
||||||
css:{
|
css:{
|
||||||
active: $data.isActive
|
active: $parent.activeTab() === $data
|
||||||
},
|
},
|
||||||
hasFocus: $data.hasFocus,
|
hasFocus: $data.hasFocus,
|
||||||
event: { keypress: onKeyPressActivate },
|
event: { keypress: onKeyPressActivate },
|
||||||
@ -33,8 +37,8 @@
|
|||||||
data-bind="
|
data-bind="
|
||||||
click: onErrorDetailsClick,
|
click: onErrorDetailsClick,
|
||||||
event: { keypress: onErrorDetailsKeyPress },
|
event: { keypress: onErrorDetailsKeyPress },
|
||||||
attr: { tabindex: errorDetailsTabIndex },
|
attr: { tabindex: $parent.activeTab() === $data ? 0 : null },
|
||||||
css: { actionsEnabled: isActive },
|
css: { actionsEnabled: $parent.activeTab() === $data },
|
||||||
visible: isExecutionError"
|
visible: isExecutionError"
|
||||||
>
|
>
|
||||||
<span class="errorIcon"></span>
|
<span class="errorIcon"></span>
|
||||||
@ -56,11 +60,14 @@
|
|||||||
data-bind="
|
data-bind="
|
||||||
click: $data.onCloseTabButtonClick,
|
click: $data.onCloseTabButtonClick,
|
||||||
event: { keypress: onKeyPressClose },
|
event: { keypress: onKeyPressClose },
|
||||||
attr: { tabindex: $data.closeButtonTabIndex },
|
attr: { tabindex: $parent.activeTab() === $data ? 0 : null },
|
||||||
visible: $data.isActive() || $data.isMouseOver()"
|
visible: $parent.activeTab() === $data || $data.isMouseOver()"
|
||||||
title="Close"
|
title="Close"
|
||||||
>
|
>
|
||||||
<span class="tabIcon close-Icon" data-bind="visible: $data.isActive() || $data.isMouseOver()">
|
<span
|
||||||
|
class="tabIcon close-Icon"
|
||||||
|
data-bind="visible: $parent.activeTab() === $data || $data.isMouseOver()"
|
||||||
|
>
|
||||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -77,7 +84,7 @@
|
|||||||
<!-- Tabs Panes -- Start -->
|
<!-- Tabs Panes -- Start -->
|
||||||
<div class="tabPanesContainer">
|
<div class="tabPanesContainer">
|
||||||
<!-- ko foreach: openedTabs -->
|
<!-- ko foreach: openedTabs -->
|
||||||
<div class="tabs-container" data-bind="visible: $data.isActive">
|
<div class="tabs-container" data-bind="visible: $parent.activeTab() === $data">
|
||||||
<span
|
<span
|
||||||
data-bind="class: $data.constructor.component.name, component: { name: $data.constructor.component.name, params: $data }"
|
data-bind="class: $data.constructor.component.name, component: { name: $data.constructor.component.name, params: $data }"
|
||||||
></span>
|
></span>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { TabsManager } from "./TabsManager";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import DocumentsTab from "./DocumentsTab";
|
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import QueryTab from "./QueryTab";
|
|
||||||
import DocumentId from "../Tree/DocumentId";
|
import DocumentId from "../Tree/DocumentId";
|
||||||
|
import DocumentsTab from "./DocumentsTab";
|
||||||
|
import QueryTab from "./QueryTab";
|
||||||
|
import { TabsManager } from "./TabsManager";
|
||||||
|
|
||||||
describe("Tabs manager tests", () => {
|
describe("Tabs manager tests", () => {
|
||||||
let tabsManager: TabsManager;
|
let tabsManager: TabsManager;
|
||||||
@ -50,7 +50,6 @@ describe("Tabs manager tests", () => {
|
|||||||
database,
|
database,
|
||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
onUpdateTabsButtons: undefined,
|
onUpdateTabsButtons: undefined,
|
||||||
});
|
});
|
||||||
@ -63,7 +62,6 @@ describe("Tabs manager tests", () => {
|
|||||||
title: "",
|
title: "",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
hashLocation: "",
|
hashLocation: "",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
onUpdateTabsButtons: undefined,
|
onUpdateTabsButtons: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,10 +70,7 @@ describe("Tabs manager tests", () => {
|
|||||||
documentsTab.tabId = "2";
|
documentsTab.tabId = "2";
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => (tabsManager = new TabsManager()));
|
||||||
tabsManager = new TabsManager();
|
|
||||||
explorer.tabsManager = tabsManager;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("open new tabs", () => {
|
it("open new tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
tabsManager.activateNewTab(queryTab);
|
||||||
@ -122,7 +117,7 @@ describe("Tabs manager tests", () => {
|
|||||||
tabsManager.activateNewTab(queryTab);
|
tabsManager.activateNewTab(queryTab);
|
||||||
tabsManager.activateNewTab(documentsTab);
|
tabsManager.activateNewTab(documentsTab);
|
||||||
|
|
||||||
tabsManager.closeTab(documentsTab.tabId, explorer);
|
tabsManager.closeTab(documentsTab);
|
||||||
expect(tabsManager.openedTabs().length).toBe(1);
|
expect(tabsManager.openedTabs().length).toBe(1);
|
||||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
||||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
expect(tabsManager.activeTab()).toEqual(queryTab);
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
export class TabsManager {
|
export class TabsManager {
|
||||||
public openedTabs: ko.ObservableArray<TabsBase>;
|
public openedTabs = ko.observableArray<TabsBase>([]);
|
||||||
public activeTab: ko.Observable<TabsBase>;
|
public activeTab = ko.observable<TabsBase>();
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.openedTabs = ko.observableArray<TabsBase>([]);
|
|
||||||
this.activeTab = ko.observable<TabsBase>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public activateNewTab(tab: TabsBase): void {
|
public activateNewTab(tab: TabsBase): void {
|
||||||
this.openedTabs.push(tab);
|
this.openedTabs.push(tab);
|
||||||
@ -18,66 +12,43 @@ export class TabsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public activateTab(tab: TabsBase): void {
|
public activateTab(tab: TabsBase): void {
|
||||||
this.activeTab() && this.activeTab().isActive(false);
|
if (this.openedTabs().includes(tab)) {
|
||||||
tab.isActive(true);
|
tab.manager = this;
|
||||||
this.activeTab(tab);
|
this.activeTab(tab);
|
||||||
|
tab.onActivate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTabs(tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] {
|
public getTabs(tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] {
|
||||||
return this.openedTabs().filter((openedTab: TabsBase) => {
|
return this.openedTabs().filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab)));
|
||||||
return openedTab.tabKind === tabKind && (!comparator || comparator(openedTab));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshActiveTab(comparator: (tab: TabsBase) => boolean): void {
|
public refreshActiveTab(comparator: (tab: TabsBase) => boolean): void {
|
||||||
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
||||||
this.openedTabs().forEach((tab: TabsBase) => {
|
this.activeTab() && comparator(this.activeTab()) && this.activeTab().onActivate();
|
||||||
if (comparator(tab) && tab.isActive()) {
|
|
||||||
tab.onActivate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeTabById(tabId: string): void {
|
|
||||||
this.openedTabs.remove((tab: TabsBase) => tab.tabId === tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeTabByComparator(comparator: (tab: TabsBase) => boolean): void {
|
|
||||||
this.openedTabs.remove((tab: TabsBase) => comparator(tab));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeTabsByComparator(comparator: (tab: TabsBase) => boolean): void {
|
public closeTabsByComparator(comparator: (tab: TabsBase) => boolean): void {
|
||||||
this.activeTab() && this.activeTab().isActive(false);
|
this.openedTabs()
|
||||||
this.activeTab(undefined);
|
.filter(comparator)
|
||||||
this.openedTabs().forEach((tab: TabsBase) => {
|
.forEach((tab) => tab.onCloseTabButtonClick());
|
||||||
if (comparator(tab)) {
|
|
||||||
tab.onCloseTabButtonClick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeTabs(): void {
|
public closeTab(tab: TabsBase): void {
|
||||||
this.openedTabs([]);
|
const tabIndex = this.openedTabs().indexOf(tab);
|
||||||
}
|
|
||||||
|
|
||||||
public closeTab(tabId: string, explorer: Explorer): void {
|
|
||||||
const tabIndex: number = this.openedTabs().findIndex((tab: TabsBase) => tab.tabId === tabId);
|
|
||||||
if (tabIndex !== -1) {
|
if (tabIndex !== -1) {
|
||||||
const tabToActive: TabsBase = this.openedTabs()[tabIndex + 1] || this.openedTabs()[tabIndex - 1];
|
this.openedTabs.remove(tab);
|
||||||
this.openedTabs()[tabIndex].isActive(false);
|
tab.manager = undefined;
|
||||||
this.removeTabById(tabId);
|
|
||||||
if (tabToActive) {
|
if (this.openedTabs().length === 0) {
|
||||||
tabToActive.isActive(true);
|
|
||||||
this.activeTab(tabToActive);
|
|
||||||
} else {
|
|
||||||
explorer.selectedNode(undefined);
|
|
||||||
explorer.onUpdateTabsButtons([]);
|
|
||||||
this.activeTab(undefined);
|
this.activeTab(undefined);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public isTabActive(tabKind: ViewModels.CollectionTabKind): boolean {
|
if (tab === this.activeTab()) {
|
||||||
return this.activeTab() && this.activeTab().tabKind === tabKind;
|
const tabToTheRight = this.openedTabs()[tabIndex];
|
||||||
|
const lastOpenTab = this.openedTabs()[this.openedTabs().length - 1];
|
||||||
|
this.activateTab(tabToTheRight ?? lastOpenTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,7 +301,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
documentIds: ko.observableArray<DocumentId>([]),
|
documentIds: ko.observableArray<DocumentId>([]),
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "Items",
|
title: "Items",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
||||||
@ -349,7 +348,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
conflictIds: ko.observableArray<ConflictId>([]),
|
conflictIds: ko.observableArray<ConflictId>([]),
|
||||||
tabKind: ViewModels.CollectionTabKind.Conflicts,
|
tabKind: ViewModels.CollectionTabKind.Conflicts,
|
||||||
title: "Conflicts",
|
title: "Conflicts",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
|
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
|
||||||
@ -406,12 +404,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabKind: ViewModels.CollectionTabKind.QueryTables,
|
tabKind: ViewModels.CollectionTabKind.QueryTables,
|
||||||
title: title,
|
title: title,
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
|
|
||||||
collection: this,
|
collection: this,
|
||||||
|
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
@ -463,7 +458,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
||||||
collectionId: this.id(),
|
collectionId: this.id(),
|
||||||
isActive: ko.observable(false),
|
|
||||||
databaseId: this.databaseId,
|
databaseId: this.databaseId,
|
||||||
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
@ -513,7 +507,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
@ -556,7 +549,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -599,7 +591,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
queryText: queryText,
|
queryText: queryText,
|
||||||
partitionKey: collection.partitionKey,
|
partitionKey: collection.partitionKey,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
@ -629,7 +620,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoQuery`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoQuery`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
partitionKey: collection.partitionKey,
|
partitionKey: collection.partitionKey,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
@ -661,7 +651,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
||||||
collectionId: this.id(),
|
collectionId: this.id(),
|
||||||
isActive: ko.observable(false),
|
|
||||||
databaseId: this.databaseId,
|
databaseId: this.databaseId,
|
||||||
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
@ -680,7 +669,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -78,7 +78,6 @@ export default class Database implements ViewModels.Database {
|
|||||||
rid: this.rid,
|
rid: this.rid,
|
||||||
database: this,
|
database: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
|
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
};
|
};
|
||||||
|
@ -92,7 +92,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
queryText: queryText,
|
queryText: queryText,
|
||||||
partitionKey: collection.partitionKey,
|
partitionKey: collection.partitionKey,
|
||||||
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
|
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
|
||||||
@ -139,7 +138,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
documentIds: ko.observableArray<DocumentId>([]),
|
documentIds: ko.observableArray<DocumentId>([]),
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
title: "Items",
|
title: "Items",
|
||||||
isActive: ko.observable<boolean>(false),
|
|
||||||
collection: this,
|
collection: this,
|
||||||
node: this,
|
node: this,
|
||||||
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
||||||
|
@ -76,7 +76,6 @@ export default class StoredProcedure {
|
|||||||
collection: source,
|
collection: source,
|
||||||
node: source,
|
node: source,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -123,7 +122,6 @@ export default class StoredProcedure {
|
|||||||
this.collection.databaseId,
|
this.collection.databaseId,
|
||||||
this.collection.id()
|
this.collection.id()
|
||||||
)}/sprocs/${this.id()}`,
|
)}/sprocs/${this.id()}`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -138,7 +136,7 @@ export default class StoredProcedure {
|
|||||||
|
|
||||||
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
|
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.tabsManager.removeTabByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
this.container.tabsManager.closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
||||||
this.collection.children.remove(this);
|
this.collection.children.remove(this);
|
||||||
},
|
},
|
||||||
(reason) => {}
|
(reason) => {}
|
||||||
|
@ -58,7 +58,6 @@ export default class Trigger {
|
|||||||
collection: source,
|
collection: source,
|
||||||
node: source,
|
node: source,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -98,7 +97,6 @@ export default class Trigger {
|
|||||||
this.collection.databaseId,
|
this.collection.databaseId,
|
||||||
this.collection.id()
|
this.collection.id()
|
||||||
)}/triggers/${this.id()}`,
|
)}/triggers/${this.id()}`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,7 +111,7 @@ export default class Trigger {
|
|||||||
|
|
||||||
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
|
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.tabsManager.removeTabByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||||
this.collection.children.remove(this);
|
this.collection.children.remove(this);
|
||||||
},
|
},
|
||||||
(reason) => {}
|
(reason) => {}
|
||||||
|
@ -44,7 +44,6 @@ export default class UserDefinedFunction {
|
|||||||
collection: source,
|
collection: source,
|
||||||
node: source,
|
node: source,
|
||||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,7 +81,6 @@ export default class UserDefinedFunction {
|
|||||||
this.collection.databaseId,
|
this.collection.databaseId,
|
||||||
this.collection.id()
|
this.collection.id()
|
||||||
)}/udfs/${this.id()}`,
|
)}/udfs/${this.id()}`,
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,7 +104,7 @@ export default class UserDefinedFunction {
|
|||||||
|
|
||||||
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
|
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.tabsManager.removeTabByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||||
this.collection.children.remove(this);
|
this.collection.children.remove(this);
|
||||||
},
|
},
|
||||||
(reason) => {}
|
(reason) => {}
|
||||||
|
Loading…
Reference in New Issue
Block a user