mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 09:20:16 +00:00
Create tabs manager and refactor tab related logic (#66)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import DeleteIcon from "../../../images/delete.svg";
|
||||
import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos";
|
||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||
|
||||
export class ConflictsTab extends TabsBase implements ViewModels.ConflictsTab {
|
||||
export default class ConflictsTab extends TabsBase implements ViewModels.ConflictsTab {
|
||||
public selectedConflictId: ko.Observable<ViewModels.ConflictId>;
|
||||
public selectedConflictContent: ViewModels.Editable<string>;
|
||||
public selectedConflictCurrent: ViewModels.Editable<string>;
|
||||
|
||||
@@ -20,8 +20,7 @@ describe("Documents tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(documentsTab.buildQuery("")).toContain("select");
|
||||
@@ -104,8 +103,7 @@ describe("Documents tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(documentsTab.showPartitionKey).toBe(false);
|
||||
@@ -124,8 +122,7 @@ describe("Documents tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(documentsTab.showPartitionKey).toBe(false);
|
||||
@@ -144,8 +141,7 @@ describe("Documents tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(documentsTab.showPartitionKey).toBe(true);
|
||||
@@ -164,8 +160,7 @@ describe("Documents tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(documentsTab.showPartitionKey).toBe(false);
|
||||
@@ -184,8 +179,7 @@ describe("Documents tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(documentsTab.showPartitionKey).toBe(true);
|
||||
|
||||
@@ -28,8 +28,7 @@ describe("Query Tab", () => {
|
||||
selfLink: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
hashLocation: "",
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -76,8 +76,7 @@ describe("Settings tab", () => {
|
||||
quotaInfo,
|
||||
null
|
||||
),
|
||||
onUpdateTabsButtons: undefined,
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: undefined
|
||||
});
|
||||
};
|
||||
|
||||
@@ -196,8 +195,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
@@ -221,8 +219,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
@@ -241,8 +238,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.shouldUpdateCollection()).toBe(false);
|
||||
@@ -281,8 +277,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
|
||||
@@ -299,8 +294,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
|
||||
@@ -326,8 +320,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
|
||||
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
|
||||
@@ -408,8 +401,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: getCollection(defaultApi, partitionKeyOption),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -555,8 +547,7 @@ describe("Settings tab", () => {
|
||||
hashLocation: "",
|
||||
isActive: ko.observable(false),
|
||||
collection: getCollection(autoPilotTier),
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
|
||||
openedTabs: []
|
||||
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
|
||||
});
|
||||
}
|
||||
describe("Visible", () => {
|
||||
|
||||
@@ -252,7 +252,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.Settings
|
||||
this.throughputModeRadioName = `throughputModeRadio${this.tabId}`;
|
||||
|
||||
this.changeFeedPolicyToggled = editable.observable<ChangeFeedPolicyToggledState>(
|
||||
this.collection.rawDataModel.changeFeedPolicy != null
|
||||
this.collection.rawDataModel?.changeFeedPolicy != null
|
||||
? ChangeFeedPolicyToggledState.On
|
||||
: ChangeFeedPolicyToggledState.Off
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import TriggerTabTemplate from "./TriggerTab.html";
|
||||
import UserDefinedFunctionTabTemplate from "./UserDefinedFunctionTab.html";
|
||||
import GalleryTabTemplate from "./GalleryTab.html";
|
||||
import NotebookViewerTabTemplate from "./NotebookViewerTab.html";
|
||||
import TabsManagerTemplate from "./TabsManager.html";
|
||||
|
||||
export class TabComponent {
|
||||
constructor(data: any) {
|
||||
@@ -23,6 +24,15 @@ export class TabComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export class TabsManager {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TabComponent,
|
||||
template: TabsManagerTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class DocumentsTab {
|
||||
constructor() {
|
||||
return {
|
||||
|
||||
@@ -24,8 +24,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
|
||||
public tabKind: ViewModels.CollectionTabKind;
|
||||
public tabTitle: ko.Observable<string>;
|
||||
public tabPath: ko.Observable<string>;
|
||||
public nextTab: ko.Observable<ViewModels.Tab>;
|
||||
public previousTab: ko.Observable<ViewModels.Tab>;
|
||||
public closeButtonTabIndex: ko.Computed<number>;
|
||||
public errorDetailsTabIndex: ko.Computed<number>;
|
||||
public hashLocation: ko.Observable<string>;
|
||||
@@ -55,8 +53,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
|
||||
(options.tabPath && ko.observable<string>(options.tabPath)) ||
|
||||
(this.collection &&
|
||||
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
|
||||
this.nextTab = ko.observable<ViewModels.Tab>();
|
||||
this.previousTab = ko.observable<ViewModels.Tab>();
|
||||
this.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
|
||||
this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
|
||||
this.isExecutionError = ko.observable<boolean>(false);
|
||||
@@ -80,34 +76,11 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
|
||||
return true;
|
||||
})
|
||||
};
|
||||
|
||||
const openedTabs = options.openedTabs;
|
||||
if (openedTabs && openedTabs.length && openedTabs.length > 0) {
|
||||
const lastTab = openedTabs[openedTabs.length - 1];
|
||||
lastTab && lastTab.nextTab(this);
|
||||
this.previousTab(lastTab);
|
||||
}
|
||||
}
|
||||
|
||||
public onCloseTabButtonClick(): Q.Promise<any> {
|
||||
const previousTab = this.previousTab();
|
||||
const nextTab = this.nextTab();
|
||||
|
||||
previousTab && previousTab.nextTab(nextTab);
|
||||
nextTab && nextTab.previousTab(previousTab);
|
||||
|
||||
this.getContainer().openedTabs.remove(tab => tab.tabId === this.tabId);
|
||||
|
||||
const tabToActivate = nextTab || previousTab;
|
||||
|
||||
if (!tabToActivate) {
|
||||
this.getContainer().selectedNode(null);
|
||||
this.getContainer().onUpdateTabsButtons([]);
|
||||
this.getContainer().activeTab(null);
|
||||
} else {
|
||||
tabToActivate.isActive(true);
|
||||
this.getContainer().activeTab(tabToActivate);
|
||||
}
|
||||
public onCloseTabButtonClick(): void {
|
||||
const explorer: ViewModels.Explorer = this.getContainer();
|
||||
explorer.tabsManager.closeTab(this.tabId, explorer);
|
||||
|
||||
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
||||
databaseAccountName: this.getContainer().databaseAccount().name,
|
||||
@@ -115,16 +88,10 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.tabTitle()
|
||||
});
|
||||
return Q();
|
||||
}
|
||||
|
||||
public onTabClick(): Q.Promise<any> {
|
||||
for (let i = 0; i < this.getContainer().openedTabs().length; i++) {
|
||||
const tab = this.getContainer().openedTabs()[i];
|
||||
tab.isActive(false);
|
||||
}
|
||||
this.isActive(true);
|
||||
this.getContainer().activeTab(this);
|
||||
this.getContainer().tabsManager.activateTab(this);
|
||||
return Q();
|
||||
}
|
||||
|
||||
|
||||
148
src/Explorer/Tabs/TabsManager.html
Normal file
148
src/Explorer/Tabs/TabsManager.html
Normal file
@@ -0,0 +1,148 @@
|
||||
<div id="content" class="flexContainer hideOverflows" data-bind="visible: openedTabs && openedTabs().length > 0">
|
||||
<!-- Tabs - Start -->
|
||||
<div class="nav-tabs-margin">
|
||||
<ul class="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||
<!-- ko foreach: openedTabs -->
|
||||
<li
|
||||
class="tabList"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.tabPath,
|
||||
'aria-selected': $data.isActive,
|
||||
'aria-expanded': $data.isActive,
|
||||
'aria-controls': $data.tabId
|
||||
},
|
||||
css:{
|
||||
active: $data.isActive
|
||||
},
|
||||
hasFocus: $data.hasFocus,
|
||||
event: { keypress: onKeyPressActivate },
|
||||
click: $data.onTabClick,"
|
||||
tabindex="0"
|
||||
role="tab"
|
||||
>
|
||||
<span class="tabNavContentContainer">
|
||||
<a data-toggle="tab" data-bind="attr: { href: '#' + $data.tabId }" tabindex="-1">
|
||||
<div class="tab_Content">
|
||||
<span class="statusIconContainer">
|
||||
<div
|
||||
class="errorIconContainer"
|
||||
id="errorStatusIcon"
|
||||
title="Click to view more details"
|
||||
role="button"
|
||||
data-bind="
|
||||
click: onErrorDetailsClick,
|
||||
event: { keypress: onErrorDetailsKeyPress },
|
||||
attr: { tabindex: errorDetailsTabIndex },
|
||||
css: { actionsEnabled: isActive },
|
||||
visible: isExecutionError"
|
||||
>
|
||||
<span class="errorIcon"></span>
|
||||
</div>
|
||||
<img
|
||||
class="loadingIcon"
|
||||
title="Loading"
|
||||
src="/circular_loader_black_16x16.gif"
|
||||
data-bind="visible: $data.isExecuting"
|
||||
alt="Loading"
|
||||
/>
|
||||
</span>
|
||||
<span class="tabNavText" data-bind="text: $data.tabTitle"></span>
|
||||
<span class="tabIconSection">
|
||||
<span
|
||||
aria-label="Close Tab"
|
||||
role="button"
|
||||
class="cancelButton"
|
||||
data-bind="
|
||||
click: $data.onCloseTabButtonClick,
|
||||
event: { keypress: onKeyPressClose },
|
||||
attr: { tabindex: $data.closeButtonTabIndex },
|
||||
visible: $data.isActive() || $data.isMouseOver()"
|
||||
title="Close"
|
||||
>
|
||||
<span class="tabIcon close-Icon" data-bind="visible: $data.isActive() || $data.isMouseOver()">
|
||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Tabs -- End -->
|
||||
|
||||
<!-- Tabs Panes -- Start -->
|
||||
<div class="tabPanesContainer">
|
||||
<!-- ko foreach: openedTabs -->
|
||||
<div class="tabs-container" data-bind="visible: $data.isActive">
|
||||
<!-- ko if: $data.tabKind === 0 -->
|
||||
<documents-tab params="{data: $data}"></documents-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 1 -->
|
||||
<settings-tab params="{data: $data}"></settings-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 2 -->
|
||||
<stored-procedure-tab params="{data: $data}"></stored-procedure-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 3 -->
|
||||
<user-defined-function-tab params="{data: $data}"></user-defined-function-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 4 -->
|
||||
<trigger-tab params="{data: $data}"></trigger-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 5 -->
|
||||
<query-tab params="{data: $data}"></query-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 6 -->
|
||||
<graph-tab params="{data: $data}"></graph-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 9 -->
|
||||
<tables-query-tab class="flexContainer" params="{data: $data}"></tables-query-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 10 -->
|
||||
<mongo-shell-tab params="{data: $data}"></mongo-shell-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 11 -->
|
||||
<database-settings-tab params="{data: $data}"></database-settings-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 12 -->
|
||||
<conflicts-tab params="{data: $data}"></conflicts-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 14 -->
|
||||
<terminal-tab params="{data: $data}"></terminal-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 15 -->
|
||||
<notebookv2-tab params="{data: $data}"></notebookv2-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 16 -->
|
||||
<spark-master-tab params="{data: $data}"></spark-master-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 17 -->
|
||||
<gallery-tab params="{data: $data}"></gallery-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 18 -->
|
||||
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<!-- Tabs Panes - End -->
|
||||
</div>
|
||||
138
src/Explorer/Tabs/TabsManager.test.ts
Normal file
138
src/Explorer/Tabs/TabsManager.test.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { CollectionStub, DatabaseStub } from "../../Explorer/OpenActionsStubs";
|
||||
import { DataAccessUtility } from "../../Platform/Portal/DataAccessUtility";
|
||||
import { TabsManager } from "./TabsManager";
|
||||
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
|
||||
import DocumentsTab from "./DocumentsTab";
|
||||
import Explorer from "../Explorer";
|
||||
import QueryTab from "./QueryTab";
|
||||
|
||||
describe("Tabs manager tests", () => {
|
||||
let tabsManager: TabsManager;
|
||||
let explorer: ViewModels.Explorer;
|
||||
let database: ViewModels.Database;
|
||||
let collection: ViewModels.Collection;
|
||||
let queryTab: QueryTab;
|
||||
let documentsTab: DocumentsTab;
|
||||
|
||||
beforeAll(() => {
|
||||
explorer = new Explorer({ documentClientUtility: undefined, notificationsClient: undefined, isEmulator: false });
|
||||
explorer.databaseAccount = ko.observable<ViewModels.DatabaseAccount>({
|
||||
id: "test",
|
||||
name: "test",
|
||||
location: "",
|
||||
type: "",
|
||||
kind: "",
|
||||
tags: "",
|
||||
properties: undefined
|
||||
});
|
||||
|
||||
database = new DatabaseStub({
|
||||
container: explorer,
|
||||
id: ko.observable<string>("test"),
|
||||
isDatabaseShared: () => false
|
||||
});
|
||||
database.isDatabaseExpanded = ko.observable<boolean>(true);
|
||||
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
|
||||
collection = new CollectionStub({
|
||||
container: explorer,
|
||||
databaseId: "test",
|
||||
id: ko.observable<string>("test")
|
||||
});
|
||||
collection.getDatabase = (): ViewModels.Database => database;
|
||||
collection.isCollectionExpanded = ko.observable<boolean>(true);
|
||||
collection.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
|
||||
queryTab = new QueryTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
collection,
|
||||
database,
|
||||
title: "",
|
||||
tabPath: "",
|
||||
documentClientUtility: explorer.documentClientUtility,
|
||||
selfLink: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
hashLocation: "",
|
||||
onUpdateTabsButtons: undefined
|
||||
});
|
||||
|
||||
documentsTab = new DocumentsTab({
|
||||
partitionKey: undefined,
|
||||
documentIds: ko.observableArray<ViewModels.DocumentId>(),
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
collection,
|
||||
title: "",
|
||||
tabPath: "",
|
||||
documentClientUtility: new DocumentClientUtilityBase(new DataAccessUtility()),
|
||||
selfLink: "",
|
||||
hashLocation: "",
|
||||
isActive: ko.observable<boolean>(false),
|
||||
onUpdateTabsButtons: undefined
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
tabsManager = new TabsManager();
|
||||
explorer.tabsManager = tabsManager;
|
||||
});
|
||||
|
||||
it("open new tabs", () => {
|
||||
tabsManager.activateNewTab(queryTab);
|
||||
expect(tabsManager.openedTabs().length).toBe(1);
|
||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
||||
expect(tabsManager.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);
|
||||
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);
|
||||
expect(queryTab.isActive()).toBe(true);
|
||||
expect(documentsTab.isActive()).toBe(false);
|
||||
});
|
||||
|
||||
it("get tabs", () => {
|
||||
tabsManager.activateNewTab(queryTab);
|
||||
tabsManager.activateNewTab(documentsTab);
|
||||
|
||||
const queryTabs: ViewModels.Tab[] = tabsManager.getTabs(ViewModels.CollectionTabKind.Query);
|
||||
expect(queryTabs.length).toBe(1);
|
||||
expect(queryTabs[0]).toEqual(queryTab);
|
||||
|
||||
const documentsTabs: ViewModels.Tab[] = tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab: ViewModels.Tab) => tab.tabId === documentsTab.tabId
|
||||
);
|
||||
expect(documentsTabs.length).toBe(1);
|
||||
expect(documentsTabs[0]).toEqual(documentsTab);
|
||||
});
|
||||
|
||||
it("close tabs", () => {
|
||||
tabsManager.activateNewTab(queryTab);
|
||||
tabsManager.activateNewTab(documentsTab);
|
||||
|
||||
tabsManager.closeTab(documentsTab.tabId, explorer);
|
||||
expect(tabsManager.openedTabs().length).toBe(1);
|
||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
||||
expect(queryTab.isActive()).toBe(true);
|
||||
expect(documentsTab.isActive()).toBe(false);
|
||||
|
||||
tabsManager.closeTabsByComparator((tab: ViewModels.Tab) => tab.tabId === queryTab.tabId);
|
||||
expect(tabsManager.openedTabs().length).toBe(0);
|
||||
expect(tabsManager.activeTab()).toEqual(undefined);
|
||||
expect(queryTab.isActive()).toBe(false);
|
||||
});
|
||||
});
|
||||
96
src/Explorer/Tabs/TabsManager.ts
Normal file
96
src/Explorer/Tabs/TabsManager.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import TabsManagerTemplate from "./TabsManager.html";
|
||||
|
||||
export class TabsManager {
|
||||
public openedTabs: ko.ObservableArray<ViewModels.Tab>;
|
||||
public activeTab: ko.Observable<ViewModels.Tab>;
|
||||
|
||||
constructor() {
|
||||
this.openedTabs = ko.observableArray<ViewModels.Tab>([]);
|
||||
this.activeTab = ko.observable<ViewModels.Tab>();
|
||||
}
|
||||
|
||||
public activateNewTab(tab: ViewModels.Tab): void {
|
||||
this.openedTabs.push(tab);
|
||||
this.activateTab(tab);
|
||||
}
|
||||
|
||||
public activateTab(tab: ViewModels.Tab): void {
|
||||
this.activeTab() && this.activeTab().isActive(false);
|
||||
tab.isActive(true);
|
||||
this.activeTab(tab);
|
||||
}
|
||||
|
||||
public getTabs(
|
||||
tabKind: ViewModels.CollectionTabKind,
|
||||
comparator?: (tab: ViewModels.Tab) => boolean
|
||||
): ViewModels.Tab[] {
|
||||
return this.openedTabs().filter((openedTab: ViewModels.Tab) => {
|
||||
return openedTab.tabKind === tabKind && (!comparator || comparator(openedTab));
|
||||
});
|
||||
}
|
||||
|
||||
public refreshActiveTab(comparator: (tab: ViewModels.Tab) => boolean): void {
|
||||
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
||||
this.openedTabs().forEach((tab: ViewModels.Tab) => {
|
||||
if (comparator(tab) && tab.isActive()) {
|
||||
tab.onActivate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public removeTabById(tabId: string): void {
|
||||
this.openedTabs.remove((tab: ViewModels.Tab) => tab.tabId === tabId);
|
||||
}
|
||||
|
||||
public removeTabByComparator(comparator: (tab: ViewModels.Tab) => boolean): void {
|
||||
this.openedTabs.remove((tab: ViewModels.Tab) => comparator(tab));
|
||||
}
|
||||
|
||||
public closeTabsByComparator(comparator: (tab: ViewModels.Tab) => boolean): void {
|
||||
this.activeTab() && this.activeTab().isActive(false);
|
||||
this.activeTab(undefined);
|
||||
this.openedTabs().forEach((tab: ViewModels.Tab) => {
|
||||
if (comparator(tab)) {
|
||||
tab.onCloseTabButtonClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public closeTabs(): void {
|
||||
this.openedTabs([]);
|
||||
}
|
||||
|
||||
public closeTab(tabId: string, explorer: ViewModels.Explorer): void {
|
||||
const tabIndex: number = this.openedTabs().findIndex((tab: ViewModels.Tab) => tab.tabId === tabId);
|
||||
if (tabIndex !== -1) {
|
||||
const tabToActive: ViewModels.Tab = this.openedTabs()[tabIndex + 1] || this.openedTabs()[tabIndex - 1];
|
||||
this.openedTabs()[tabIndex].isActive(false);
|
||||
this.removeTabById(tabId);
|
||||
if (tabToActive) {
|
||||
tabToActive.isActive(true);
|
||||
this.activeTab(tabToActive);
|
||||
} else {
|
||||
explorer.selectedNode(undefined);
|
||||
explorer.onUpdateTabsButtons([]);
|
||||
this.activeTab(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public isTabActive(tabKind: ViewModels.CollectionTabKind): boolean {
|
||||
return this.activeTab() && this.activeTab().tabKind === tabKind;
|
||||
}
|
||||
}
|
||||
|
||||
function TabsManagerWrapperViewModel(params: { data: TabsManager }) {
|
||||
return params.data;
|
||||
}
|
||||
|
||||
export function TabsManagerKOComponent(): unknown {
|
||||
return {
|
||||
viewModel: TabsManagerWrapperViewModel,
|
||||
template: TabsManagerTemplate
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user