Move tabs manager to zustand (#915)

This commit is contained in:
victor-meng
2021-07-08 21:32:22 -07:00
committed by GitHub
parent f4eef1b61b
commit f8ab0a82e0
42 changed files with 609 additions and 663 deletions

View File

@@ -8,7 +8,7 @@ import { configContext, Platform, updateConfigContext } from "../ConfigContext";
import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
import Explorer, { ExplorerParams } from "../Explorer/Explorer";
import Explorer from "../Explorer/Explorer";
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
import { useDatabases } from "../Explorer/useDatabases";
import {
@@ -37,20 +37,20 @@ import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
// Please tread carefully :)
export function useKnockoutExplorer(platform: Platform, explorerParams: ExplorerParams): Explorer {
export function useKnockoutExplorer(platform: Platform): Explorer {
const [explorer, setExplorer] = useState<Explorer>();
useEffect(() => {
const effect = async () => {
if (platform) {
if (platform === Platform.Hosted) {
const explorer = await configureHosted(explorerParams);
const explorer = await configureHosted();
setExplorer(explorer);
} else if (platform === Platform.Emulator) {
const explorer = configureEmulator(explorerParams);
const explorer = configureEmulator();
setExplorer(explorer);
} else if (platform === Platform.Portal) {
const explorer = await configurePortal(explorerParams);
const explorer = await configurePortal();
setExplorer(explorer);
}
}
@@ -67,21 +67,21 @@ export function useKnockoutExplorer(platform: Platform, explorerParams: Explorer
return explorer;
}
async function configureHosted(explorerParams: ExplorerParams): Promise<Explorer> {
async function configureHosted(): Promise<Explorer> {
const win = (window as unknown) as HostedExplorerChildFrame;
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
return configureHostedWithEncryptedToken(win.hostedConfig, explorerParams);
return configureHostedWithEncryptedToken(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
return configureHostedWithResourceToken(win.hostedConfig, explorerParams);
return configureHostedWithResourceToken(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
return configureHostedWithConnectionString(win.hostedConfig, explorerParams);
return configureHostedWithConnectionString(win.hostedConfig);
} else if (win.hostedConfig.authType === AuthType.AAD) {
return configureHostedWithAAD(win.hostedConfig, explorerParams);
return configureHostedWithAAD(win.hostedConfig);
}
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
}
async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParams): Promise<Explorer> {
async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
// TODO: Refactor. updateUserContext needs to be called twice because listKeys below depends on userContext.authorizationToken
updateUserContext({
authType: AuthType.AAD,
@@ -120,11 +120,11 @@ async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParam
databaseAccount: config.databaseAccount,
masterKey: keys.primaryMasterKey,
});
const explorer = new Explorer(explorerParams);
const explorer = new Explorer();
return explorer;
}
function configureHostedWithConnectionString(config: ConnectionString, explorerParams: ExplorerParams): Explorer {
function configureHostedWithConnectionString(config: ConnectionString): Explorer {
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
const databaseAccount = {
id: "",
@@ -142,11 +142,11 @@ function configureHostedWithConnectionString(config: ConnectionString, explorerP
databaseAccount,
masterKey: config.masterKey,
});
const explorer = new Explorer(explorerParams);
const explorer = new Explorer();
return explorer;
}
function configureHostedWithResourceToken(config: ResourceToken, explorerParams: ExplorerParams): Explorer {
function configureHostedWithResourceToken(config: ResourceToken): Explorer {
const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken);
const databaseAccount = {
id: "",
@@ -167,11 +167,11 @@ function configureHostedWithResourceToken(config: ResourceToken, explorerParams:
partitionKey: parsedResourceToken.partitionKey,
},
});
const explorer = new Explorer(explorerParams);
const explorer = new Explorer();
return explorer;
}
function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParams: ExplorerParams): Explorer {
function configureHostedWithEncryptedToken(config: EncryptedToken): Explorer {
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
updateUserContext({
authType: AuthType.EncryptedToken,
@@ -185,20 +185,20 @@ function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParam
properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
},
});
const explorer = new Explorer(explorerParams);
const explorer = new Explorer();
return explorer;
}
function configureEmulator(explorerParams: ExplorerParams): Explorer {
function configureEmulator(): Explorer {
updateUserContext({
databaseAccount: emulatorAccount,
authType: AuthType.MasterKey,
});
const explorer = new Explorer(explorerParams);
const explorer = new Explorer();
return explorer;
}
async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer> {
async function configurePortal(): Promise<Explorer> {
updateUserContext({
authType: AuthType.AAD,
});
@@ -214,7 +214,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer
);
console.dir(message);
updateContextsFromPortalMessage(message);
const explorer = new Explorer(explorerParams);
const explorer = new Explorer();
// In development mode, save the iframe message from the portal in session storage.
// This allows webpack hot reload to funciton properly
if (process.env.NODE_ENV === "development") {
@@ -250,7 +250,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer
}
updateContextsFromPortalMessage(inputs);
const explorer = new Explorer(explorerParams);
const explorer = new Explorer();
resolve(explorer);
if (openAction) {
handleOpenAction(openAction, useDatabases.getState().databases, explorer);

View File

@@ -1,18 +1,77 @@
import { useState } from "react";
import create, { UseStore } from "zustand";
import * as ViewModels from "../Contracts/ViewModels";
import TabsBase from "../Explorer/Tabs/TabsBase";
import { TabsManager } from "../Explorer/Tabs/TabsManager";
import { useObservable } from "./useObservable";
export type UseTabs = {
tabs: readonly TabsBase[];
interface TabsState {
openedTabs: TabsBase[];
activeTab: TabsBase;
tabsManager: TabsManager;
};
export function useTabs(): UseTabs {
const [tabsManager] = useState(() => new TabsManager());
const tabs = useObservable(tabsManager.openedTabs);
const activeTab = useObservable(tabsManager.activeTab);
return { tabs, activeTab, tabsManager };
activateTab: (tab: TabsBase) => void;
activateNewTab: (tab: TabsBase) => void;
updateTab: (tab: TabsBase) => void;
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean) => TabsBase[];
refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void;
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean) => void;
closeTab: (tab: TabsBase) => void;
}
export const useTabs: UseStore<TabsState> = create((set, get) => ({
openedTabs: [],
activeTab: undefined,
activateTab: (tab: TabsBase): void => {
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
set({ activeTab: tab });
tab.onActivate();
}
},
activateNewTab: (tab: TabsBase): void => {
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab }));
tab.onActivate();
},
updateTab: (tab: TabsBase) => {
if (get().activeTab?.tabId === tab.tabId) {
set({ activeTab: tab });
}
set((state) => ({
openedTabs: state.openedTabs.map((openedTab) => {
if (openedTab.tabId === tab.tabId) {
return tab;
}
return openedTab;
}),
}));
},
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
const activeTab = get().activeTab;
activeTab && comparator(activeTab) && activeTab.onActivate();
},
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
get()
.openedTabs.filter(comparator)
.forEach((tab) => tab.onCloseTabButtonClick()),
closeTab: (tab: TabsBase): void => {
let tabIndex: number;
const { activeTab, openedTabs } = get();
const updatedTabs = openedTabs.filter((openedTab, index) => {
if (tab.tabId === openedTab.tabId) {
tabIndex = index;
return false;
}
return true;
});
if (updatedTabs.length === 0) {
set({ activeTab: undefined });
}
if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
const tabToTheRight = updatedTabs[tabIndex];
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
set({ activeTab: tabToTheRight || lastOpenTab });
}
set({ openedTabs: updatedTabs });
},
}));