{
}
/* Scale & Settings */
- const isShared = useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
+ const isShared = useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
const label = isShared ? "Settings" : "Scale & Settings";
items.push({
diff --git a/src/Explorer/Tables/Constants.ts b/src/Explorer/Tables/Constants.ts
index 5d5d60fee..9bb313b7a 100644
--- a/src/Explorer/Tables/Constants.ts
+++ b/src/Explorer/Tables/Constants.ts
@@ -1,4 +1,4 @@
-export var TableType = {
+export const TableType = {
String: "String",
Boolean: "Boolean",
Binary: "Binary",
@@ -9,7 +9,7 @@ export var TableType = {
Int64: "Int64",
};
-export var CassandraType = {
+export const CassandraType = {
Ascii: "Ascii",
Bigint: "Bigint",
Blob: "Blob",
@@ -27,12 +27,12 @@ export var CassandraType = {
Tinyint: "Tinyint",
};
-export var ClauseRule = {
+export const ClauseRule = {
And: "And",
Or: "Or",
};
-export var Operator = {
+export const Operator = {
EqualTo: "==",
GreaterThan: ">",
GreaterThanOrEqualTo: ">=",
@@ -42,7 +42,7 @@ export var Operator = {
Equal: "=",
};
-export var ODataOperator = {
+export const ODataOperator = {
EqualTo: "eq",
GreaterThan: "gt",
GreaterThanOrEqualTo: "ge",
@@ -51,7 +51,7 @@ export var ODataOperator = {
NotEqualTo: "ne",
};
-export var timeOptions = {
+export const timeOptions = {
lastHour: "Last hour",
last24Hours: "Last 24 hours",
last7Days: "Last 7 days",
@@ -62,7 +62,7 @@ export var timeOptions = {
custom: "Custom...",
};
-export var htmlSelectors = {
+export const htmlSelectors = {
dataTableSelector: "#storageTable",
dataTableAllRowsSelector: "#storageTable tbody tr",
dataTableHeadRowSelector: ".dataTable thead tr",
@@ -84,9 +84,9 @@ export var htmlSelectors = {
selectAllDropdownSelector: "#select-all-dropdown",
};
-export var defaultHeader = " ";
+export const defaultHeader = " ";
-export var EntityKeyNames = {
+export const EntityKeyNames = {
PartitionKey: "PartitionKey",
RowKey: "RowKey",
Timestamp: "Timestamp",
@@ -94,7 +94,7 @@ export var EntityKeyNames = {
Etag: "etag",
};
-export var htmlAttributeNames = {
+export const htmlAttributeNames = {
dataTableNameAttr: "name_attr",
dataTableContentTypeAttr: "contentType_attr",
dataTableSnapshotAttr: "snapshot_attr",
@@ -103,14 +103,14 @@ export var htmlAttributeNames = {
dataTableHeaderIndex: "data-column-index",
};
-export var cssColors = {
+export const cssColors = {
commonControlsButtonActive: "#B4C7DC" /* A darker shade of [{common-controls-button-hover-background}] */,
};
-export var clauseGroupColors = ["#ffe1ff", "#fffacd", "#f0ffff", "#ffefd5", "#f0fff0"];
-export var transparentColor = "transparent";
+export const clauseGroupColors = ["#ffe1ff", "#fffacd", "#f0ffff", "#ffefd5", "#f0fff0"];
+export const transparentColor = "transparent";
-export var keyCodes = {
+export const keyCodes = {
RightClick: 3,
Enter: 13,
Esc: 27,
@@ -163,7 +163,7 @@ export var keyCodes = {
Dash: 189,
};
-export var InputType = {
+export const InputType = {
Text: "text",
// Chrome doesn't support datetime, instead, datetime-local is supported.
DateTime: "datetime-local",
diff --git a/src/Explorer/Tabs/MongoShellTab/MongoShellTab.tsx b/src/Explorer/Tabs/MongoShellTab/MongoShellTab.tsx
index edec5ba6b..d40965849 100644
--- a/src/Explorer/Tabs/MongoShellTab/MongoShellTab.tsx
+++ b/src/Explorer/Tabs/MongoShellTab/MongoShellTab.tsx
@@ -1,6 +1,7 @@
import React from "react";
import * as DataModels from "../../../Contracts/DataModels";
import type { TabOptions } from "../../../Contracts/ViewModels";
+import { useTabs } from "../../../hooks/useTabs";
import Explorer from "../../Explorer";
import TabsBase from "../TabsBase";
import MongoShellTabComponent, { IMongoShellTabAccessor, IMongoShellTabComponentProps } from "./MongoShellTabComponent";
@@ -33,7 +34,7 @@ export class NewMongoShellTab extends TabsBase {
}
public onTabClick(): void {
- this.manager?.activateTab(this);
+ useTabs.getState().activateTab(this);
this.iMongoShellTabAccessor.onTabClickEvent();
}
}
diff --git a/src/Explorer/Tabs/QueryTab/QueryTab.tsx b/src/Explorer/Tabs/QueryTab/QueryTab.tsx
index 6a847c92f..11d609c87 100644
--- a/src/Explorer/Tabs/QueryTab/QueryTab.tsx
+++ b/src/Explorer/Tabs/QueryTab/QueryTab.tsx
@@ -1,6 +1,7 @@
import React from "react";
import * as DataModels from "../../../Contracts/DataModels";
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
+import { useTabs } from "../../../hooks/useTabs";
import Explorer from "../../Explorer";
import { IQueryTabComponentProps, ITabAccessor } from "../../Tabs/QueryTab/QueryTabComponent";
import TabsBase from "../TabsBase";
@@ -40,12 +41,12 @@ export class NewQueryTab extends TabsBase {
}
public onTabClick(): void {
- this.manager?.activateTab(this);
+ useTabs.getState().activateTab(this);
this.iTabAccessor.onTabClickEvent();
}
public onCloseTabButtonClick(): void {
- this.manager?.closeTab(this);
+ useTabs.getState().closeTab(this);
if (this.iTabAccessor) {
this.iTabAccessor.onCloseClickEvent(true);
}
diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx
index 73f5bf8b6..e8744441e 100644
--- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx
+++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx
@@ -29,7 +29,6 @@ import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import TabsBase from "../TabsBase";
-import { TabsManager } from "../TabsManager";
import "./QueryTabComponent.less";
enum ToggleState {
@@ -65,7 +64,6 @@ export interface IQueryTabComponentProps {
partitionKey: DataModels.PartitionKey;
container: Explorer;
activeTab?: TabsBase;
- tabManager?: TabsManager;
onTabAccessor: (instance: ITabAccessor) => void;
isPreferredApiMongoDB?: boolean;
monacoEditorSetting?: string;
diff --git a/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTab.tsx b/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTab.tsx
index 488944a05..b7b0f1673 100644
--- a/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTab.tsx
+++ b/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTab.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
+import { useTabs } from "../../../hooks/useTabs";
import Explorer from "../../Explorer";
import StoredProcedure from "../../Tree/StoredProcedure";
import ScriptTabBase from "../ScriptTabBase";
@@ -51,12 +52,12 @@ export class NewStoredProcedureTab extends ScriptTabBase {
}
public onTabClick(): void {
- this.manager?.activateTab(this);
+ useTabs.getState().activateTab(this);
this.iStoreProcAccessor.onTabClickEvent();
}
public onCloseTabButtonClick(): void {
- this.manager?.closeTab(this);
+ useTabs.getState().closeTab(this);
}
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
diff --git a/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTabComponent.tsx b/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTabComponent.tsx
index 71423e60e..b0d156b44 100644
--- a/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTabComponent.tsx
+++ b/src/Explorer/Tabs/StoredProcedureTab/StoredProcedureTabComponent.tsx
@@ -10,6 +10,7 @@ import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProc
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
import * as ViewModels from "../../../Contracts/ViewModels";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
+import { useTabs } from "../../../hooks/useTabs";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer";
@@ -144,7 +145,7 @@ export default class StoredProcedureTabComponent extends React.Component<
}
public onTabClick(): void {
- if (this.props.container.tabsManager.openedTabs().length > 0) {
+ if (useTabs.getState().openedTabs.length > 0) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
}
@@ -396,10 +397,8 @@ export default class StoredProcedureTabComponent extends React.Component<
editorModel && editorModel.setValue(createdResource.body as string);
this.props.scriptTabBaseInstance.editorContent.setBaseline(createdResource.body as string);
this.node = this.collection.createStoredProcedureNode(createdResource);
- this.props.container.tabsManager.openedTabs()[
- this.props.container.tabsManager.openedTabs().length - 1
- ].node = this.node;
-
+ this.props.scriptTabBaseInstance.node = this.node;
+ useTabs.getState().updateTab(this.props.scriptTabBaseInstance);
this.props.scriptTabBaseInstance.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
this.setState({
diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx
index 9c4de6477..caf13ef7d 100644
--- a/src/Explorer/Tabs/Tabs.tsx
+++ b/src/Explorer/Tabs/Tabs.tsx
@@ -3,28 +3,32 @@ import React, { useEffect, useRef, useState } from "react";
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
import errorIcon from "../../../images/close-black.svg";
import { useObservable } from "../../hooks/useObservable";
+import { useTabs } from "../../hooks/useTabs";
import TabsBase from "./TabsBase";
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
-export const Tabs = ({ tabs, activeTab }: { tabs: readonly Tab[]; activeTab: Tab }): JSX.Element => (
-
-
-
-
- {tabs.map((tab) => (
-
+export const Tabs = (): JSX.Element => {
+ const { openedTabs, activeTab } = useTabs();
+ return (
+
+
+
+
+ {openedTabs.map((tab) => (
+
+ ))}
+
+
+
+ {openedTabs.map((tab) => (
+
))}
-
-
-
- {tabs.map((tab) => (
-
- ))}
+
-
-);
+ );
+};
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
const [hovering, setHovering] = useState(false);
diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts
index ccedf002b..308bb451f 100644
--- a/src/Explorer/Tabs/TabsBase.ts
+++ b/src/Explorer/Tabs/TabsBase.ts
@@ -4,6 +4,7 @@ import * as ThemeUtility from "../../Common/ThemeUtility";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
+import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
@@ -11,7 +12,6 @@ import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { useSelectedNode } from "../useSelectedNode";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
-import { TabsManager } from "./TabsManager";
// TODO: Use specific actions for logging telemetry data
export default class TabsBase extends WaitsForTemplateViewModel {
private static id = 0;
@@ -28,7 +28,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
public isExecutionError = ko.observable(false);
public isExecuting = ko.observable(false);
public pendingNotification?: ko.Observable
;
- public manager?: TabsManager;
protected _theme: string;
public onLoadStartKey: number;
@@ -60,7 +59,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
}
public onCloseTabButtonClick(): void {
- this.manager?.closeTab(this);
+ useTabs.getState().closeTab(this);
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
tabName: this.constructor.name,
dataExplorerArea: Constants.Areas.Tab,
@@ -70,7 +69,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
}
public onTabClick(): void {
- this.manager?.activateTab(this);
+ useTabs.getState().activateTab(this);
}
protected updateSelectedNode(): void {
@@ -105,7 +104,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
/** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */
public isActive() {
- return this === this.manager?.activeTab();
+ return this === useTabs.getState().activeTab;
}
public onActivate(): void {
diff --git a/src/Explorer/Tabs/TabsManager.ts b/src/Explorer/Tabs/TabsManager.ts
deleted file mode 100644
index 95221a685..000000000
--- a/src/Explorer/Tabs/TabsManager.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as ko from "knockout";
-import * as ViewModels from "../../Contracts/ViewModels";
-import TabsBase from "./TabsBase";
-
-export class TabsManager {
- public openedTabs = ko.observableArray([]);
- public activeTab = ko.observable();
-
- public activateNewTab(tab: TabsBase): void {
- this.openedTabs.push(tab);
- this.activateTab(tab);
- }
-
- public activateTab(tab: TabsBase): void {
- if (this.openedTabs().includes(tab)) {
- tab.manager = this;
- this.activeTab(tab);
- tab.onActivate();
- }
- }
-
- public getTabs(tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] {
- return this.openedTabs().filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab)));
- }
-
- public refreshActiveTab(comparator: (tab: TabsBase) => boolean): void {
- // ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
- this.activeTab() && comparator(this.activeTab()) && this.activeTab().onActivate();
- }
-
- public closeTabsByComparator(comparator: (tab: TabsBase) => boolean): void {
- this.openedTabs()
- .filter(comparator)
- .forEach((tab) => tab.onCloseTabButtonClick());
- }
-
- public closeTab(tab: TabsBase): void {
- const tabIndex = this.openedTabs().indexOf(tab);
- if (tabIndex !== -1) {
- this.openedTabs.remove(tab);
- tab.manager = undefined;
-
- if (this.openedTabs().length === 0) {
- this.activeTab(undefined);
- }
-
- if (tab === this.activeTab()) {
- const tabToTheRight = this.openedTabs()[tabIndex];
- const lastOpenTab = this.openedTabs()[this.openedTabs().length - 1];
- this.activateTab(tabToTheRight ?? lastOpenTab);
- }
- }
- }
-}
diff --git a/src/Explorer/Tabs/TabsManager.test.ts b/src/Explorer/Tabs/useTabs.test.ts
similarity index 57%
rename from src/Explorer/Tabs/TabsManager.test.ts
rename to src/Explorer/Tabs/useTabs.test.ts
index 4106eba3c..8d8148c69 100644
--- a/src/Explorer/Tabs/TabsManager.test.ts
+++ b/src/Explorer/Tabs/useTabs.test.ts
@@ -1,23 +1,19 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { updateUserContext } from "../../UserContext";
-import Explorer from "../Explorer";
+import { container } from "../Controls/Settings/TestUtils";
import DocumentId from "../Tree/DocumentId";
-import { container } from "./../Controls/Settings/TestUtils";
import DocumentsTab from "./DocumentsTab";
import { NewQueryTab } from "./QueryTab/QueryTab";
-import { TabsManager } from "./TabsManager";
-describe("Tabs manager tests", () => {
- let tabsManager: TabsManager;
- let explorer: Explorer;
+describe("useTabs tests", () => {
let database: ViewModels.Database;
let collection: ViewModels.Collection;
let queryTab: NewQueryTab;
let documentsTab: DocumentsTab;
beforeEach(() => {
- explorer = new Explorer();
updateUserContext({
databaseAccount: {
id: "test",
@@ -30,7 +26,6 @@ describe("Tabs manager tests", () => {
});
database = {
- container: explorer,
id: ko.observable("test"),
isDatabaseShared: () => false,
} as ViewModels.Database;
@@ -38,7 +33,6 @@ describe("Tabs manager tests", () => {
database.selectedSubnodeKind = ko.observable();
collection = {
- container: explorer,
databaseId: "test",
id: ko.observable("test"),
} as ViewModels.Collection;
@@ -76,63 +70,70 @@ describe("Tabs manager tests", () => {
documentsTab.tabId = "2";
});
- beforeEach(() => (tabsManager = new TabsManager()));
+ beforeEach(() => useTabs.setState({ openedTabs: [], activeTab: undefined }));
it("open new tabs", () => {
- tabsManager.activateNewTab(queryTab);
- expect(tabsManager.openedTabs().length).toBe(1);
- expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
- expect(tabsManager.activeTab()).toEqual(queryTab);
+ const { activateNewTab } = useTabs.getState();
+ activateNewTab(queryTab);
+ let tabsState = useTabs.getState();
+ expect(tabsState.openedTabs.length).toBe(1);
+ expect(tabsState.openedTabs[0]).toEqual(queryTab);
+ expect(tabsState.activeTab).toEqual(queryTab);
expect(queryTab.isActive()).toBe(true);
- tabsManager.activateNewTab(documentsTab);
- expect(tabsManager.openedTabs().length).toBe(2);
- expect(tabsManager.openedTabs()[1]).toEqual(documentsTab);
- expect(tabsManager.activeTab()).toEqual(documentsTab);
+ activateNewTab(documentsTab);
+ tabsState = useTabs.getState();
+ expect(tabsState.openedTabs.length).toBe(2);
+ expect(tabsState.openedTabs[1]).toEqual(documentsTab);
+ expect(tabsState.activeTab).toEqual(documentsTab);
expect(queryTab.isActive()).toBe(false);
expect(documentsTab.isActive()).toBe(true);
});
it("open existing tabs", () => {
- tabsManager.activateNewTab(queryTab);
- tabsManager.activateNewTab(documentsTab);
- tabsManager.activateTab(queryTab);
- expect(tabsManager.openedTabs().length).toBe(2);
- expect(tabsManager.activeTab()).toEqual(queryTab);
+ const { activateNewTab, activateTab } = useTabs.getState();
+ activateNewTab(queryTab);
+ activateNewTab(documentsTab);
+ activateTab(queryTab);
+
+ const { openedTabs, activeTab } = useTabs.getState();
+ expect(openedTabs.length).toBe(2);
+ expect(activeTab).toEqual(queryTab);
expect(queryTab.isActive()).toBe(true);
expect(documentsTab.isActive()).toBe(false);
});
it("get tabs", () => {
- tabsManager.activateNewTab(queryTab);
- tabsManager.activateNewTab(documentsTab);
+ const { activateNewTab, getTabs } = useTabs.getState();
+ activateNewTab(queryTab);
+ activateNewTab(documentsTab);
- const queryTabs = tabsManager.getTabs(ViewModels.CollectionTabKind.Query);
+ const queryTabs = getTabs(ViewModels.CollectionTabKind.Query);
expect(queryTabs.length).toBe(1);
expect(queryTabs[0]).toEqual(queryTab);
- const documentsTabs = tabsManager.getTabs(
- ViewModels.CollectionTabKind.Documents,
- (tab) => tab.tabId === documentsTab.tabId
- );
+ const documentsTabs = getTabs(ViewModels.CollectionTabKind.Documents, (tab) => tab.tabId === documentsTab.tabId);
expect(documentsTabs.length).toBe(1);
expect(documentsTabs[0]).toEqual(documentsTab);
});
it("close tabs", () => {
- tabsManager.activateNewTab(queryTab);
- tabsManager.activateNewTab(documentsTab);
+ const { activateNewTab, closeTab, closeTabsByComparator } = useTabs.getState();
+ activateNewTab(queryTab);
+ activateNewTab(documentsTab);
+ closeTab(documentsTab);
- tabsManager.closeTab(documentsTab);
- expect(tabsManager.openedTabs().length).toBe(1);
- expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
- expect(tabsManager.activeTab()).toEqual(queryTab);
+ let tabsState = useTabs.getState();
+ expect(tabsState.openedTabs.length).toBe(1);
+ expect(tabsState.openedTabs[0]).toEqual(queryTab);
+ expect(tabsState.activeTab).toEqual(queryTab);
expect(queryTab.isActive()).toBe(true);
expect(documentsTab.isActive()).toBe(false);
- tabsManager.closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
- expect(tabsManager.openedTabs().length).toBe(0);
- expect(tabsManager.activeTab()).toEqual(undefined);
+ closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
+ tabsState = useTabs.getState();
+ expect(tabsState.openedTabs.length).toBe(0);
+ expect(tabsState.activeTab).toEqual(undefined);
expect(queryTab.isActive()).toBe(false);
});
});
diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts
index 7a9fcb22f..d2ceb8deb 100644
--- a/src/Explorer/Tree/Collection.ts
+++ b/src/Explorer/Tree/Collection.ts
@@ -15,6 +15,7 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { UploadDetailsRecord } from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
@@ -239,9 +240,11 @@ export default class Collection implements ViewModels.Collection {
this.expandCollection();
}
useCommandBar.getState().setContextButtons([]);
- this.container.tabsManager.refreshActiveTab(
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ );
}
public collapseCollection() {
@@ -288,14 +291,16 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree,
});
- const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.Documents,
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- ) as DocumentsTab[];
+ const documentsTabs: DocumentsTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.Documents,
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ ) as DocumentsTab[];
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
if (documentsTab) {
- this.container.tabsManager.activateTab(documentsTab);
+ useTabs.getState().activateTab(documentsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseName: this.databaseId,
@@ -317,7 +322,7 @@ export default class Collection implements ViewModels.Collection {
onLoadStartKey: startKey,
});
- this.container.tabsManager.activateNewTab(documentsTab);
+ useTabs.getState().activateNewTab(documentsTab);
}
}
@@ -333,14 +338,16 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree,
});
- const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.Conflicts,
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- ) as ConflictsTab[];
+ const conflictsTabs: ConflictsTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.Conflicts,
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ ) as ConflictsTab[];
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
if (conflictsTab) {
- this.container.tabsManager.activateTab(conflictsTab);
+ useTabs.getState().activateTab(conflictsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseName: this.databaseId,
@@ -362,7 +369,7 @@ export default class Collection implements ViewModels.Collection {
onLoadStartKey: startKey,
});
- this.container.tabsManager.activateNewTab(conflictsTab);
+ useTabs.getState().activateNewTab(conflictsTab);
}
}
@@ -384,14 +391,16 @@ export default class Collection implements ViewModels.Collection {
});
}
- const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.QueryTables,
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- ) as QueryTablesTab[];
+ const queryTablesTabs: QueryTablesTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.QueryTables,
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ ) as QueryTablesTab[];
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
if (queryTablesTab) {
- this.container.tabsManager.activateTab(queryTablesTab);
+ useTabs.getState().activateTab(queryTablesTab);
} else {
this.documentIds([]);
let title = `Entities`;
@@ -415,7 +424,7 @@ export default class Collection implements ViewModels.Collection {
onLoadStartKey: startKey,
});
- this.container.tabsManager.activateNewTab(queryTablesTab);
+ useTabs.getState().activateNewTab(queryTablesTab);
}
}
@@ -431,14 +440,16 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree,
});
- const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.Graph,
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- ) as GraphTab[];
+ const graphTabs: GraphTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.Graph,
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ ) as GraphTab[];
let graphTab: GraphTab = graphTabs && graphTabs[0];
if (graphTab) {
- this.container.tabsManager.activateTab(graphTab);
+ useTabs.getState().activateTab(graphTab);
} else {
this.documentIds([]);
const title = "Graph";
@@ -466,7 +477,7 @@ export default class Collection implements ViewModels.Collection {
onLoadStartKey: startKey,
});
- this.container.tabsManager.activateNewTab(graphTab);
+ useTabs.getState().activateNewTab(graphTab);
}
}
@@ -482,14 +493,16 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree,
});
- const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.Documents,
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- ) as MongoDocumentsTab[];
+ const mongoDocumentsTabs: MongoDocumentsTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.Documents,
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ ) as MongoDocumentsTab[];
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
if (mongoDocumentsTab) {
- this.container.tabsManager.activateTab(mongoDocumentsTab);
+ useTabs.getState().activateTab(mongoDocumentsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseName: this.databaseId,
@@ -510,7 +523,7 @@ export default class Collection implements ViewModels.Collection {
node: this,
onLoadStartKey: startKey,
});
- this.container.tabsManager.activateNewTab(mongoDocumentsTab);
+ useTabs.getState().activateNewTab(mongoDocumentsTab);
}
};
@@ -525,13 +538,13 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree,
});
- for (const tab of this.container.tabsManager.openedTabs()) {
+ for (const tab of useTabs.getState().openedTabs) {
if (
tab instanceof SchemaAnalyzerTab &&
tab.collection?.databaseId === this.databaseId &&
tab.collection?.id() === this.id()
) {
- return this.container.tabsManager.activateTab(tab);
+ return useTabs.getState().activateTab(tab);
}
}
@@ -542,7 +555,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Schema",
});
this.documentIds([]);
- this.container.tabsManager.activateNewTab(
+ useTabs.getState().activateNewTab(
new SchemaAnalyzerTab({
account: userContext.databaseAccount,
masterKey: userContext.masterKey || "",
@@ -571,12 +584,9 @@ export default class Collection implements ViewModels.Collection {
});
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
- const matchingTabs = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.CollectionSettingsV2,
- (tab) => {
- return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
- }
- );
+ const matchingTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.CollectionSettingsV2, (tab) => {
+ return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
+ });
const traceStartData = {
databaseName: this.databaseId,
@@ -608,15 +618,15 @@ export default class Collection implements ViewModels.Collection {
settingsTabOptions.onLoadStartKey = startKey;
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
- this.container.tabsManager.activateNewTab(settingsTabV2);
+ useTabs.getState().activateNewTab(settingsTabV2);
} else {
- this.container.tabsManager.activateTab(settingsTabV2);
+ useTabs.getState().activateTab(settingsTabV2);
}
};
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source;
- const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
+ const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseName: this.databaseId,
@@ -626,7 +636,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title,
});
- this.container.tabsManager.activateNewTab(
+ useTabs.getState().activateNewTab(
new NewQueryTab(
{
tabKind: ViewModels.CollectionTabKind.Query,
@@ -645,7 +655,7 @@ export default class Collection implements ViewModels.Collection {
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source;
- const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
+ const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
@@ -672,11 +682,11 @@ export default class Collection implements ViewModels.Collection {
}
);
- this.container.tabsManager.activateNewTab(newMongoQueryTab);
+ useTabs.getState().activateNewTab(newMongoQueryTab);
}
public onNewGraphClick() {
- const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
+ const id: number = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
const title: string = "Graph Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
@@ -702,13 +712,11 @@ export default class Collection implements ViewModels.Collection {
onLoadStartKey: startKey,
});
- this.container.tabsManager.activateNewTab(graphTab);
+ useTabs.getState().activateNewTab(graphTab);
}
public onNewMongoShellClick() {
- const mongoShellTabs = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.MongoShell
- ) as NewMongoShellTab[];
+ const mongoShellTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.MongoShell) as NewMongoShellTab[];
let index = 1;
if (mongoShellTabs.length > 0) {
@@ -729,7 +737,7 @@ export default class Collection implements ViewModels.Collection {
}
);
- this.container.tabsManager.activateNewTab(mongoShellTab);
+ useTabs.getState().activateNewTab(mongoShellTab);
}
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
@@ -787,9 +795,11 @@ export default class Collection implements ViewModels.Collection {
} else {
this.expandStoredProcedures();
}
- this.container.tabsManager.refreshActiveTab(
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ );
}
public expandStoredProcedures() {
@@ -846,9 +856,11 @@ export default class Collection implements ViewModels.Collection {
} else {
this.expandUserDefinedFunctions();
}
- this.container.tabsManager.refreshActiveTab(
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ );
}
public expandUserDefinedFunctions() {
@@ -905,9 +917,11 @@ export default class Collection implements ViewModels.Collection {
} else {
this.expandTriggers();
}
- this.container.tabsManager.refreshActiveTab(
- (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
+ );
}
public expandTriggers() {
diff --git a/src/Explorer/Tree/Database.test.tsx b/src/Explorer/Tree/Database.test.tsx
index 87c75f8c5..47e4b665a 100644
--- a/src/Explorer/Tree/Database.test.tsx
+++ b/src/Explorer/Tree/Database.test.tsx
@@ -1,7 +1,7 @@
-import * as ko from "knockout";
import { HttpStatusCodes } from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import { JunoClient } from "../../Juno/JunoClient";
+import { Features } from "../../Platform/Hosted/extractFeatures";
import { updateUserContext, userContext } from "../../UserContext";
import Explorer from "../Explorer";
import Database from "./Database";
@@ -35,7 +35,6 @@ describe("Add Schema", () => {
collection.analyticalStorageTtl = undefined;
const database = new Database(createMockContainer(), collection);
database.container = createMockContainer();
- database.container.isSchemaEnabled = ko.computed(() => false);
database.junoClient = new JunoClient();
database.junoClient.requestSchema = jest.fn();
@@ -52,7 +51,11 @@ describe("Add Schema", () => {
const database = new Database(createMockContainer(), collection);
database.container = createMockContainer();
- database.container.isSchemaEnabled = ko.computed(() => true);
+ updateUserContext({
+ features: {
+ enableSchema: true,
+ } as Features,
+ });
database.junoClient = new JunoClient();
database.junoClient.requestSchema = jest.fn();
diff --git a/src/Explorer/Tree/Database.tsx b/src/Explorer/Tree/Database.tsx
index cfa7decf0..5d4f83fc7 100644
--- a/src/Explorer/Tree/Database.tsx
+++ b/src/Explorer/Tree/Database.tsx
@@ -11,6 +11,7 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { useSidePanel } from "../../hooks/useSidePanel";
+import { useTabs } from "../../hooks/useTabs";
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -67,7 +68,7 @@ export default class Database implements ViewModels.Database {
const pendingNotificationsPromise: Promise = this.getPendingThroughputSplitNotification();
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
- const matchingTabs = this.container.tabsManager.getTabs(tabKind, (tab) => tab.node?.id() === this.id());
+ const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
let settingsTab = matchingTabs?.[0] as DatabaseSettingsTabV2;
if (!settingsTab) {
@@ -91,7 +92,7 @@ export default class Database implements ViewModels.Database {
};
settingsTab = new DatabaseSettingsTabV2(tabOptions);
settingsTab.pendingNotification(pendingNotification);
- this.container.tabsManager.activateNewTab(settingsTab);
+ useTabs.getState().activateNewTab(settingsTab);
},
(error) => {
const errorMessage = getErrorMessage(error);
@@ -116,11 +117,11 @@ export default class Database implements ViewModels.Database {
pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => {
settingsTab.pendingNotification(pendingNotification);
- this.container.tabsManager.activateTab(settingsTab);
+ useTabs.getState().activateTab(settingsTab);
},
() => {
settingsTab.pendingNotification(undefined);
- this.container.tabsManager.activateTab(settingsTab);
+ useTabs.getState().activateTab(settingsTab);
}
);
}
@@ -312,7 +313,7 @@ export default class Database implements ViewModels.Database {
let checkForSchema: NodeJS.Timeout;
interval = interval || 5000;
- if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
+ if (collection.analyticalStorageTtl !== undefined && userContext.features.enableSchema) {
collection.requestSchema = () => {
this.junoClient.requestSchema({
id: undefined,
diff --git a/src/Explorer/Tree/ResourceTokenCollection.ts b/src/Explorer/Tree/ResourceTokenCollection.ts
index cd4e82d03..04daa74a3 100644
--- a/src/Explorer/Tree/ResourceTokenCollection.ts
+++ b/src/Explorer/Tree/ResourceTokenCollection.ts
@@ -2,6 +2,7 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
@@ -77,7 +78,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source;
- const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
+ const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseName: this.databaseId,
@@ -87,7 +88,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabTitle: title,
});
- this.container.tabsManager.activateNewTab(
+ useTabs.getState().activateNewTab(
new NewQueryTab(
{
tabKind: ViewModels.CollectionTabKind.Query,
@@ -115,16 +116,18 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
dataExplorerArea: Constants.Areas.ResourceTree,
});
- const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.Documents,
- (tab: TabsBase) =>
- tab.collection?.id() === this.id() &&
- (tab.collection as ViewModels.CollectionBase).databaseId === this.databaseId
- ) as DocumentsTab[];
+ const documentsTabs: DocumentsTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.Documents,
+ (tab: TabsBase) =>
+ tab.collection?.id() === this.id() &&
+ (tab.collection as ViewModels.CollectionBase).databaseId === this.databaseId
+ ) as DocumentsTab[];
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
if (documentsTab) {
- this.container.tabsManager.activateTab(documentsTab);
+ useTabs.getState().activateTab(documentsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseName: this.databaseId,
@@ -146,7 +149,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
onLoadStartKey: startKey,
});
- this.container.tabsManager.activateNewTab(documentsTab);
+ useTabs.getState().activateNewTab(documentsTab);
}
}
diff --git a/src/Explorer/Tree/ResourceTreeAdapter.test.ts b/src/Explorer/Tree/ResourceTreeAdapter.test.ts
index 5a51a026a..05bebd913 100644
--- a/src/Explorer/Tree/ResourceTreeAdapter.test.ts
+++ b/src/Explorer/Tree/ResourceTreeAdapter.test.ts
@@ -1,28 +1,34 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import TabsBase from "../Tabs/TabsBase";
import { useSelectedNode } from "../useSelectedNode";
-describe("useSelectedNode.getState()", () => {
+describe("useSelectedNode", () => {
const mockTab = {
tabKind: ViewModels.CollectionTabKind.Documents,
} as TabsBase;
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
describe("isDataNodeSelected", () => {
- afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
+ afterEach(() => {
+ useSelectedNode.getState().setSelectedNode(undefined);
+ useTabs.setState({ activeTab: undefined });
+ });
it("it should not select if no selected node", () => {
- const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
+ useTabs.setState({ activeTab: mockTab });
+ const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
expect(isDataNodeSelected).toBeFalsy();
});
it("it should not select incorrect subnodekinds", () => {
+ useTabs.setState({ activeTab: mockTab });
useSelectedNode.getState().setSelectedNode({
nodeKind: "nodeKind",
rid: "rid",
id: ko.observable("id"),
});
- const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
+ const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
expect(isDataNodeSelected).toBeFalsy();
});
@@ -32,11 +38,12 @@ describe("useSelectedNode.getState()", () => {
rid: "rid",
id: ko.observable("id"),
});
- const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(undefined, "foo", "bar", undefined);
+ const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
expect(isDataNodeSelected).toBeFalsy();
});
it("should select if correct database node regardless of subnodekinds", () => {
+ useTabs.setState({ activeTab: mockTab });
const subNodeKind = ViewModels.CollectionTabKind.Documents;
useSelectedNode.getState().setSelectedNode({
nodeKind: "Database",
@@ -46,7 +53,7 @@ describe("useSelectedNode.getState()", () => {
} as ViewModels.TreeNode);
const isDataNodeSelected = useSelectedNode
.getState()
- .isDataNodeSelected(mockTab, "dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
+ .isDataNodeSelected("dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
expect(isDataNodeSelected).toBeTruthy();
});
@@ -55,6 +62,7 @@ describe("useSelectedNode.getState()", () => {
let activeTab = {
tabKind: subNodeKind,
} as TabsBase;
+ useTabs.setState({ activeTab });
useSelectedNode.getState().setSelectedNode({
nodeKind: "Collection",
rid: "collrid",
@@ -62,15 +70,14 @@ describe("useSelectedNode.getState()", () => {
id: ko.observable("collid"),
selectedSubnodeKind: ko.observable(subNodeKind),
} as ViewModels.TreeNode);
- let isDataNodeSelected = useSelectedNode
- .getState()
- .isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
+ let isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
expect(isDataNodeSelected).toBeTruthy();
subNodeKind = ViewModels.CollectionTabKind.Graph;
activeTab = {
tabKind: subNodeKind,
} as TabsBase;
+ useTabs.setState({ activeTab });
useSelectedNode.getState().setSelectedNode({
nodeKind: "Collection",
rid: "collrid",
@@ -78,7 +85,7 @@ describe("useSelectedNode.getState()", () => {
id: ko.observable("collid"),
selectedSubnodeKind: ko.observable(subNodeKind),
} as ViewModels.TreeNode);
- isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
+ isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
expect(isDataNodeSelected).toBeTruthy();
});
@@ -93,9 +100,10 @@ describe("useSelectedNode.getState()", () => {
const activeTab = {
tabKind: ViewModels.CollectionTabKind.Documents,
} as TabsBase;
+ useTabs.setState({ activeTab });
const isDataNodeSelected = useSelectedNode
.getState()
- .isDataNodeSelected(activeTab, "dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
+ .isDataNodeSelected("dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
expect(isDataNodeSelected).toBeFalsy();
});
});
diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx
index a499857c2..936046567 100644
--- a/src/Explorer/Tree/ResourceTreeAdapter.tsx
+++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx
@@ -16,6 +16,7 @@ import { Areas } from "../../Common/Constants";
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { IPinnedRepo } from "../../Juno/JunoClient";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
@@ -57,7 +58,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.parameters = ko.observable(Date.now());
useSelectedNode.subscribe(() => this.triggerRender());
- this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender());
+ useTabs.subscribe(
+ () => this.triggerRender(),
+ (state) => state.activeTab
+ );
useNotebook.subscribe(
() => this.triggerRender(),
(state) => state.isNotebookEnabled
@@ -188,8 +192,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isExpanded: false,
className: "databaseHeader",
children: [],
- isSelected: () =>
- useSelectedNode.getState().isDataNodeSelected(this.container.tabsManager.activeTab(), database.id()),
+ isSelected: () => useSelectedNode.getState().isDataNodeSelected(database.id()),
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()),
onClick: async (isExpanded) => {
// Rewritten version of expandCollapseDatabase():
@@ -204,7 +207,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
databaseNode.isLoading = false;
useSelectedNode.getState().setSelectedNode(database);
useCommandBar.getState().setContextButtons([]);
- this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
+ useTabs.getState().refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
},
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
};
@@ -215,9 +218,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), database.id(), undefined, [
- ViewModels.CollectionTabKind.DatabaseSettings,
- ]),
+ .isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]),
onClick: database.onSettingsClick.bind(database),
});
}
@@ -265,7 +266,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
+ .isDataNodeSelected(collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.Documents,
ViewModels.CollectionTabKind.Graph,
]),
@@ -283,9 +284,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
- ViewModels.CollectionTabKind.SchemaAnalyzer,
- ]),
+ .isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
});
}
@@ -296,9 +295,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
- ViewModels.CollectionTabKind.Settings,
- ]),
+ .isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]),
});
}
@@ -326,9 +323,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
- ViewModels.CollectionTabKind.Conflicts,
- ]),
+ .isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]),
});
}
@@ -343,10 +338,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
// Rewritten version of expandCollapseCollection
useSelectedNode.getState().setSelectedNode(collection);
useCommandBar.getState().setContextButtons([]);
- this.container.tabsManager.refreshActiveTab(
- (tab: TabsBase) =>
- tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab: TabsBase) =>
+ tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
+ );
},
onExpanded: () => {
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
@@ -355,10 +352,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
collection.loadTriggers();
}
},
- isSelected: () =>
- useSelectedNode
- .getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
+ isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
};
}
@@ -372,17 +366,19 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
+ .isDataNodeSelected(collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.StoredProcedures,
]),
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
})),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
- this.container.tabsManager.refreshActiveTab(
- (tab: TabsBase) =>
- tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab: TabsBase) =>
+ tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
+ );
},
};
}
@@ -396,7 +392,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
+ .isDataNodeSelected(collection.databaseId, collection.id(), [
ViewModels.CollectionTabKind.UserDefinedFunctions,
]),
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
@@ -406,10 +402,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
})),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
- this.container.tabsManager.refreshActiveTab(
- (tab: TabsBase) =>
- tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab: TabsBase) =>
+ tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
+ );
},
};
}
@@ -423,17 +421,17 @@ export class ResourceTreeAdapter implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
- ViewModels.CollectionTabKind.Triggers,
- ]),
+ .isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]),
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
})),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
- this.container.tabsManager.refreshActiveTab(
- (tab: TabsBase) =>
- tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
- );
+ useTabs
+ .getState()
+ .refreshActiveTab(
+ (tab: TabsBase) =>
+ tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
+ );
},
};
}
@@ -452,9 +450,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
children: this.getSchemaNodes(collection.schema.fields),
onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Schema);
- this.container.tabsManager.refreshActiveTab(
- (tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid
- );
+ useTabs.getState().refreshActiveTab((tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid);
},
};
}
@@ -584,7 +580,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
className: "notebookHeader galleryHeader",
onClick: () => this.container.openGallery(),
isSelected: () => {
- const activeTab = this.container.tabsManager.activeTab();
+ const activeTab = useTabs.getState().activeTab;
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
},
};
@@ -678,7 +674,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
className: "notebookHeader",
onClick: () => onFileClick(item),
isSelected: () => {
- const activeTab = this.container.tabsManager.activeTab();
+ const activeTab = useTabs.getState().activeTab;
return (
activeTab &&
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
@@ -833,7 +829,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
}
},
isSelected: () => {
- const activeTab = this.container.tabsManager.activeTab();
+ const activeTab = useTabs.getState().activeTab;
return (
activeTab &&
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
diff --git a/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx b/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
index 59a867a83..49529f0d7 100644
--- a/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
+++ b/src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
@@ -3,6 +3,7 @@ import * as React from "react";
import CollectionIcon from "../../../images/tree-collection.svg";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { userContext } from "../../UserContext";
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
@@ -24,7 +25,10 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
(state) => state.resourceTokenCollection
);
useSelectedNode.subscribe(() => this.triggerRender());
- this.container.tabsManager && this.container.tabsManager.activeTab.subscribe(() => this.triggerRender());
+ useTabs.subscribe(
+ () => this.triggerRender(),
+ (state) => state.activeTab
+ );
this.triggerRender();
}
@@ -55,9 +59,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
isSelected: () =>
useSelectedNode
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
- ViewModels.CollectionTabKind.Documents,
- ]),
+ .isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Documents]),
});
const collectionNode: TreeNode = {
@@ -70,14 +72,13 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
// Rewritten version of expandCollapseCollection
useSelectedNode.getState().setSelectedNode(collection);
useCommandBar.getState().setContextButtons([]);
- this.container.tabsManager.refreshActiveTab(
- (tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
- );
- },
- isSelected: () =>
- useSelectedNode
+ useTabs
.getState()
- .isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
+ .refreshActiveTab(
+ (tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
+ );
+ },
+ isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
};
return {
diff --git a/src/Explorer/Tree/StoredProcedure.ts b/src/Explorer/Tree/StoredProcedure.ts
index 903e5d088..8e6f77ece 100644
--- a/src/Explorer/Tree/StoredProcedure.ts
+++ b/src/Explorer/Tree/StoredProcedure.ts
@@ -4,6 +4,7 @@ import * as Constants from "../../Common/Constants";
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
@@ -62,7 +63,7 @@ export default class StoredProcedure {
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
- const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
+ const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
const storedProcedure = {
id: "",
body: sampleStoredProcedureBody,
@@ -84,7 +85,7 @@ export default class StoredProcedure {
}
);
- source.container.tabsManager.activateNewTab(storedProcedureTab);
+ useTabs.getState().activateNewTab(storedProcedureTab);
}
public select() {
@@ -99,14 +100,16 @@ export default class StoredProcedure {
public open = () => {
this.select();
- const storedProcedureTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.StoredProcedures,
- (tab: TabsBase) => tab.node && tab.node.rid === this.rid
- ) as NewStoredProcedureTab[];
+ const storedProcedureTabs: NewStoredProcedureTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.StoredProcedures,
+ (tab: TabsBase) => tab.node && tab.node.rid === this.rid
+ ) as NewStoredProcedureTab[];
let storedProcedureTab: NewStoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
if (storedProcedureTab) {
- this.container.tabsManager.activateTab(storedProcedureTab);
+ useTabs.getState().activateTab(storedProcedureTab);
} else {
const storedProcedureData = {
_rid: this.rid,
@@ -131,7 +134,7 @@ export default class StoredProcedure {
}
);
- this.container.tabsManager.activateNewTab(storedProcedureTab);
+ useTabs.getState().activateNewTab(storedProcedureTab);
}
};
public delete() {
@@ -141,7 +144,7 @@ export default class StoredProcedure {
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
- this.container.tabsManager.closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
+ useTabs.getState().closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
@@ -149,10 +152,12 @@ export default class StoredProcedure {
}
public execute(params: string[], partitionKeyValue?: string): void {
- const sprocTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.StoredProcedures,
- (tab: TabsBase) => tab.node && tab.node.rid === this.rid
- ) as NewStoredProcedureTab[];
+ const sprocTabs: NewStoredProcedureTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.StoredProcedures,
+ (tab: TabsBase) => tab.node && tab.node.rid === this.rid
+ ) as NewStoredProcedureTab[];
const sprocTab: NewStoredProcedureTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
sprocTab.isExecuting(true);
this.container &&
diff --git a/src/Explorer/Tree/Trigger.ts b/src/Explorer/Tree/Trigger.ts
index 2aa44a17e..d90428b65 100644
--- a/src/Explorer/Tree/Trigger.ts
+++ b/src/Explorer/Tree/Trigger.ts
@@ -3,6 +3,7 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
@@ -42,7 +43,7 @@ export default class Trigger {
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
- const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
+ const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
const trigger = {
id: "",
body: "function trigger(){}",
@@ -60,20 +61,19 @@ export default class Trigger {
node: source,
});
- source.container.tabsManager.activateNewTab(triggerTab);
+ useTabs.getState().activateNewTab(triggerTab);
}
public open = () => {
this.select();
- const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.Triggers,
- (tab) => tab.node && tab.node.rid === this.rid
- ) as TriggerTab[];
+ const triggerTabs: TriggerTab[] = useTabs
+ .getState()
+ .getTabs(ViewModels.CollectionTabKind.Triggers, (tab) => tab.node && tab.node.rid === this.rid) as TriggerTab[];
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
if (triggerTab) {
- this.container.tabsManager.activateTab(triggerTab);
+ useTabs.getState().activateTab(triggerTab);
} else {
const triggerData = {
_rid: this.rid,
@@ -94,7 +94,7 @@ export default class Trigger {
node: this,
});
- this.container.tabsManager.activateNewTab(triggerTab);
+ useTabs.getState().activateNewTab(triggerTab);
}
};
@@ -105,7 +105,7 @@ export default class Trigger {
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
- this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
+ useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
diff --git a/src/Explorer/Tree/UserDefinedFunction.ts b/src/Explorer/Tree/UserDefinedFunction.ts
index ef4a5e180..2d2b64889 100644
--- a/src/Explorer/Tree/UserDefinedFunction.ts
+++ b/src/Explorer/Tree/UserDefinedFunction.ts
@@ -3,6 +3,7 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
import * as ViewModels from "../../Contracts/ViewModels";
+import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
@@ -30,7 +31,7 @@ export default class UserDefinedFunction {
}
public static create(source: ViewModels.Collection, event: MouseEvent) {
- const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
+ const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
const userDefinedFunction = {
id: "",
body: "function userDefinedFunction(){}",
@@ -46,20 +47,22 @@ export default class UserDefinedFunction {
node: source,
});
- source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
+ useTabs.getState().activateNewTab(userDefinedFunctionTab);
}
public open = () => {
this.select();
- const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
- ViewModels.CollectionTabKind.UserDefinedFunctions,
- (tab) => tab.node?.rid === this.rid
- ) as UserDefinedFunctionTab[];
+ const userDefinedFunctionTabs: UserDefinedFunctionTab[] = useTabs
+ .getState()
+ .getTabs(
+ ViewModels.CollectionTabKind.UserDefinedFunctions,
+ (tab) => tab.node?.rid === this.rid
+ ) as UserDefinedFunctionTab[];
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
if (userDefinedFunctionTab) {
- this.container.tabsManager.activateTab(userDefinedFunctionTab);
+ useTabs.getState().activateTab(userDefinedFunctionTab);
} else {
const userDefinedFunctionData = {
_rid: this.rid,
@@ -78,7 +81,7 @@ export default class UserDefinedFunction {
node: this,
});
- this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
+ useTabs.getState().activateNewTab(userDefinedFunctionTab);
}
};
@@ -98,7 +101,7 @@ export default class UserDefinedFunction {
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
- this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
+ useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
diff --git a/src/Explorer/useDatabases.ts b/src/Explorer/useDatabases.ts
index db16e5766..0a1f3ae45 100644
--- a/src/Explorer/useDatabases.ts
+++ b/src/Explorer/useDatabases.ts
@@ -2,6 +2,7 @@ import _ from "underscore";
import create, { UseStore } from "zustand";
import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels";
+import { useSelectedNode } from "./useSelectedNode";
interface DatabasesState {
databases: ViewModels.Database[];
@@ -17,6 +18,7 @@ interface DatabasesState {
isLastCollection: () => boolean;
loadDatabaseOffers: () => Promise;
isFirstResourceCreated: () => boolean;
+ findSelectedDatabase: () => ViewModels.Database;
}
export const useDatabases: UseStore = create((set, get) => ({
@@ -112,4 +114,19 @@ export const useDatabases: UseStore = create((set, get) => ({
return false;
});
},
+ findSelectedDatabase: (): ViewModels.Database => {
+ const selectedNode = useSelectedNode.getState().selectedNode;
+ if (!selectedNode) {
+ return undefined;
+ }
+ if (selectedNode.nodeKind === "Database") {
+ return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
+ }
+
+ if (selectedNode.nodeKind === "Collection") {
+ return selectedNode.database;
+ }
+
+ return selectedNode.collection?.database;
+ },
}));
diff --git a/src/Explorer/useSelectedNode.ts b/src/Explorer/useSelectedNode.ts
index 2e29a588e..15f953641 100644
--- a/src/Explorer/useSelectedNode.ts
+++ b/src/Explorer/useSelectedNode.ts
@@ -1,17 +1,13 @@
-import _ from "underscore";
import create, { UseStore } from "zustand";
import * as ViewModels from "../Contracts/ViewModels";
-import TabsBase from "./Tabs/TabsBase";
-import { useDatabases } from "./useDatabases";
+import { useTabs } from "../hooks/useTabs";
export interface SelectedNodeState {
selectedNode: ViewModels.TreeNode;
setSelectedNode: (node: ViewModels.TreeNode) => void;
isDatabaseNodeOrNoneSelected: () => boolean;
- findSelectedDatabase: () => ViewModels.Database;
findSelectedCollection: () => ViewModels.Collection;
isDataNodeSelected: (
- activeTab: TabsBase,
databaseId: string,
collectionId?: string,
subnodeKinds?: ViewModels.CollectionTabKind[]
@@ -25,30 +21,11 @@ export const useSelectedNode: UseStore = create((set, get) =>
const selectedNode = get().selectedNode;
return !selectedNode || selectedNode.nodeKind === "Database";
},
- findSelectedDatabase: (): ViewModels.Database => {
- const selectedNode = get().selectedNode;
- if (!selectedNode) {
- return undefined;
- }
- if (selectedNode.nodeKind === "Database") {
- return _.find(
- useDatabases.getState().databases,
- (database: ViewModels.Database) => database.id() === selectedNode.id()
- );
- }
-
- if (selectedNode.nodeKind === "Collection") {
- return selectedNode.database;
- }
-
- return selectedNode.collection?.database;
- },
findSelectedCollection: (): ViewModels.Collection => {
const selectedNode = get().selectedNode;
return (selectedNode.nodeKind === "Collection" ? selectedNode : selectedNode.collection) as ViewModels.Collection;
},
isDataNodeSelected: (
- activeTab: TabsBase,
databaseId: string,
collectionId?: string,
subnodeKinds?: ViewModels.CollectionTabKind[]
@@ -70,6 +47,7 @@ export const useSelectedNode: UseStore = create((set, get) =>
return true;
}
+ const activeTab = useTabs.getState().activeTab;
const selectedSubnodeKind = collectionId
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
diff --git a/src/GalleryViewer/GalleryViewer.tsx b/src/GalleryViewer/GalleryViewer.tsx
index 07fcb733e..e630ee7b2 100644
--- a/src/GalleryViewer/GalleryViewer.tsx
+++ b/src/GalleryViewer/GalleryViewer.tsx
@@ -42,10 +42,10 @@ const onInit = async () => {
practices, and how to get started with Azure Cosmos DB.
- If you'd like to run or edit the notebook in your own Azure Cosmos DB account,{" "}
+ If {`you'd`} like to run or edit the notebook in your own Azure Cosmos DB account,{" "}
sign in and select an account with{" "}
notebooks enabled. From there, you can download the sample to your
- account. If you don't have an account yet, you can{" "}
+ account. If you {`don't`} have an account yet, you can{" "}
create one from the Azure portal.
diff --git a/src/Main.tsx b/src/Main.tsx
index 566317825..c85a39f10 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -34,7 +34,6 @@ import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
import "./Explorer/Controls/TreeComponent/treeComponent.less";
-import { ExplorerParams } from "./Explorer/Explorer";
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
@@ -56,14 +55,10 @@ initializeIcons();
const App: React.FunctionComponent = () => {
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState
(true);
- const { tabs, activeTab, tabsManager } = useTabs();
-
- const explorerParams: ExplorerParams = {
- tabsManager,
- };
+ const openedTabs = useTabs((state) => state.openedTabs);
const config = useConfig();
- const explorer = useKnockoutExplorer(config?.platform, explorerParams);
+ const explorer = useKnockoutExplorer(config?.platform);
const toggleLeftPaneExpanded = () => {
setIsLeftPaneExpanded(!isLeftPaneExpanded);
@@ -100,8 +95,8 @@ const App: React.FunctionComponent = () => {
{/* Collections Tree - End */}
- {tabs.length === 0 && }
-
+ {openedTabs.length === 0 && }
+
{/* Collections Tree and Tabs - End */}
();
useEffect(() => {
const effect = async () => {
if (platform) {
if (platform === Platform.Hosted) {
- const explorer = await configureHosted(explorerParams);
+ const explorer = await configureHosted();
setExplorer(explorer);
} else if (platform === Platform.Emulator) {
- const explorer = configureEmulator(explorerParams);
+ const explorer = configureEmulator();
setExplorer(explorer);
} else if (platform === Platform.Portal) {
- const explorer = await configurePortal(explorerParams);
+ const explorer = await configurePortal();
setExplorer(explorer);
}
}
@@ -67,21 +67,21 @@ export function useKnockoutExplorer(platform: Platform, explorerParams: Explorer
return explorer;
}
-async function configureHosted(explorerParams: ExplorerParams): Promise {
+async function configureHosted(): Promise {
const win = (window as unknown) as HostedExplorerChildFrame;
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
- return configureHostedWithEncryptedToken(win.hostedConfig, explorerParams);
+ return configureHostedWithEncryptedToken(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
- return configureHostedWithResourceToken(win.hostedConfig, explorerParams);
+ return configureHostedWithResourceToken(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
- return configureHostedWithConnectionString(win.hostedConfig, explorerParams);
+ return configureHostedWithConnectionString(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.AAD) {
- return configureHostedWithAAD(win.hostedConfig, explorerParams);
+ return configureHostedWithAAD(win.hostedConfig);
}
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
}
-async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParams): Promise {
+async function configureHostedWithAAD(config: AAD): Promise {
// TODO: Refactor. updateUserContext needs to be called twice because listKeys below depends on userContext.authorizationToken
updateUserContext({
authType: AuthType.AAD,
@@ -120,11 +120,11 @@ async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParam
databaseAccount: config.databaseAccount,
masterKey: keys.primaryMasterKey,
});
- const explorer = new Explorer(explorerParams);
+ const explorer = new Explorer();
return explorer;
}
-function configureHostedWithConnectionString(config: ConnectionString, explorerParams: ExplorerParams): Explorer {
+function configureHostedWithConnectionString(config: ConnectionString): Explorer {
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
const databaseAccount = {
id: "",
@@ -142,11 +142,11 @@ function configureHostedWithConnectionString(config: ConnectionString, explorerP
databaseAccount,
masterKey: config.masterKey,
});
- const explorer = new Explorer(explorerParams);
+ const explorer = new Explorer();
return explorer;
}
-function configureHostedWithResourceToken(config: ResourceToken, explorerParams: ExplorerParams): Explorer {
+function configureHostedWithResourceToken(config: ResourceToken): Explorer {
const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken);
const databaseAccount = {
id: "",
@@ -167,11 +167,11 @@ function configureHostedWithResourceToken(config: ResourceToken, explorerParams:
partitionKey: parsedResourceToken.partitionKey,
},
});
- const explorer = new Explorer(explorerParams);
+ const explorer = new Explorer();
return explorer;
}
-function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParams: ExplorerParams): Explorer {
+function configureHostedWithEncryptedToken(config: EncryptedToken): Explorer {
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
updateUserContext({
authType: AuthType.EncryptedToken,
@@ -185,20 +185,20 @@ function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParam
properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
},
});
- const explorer = new Explorer(explorerParams);
+ const explorer = new Explorer();
return explorer;
}
-function configureEmulator(explorerParams: ExplorerParams): Explorer {
+function configureEmulator(): Explorer {
updateUserContext({
databaseAccount: emulatorAccount,
authType: AuthType.MasterKey,
});
- const explorer = new Explorer(explorerParams);
+ const explorer = new Explorer();
return explorer;
}
-async function configurePortal(explorerParams: ExplorerParams): Promise {
+async function configurePortal(): Promise {
updateUserContext({
authType: AuthType.AAD,
});
@@ -214,7 +214,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise new TabsManager());
- const tabs = useObservable(tabsManager.openedTabs);
- const activeTab = useObservable(tabsManager.activeTab);
-
- return { tabs, activeTab, tabsManager };
+ activateTab: (tab: TabsBase) => void;
+ activateNewTab: (tab: TabsBase) => void;
+ updateTab: (tab: TabsBase) => void;
+ getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean) => TabsBase[];
+ refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void;
+ closeTabsByComparator: (comparator: (tab: TabsBase) => boolean) => void;
+ closeTab: (tab: TabsBase) => void;
}
+
+export const useTabs: UseStore = create((set, get) => ({
+ openedTabs: [],
+ activeTab: undefined,
+ activateTab: (tab: TabsBase): void => {
+ if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
+ set({ activeTab: tab });
+ tab.onActivate();
+ }
+ },
+ activateNewTab: (tab: TabsBase): void => {
+ set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab }));
+ tab.onActivate();
+ },
+ updateTab: (tab: TabsBase) => {
+ if (get().activeTab?.tabId === tab.tabId) {
+ set({ activeTab: tab });
+ }
+
+ set((state) => ({
+ openedTabs: state.openedTabs.map((openedTab) => {
+ if (openedTab.tabId === tab.tabId) {
+ return tab;
+ }
+ return openedTab;
+ }),
+ }));
+ },
+ getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
+ get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
+ refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
+ // ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
+ const activeTab = get().activeTab;
+ activeTab && comparator(activeTab) && activeTab.onActivate();
+ },
+ closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
+ get()
+ .openedTabs.filter(comparator)
+ .forEach((tab) => tab.onCloseTabButtonClick()),
+ closeTab: (tab: TabsBase): void => {
+ let tabIndex: number;
+ const { activeTab, openedTabs } = get();
+ const updatedTabs = openedTabs.filter((openedTab, index) => {
+ if (tab.tabId === openedTab.tabId) {
+ tabIndex = index;
+ return false;
+ }
+ return true;
+ });
+ if (updatedTabs.length === 0) {
+ set({ activeTab: undefined });
+ }
+
+ if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
+ const tabToTheRight = updatedTabs[tabIndex];
+ const lastOpenTab = updatedTabs[updatedTabs.length - 1];
+ set({ activeTab: tabToTheRight || lastOpenTab });
+ }
+
+ set({ openedTabs: updatedTabs });
+ },
+}));