Create tabs manager and refactor tab related logic (#66)

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
victor-meng 2020-07-09 13:53:37 -07:00 committed by GitHub
parent 326bd4f494
commit 4068a9fbaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 3430 additions and 3139 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2349,6 +2349,11 @@ a:link {
text-decoration: none; text-decoration: none;
} }
.tabsContainer {
height: 100%;
overflow: hidden;
}
.tabs { .tabs {
position: relative; position: relative;
margin: 15px 0 25px 0; margin: 15px 0 25px 0;

2808
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@ import { QueryMetrics } from "@azure/cosmos";
import { SetupNotebooksPane } from "../Explorer/Panes/SetupNotebooksPane"; import { SetupNotebooksPane } from "../Explorer/Panes/SetupNotebooksPane";
import { Splitter } from "../Common/Splitter"; import { Splitter } from "../Common/Splitter";
import { StringInputPane } from "../Explorer/Panes/StringInputPane"; import { StringInputPane } from "../Explorer/Panes/StringInputPane";
import { TabsManager } from "../Explorer/Tabs/TabsManager";
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent"; import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
import { UploadDetails } from "../workers/upload/definitions"; import { UploadDetails } from "../workers/upload/definitions";
import { UploadItemsPaneAdapter } from "../Explorer/Panes/UploadItemsPaneAdapter"; import { UploadItemsPaneAdapter } from "../Explorer/Panes/UploadItemsPaneAdapter";
@ -121,9 +122,8 @@ export interface Explorer {
isResourceTokenCollectionNodeSelected: ko.Computed<boolean>; isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
// Tabs // Tabs
openedTabs: ko.ObservableArray<Tab>;
activeTab: ko.Observable<Tab>;
isTabsContentExpanded: ko.Observable<boolean>; isTabsContentExpanded: ko.Observable<boolean>;
tabsManager: TabsManager;
// Contextual Panes // Contextual Panes
addDatabasePane: AddDatabasePane; addDatabasePane: AddDatabasePane;
@ -161,8 +161,6 @@ export interface Explorer {
refreshDatabaseForResourceToken(): Q.Promise<void>; refreshDatabaseForResourceToken(): Q.Promise<void>;
refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any>; refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any>;
closeAllPanes(): void; closeAllPanes(): void;
closeAllTabsForResource(resourceId: string): void;
findActiveTab(): Tab; // TODO Deprecate in favor activeTab
findSelectedDatabase(): Database; findSelectedDatabase(): Database;
findDatabaseWithId(databaseRid: string): Database; findDatabaseWithId(databaseRid: string): Database;
isLastDatabase(): boolean; isLastDatabase(): boolean;
@ -221,7 +219,6 @@ export interface Explorer {
sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>; sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
arcadiaToken: ko.Observable<string>; arcadiaToken: ko.Observable<string>;
arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>; arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
isNotebookTabActive: ko.Computed<boolean>;
memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
notebookManager?: any; // This is dynamically loaded notebookManager?: any; // This is dynamically loaded
openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise
@ -250,7 +247,6 @@ export interface Explorer {
readFile: (notebookFile: NotebookContentItem) => Promise<string>; readFile: (notebookFile: NotebookContentItem) => Promise<string>;
downloadFile: (notebookFile: NotebookContentItem) => Promise<void>; downloadFile: (notebookFile: NotebookContentItem) => Promise<void>;
createNotebookContentItemFile: (name: string, filepath: string) => NotebookContentItem; createNotebookContentItemFile: (name: string, filepath: string) => NotebookContentItem;
closeNotebookTab: (filepath: string) => void;
refreshContentItem(item: NotebookContentItem): Promise<void>; refreshContentItem(item: NotebookContentItem): Promise<void>;
getNotebookBasePath(): string; getNotebookBasePath(): string;
@ -381,7 +377,6 @@ export interface Database extends TreeNode {
findCollectionWithId(collectionRid: string): Collection; findCollectionWithId(collectionRid: string): Collection;
openAddCollection(database: Database, event: MouseEvent): void; openAddCollection(database: Database, event: MouseEvent): void;
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void; onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
refreshTabSelectedState(): void;
readSettings(): void; readSettings(): void;
onSettingsClick: () => void; onSettingsClick: () => void;
} }
@ -403,7 +398,6 @@ export interface CollectionBase extends TreeNode {
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void; onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
expandCollection(): Q.Promise<any>; expandCollection(): Q.Promise<any>;
collapseCollection(): void; collapseCollection(): void;
refreshActiveTab(): void;
getDatabase(): Database; getDatabase(): Database;
} }
@ -837,7 +831,6 @@ export interface TabOptions {
onUpdateTabsButtons: (buttons: NavbarButtonConfig[]) => void; onUpdateTabsButtons: (buttons: NavbarButtonConfig[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>; isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number; onLoadStartKey?: number;
openedTabs: Tab[];
// TODO Remove the flag and use a context to handle this // TODO Remove the flag and use a context to handle this
// TODO: 145357 Remove dependency on collection/database and add abstraction // TODO: 145357 Remove dependency on collection/database and add abstraction
@ -931,14 +924,12 @@ export interface Tab {
tabTitle: ko.Observable<string>; tabTitle: ko.Observable<string>;
hashLocation: ko.Observable<string>; hashLocation: ko.Observable<string>;
closeTabButton: Button; closeTabButton: Button;
onCloseTabButtonClick(): Q.Promise<any>; onCloseTabButtonClick(): void;
onTabClick(): Q.Promise<any>; onTabClick(): Q.Promise<any>;
onKeyPressActivate(source: any, event: KeyboardEvent): void; onKeyPressActivate(source: any, event: KeyboardEvent): void;
onKeyPressClose(source: any, event: KeyboardEvent): void; onKeyPressClose(source: any, event: KeyboardEvent): void;
onActivate(): Q.Promise<any>; onActivate(): Q.Promise<any>;
refresh(): void; refresh(): void;
nextTab: ko.Observable<Tab>;
previousTab: ko.Observable<Tab>;
closeButtonTabIndex: ko.Computed<number>; closeButtonTabIndex: ko.Computed<number>;
isExecutionError: ko.Observable<boolean>; isExecutionError: ko.Observable<boolean>;
isExecuting: ko.Observable<boolean>; isExecuting: ko.Observable<boolean>;

View File

@ -10,6 +10,7 @@ import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleCompo
import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead"; import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent"; import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar"; import { ToolbarComponent } from "./Controls/Toolbar/Toolbar";
@ -26,6 +27,7 @@ ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input", ThroughputInputComponent); ko.components.register("throughput-input", ThroughputInputComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent());
// Collection Tabs // Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab()); ko.components.register("documents-tab", new TabComponents.DocumentsTab());

View File

@ -77,6 +77,7 @@ import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenCompone
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter"; import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane"; import { StringInputPane } from "./Panes/StringInputPane";
import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane"; import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane";
import { TabsManager } from "./Tabs/TabsManager";
import { UploadFilePane } from "./Panes/UploadFilePane"; import { UploadFilePane } from "./Panes/UploadFilePane";
import { UploadItemsPane } from "./Panes/UploadItemsPane"; import { UploadItemsPane } from "./Panes/UploadItemsPane";
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter"; import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
@ -161,11 +162,10 @@ export default class Explorer implements ViewModels.Explorer {
private resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken; private resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
// Tabs // Tabs
public openedTabs: ko.ObservableArray<ViewModels.Tab>;
public activeTab: ko.Observable<ViewModels.Tab>;
public isTabsContentExpanded: ko.Observable<boolean>; public isTabsContentExpanded: ko.Observable<boolean>;
public galleryTab: any; public galleryTab: any;
public notebookViewerTab: any; public notebookViewerTab: any;
public tabsManager: TabsManager;
// Contextual panes // Contextual panes
public addDatabasePane: ViewModels.AddDatabasePane; public addDatabasePane: ViewModels.AddDatabasePane;
@ -229,7 +229,6 @@ export default class Explorer implements ViewModels.Explorer {
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>; public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>; public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Observable<boolean>; public isSynapseLinkUpdating: ko.Observable<boolean>;
public isNotebookTabActive: ko.Computed<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any; // This is dynamically loaded public notebookManager?: any; // This is dynamically loaded
@ -296,14 +295,10 @@ export default class Explorer implements ViewModels.Explorer {
this.arcadiaToken = ko.observable<string>(); this.arcadiaToken = ko.observable<string>();
this.arcadiaToken.subscribe((token: string) => { this.arcadiaToken.subscribe((token: string) => {
if (token) { if (token) {
this.openedTabs && const notebookTabs = this.tabsManager.getTabs(ViewModels.CollectionTabKind.NotebookV2);
this.openedTabs().forEach(tab => { (notebookTabs || []).forEach((tab: NotebookV2Tab) => {
if (tab.tabKind === ViewModels.CollectionTabKind.Notebook) { tab.reconfigureServiceEndpoints();
throw new Error("NotebookTab is deprecated. Use NotebookV2Tab"); });
} else if (tab.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
(tab as NotebookV2Tab).reconfigureServiceEndpoints();
}
});
} }
}); });
this.isNotebooksEnabledForAccount = ko.observable(false); this.isNotebooksEnabledForAccount = ko.observable(false);
@ -771,12 +766,8 @@ export default class Explorer implements ViewModels.Explorer {
container: this container: this
}); });
this.openedTabs = ko.observableArray<ViewModels.Tab>([]); this.tabsManager = new TabsManager();
this.activeTab = ko.observable(null);
this.isNotebookTabActive = ko.computed<boolean>(() => {
// only show memory tracker if the current active tab is a notebook
return this.activeTab() && this.activeTab().tabKind === ViewModels.CollectionTabKind.NotebookV2;
});
this._panes = [ this._panes = [
this.addDatabasePane, this.addDatabasePane,
this.addCollectionPane, this.addCollectionPane,
@ -1262,7 +1253,7 @@ export default class Explorer implements ViewModels.Explorer {
class: "connectDialogButtons okBtn connectOkBtns", class: "connectDialogButtons okBtn connectOkBtns",
click: () => { click: () => {
$("#contextSwitchPrompt").dialog("close"); $("#contextSwitchPrompt").dialog("close");
this.openedTabs([]); // clear all tabs so we dont leave any tabs from previous session open this.tabsManager.closeTabs(); // clear all tabs so we dont leave any tabs from previous session open
this.renewShareAccess(connectionString); this.renewShareAccess(connectionString);
} }
}; };
@ -2090,10 +2081,6 @@ export default class Explorer implements ViewModels.Explorer {
return Q(); return Q();
} }
public findActiveTab(): ViewModels.Tab {
return this.activeTab();
}
public findSelectedCollection(): ViewModels.Collection { public findSelectedCollection(): ViewModels.Collection {
if (this.selectedNode().nodeKind === "Collection") { if (this.selectedNode().nodeKind === "Collection") {
return this.findSelectedCollectionForSelectedNode(); return this.findSelectedCollectionForSelectedNode();
@ -2106,11 +2093,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedStoredProcedure(): ViewModels.StoredProcedure { public findSelectedStoredProcedure(): ViewModels.StoredProcedure {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.storedProcedures(), (storedProcedure: ViewModels.StoredProcedure) => { return _.find(selectedCollection.storedProcedures(), (storedProcedure: ViewModels.StoredProcedure) => {
const openedSprocTab = this.openedTabs().filter( const openedSprocTab = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.StoredProcedures,
tab.node && (tab: ViewModels.Tab) => tab.node && tab.node.rid === storedProcedure.rid
tab.node.rid === storedProcedure.rid &&
tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures
); );
return ( return (
storedProcedure.rid === this.selectedNode().rid || storedProcedure.rid === this.selectedNode().rid ||
@ -2122,11 +2107,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedUDF(): ViewModels.UserDefinedFunction { public findSelectedUDF(): ViewModels.UserDefinedFunction {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: ViewModels.UserDefinedFunction) => { return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: ViewModels.UserDefinedFunction) => {
const openedUdfTab = this.openedTabs().filter( const openedUdfTab = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.UserDefinedFunctions,
tab.node && (tab: ViewModels.Tab) => tab.node && tab.node.rid === userDefinedFunction.rid
tab.node.rid === userDefinedFunction.rid &&
tab.tabKind === ViewModels.CollectionTabKind.UserDefinedFunctions
); );
return ( return (
userDefinedFunction.rid === this.selectedNode().rid || userDefinedFunction.rid === this.selectedNode().rid ||
@ -2138,9 +2121,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedTrigger(): ViewModels.Trigger { public findSelectedTrigger(): ViewModels.Trigger {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.triggers(), (trigger: ViewModels.Trigger) => { return _.find(selectedCollection.triggers(), (trigger: ViewModels.Trigger) => {
const openedTriggerTab = this.openedTabs().filter( const openedTriggerTab = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.Triggers,
tab.node && tab.node.rid === trigger.rid && tab.tabKind === ViewModels.CollectionTabKind.Triggers (tab: ViewModels.Tab) => tab.node && tab.node.rid === trigger.rid
); );
return ( return (
trigger.rid === this.selectedNode().rid || trigger.rid === this.selectedNode().rid ||
@ -2149,16 +2132,6 @@ export default class Explorer implements ViewModels.Explorer {
}); });
} }
public closeAllTabsForResource(resourceId: string): void {
const currentlyActiveTabs = this.openedTabs().filter((tab: ViewModels.Tab) => tab.isActive && tab.isActive());
currentlyActiveTabs.forEach((tab: ViewModels.Tab) => tab.isActive(false));
this.activeTab(null);
const openedTabsForResource = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === resourceId
);
openedTabsForResource.forEach((tab: ViewModels.Tab) => tab.onCloseTabButtonClick());
}
public closeAllPanes(): void { public closeAllPanes(): void {
this._panes.forEach((pane: ViewModels.ContextualPane) => pane.close()); this._panes.forEach((pane: ViewModels.ContextualPane) => pane.close());
} }
@ -2235,7 +2208,9 @@ export default class Explorer implements ViewModels.Explorer {
if (isNewDatabase) { if (isNewDatabase) {
database.expandDatabase(); database.expandDatabase();
} }
database.refreshTabSelectedState(); this.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === database.rid
);
}) })
); );
}); });
@ -2338,7 +2313,7 @@ export default class Explorer implements ViewModels.Explorer {
} }
const urlPrefixWithKeyParam: string = `${config.hostedExplorerURL}?key=`; const urlPrefixWithKeyParam: string = `${config.hostedExplorerURL}?key=`;
const currentActiveTab: ViewModels.Tab = this.findActiveTab(); const currentActiveTab: ViewModels.Tab = this.tabsManager.activeTab();
return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`; return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`;
} }
@ -2600,46 +2575,48 @@ export default class Explorer implements ViewModels.Explorer {
if (!notebookContentItem || !notebookContentItem.path) { if (!notebookContentItem || !notebookContentItem.path) {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`); throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
} }
const openedNotebookTabs = this.getNotebookTabsForFilepath(notebookContentItem.path);
if (openedNotebookTabs.length > 0) { const notebookTabs: NotebookV2Tab[] = this.tabsManager.getTabs(
openedNotebookTabs[0].onTabClick(); ViewModels.CollectionTabKind.NotebookV2,
openedNotebookTabs[0].onActivate(); (tab: ViewModels.Tab) =>
return true; (tab as NotebookV2Tab).notebookPath &&
FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), notebookContentItem.path)
) as NotebookV2Tab[];
let notebookTab: NotebookV2Tab = notebookTabs && notebookTabs[0];
if (notebookTab) {
this.tabsManager.activateTab(notebookTab);
} else {
const options: ViewModels.NotebookTabOptions = {
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookV2,
node: null,
title: notebookContentItem.name,
tabPath: notebookContentItem.path,
documentClientUtility: null,
collection: null,
selfLink: null,
masterKey: CosmosClient.masterKey() || "",
hashLocation: "notebooks",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
notebookContentItem
};
try {
const NotebookTabV2 = await import(/* webpackChunkName: "NotebookV2Tab" */ "./Tabs/NotebookV2Tab");
notebookTab = new NotebookTabV2.default(options);
this.tabsManager.activateNewTab(notebookTab);
} catch (reason) {
console.error("Import NotebookV2Tab failed!", reason);
return false;
}
} }
const options: ViewModels.NotebookTabOptions = {
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookV2,
node: null,
title: notebookContentItem.name,
tabPath: notebookContentItem.path,
documentClientUtility: null,
collection: null,
selfLink: null,
masterKey: CosmosClient.masterKey() || "",
hashLocation: "notebooks",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
notebookContentItem,
openedTabs: this.openedTabs()
};
try {
const NotebookTabV2 = await import(/* webpackChunkName: "NotebookV2Tab" */ "./Tabs/NotebookV2Tab");
const notebookTab = new NotebookTabV2.default(options);
this.openedTabs.push(notebookTab);
// Activate
notebookTab.onTabClick();
} catch (reason) {
console.error("Import NotebookV2Tab failed!", reason);
return false;
}
return true; return true;
} }
@ -2652,10 +2629,11 @@ export default class Explorer implements ViewModels.Explorer {
} }
// Don't delete if tab is open to avoid accidental deletion // Don't delete if tab is open to avoid accidental deletion
const openedNotebookTabs = this.openedTabs().filter( const openedNotebookTabs = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.NotebookV2,
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && (tab: NotebookV2Tab) => {
(tab as NotebookV2Tab).notebookPath() === notebookFile.path return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path);
}
); );
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again."); this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
@ -2675,17 +2653,15 @@ export default class Explorer implements ViewModels.Explorer {
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input) onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
}) })
.then(newNotebookFile => { .then(newNotebookFile => {
this.openedTabs() const notebookTabs: ViewModels.Tab[] = this.tabsManager.getTabs(
.filter( ViewModels.CollectionTabKind.NotebookV2,
(tab: ViewModels.Tab) => (tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && );
FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), originalPath) notebookTabs.forEach(tab => {
) tab.tabTitle(newNotebookFile.name);
.forEach(tab => { tab.tabPath(newNotebookFile.path);
tab.tabTitle(newNotebookFile.name); (tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
tab.tabPath(newNotebookFile.path); });
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
return newNotebookFile; return newNotebookFile;
}); });
@ -2865,39 +2841,34 @@ export default class Explorer implements ViewModels.Explorer {
await this.initSparkConnectionInfo(this.databaseAccount()); await this.initSparkConnectionInfo(this.databaseAccount());
} }
const openedSparkMasterTabs = this.openedTabs().filter( const sparkMasterTabs: SparkMasterTab[] = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.SparkMasterTab ViewModels.CollectionTabKind.SparkMasterTab
); ) as SparkMasterTab[];
if (openedSparkMasterTabs.length > 0) { let sparkMasterTab: SparkMasterTab = sparkMasterTabs && sparkMasterTabs[0];
openedSparkMasterTabs[0].onTabClick();
openedSparkMasterTabs[0].onActivate(); if (sparkMasterTab) {
return; this.tabsManager.activateTab(sparkMasterTab);
} else {
sparkMasterTab = new SparkMasterTab({
clusterConnectionInfo: this.sparkClusterConnectionInfo(),
tabKind: ViewModels.CollectionTabKind.SparkMasterTab,
node: null,
title: "Apache Spark",
tabPath: "",
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: "sparkmaster",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this
});
this.tabsManager.activateNewTab(sparkMasterTab);
} }
const sparkMasterTab = new SparkMasterTab({
clusterConnectionInfo: this.sparkClusterConnectionInfo(),
tabKind: ViewModels.CollectionTabKind.SparkMasterTab,
node: null,
title: "Apache Spark",
tabPath: "",
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: "sparkmaster",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
openedTabs: this.openedTabs(),
container: this
});
this.openedTabs.push(sparkMasterTab);
// Activate
sparkMasterTab.onTabClick();
return;
} }
private refreshNotebookList = async (): Promise<void> => { private refreshNotebookList = async (): Promise<void> => {
@ -2920,9 +2891,11 @@ export default class Explorer implements ViewModels.Explorer {
} }
// Don't delete if tab is open to avoid accidental deletion // Don't delete if tab is open to avoid accidental deletion
const openedNotebookTabs = this.openedTabs().filter( const openedNotebookTabs = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.NotebookV2,
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && (tab as NotebookV2Tab).notebookPath() === item.path (tab: NotebookV2Tab) => {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path);
}
); );
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again."); this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
@ -3084,94 +3057,79 @@ export default class Explorer implements ViewModels.Explorer {
throw new Error("Terminal kind: ${kind} not supported"); throw new Error("Terminal kind: ${kind} not supported");
} }
const openedTabs = this.openedTabs().filter( const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Terminal ViewModels.CollectionTabKind.Terminal,
); (tab: ViewModels.Tab) => tab.hashLocation() == hashLocation
) as TerminalTab[];
let terminalTab: TerminalTab = terminalTabs && terminalTabs[0];
for (let i = 0; i < openedTabs.length; ++i) { if (terminalTab) {
if (openedTabs[i].hashLocation() == hashLocation) { this.tabsManager.activateTab(terminalTab);
openedTabs[i].onTabClick(); } else {
openedTabs[i].onActivate(); const newTab = new TerminalTab({
return; account: CosmosClient.databaseAccount(),
} tabKind: ViewModels.CollectionTabKind.Terminal,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
kind: kind
});
this.tabsManager.activateNewTab(newTab);
} }
const newTab = new TerminalTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Terminal,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
openedTabs: this.openedTabs(),
kind: kind
});
this.openedTabs.push(newTab);
// Activate
newTab.onTabClick();
} }
public async openGallery(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) { public async openGallery(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) {
let title: string; let title: string = "Gallery";
let hashLocation: string; let hashLocation: string = "gallery";
title = "Gallery"; const galleryTabs = this.tabsManager.getTabs(
hashLocation = "gallery"; ViewModels.CollectionTabKind.Gallery,
(tab: ViewModels.Tab) => tab.hashLocation() == hashLocation
const openedTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Gallery
); );
let galleryTab = galleryTabs && galleryTabs[0];
for (let i = 0; i < openedTabs.length; ++i) { if (galleryTab) {
if (openedTabs[i].hashLocation() == hashLocation) { this.tabsManager.activateTab(galleryTab);
openedTabs[i].onTabClick(); (galleryTab as any).updateGalleryParams(notebookUrl, galleryItem, isFavorite);
openedTabs[i].onActivate(); } else {
(openedTabs[i] as any).updateGalleryParams(notebookUrl, galleryItem, isFavorite); if (!this.galleryTab) {
return; this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab");
} }
const newTab = new this.galleryTab.default({
// GalleryTabOptions
account: CosmosClient.databaseAccount(),
container: this,
junoClient: this.notebookManager?.junoClient,
notebookUrl,
galleryItem,
isFavorite,
// TabOptions
tabKind: ViewModels.CollectionTabKind.Gallery,
title: title,
tabPath: title,
documentClientUtility: null,
selfLink: null,
isActive: ko.observable(false),
hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons,
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null
});
this.tabsManager.activateNewTab(newTab);
} }
if (!this.galleryTab) {
this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab");
}
const newTab = new this.galleryTab.default({
// GalleryTabOptions
account: CosmosClient.databaseAccount(),
container: this,
junoClient: this.notebookManager?.junoClient,
notebookUrl,
galleryItem,
isFavorite,
// TabOptions
tabKind: ViewModels.CollectionTabKind.Gallery,
title: title,
tabPath: title,
documentClientUtility: null,
selfLink: null,
isActive: ko.observable(false),
hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons,
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
openedTabs: this.openedTabs()
});
this.openedTabs.push(newTab);
// Activate
newTab.onTabClick();
} }
public async openNotebookViewer(notebookUrl: string) { public async openNotebookViewer(notebookUrl: string) {
@ -3189,41 +3147,37 @@ export default class Explorer implements ViewModels.Explorer {
return notebookViewerTab.notebookUrl === notebookUrl; return notebookViewerTab.notebookUrl === notebookUrl;
}; };
const openedTabs = this.openedTabs().filter( const notebookViewerTabs = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.NotebookViewer && isNotebookViewerOpen(tab) ViewModels.CollectionTabKind.NotebookV2,
); (tab: ViewModels.Tab) => {
return tab.hashLocation() == hashLocation && isNotebookViewerOpen(tab);
for (let i = 0; i < openedTabs.length; ++i) {
if (openedTabs[i].hashLocation() == hashLocation) {
openedTabs[i].onTabClick();
openedTabs[i].onActivate();
return;
} }
);
let notebookViewerTab = notebookViewerTabs && notebookViewerTabs[0];
if (notebookViewerTab) {
this.tabsManager.activateNewTab(notebookViewerTab);
} else {
notebookViewerTab = new this.notebookViewerTab.default({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookViewer,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
notebookUrl
});
this.tabsManager.activateNewTab(notebookViewerTab);
} }
const newTab = new this.notebookViewerTab.default({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookViewer,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
openedTabs: this.openedTabs(),
notebookUrl
});
this.openedTabs.push(newTab);
// Activate
newTab.onTabClick();
} }
public onNewCollectionClicked(): void { public onNewCollectionClicked(): void {
@ -3235,24 +3189,8 @@ export default class Explorer implements ViewModels.Explorer {
document.getElementById("linkAddCollection").focus(); document.getElementById("linkAddCollection").focus();
} }
private getNotebookTabsForFilepath(filepath: string): ViewModels.Tab[] {
return this.openedTabs().filter(
(tab: ViewModels.Tab) =>
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
(tab as any).notebookPath &&
FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
}
public closeNotebookTab(filepath: string): void {
if (!filepath) {
return;
}
this.getNotebookTabsForFilepath(filepath).forEach(tab => tab.onCloseTabButtonClick());
}
private refreshCommandBarButtons(): void { private refreshCommandBarButtons(): void {
const activeTab = this.findActiveTab(); const activeTab = this.tabsManager.activeTab();
if (activeTab) { if (activeTab) {
activeTab.onActivate(); // TODO only update tabs buttons? activeTab.onActivate(); // TODO only update tabs buttons?
} else { } else {

View File

@ -16,10 +16,14 @@ export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>; public parameters: ko.Observable<number>;
public container: ViewModels.Explorer; public container: ViewModels.Explorer;
private tabsButtons: ViewModels.NavbarButtonConfig[]; private tabsButtons: ViewModels.NavbarButtonConfig[];
private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: ViewModels.Explorer) { constructor(container: ViewModels.Explorer) {
this.container = container; this.container = container;
this.tabsButtons = []; this.tabsButtons = [];
this.isNotebookTabActive = ko.computed(() =>
container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
);
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates // These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [ const toWatch = [
@ -39,7 +43,7 @@ export class CommandBarComponentAdapter implements ReactAdapter {
container.isHostedDataExplorerEnabled, container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating, container.isSynapseLinkUpdating,
container.databaseAccount, container.databaseAccount,
container.isNotebookTabActive this.isNotebookTabActive
]; ];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender()); ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
@ -74,7 +78,7 @@ export class CommandBarComponentAdapter implements ReactAdapter {
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor); const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true)); uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (this.container && this.container.isNotebookTabActive()) { if (this.isNotebookTabActive()) {
uiFabricControlButtons.unshift( uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo) CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
); );

View File

@ -43,6 +43,7 @@ import { CdbAppState } from "./types";
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils"; import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
import * as TextFile from "./contents/file/text-file"; import * as TextFile from "./contents/file/text-file";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import { FileSystemUtil } from "../FileSystemUtil";
interface NotebookServiceConfig extends JupyterServerConfig { interface NotebookServiceConfig extends JupyterServerConfig {
userPuid?: string; userPuid?: string;
@ -806,7 +807,9 @@ const closeUnsupportedMimetypesEpic = (
if (explorer && !TextFile.handles(mimetype)) { if (explorer && !TextFile.handles(mimetype)) {
const filepath = action.payload.filepath; const filepath = action.payload.filepath;
// Close tab and show error message // Close tab and show error message
explorer.closeNotebookTab(filepath); explorer.tabsManager.closeTabsByComparator(
tab => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`; const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
explorer.showOkModalDialog("File cannot be rendered", msg); explorer.showOkModalDialog("File cannot be rendered", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
@ -832,7 +835,9 @@ const closeContentFailedToFetchEpic = (
if (explorer) { if (explorer) {
const filepath = action.payload.filepath; const filepath = action.payload.filepath;
// Close tab and show error message // Close tab and show error message
explorer.closeNotebookTab(filepath); explorer.tabsManager.closeTabsByComparator(
tab => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
const msg = `Failed to load file: ${filepath}.`; const msg = `Failed to load file: ${filepath}.`;
explorer.showOkModalDialog("Failure to load", msg); explorer.showOkModalDialog("Failure to load", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);

View File

@ -22,6 +22,7 @@ import { Versions } from "../../src/Contracts/ExplorerContracts";
import { CollectionCreationDefaults } from "../Shared/Constants"; import { CollectionCreationDefaults } from "../Shared/Constants";
import { IGalleryItem } from "../Juno/JunoClient"; import { IGalleryItem } from "../Juno/JunoClient";
import { ReactAdapter } from "../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import { TabsManager } from "./Tabs/TabsManager";
export class ExplorerStub implements ViewModels.Explorer { export class ExplorerStub implements ViewModels.Explorer {
public flight: ko.Observable<string>; public flight: ko.Observable<string>;
@ -69,7 +70,6 @@ export class ExplorerStub implements ViewModels.Explorer {
public isLeftPaneExpanded: ko.Observable<boolean>; public isLeftPaneExpanded: ko.Observable<boolean>;
public selectedNode: ko.Observable<ViewModels.TreeNode>; public selectedNode: ko.Observable<ViewModels.TreeNode>;
public isRefreshingExplorer: ko.Observable<boolean>; public isRefreshingExplorer: ko.Observable<boolean>;
public openedTabs: ko.ObservableArray<ViewModels.Tab>;
public isTabsContentExpanded: ko.Observable<boolean>; public isTabsContentExpanded: ko.Observable<boolean>;
public addCollectionPane: ViewModels.AddCollectionPane; public addCollectionPane: ViewModels.AddCollectionPane;
public addDatabasePane: ViewModels.AddDatabasePane; public addDatabasePane: ViewModels.AddDatabasePane;
@ -101,7 +101,6 @@ export class ExplorerStub implements ViewModels.Explorer {
public canExceedMaximumValue: ko.Computed<boolean>; public canExceedMaximumValue: ko.Computed<boolean>;
public isHostedDataExplorerEnabled: ko.Computed<boolean>; public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>(Versions.DataExplorer); public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>(Versions.DataExplorer);
public activeTab: ko.Observable<ViewModels.Tab>;
public mostRecentActivity: MostRecentActivity; public mostRecentActivity: MostRecentActivity;
public isNotebookEnabled: ko.Observable<boolean>; public isNotebookEnabled: ko.Observable<boolean>;
public isSparkEnabled: ko.Observable<boolean>; public isSparkEnabled: ko.Observable<boolean>;
@ -119,7 +118,6 @@ export class ExplorerStub implements ViewModels.Explorer {
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>; public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>; public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Observable<boolean>; public isSynapseLinkUpdating: ko.Observable<boolean>;
public isNotebookTabActive: ko.Computed<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any; public notebookManager?: any;
public openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void; public openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
@ -130,6 +128,7 @@ export class ExplorerStub implements ViewModels.Explorer {
public resourceTokenPartitionKey: ko.Observable<string>; public resourceTokenPartitionKey: ko.Observable<string>;
public isAuthWithResourceToken: ko.Observable<boolean>; public isAuthWithResourceToken: ko.Observable<boolean>;
public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>; public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
public tabsManager: TabsManager;
private _featureEnabledReturnValue: boolean; private _featureEnabledReturnValue: boolean;
@ -248,10 +247,6 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
public findActiveTab(): ViewModels.Tab {
throw new Error("Not implemented");
}
public findSelectedStoredProcedure(): ViewModels.StoredProcedure { public findSelectedStoredProcedure(): ViewModels.StoredProcedure {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
@ -300,10 +295,6 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
public closeAllTabsForResource(resourceId: string): void {
throw new Error("Not implemented");
}
public getPlatformType(): PlatformType { public getPlatformType(): PlatformType {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
@ -426,10 +417,6 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
public closeNotebookTab(filepath: string): void {
throw new Error("Not implemented");
}
public refreshContentItem(item: NotebookContentItem): Promise<void> { public refreshContentItem(item: NotebookContentItem): Promise<void> {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
@ -511,10 +498,6 @@ export class DatabaseStub implements ViewModels.Database {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
public refreshTabSelectedState(): void {
throw new Error("Not implemented");
}
public readSettings() { public readSettings() {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
@ -779,10 +762,6 @@ export class CollectionStub implements ViewModels.Collection {
throw new Error("Not implemented"); throw new Error("Not implemented");
}; };
public refreshActiveTab = (): void => {
throw new Error("Not implemented");
};
public getLabel(): string { public getLabel(): string {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }

View File

@ -87,7 +87,7 @@ export class BrowseQueriesPane extends ContextualPaneBase implements ViewModels.
} else { } else {
selectedCollection.onNewQueryClick(selectedCollection, null); selectedCollection.onNewQueryClick(selectedCollection, null);
} }
const queryTab: ViewModels.QueryTab = this.container.findActiveTab() as ViewModels.QueryTab; const queryTab: ViewModels.QueryTab = this.container.tabsManager.activeTab() as ViewModels.QueryTab;
queryTab.tabTitle(savedQuery.queryName); queryTab.tabTitle(savedQuery.queryName);
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`); queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
queryTab.initialEditorContent(savedQuery.query); queryTab.initialEditorContent(savedQuery.query);

View File

@ -66,7 +66,9 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
this.isExecuting(false); this.isExecuting(false);
this.close(); this.close();
this.container.selectedNode(selectedCollection.database); this.container.selectedNode(selectedCollection.database);
this.container.closeAllTabsForResource(selectedCollection.rid); this.container.tabsManager?.closeTabsByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === selectedCollection.rid
);
this.container.refreshAllDatabases(); this.container.refreshAllDatabases();
this.resetData(); this.resetData();
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(

View File

@ -11,6 +11,7 @@ import Explorer from "../Explorer";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs"; import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { TreeNode } from "../../Contracts/ViewModels"; import { TreeNode } from "../../Contracts/ViewModels";
import { TabsManager } from "../Tabs/TabsManager";
describe("Delete Database Confirmation Pane", () => { describe("Delete Database Confirmation Pane", () => {
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => { describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
@ -123,6 +124,7 @@ describe("Delete Database Confirmation Pane", () => {
); );
sinon.stub(fakeExplorer, "documentClientUtility").value(fakeDocumentClientUtility); sinon.stub(fakeExplorer, "documentClientUtility").value(fakeDocumentClientUtility);
sinon.stub(fakeExplorer, "selectedNode").value(ko.observable<TreeNode>()); sinon.stub(fakeExplorer, "selectedNode").value(ko.observable<TreeNode>());
sinon.stub(fakeExplorer, "tabsManager").value(new TabsManager());
fakeExplorer.isLastNonEmptyDatabase.returns(true); fakeExplorer.isLastNonEmptyDatabase.returns(true);
let pane = new DeleteDatabaseConfirmationPane({ let pane = new DeleteDatabaseConfirmationPane({

View File

@ -67,11 +67,17 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase
this.isExecuting(false); this.isExecuting(false);
this.close(); this.close();
this.container.refreshAllDatabases(); this.container.refreshAllDatabases();
this.container.closeAllTabsForResource(selectedDatabase.rid); this.container.tabsManager.closeTabsByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === selectedDatabase.rid
);
this.container.selectedNode(null); this.container.selectedNode(null);
selectedDatabase selectedDatabase
.collections() .collections()
.forEach((collection: ViewModels.Collection) => this.container.closeAllTabsForResource(collection.rid)); .forEach((collection: ViewModels.Collection) =>
this.container.tabsManager.closeTabsByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === collection.rid
)
);
this.resetData(); this.resetData();
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.DeleteDatabase, Action.DeleteDatabase,

View File

@ -111,7 +111,7 @@ export class LoadQueryPane extends ContextualPaneBase implements ViewModels.Load
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (evt: any): void => { reader.onload = (evt: any): void => {
const fileData: string = evt.target.result; const fileData: string = evt.target.result;
const queryTab: ViewModels.QueryTab = this.container.findActiveTab() as ViewModels.QueryTab; const queryTab: ViewModels.QueryTab = this.container.tabsManager.activeTab() as ViewModels.QueryTab;
queryTab.initialEditorContent(fileData); queryTab.initialEditorContent(fileData);
queryTab.sqlQueryEditorContent(fileData); queryTab.sqlQueryEditorContent(fileData);
deferred.resolve(); deferred.resolve();

View File

@ -53,7 +53,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase implements ViewMode
const apiKind: DataModels.ApiKind = const apiKind: DataModels.ApiKind =
this.container && DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience()); this.container && DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience());
const hasOpenedTabs: boolean = const hasOpenedTabs: boolean =
(this.container && this.container.openedTabs() && this.container.openedTabs().length > 0) || false; (this.container && this.container.tabsManager && this.container.tabsManager.openedTabs().length > 0) || false;
if (!inputMetadata || inputMetadata.apiKind == null || !inputMetadata.accountName) { if (!inputMetadata || inputMetadata.apiKind == null || !inputMetadata.accountName) {
this.formErrors("Invalid connection string input"); this.formErrors("Invalid connection string input");

View File

@ -34,7 +34,8 @@ export class SaveQueryPane extends ContextualPaneBase {
} }
const queryName: string = this.queryName(); const queryName: string = this.queryName();
const queryTab: ViewModels.QueryTab = this.container && (this.container.findActiveTab() as ViewModels.QueryTab); const queryTab: ViewModels.QueryTab =
this.container && (this.container.tabsManager.activeTab() as ViewModels.QueryTab);
const query: string = queryTab && queryTab.sqlQueryEditorContent(); const query: string = queryTab && queryTab.sqlQueryEditorContent();
if (!queryName || queryName.length === 0) { if (!queryName || queryName.length === 0) {
this.formErrors("No query name specified"); this.formErrors("No query name specified");

View File

@ -1,15 +1,16 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil"; import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import { SplashScreenComponentAdapter } from "./SplashScreenComponentApdapter"; import { SplashScreenComponentAdapter } from "./SplashScreenComponentApdapter";
import { TabsManager } from "../Tabs/TabsManager";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
jest.mock("../Explorer"); jest.mock("../Explorer");
const createExplorer = () => { const createExplorer = () => {
const mock = new Explorer({} as any); const mock = new Explorer({} as any);
mock.openedTabs = ko.observableArray([]);
mock.selectedNode = ko.observable(); mock.selectedNode = ko.observable();
mock.isNotebookEnabled = ko.observable(false); mock.isNotebookEnabled = ko.observable(false);
mock.addCollectionText = ko.observable("add collection"); mock.addCollectionText = ko.observable("add collection");
mock.tabsManager = new TabsManager();
return mock as jest.Mocked<Explorer>; return mock as jest.Mocked<Explorer>;
}; };

View File

@ -31,7 +31,7 @@ export class SplashScreenComponentAdapter implements ReactAdapter {
constructor(private container: ViewModels.Explorer) { constructor(private container: ViewModels.Explorer) {
this.parameters = ko.observable<number>(Date.now()); this.parameters = ko.observable<number>(Date.now());
this.container.openedTabs.subscribe(tabs => { this.container.tabsManager.openedTabs.subscribe((tabs: ViewModels.Tab[]) => {
if (tabs.length === 0) { if (tabs.length === 0) {
this.forceRender(); this.forceRender();
} }

View File

@ -21,7 +21,7 @@ import DeleteIcon from "../../../images/delete.svg";
import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos"; import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities"; 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 selectedConflictId: ko.Observable<ViewModels.ConflictId>;
public selectedConflictContent: ViewModels.Editable<string>; public selectedConflictContent: ViewModels.Editable<string>;
public selectedConflictCurrent: ViewModels.Editable<string>; public selectedConflictCurrent: ViewModels.Editable<string>;

View File

@ -20,8 +20,7 @@ describe("Documents tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable<boolean>(false), isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(documentsTab.buildQuery("")).toContain("select"); expect(documentsTab.buildQuery("")).toContain("select");
@ -104,8 +103,7 @@ describe("Documents tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable<boolean>(false), isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(documentsTab.showPartitionKey).toBe(false); expect(documentsTab.showPartitionKey).toBe(false);
@ -124,8 +122,7 @@ describe("Documents tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable<boolean>(false), isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(documentsTab.showPartitionKey).toBe(false); expect(documentsTab.showPartitionKey).toBe(false);
@ -144,8 +141,7 @@ describe("Documents tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable<boolean>(false), isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(documentsTab.showPartitionKey).toBe(true); expect(documentsTab.showPartitionKey).toBe(true);
@ -164,8 +160,7 @@ describe("Documents tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable<boolean>(false), isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(documentsTab.showPartitionKey).toBe(false); expect(documentsTab.showPartitionKey).toBe(false);
@ -184,8 +179,7 @@ describe("Documents tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable<boolean>(false), isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(documentsTab.showPartitionKey).toBe(true); expect(documentsTab.showPartitionKey).toBe(true);

View File

@ -28,8 +28,7 @@ describe("Query Tab", () => {
selfLink: "", selfLink: "",
isActive: ko.observable<boolean>(false), isActive: ko.observable<boolean>(false),
hashLocation: "", hashLocation: "",
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
} }

View File

@ -76,8 +76,7 @@ describe("Settings tab", () => {
quotaInfo, quotaInfo,
null null
), ),
onUpdateTabsButtons: undefined, onUpdateTabsButtons: undefined
openedTabs: []
}); });
}; };
@ -196,8 +195,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(settingsTab.shouldUpdateCollection()).toBe(false); expect(settingsTab.shouldUpdateCollection()).toBe(false);
@ -221,8 +219,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(settingsTab.shouldUpdateCollection()).toBe(false); expect(settingsTab.shouldUpdateCollection()).toBe(false);
@ -241,8 +238,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(settingsTab.shouldUpdateCollection()).toBe(false); expect(settingsTab.shouldUpdateCollection()).toBe(false);
@ -281,8 +277,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null); expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
@ -299,8 +294,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null); expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
@ -326,8 +320,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null); expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
@ -408,8 +401,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: getCollection(defaultApi, partitionKeyOption), collection: getCollection(defaultApi, partitionKeyOption),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
} }
@ -555,8 +547,7 @@ describe("Settings tab", () => {
hashLocation: "", hashLocation: "",
isActive: ko.observable(false), isActive: ko.observable(false),
collection: getCollection(autoPilotTier), collection: getCollection(autoPilotTier),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}, onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
openedTabs: []
}); });
} }
describe("Visible", () => { describe("Visible", () => {

View File

@ -252,7 +252,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.Settings
this.throughputModeRadioName = `throughputModeRadio${this.tabId}`; this.throughputModeRadioName = `throughputModeRadio${this.tabId}`;
this.changeFeedPolicyToggled = editable.observable<ChangeFeedPolicyToggledState>( this.changeFeedPolicyToggled = editable.observable<ChangeFeedPolicyToggledState>(
this.collection.rawDataModel.changeFeedPolicy != null this.collection.rawDataModel?.changeFeedPolicy != null
? ChangeFeedPolicyToggledState.On ? ChangeFeedPolicyToggledState.On
: ChangeFeedPolicyToggledState.Off : ChangeFeedPolicyToggledState.Off
); );

View File

@ -16,6 +16,7 @@ import TriggerTabTemplate from "./TriggerTab.html";
import UserDefinedFunctionTabTemplate from "./UserDefinedFunctionTab.html"; import UserDefinedFunctionTabTemplate from "./UserDefinedFunctionTab.html";
import GalleryTabTemplate from "./GalleryTab.html"; import GalleryTabTemplate from "./GalleryTab.html";
import NotebookViewerTabTemplate from "./NotebookViewerTab.html"; import NotebookViewerTabTemplate from "./NotebookViewerTab.html";
import TabsManagerTemplate from "./TabsManager.html";
export class TabComponent { export class TabComponent {
constructor(data: any) { constructor(data: any) {
@ -23,6 +24,15 @@ export class TabComponent {
} }
} }
export class TabsManager {
constructor() {
return {
viewModel: TabComponent,
template: TabsManagerTemplate
};
}
}
export class DocumentsTab { export class DocumentsTab {
constructor() { constructor() {
return { return {

View File

@ -24,8 +24,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
public tabKind: ViewModels.CollectionTabKind; public tabKind: ViewModels.CollectionTabKind;
public tabTitle: ko.Observable<string>; public tabTitle: ko.Observable<string>;
public tabPath: ko.Observable<string>; public tabPath: ko.Observable<string>;
public nextTab: ko.Observable<ViewModels.Tab>;
public previousTab: ko.Observable<ViewModels.Tab>;
public closeButtonTabIndex: ko.Computed<number>; public closeButtonTabIndex: ko.Computed<number>;
public errorDetailsTabIndex: ko.Computed<number>; public errorDetailsTabIndex: ko.Computed<number>;
public hashLocation: ko.Observable<string>; public hashLocation: ko.Observable<string>;
@ -55,8 +53,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
(options.tabPath && ko.observable<string>(options.tabPath)) || (options.tabPath && ko.observable<string>(options.tabPath)) ||
(this.collection && (this.collection &&
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`)); ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
this.nextTab = ko.observable<ViewModels.Tab>();
this.previousTab = ko.observable<ViewModels.Tab>();
this.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null)); this.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null)); this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.isExecutionError = ko.observable<boolean>(false); this.isExecutionError = ko.observable<boolean>(false);
@ -80,34 +76,11 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
return true; 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> { public onCloseTabButtonClick(): void {
const previousTab = this.previousTab(); const explorer: ViewModels.Explorer = this.getContainer();
const nextTab = this.nextTab(); explorer.tabsManager.closeTab(this.tabId, explorer);
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);
}
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, { TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
databaseAccountName: this.getContainer().databaseAccount().name, databaseAccountName: this.getContainer().databaseAccount().name,
@ -115,16 +88,10 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle() tabTitle: this.tabTitle()
}); });
return Q();
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Q.Promise<any> {
for (let i = 0; i < this.getContainer().openedTabs().length; i++) { this.getContainer().tabsManager.activateTab(this);
const tab = this.getContainer().openedTabs()[i];
tab.isActive(false);
}
this.isActive(true);
this.getContainer().activeTab(this);
return Q(); return Q();
} }

View 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>

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

View 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
};
}

View File

@ -16,7 +16,10 @@ import { OfferUtils } from "../../Utils/OfferUtils";
import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions"; import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient"; import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
import { ConflictsTab } from "../Tabs/ConflictsTab"; import ConflictId from "./ConflictId";
import DocumentId from "./DocumentId";
import ConflictsTab from "../Tabs/ConflictsTab";
import DocumentsTab from "../Tabs/DocumentsTab"; import DocumentsTab from "../Tabs/DocumentsTab";
import GraphTab from "../Tabs/GraphTab"; import GraphTab from "../Tabs/GraphTab";
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab"; import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
@ -25,8 +28,6 @@ import MongoShellTab from "../Tabs/MongoShellTab";
import QueryTab from "../Tabs/QueryTab"; import QueryTab from "../Tabs/QueryTab";
import QueryTablesTab from "../Tabs/QueryTablesTab"; import QueryTablesTab from "../Tabs/QueryTablesTab";
import SettingsTab from "../Tabs/SettingsTab"; import SettingsTab from "../Tabs/SettingsTab";
import ConflictId from "./ConflictId";
import DocumentId from "./DocumentId";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger"; import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction"; import UserDefinedFunction from "./UserDefinedFunction";
@ -229,7 +230,9 @@ export default class Collection implements ViewModels.Collection {
this.expandCollection(); this.expandCollection();
} }
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public collapseCollection() { public collapseCollection() {
@ -278,14 +281,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); ViewModels.CollectionTabKind.Documents,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as DocumentsTab[];
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
let documentsTab: ViewModels.Tab = openedTabs if (documentsTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(documentsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0]; } else {
if (!documentsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@ -295,6 +299,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Items" tabTitle: "Items"
}); });
this.documentIds([]); this.documentIds([]);
documentsTab = new DocumentsTab({ documentsTab = new DocumentsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
documentIds: ko.observableArray<DocumentId>([]), documentIds: ko.observableArray<DocumentId>([]),
@ -309,14 +314,11 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Documents`, tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab);
}
// Activate this.container.tabsManager.activateNewTab(documentsTab);
documentsTab.onTabClick(); }
} }
public onConflictsClick() { public onConflictsClick() {
@ -331,14 +333,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); ViewModels.CollectionTabKind.Conflicts,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as ConflictsTab[];
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
let conflictsTab: ViewModels.Tab = openedTabs if (conflictsTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(conflictsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Conflicts)[0]; } else {
if (!conflictsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@ -348,7 +351,8 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Conflicts" tabTitle: "Conflicts"
}); });
this.documentIds([]); this.documentIds([]);
conflictsTab = new ConflictsTab({
const conflictsTab: ConflictsTab = new ConflictsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
conflictIds: ko.observableArray<ConflictId>([]), conflictIds: ko.observableArray<ConflictId>([]),
tabKind: ViewModels.CollectionTabKind.Conflicts, tabKind: ViewModels.CollectionTabKind.Conflicts,
@ -362,14 +366,11 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Conflicts`, tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(conflictsTab);
}
// Activate this.container.tabsManager.activateNewTab(conflictsTab);
conflictsTab.onTabClick(); }
} }
public onTableEntitiesClick() { public onTableEntitiesClick() {
@ -390,14 +391,15 @@ export default class Collection implements ViewModels.Collection {
}); });
} }
// create entities tab if not created yet const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); ViewModels.CollectionTabKind.QueryTables,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as QueryTablesTab[];
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
let documentsTab: ViewModels.Tab = openedTabs if (queryTablesTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(queryTablesTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.QueryTables)[0]; } else {
if (!documentsTab) {
this.documentIds([]); this.documentIds([]);
let title = `Entities`; let title = `Entities`;
if (this.container.isPreferredApiCassandra()) { if (this.container.isPreferredApiCassandra()) {
@ -411,7 +413,8 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: title tabTitle: title
}); });
documentsTab = new QueryTablesTab({
queryTablesTab = new QueryTablesTab({
tabKind: ViewModels.CollectionTabKind.QueryTables, tabKind: ViewModels.CollectionTabKind.QueryTables,
title: title, title: title,
tabPath: "", tabPath: "",
@ -423,14 +426,11 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab);
}
// Activate this.container.tabsManager.activateNewTab(queryTablesTab);
documentsTab.onTabClick(); }
} }
public onGraphDocumentsClick() { public onGraphDocumentsClick() {
@ -445,55 +445,50 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); ViewModels.CollectionTabKind.Graph,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as GraphTab[];
let graphTab: GraphTab = graphTabs && graphTabs[0];
let documentsTab: ViewModels.Tab = openedTabs if (graphTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(graphTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Graph)[0]; } else {
if (!documentsTab) {
this.documentIds([]); this.documentIds([]);
documentsTab = this._createGraphTab("Graph"); const title = "Graph";
this.container.openedTabs.push(documentsTab); const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
});
graphTab = new GraphTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Graph,
node: this,
title: title,
tabPath: "",
documentClientUtility: this.container.documentClientUtility,
collection: this,
selfLink: this.self,
masterKey: CosmosClient.masterKey() || "",
collectionPartitionKeyProperty: this.partitionKeyProperty,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
collectionId: this.id(),
isActive: ko.observable(false),
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.tabsManager.activateNewTab(graphTab);
} }
// Activate
documentsTab.onTabClick();
} }
private _createGraphTab = (title: string): ViewModels.GraphTab => {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
});
// TODO where does the success/failure trace for this tab go?
return new GraphTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Graph,
node: this,
title: title,
tabPath: "",
documentClientUtility: this.container.documentClientUtility,
collection: this,
selfLink: this.self,
masterKey: CosmosClient.masterKey() || "",
collectionPartitionKeyProperty: this.partitionKeyProperty,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
collectionId: this.id(),
isActive: ko.observable(false),
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
});
};
public onMongoDBDocumentsClick = () => { public onMongoDBDocumentsClick = () => {
this.container.selectedNode(this); this.container.selectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
@ -506,14 +501,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); ViewModels.CollectionTabKind.Documents,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as MongoDocumentsTab[];
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
let documentsTab: ViewModels.Tab = openedTabs if (mongoDocumentsTab) {
.filter(tab => tab.collection && tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(mongoDocumentsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0]; } else {
if (!documentsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@ -523,7 +519,8 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Documents" tabTitle: "Documents"
}); });
this.documentIds([]); this.documentIds([]);
documentsTab = new MongoDocumentsTab({
mongoDocumentsTab = new MongoDocumentsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
documentIds: this.documentIds, documentIds: this.documentIds,
tabKind: ViewModels.CollectionTabKind.Documents, tabKind: ViewModels.CollectionTabKind.Documents,
@ -537,14 +534,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab); this.container.tabsManager.activateNewTab(mongoDocumentsTab);
} }
// Activate
documentsTab.onTabClick();
}; };
public onSettingsClick = () => { public onSettingsClick = () => {
@ -559,14 +552,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create settings tab if not created yet
const openedTabs = this.container.openedTabs();
let settingsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Settings)[0];
const tabTitle = "Scale & Settings"; const tabTitle = "Scale & Settings";
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification(); const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
const matchingTabs: ViewModels.Tab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Settings,
(tab: ViewModels.Tab) => {
return tab.collection && tab.collection.rid === this.rid;
}
);
let settingsTab: SettingsTab = matchingTabs && (matchingTabs[0] as SettingsTab);
if (!settingsTab) { if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
@ -592,12 +586,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
(settingsTab as ViewModels.SettingsTab).pendingNotification(pendingNotification); this.container.tabsManager.activateNewTab(settingsTab);
this.container.openedTabs.push(settingsTab); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); // Activate
}, },
(error: any) => { (error: any) => {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
@ -623,12 +615,12 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
pendingNotificationsPromise.then( pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => { (pendingNotification: DataModels.Notification) => {
(settingsTab as ViewModels.SettingsTab).pendingNotification(pendingNotification); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
}, },
(error: any) => { (error: any) => {
(settingsTab as ViewModels.SettingsTab).pendingNotification(undefined); settingsTab.pendingNotification(undefined);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
} }
); );
} }
@ -738,9 +730,7 @@ export default class Collection implements ViewModels.Collection {
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) { public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id; const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
@ -751,7 +741,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title tabTitle: title
}); });
let queryTab: ViewModels.Tab = new QueryTab({ const queryTab: QueryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query, tabKind: ViewModels.CollectionTabKind.Query,
title: title, title: title,
tabPath: "", tabPath: "",
@ -764,20 +754,15 @@ export default class Collection implements ViewModels.Collection {
queryText: queryText, queryText: queryText,
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(queryTab);
// Activate this.container.tabsManager.activateNewTab(queryTab);
queryTab.onTabClick();
} }
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) { public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id; const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
@ -789,7 +774,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title tabTitle: title
}); });
let queryTab: ViewModels.Tab = new MongoQueryTab({ const mongoQueryTab: MongoQueryTab = new MongoQueryTab({
tabKind: ViewModels.CollectionTabKind.Query, tabKind: ViewModels.CollectionTabKind.Query,
title: title, title: title,
tabPath: "", tabPath: "",
@ -801,26 +786,51 @@ export default class Collection implements ViewModels.Collection {
isActive: ko.observable(false), isActive: ko.observable(false),
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(queryTab);
// Activate this.container.tabsManager.activateNewTab(mongoQueryTab);
queryTab.onTabClick();
} }
public onNewGraphClick() { public onNewGraphClick() {
var id = this.container.openedTabs().filter(t => t.tabKind === ViewModels.CollectionTabKind.Graph).length + 1; const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
var graphTab = this._createGraphTab("Graph Query " + id); const title: string = "Graph Query " + id;
this.container.openedTabs.push(graphTab);
// Activate const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
graphTab.onTabClick(); databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
});
const graphTab: GraphTab = new GraphTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Graph,
node: this,
title: title,
tabPath: "",
documentClientUtility: this.container.documentClientUtility,
collection: this,
selfLink: this.self,
masterKey: CosmosClient.masterKey() || "",
collectionPartitionKeyProperty: this.partitionKeyProperty,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
collectionId: this.id(),
isActive: ko.observable(false),
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.tabsManager.activateNewTab(graphTab);
} }
public onNewMongoShellClick() { public onNewMongoShellClick() {
var id = this.container.openedTabs().filter(t => t.tabKind === ViewModels.CollectionTabKind.MongoShell).length + 1; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.MongoShell).length + 1;
var mongoShellTab = new MongoShellTab({ const mongoShellTab: MongoShellTab = new MongoShellTab({
tabKind: ViewModels.CollectionTabKind.MongoShell, tabKind: ViewModels.CollectionTabKind.MongoShell,
title: "Shell " + id, title: "Shell " + id,
tabPath: "", tabPath: "",
@ -830,14 +840,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
selfLink: this.self, selfLink: this.self,
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(mongoShellTab); this.container.tabsManager.activateNewTab(mongoShellTab);
// Activate
mongoShellTab.onTabClick();
} }
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) { public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
@ -898,7 +904,9 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
this.expandStoredProcedures(); this.expandStoredProcedures();
} }
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public expandStoredProcedures() { public expandStoredProcedures() {
@ -955,7 +963,9 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
this.expandUserDefinedFunctions(); this.expandUserDefinedFunctions();
} }
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public expandUserDefinedFunctions() { public expandUserDefinedFunctions() {
@ -1012,7 +1022,9 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
this.expandTriggers(); this.expandTriggers();
} }
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public expandTriggers() { public expandTriggers() {
@ -1366,19 +1378,6 @@ export default class Collection implements ViewModels.Collection {
}); });
} }
public refreshActiveTab(): void {
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
const openedRelevantTabs: ViewModels.Tab[] = this.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab && tab.collection && tab.collection.rid === this.rid);
openedRelevantTabs.forEach((tab: ViewModels.Tab) => {
if (tab.isActive()) {
tab.onActivate();
}
});
}
protected _getOfferForCollection(offers: DataModels.Offer[], collection: DataModels.Collection): DataModels.Offer { protected _getOfferForCollection(offers: DataModels.Offer[], collection: DataModels.Collection): DataModels.Offer {
return _.find(offers, (offer: DataModels.Offer) => offer.resource.indexOf(collection._rid) >= 0); return _.find(offers, (offer: DataModels.Offer) => offer.resource.indexOf(collection._rid) >= 0);
} }

View File

@ -49,12 +49,12 @@ export default class Database implements ViewModels.Database {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create settings tab if not created yet
const openedTabs = this.container.openedTabs();
let settingsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.DatabaseSettings)[0];
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification(); const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
const matchingTabs: ViewModels.Tab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.DatabaseSettings,
(tab: ViewModels.Tab) => tab.rid === this.rid
);
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
if (!settingsTab) { if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
@ -78,12 +78,11 @@ export default class Database implements ViewModels.Database {
selfLink: this.self, selfLink: this.self,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(pendingNotification);
this.container.openedTabs.push(settingsTab); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); // Activate this.container.tabsManager.activateNewTab(settingsTab);
}, },
(error: any) => { (error: any) => {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
@ -109,12 +108,12 @@ export default class Database implements ViewModels.Database {
} else { } else {
pendingNotificationsPromise.then( pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => { (pendingNotification: DataModels.Notification) => {
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(pendingNotification); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
}, },
(error: any) => { (error: any) => {
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(undefined); settingsTab.pendingNotification(undefined);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
} }
); );
} }
@ -221,7 +220,9 @@ export default class Database implements ViewModels.Database {
this.expandDatabase(); this.expandDatabase();
} }
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
this.refreshTabSelectedState(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === this.rid
);
} }
public expandDatabase() { public expandDatabase() {
@ -286,18 +287,6 @@ export default class Database implements ViewModels.Database {
database.container.addCollectionPane.open(); database.container.addCollectionPane.open();
} }
public refreshTabSelectedState(): void {
const openedRelevantTabs: ViewModels.Tab[] = this.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab && tab.collection && tab.collection.getDatabase().rid === this.rid);
openedRelevantTabs.forEach((tab: ViewModels.Tab) => {
if (tab.isActive()) {
tab.onTabClick(); // this ensures the next (deepest) item in the resource tree is highlighted
}
});
}
public findCollectionWithId(collectionId: string): ViewModels.Collection { public findCollectionWithId(collectionId: string): ViewModels.Collection {
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId); return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
} }

View File

@ -73,24 +73,9 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
}); });
} }
public refreshActiveTab(): void {
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
const openedRelevantTabs: ViewModels.Tab[] = this.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab && tab.collection && tab.collection.rid === this.rid);
openedRelevantTabs.forEach((tab: ViewModels.Tab) => {
if (tab.isActive()) {
tab.onActivate();
}
});
}
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) { public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id; const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
@ -101,7 +86,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabTitle: title tabTitle: title
}); });
let queryTab: ViewModels.Tab = new QueryTab({ const queryTab: QueryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query, tabKind: ViewModels.CollectionTabKind.Query,
title: title, title: title,
tabPath: "", tabPath: "",
@ -115,13 +100,10 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(), resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(queryTab);
// Activate this.container.tabsManager.activateNewTab(queryTab);
queryTab.onTabClick();
} }
public onDocumentDBDocumentsClick() { public onDocumentDBDocumentsClick() {
@ -136,14 +118,15 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); ViewModels.CollectionTabKind.Documents,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as DocumentsTab[];
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
let documentsTab: ViewModels.Tab = openedTabs if (documentsTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(documentsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0]; } else {
if (!documentsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@ -152,6 +135,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Items" tabTitle: "Items"
}); });
documentsTab = new DocumentsTab({ documentsTab = new DocumentsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(), resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
@ -167,14 +151,11 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabPath: `${this.databaseId}>${this.id()}>Documents`, tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab);
}
// Activate this.container.tabsManager.activateNewTab(documentsTab);
documentsTab.onTabClick(); }
} }
public getDatabase(): ViewModels.Database { public getDatabase(): ViewModels.Database {

View File

@ -46,7 +46,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender()); this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
this.container.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender()); this.container.tabsManager.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender());
this.container.isNotebookEnabled.subscribe(newValue => this.triggerRender()); this.container.isNotebookEnabled.subscribe(newValue => this.triggerRender());
this.koSubsDatabaseIdMap = new ArrayHashMap(); this.koSubsDatabaseIdMap = new ArrayHashMap();
@ -171,7 +171,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
database.selectDatabase(); database.selectDatabase();
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
database.refreshTabSelectedState(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === database.rid
);
}, },
onContextMenuOpen: () => this.container.selectedNode(database) onContextMenuOpen: () => this.container.selectedNode(database)
}; };
@ -268,7 +270,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
// Rewritten version of expandCollapseCollection // Rewritten version of expandCollapseCollection
this.container.selectedNode(collection); this.container.selectedNode(collection);
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
}, },
onExpanded: () => { onExpanded: () => {
if (ResourceTreeAdapter.showScriptNodes(this.container)) { if (ResourceTreeAdapter.showScriptNodes(this.container)) {
@ -294,7 +298,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
})), })),
onClick: () => { onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures); collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
} }
}; };
} }
@ -311,7 +317,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
})), })),
onClick: () => { onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions); collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
} }
}; };
} }
@ -327,7 +335,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
})), })),
onClick: () => { onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers); collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
} }
}; };
} }
@ -409,7 +419,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
className: "notebookHeader galleryHeader", className: "notebookHeader galleryHeader",
onClick: () => this.container.openGallery(), onClick: () => this.container.openGallery(),
isSelected: () => { isSelected: () => {
const activeTab = this.container.findActiveTab(); const activeTab = this.container.tabsManager.activeTab();
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery; return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
} }
}; };
@ -517,7 +527,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
className: "notebookHeader", className: "notebookHeader",
onClick: () => onFileClick(item), onClick: () => onFileClick(item),
isSelected: () => { isSelected: () => {
const activeTab = this.container.findActiveTab(); const activeTab = this.container.tabsManager.activeTab();
return ( return (
activeTab && activeTab &&
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
@ -634,7 +644,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
}, },
isSelected: () => { isSelected: () => {
const activeTab = this.container.findActiveTab(); const activeTab = this.container.tabsManager.activeTab();
return ( return (
activeTab && activeTab &&
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
@ -657,14 +667,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
window.requestAnimationFrame(() => this.parameters(Date.now())); window.requestAnimationFrame(() => this.parameters(Date.now()));
} }
private getActiveTab(): ViewModels.Tab {
const activeTabs: ViewModels.Tab[] = this.container.openedTabs().filter((tab: ViewModels.Tab) => tab.isActive());
if (activeTabs.length) {
return activeTabs[0];
}
return undefined;
}
private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean { private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean {
if (!this.container.selectedNode || !this.container.selectedNode()) { if (!this.container.selectedNode || !this.container.selectedNode()) {
return false; return false;
@ -674,7 +676,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
if (subnodeKind === undefined) { if (subnodeKind === undefined) {
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind; return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
} else { } else {
const activeTab = this.getActiveTab(); const activeTab = this.container.tabsManager.activeTab();
let selectedSubnodeKind; let selectedSubnodeKind;
if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) { if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) {
selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind(); selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind();

View File

@ -12,9 +12,7 @@ const createMockContainer = (): ViewModels.Explorer => {
let mockContainer = {} as ViewModels.Explorer; let mockContainer = {} as ViewModels.Explorer;
mockContainer.resourceTokenCollection = createMockCollection(mockContainer); mockContainer.resourceTokenCollection = createMockCollection(mockContainer);
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>(); mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
mockContainer.activeTab = ko.observable<ViewModels.Tab>();
mockContainer.mostRecentActivity = new MostRecentActivity.MostRecentActivity(mockContainer); mockContainer.mostRecentActivity = new MostRecentActivity.MostRecentActivity(mockContainer);
mockContainer.openedTabs = ko.observableArray<ViewModels.Tab>([]);
mockContainer.onUpdateTabsButtons = () => {}; mockContainer.onUpdateTabsButtons = () => {};
return mockContainer; return mockContainer;

View File

@ -17,7 +17,8 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
this.container.resourceTokenCollection.subscribe((collection: ViewModels.CollectionBase) => this.triggerRender()); this.container.resourceTokenCollection.subscribe((collection: ViewModels.CollectionBase) => this.triggerRender());
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender()); this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
this.container.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender()); this.container.tabsManager &&
this.container.tabsManager.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender());
this.triggerRender(); this.triggerRender();
} }
@ -63,7 +64,9 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
// Rewritten version of expandCollapseCollection // Rewritten version of expandCollapseCollection
this.container.selectedNode(collection); this.container.selectedNode(collection);
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
}, },
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", undefined) isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", undefined)
}; };
@ -75,14 +78,6 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
}; };
} }
private getActiveTab(): ViewModels.Tab {
const activeTabs: ViewModels.Tab[] = this.container.openedTabs().filter((tab: ViewModels.Tab) => tab.isActive());
if (activeTabs.length) {
return activeTabs[0];
}
return undefined;
}
private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean { private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean {
if (!this.container.selectedNode || !this.container.selectedNode()) { if (!this.container.selectedNode || !this.container.selectedNode()) {
return false; return false;
@ -92,7 +87,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
if (subnodeKind) { if (subnodeKind) {
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind; return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
} else { } else {
const activeTab = this.getActiveTab(); const activeTab = this.container.tabsManager.activeTab();
let selectedSubnodeKind; let selectedSubnodeKind;
if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) { if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) {
selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind(); selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind();

View File

@ -56,15 +56,13 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { public static create(source: ViewModels.Collection, event: MouseEvent) {
const openedTabs = source.container.openedTabs(); const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
const id =
openedTabs.filter((tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures).length +
1;
const storedProcedure = <DataModels.StoredProcedure>{ const storedProcedure = <DataModels.StoredProcedure>{
id: "", id: "",
body: sampleStoredProcedureBody body: sampleStoredProcedureBody
}; };
let storedProcedureTab: ViewModels.Tab = new StoredProcedureTab({
const storedProcedureTab: StoredProcedureTab = new StoredProcedureTab({
resource: storedProcedure, resource: storedProcedure,
isNew: true, isNew: true,
tabKind: ViewModels.CollectionTabKind.StoredProcedures, tabKind: ViewModels.CollectionTabKind.StoredProcedures,
@ -76,13 +74,10 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons, onUpdateTabsButtons: source.container.onUpdateTabsButtons
openedTabs: source.container.openedTabs()
}); });
source.container.openedTabs.push(storedProcedureTab);
// Activate source.container.tabsManager.activateNewTab(storedProcedureTab);
storedProcedureTab.onTabClick();
} }
public select() { public select() {
@ -98,15 +93,15 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
public open = () => { public open = () => {
this.select(); this.select();
const openedTabs = this.container.openedTabs(); const storedProcedureTabs: StoredProcedureTab[] = this.container.tabsManager.getTabs(
const storedProcedureTabsOpen: ViewModels.Tab[] = ViewModels.CollectionTabKind.StoredProcedures,
openedTabs && (tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
openedTabs.filter( ) as StoredProcedureTab[];
tab => tab.node && tab.node.rid === this.rid && tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures let storedProcedureTab: StoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
);
let storedProcedureTab: ViewModels.Tab = if (storedProcedureTab) {
storedProcedureTabsOpen && storedProcedureTabsOpen.length > 0 && storedProcedureTabsOpen[0]; this.container.tabsManager.activateTab(storedProcedureTab);
if (!storedProcedureTab) { } else {
const storedProcedureData = <DataModels.StoredProcedure>{ const storedProcedureData = <DataModels.StoredProcedure>{
_rid: this.rid, _rid: this.rid,
_self: this.self, _self: this.self,
@ -129,14 +124,11 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
)}/sprocs/${this.id()}`, )}/sprocs/${this.id()}`,
selfLink: this.self, selfLink: this.self,
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(storedProcedureTab);
}
// Activate this.container.tabsManager.activateNewTab(storedProcedureTab);
storedProcedureTab.onTabClick(); }
}; };
public delete() { public delete() {
@ -153,7 +145,9 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
this.container.documentClientUtility.deleteStoredProcedure(this.collection, storedProcedureData).then( this.container.documentClientUtility.deleteStoredProcedure(this.collection, storedProcedureData).then(
() => { () => {
this.container.openedTabs.remove((tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid); this.container.tabsManager.removeTabByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
reason => {} reason => {}
@ -161,7 +155,11 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
} }
public execute(params: string[], partitionKeyValue?: string): void { public execute(params: string[], partitionKeyValue?: string): void {
const sprocTab: ViewModels.StoredProcedureTab = this._getCurrentStoredProcedureTab(); const sprocTabs: ViewModels.StoredProcedureTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
) as ViewModels.StoredProcedureTab[];
const sprocTab: ViewModels.StoredProcedureTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
sprocTab.isExecuting(true); sprocTab.isExecuting(true);
this.container && this.container &&
this.container.documentClientUtility this.container.documentClientUtility
@ -184,17 +182,4 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
const focusElement = document.getElementById("execute-storedproc-toggles"); const focusElement = document.getElementById("execute-storedproc-toggles");
focusElement && focusElement.focus(); focusElement && focusElement.focus();
} }
private _getCurrentStoredProcedureTab(): ViewModels.StoredProcedureTab {
const openedTabs = this.container.openedTabs();
const storedProcedureTabsOpen: ViewModels.Tab[] =
openedTabs &&
openedTabs.filter(
tab => tab.node && tab.node.rid === this.rid && tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures
);
return (storedProcedureTabsOpen &&
storedProcedureTabsOpen.length > 0 &&
storedProcedureTabsOpen[0]) as ViewModels.StoredProcedureTab;
}
} }

View File

@ -3,7 +3,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import Collection from "./Collection";
import TriggerTab from "../Tabs/TriggerTab"; import TriggerTab from "../Tabs/TriggerTab";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@ -41,10 +40,7 @@ export default class Trigger implements ViewModels.Trigger {
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
source.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Triggers).length + 1;
const trigger = <DataModels.Trigger>{ const trigger = <DataModels.Trigger>{
id: "", id: "",
body: "function trigger(){}", body: "function trigger(){}",
@ -52,7 +48,7 @@ export default class Trigger implements ViewModels.Trigger {
triggerType: "Pre" triggerType: "Pre"
}; };
let triggerTab: ViewModels.Tab = new TriggerTab({ const triggerTab: TriggerTab = new TriggerTab({
resource: trigger, resource: trigger,
isNew: true, isNew: true,
tabKind: ViewModels.CollectionTabKind.Triggers, tabKind: ViewModels.CollectionTabKind.Triggers,
@ -64,23 +60,24 @@ export default class Trigger implements ViewModels.Trigger {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons, onUpdateTabsButtons: source.container.onUpdateTabsButtons
openedTabs: source.container.openedTabs()
}); });
source.container.openedTabs.push(triggerTab); source.container.tabsManager.activateNewTab(triggerTab);
// Activate
triggerTab.onTabClick();
} }
public open = () => { public open = () => {
this.select(); this.select();
let triggerTab: ViewModels.Tab = this.container const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
.openedTabs() ViewModels.CollectionTabKind.Triggers,
.filter(tab => tab.node && tab.node.rid === this.rid)[0]; (tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
if (!triggerTab) { ) as TriggerTab[];
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
if (triggerTab) {
this.container.tabsManager.activateTab(triggerTab);
} else {
const triggerData = <DataModels.Trigger>{ const triggerData = <DataModels.Trigger>{
_rid: this.rid, _rid: this.rid,
_self: this.self, _self: this.self,
@ -105,15 +102,11 @@ export default class Trigger implements ViewModels.Trigger {
)}/triggers/${this.id()}`, )}/triggers/${this.id()}`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(triggerTab); this.container.tabsManager.activateNewTab(triggerTab);
} }
// Activate
triggerTab.onTabClick();
}; };
public delete() { public delete() {
@ -132,7 +125,9 @@ export default class Trigger implements ViewModels.Trigger {
this.container.documentClientUtility.deleteTrigger(this.collection, triggerData).then( this.container.documentClientUtility.deleteTrigger(this.collection, triggerData).then(
() => { () => {
this.container.openedTabs.remove((tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid); this.container.tabsManager.removeTabByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
reason => {} reason => {}

View File

@ -5,7 +5,6 @@ import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab"; import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Collection from "./Collection";
export default class UserDefinedFunction implements ViewModels.UserDefinedFunction { export default class UserDefinedFunction implements ViewModels.UserDefinedFunction {
public nodeKind: string; public nodeKind: string;
@ -28,15 +27,13 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
source.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
const userDefinedFunction = <DataModels.UserDefinedFunction>{ const userDefinedFunction = <DataModels.UserDefinedFunction>{
id: "", id: "",
body: "function userDefinedFunction(){}" body: "function userDefinedFunction(){}"
}; };
let userDefinedFunctionTab: ViewModels.Tab = new UserDefinedFunctionTab({
const userDefinedFunctionTab: UserDefinedFunctionTab = new UserDefinedFunctionTab({
resource: userDefinedFunction, resource: userDefinedFunction,
isNew: true, isNew: true,
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions, tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
@ -48,22 +45,24 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons, onUpdateTabsButtons: source.container.onUpdateTabsButtons
openedTabs: source.container.openedTabs()
}); });
source.container.openedTabs.push(userDefinedFunctionTab);
// Activate source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
userDefinedFunctionTab.onTabClick();
} }
public open = () => { public open = () => {
this.select(); this.select();
let userDefinedFunctionTab: ViewModels.Tab = this.container const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
.openedTabs() ViewModels.CollectionTabKind.UserDefinedFunctions,
.filter(tab => tab.node && tab.node.rid === this.rid)[0]; (tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
if (!userDefinedFunctionTab) { ) as UserDefinedFunctionTab[];
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
if (userDefinedFunctionTab) {
this.container.tabsManager.activateTab(userDefinedFunctionTab);
} else {
const userDefinedFunctionData = <DataModels.UserDefinedFunction>{ const userDefinedFunctionData = <DataModels.UserDefinedFunction>{
_rid: this.rid, _rid: this.rid,
_self: this.self, _self: this.self,
@ -86,14 +85,11 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
)}/udfs/${this.id()}`, )}/udfs/${this.id()}`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(userDefinedFunctionTab);
}
// Activate this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
userDefinedFunctionTab.onTabClick(); }
}; };
public select() { public select() {
@ -119,7 +115,9 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
}; };
this.container.documentClientUtility.deleteUserDefinedFunction(this.collection, userDefinedFunctionData).then( this.container.documentClientUtility.deleteUserDefinedFunction(this.collection, userDefinedFunctionData).then(
() => { () => {
this.container.openedTabs.remove((tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid); this.container.tabsManager.removeTabByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
reason => {} reason => {}

View File

@ -390,17 +390,15 @@ export class TabRouteHandler {
isNewScriptTab?: boolean isNewScriptTab?: boolean
): ViewModels.Tab { ): ViewModels.Tab {
const explorer: ViewModels.Explorer = (<any>window).dataExplorer; const explorer: ViewModels.Explorer = (<any>window).dataExplorer;
const matchingTabs: ViewModels.Tab[] = explorer.tabsManager.getTabs(
return _.find(explorer.openedTabs(), (tab: ViewModels.Tab) => { tabKind,
const isMatchingTabKind = (tab: ViewModels.Tab) =>
tab.collection.databaseId === databaseId && tab.collection.id() === collectionId && tab.tabKind === tabKind; tab.collection &&
tab.collection.databaseId === databaseId &&
if (isNewScriptTab) { tab.collection.id() === collectionId &&
return isMatchingTabKind && (tab as ViewModels.ScriptTab).isNew(); (!isNewScriptTab || (tab as ViewModels.ScriptTab).isNew())
} );
return matchingTabs && matchingTabs[0];
return isMatchingTabKind;
});
} }
private _findMatchingCollectionForResource(databaseId: string, collectionId: string): ViewModels.Collection { private _findMatchingCollectionForResource(databaseId: string, collectionId: string): ViewModels.Collection {

View File

@ -187,171 +187,13 @@
<div <div
class="connectExplorerContainer" class="connectExplorerContainer"
data-bind="visible: !isRefreshingExplorer() && (openedTabs == null || openedTabs().length === 0)" data-bind="visible: !isRefreshingExplorer() && tabsManager.openedTabs().length === 0"
> >
<form class="connectExplorerFormContainer"> <form class="connectExplorerFormContainer">
<div class="connectExplorer" data-bind="react: splashScreenAdapter"></div> <div class="connectExplorer" data-bind="react: splashScreenAdapter"></div>
</form> </form>
</div> </div>
<tabs-manager class="tabsContainer" params="{data: tabsManager}"></tabs-manager>
<!-- Tabs and Tabs Panes - Start -->
<div
id="content"
class="flexContainer hideOverflows"
data-bind="
visible: openedTabs && openedTabs().length > 0"
>
<!-- Tabs - Start -->
<div class="nav-tabs-margin" data-bind="visible:!isTabsContentExpanded()">
<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>
<!-- Tabs and Tabs Panes - End -->
</div> </div>
<!-- Collections Tree and Tabs - End --> <!-- Collections Tree and Tabs - End -->
<div <div