mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
Initial implementation of two-way communication with Fabric host (#1622)
* Listen to iframe messages. Test posting message. * Plug new container message to show New Container dialog * Rename message action to type * Fix format * Fix format * Remove console.log() statement * Rework fabric init flow. Implement open Collection Tab from fabric. * Rename method to better match its purpose * Update src/hooks/useKnockoutExplorer.ts Use connectionString from message Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com> * Fix format * For openTab action open first collection if not specified. Clean up FabricContract. * Reformat FabricContracts * Highlight current node selection using them token * Reformat * Automatically expand nodes in resource tree if underlying database or collection is expanded. Fix AllowedOrigins. Cleanup code. * Fix format * Fix lint issue * Don't show the home screen for Fabric (#1636) * Fix formatting * Database name to open can be overridden by value in session storage --------- Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com>
This commit is contained in:
@@ -9,8 +9,11 @@ import {
|
||||
Tree,
|
||||
TreeItem,
|
||||
TreeItemLayout,
|
||||
TreeOpenChangeData,
|
||||
TreeOpenChangeEvent,
|
||||
} from "@fluentui/react-components";
|
||||
import { MoreHorizontal20Regular } from "@fluentui/react-icons";
|
||||
import { tokens } from "@fluentui/react-theme";
|
||||
import * as React from "react";
|
||||
|
||||
export interface TreeNode2MenuItem {
|
||||
@@ -27,7 +30,7 @@ export interface TreeNode2 {
|
||||
children?: TreeNode2[];
|
||||
contextMenu?: TreeNode2MenuItem[];
|
||||
iconSrc?: string;
|
||||
// isExpanded?: boolean;
|
||||
isExpanded?: boolean;
|
||||
className?: string;
|
||||
isAlphaSorted?: boolean;
|
||||
// data?: any; // Piece of data corresponding to this node
|
||||
@@ -37,7 +40,7 @@ export interface TreeNode2 {
|
||||
isScrollable?: boolean;
|
||||
isSelected?: () => boolean;
|
||||
onClick?: () => void; // Only if a leaf, other click will expand/collapse
|
||||
onExpanded?: () => void;
|
||||
onExpanded?: () => Promise<void>;
|
||||
onCollapsed?: () => void;
|
||||
onContextMenuOpen?: () => void;
|
||||
}
|
||||
@@ -46,7 +49,6 @@ export interface TreeNode2ComponentProps {
|
||||
node: TreeNode2;
|
||||
className?: string;
|
||||
treeNodeId: string;
|
||||
globalOpenIds: string[];
|
||||
}
|
||||
|
||||
const getTreeIcon = (iconSrc: string): JSX.Element => <img src={iconSrc} alt="" style={{ width: 20, height: 20 }} />;
|
||||
@@ -54,20 +56,8 @@ const getTreeIcon = (iconSrc: string): JSX.Element => <img src={iconSrc} alt=""
|
||||
export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
|
||||
node,
|
||||
treeNodeId,
|
||||
globalOpenIds,
|
||||
}: TreeNode2ComponentProps): JSX.Element => {
|
||||
// const defaultOpenItems = node.isExpanded ? children?.map((child: TreeNode2) => child.label) : undefined;
|
||||
const [isExpanded, setIsExpanded] = React.useState<boolean>(false);
|
||||
|
||||
// Compute whether node is expanded
|
||||
React.useEffect(() => {
|
||||
const isNowExpanded = globalOpenIds && globalOpenIds.includes(treeNodeId);
|
||||
if (!isExpanded && isNowExpanded) {
|
||||
// Catch the transition non-expanded to expanded
|
||||
node.onExpanded?.();
|
||||
}
|
||||
setIsExpanded(isNowExpanded);
|
||||
}, [globalOpenIds, treeNodeId, node, isExpanded]);
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
|
||||
const getSortedChildren = (treeNode: TreeNode2): TreeNode2[] => {
|
||||
if (!treeNode || !treeNode.children) {
|
||||
@@ -95,8 +85,24 @@ export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
|
||||
return unsortedChildren;
|
||||
};
|
||||
|
||||
const onOpenChange = (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => {
|
||||
if (!node.isExpanded && data.open && node.onExpanded) {
|
||||
// Catch the transition non-expanded to expanded
|
||||
setIsLoading(true);
|
||||
node.onExpanded?.().then(() => setIsLoading(false));
|
||||
} else if (node.isExpanded && !data.open && node.onCollapsed) {
|
||||
// Catch the transition expanded to non-expanded
|
||||
node.onCollapsed?.();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TreeItem value={treeNodeId} itemType={node.children !== undefined ? "branch" : "leaf"} style={{ height: "100%" }}>
|
||||
<TreeItem
|
||||
value={treeNodeId}
|
||||
itemType={node.children !== undefined ? "branch" : "leaf"}
|
||||
style={{ height: "100%" }}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<TreeItemLayout
|
||||
className={node.className}
|
||||
actions={
|
||||
@@ -117,22 +123,21 @@ export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
expandIcon={node.isLoading ? <Spinner size="extra-tiny" /> : undefined}
|
||||
expandIcon={isLoading ? <Spinner size="extra-tiny" /> : undefined}
|
||||
iconBefore={node.iconSrc && getTreeIcon(node.iconSrc)}
|
||||
style={{
|
||||
backgroundColor: node.isSelected && node.isSelected() ? tokens.colorNeutralBackground1Selected : undefined,
|
||||
}}
|
||||
>
|
||||
<span onClick={() => node.onClick?.()}>{node.label}</span>
|
||||
</TreeItemLayout>
|
||||
{!node.isLoading && node.children?.length > 0 && (
|
||||
<Tree
|
||||
// defaultOpenItems={defaultOpenItems}
|
||||
style={{ overflow: node.isScrollable ? "auto" : undefined }}
|
||||
>
|
||||
<Tree style={{ overflow: node.isScrollable ? "auto" : undefined }}>
|
||||
{getSortedChildren(node).map((childNode: TreeNode2) => (
|
||||
<TreeNode2Component
|
||||
key={childNode.label}
|
||||
node={childNode}
|
||||
treeNodeId={`${treeNodeId}/${childNode.label}`}
|
||||
globalOpenIds={globalOpenIds}
|
||||
/>
|
||||
))}
|
||||
</Tree>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
TreeOpenChangeEvent,
|
||||
createLightTheme,
|
||||
} from "@fluentui/react-components";
|
||||
import { TreeNode2Component } from "Explorer/Controls/TreeComponent2/TreeNode2Component";
|
||||
import { TreeNode2, TreeNode2Component } from "Explorer/Controls/TreeComponent2/TreeNode2Component";
|
||||
import { useDatabaseTreeNodes } from "Explorer/Tree2/useDatabaseTreeNodes";
|
||||
import * as React from "react";
|
||||
import shallow from "zustand/shallow";
|
||||
@@ -69,16 +69,45 @@ export const ResourceTree2: React.FC<ResourceTreeProps> = ({ container }: Resour
|
||||
);
|
||||
// const { activeTab } = useTabs();
|
||||
const databaseTreeNodes = useDatabaseTreeNodes(container, isNotebookEnabled);
|
||||
const dataNodeTree = {
|
||||
const [openItems, setOpenItems] = React.useState<Iterable<TreeItemValue>>([DATA_TREE_LABEL]);
|
||||
|
||||
const dataNodeTree: TreeNode2 = {
|
||||
id: "data",
|
||||
label: DATA_TREE_LABEL,
|
||||
isExpanded: true,
|
||||
className: "accordionItemHeader",
|
||||
children: databaseTreeNodes,
|
||||
isScrollable: true,
|
||||
};
|
||||
|
||||
const [openItems, setOpenItems] = React.useState<Iterable<TreeItemValue>>([DATA_TREE_LABEL]);
|
||||
React.useEffect(() => {
|
||||
// Compute open items based on node.isExpanded
|
||||
const updateOpenItems = (node: TreeNode2, parentNodeId: string): void => {
|
||||
// This will look for ANY expanded node, event if its parent node isn't expanded
|
||||
// and add it to the openItems list
|
||||
const globalId = parentNodeId === undefined ? node.label : `${parentNodeId}/${node.label}`;
|
||||
|
||||
if (node.isExpanded) {
|
||||
let found = false;
|
||||
for (const id of openItems) {
|
||||
if (id === globalId) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
setOpenItems((prevOpenItems) => [...prevOpenItems, globalId]);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
updateOpenItems(child, globalId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateOpenItems(dataNodeTree, undefined);
|
||||
}, [databaseTreeNodes]);
|
||||
|
||||
const handleOpenChange = (event: TreeOpenChangeEvent, data: TreeOpenChangeData) => setOpenItems(data.openItems);
|
||||
|
||||
@@ -93,13 +122,7 @@ export const ResourceTree2: React.FC<ResourceTreeProps> = ({ container }: Resour
|
||||
style={{ height: "100%", width: "290px" }}
|
||||
>
|
||||
{[dataNodeTree].map((node) => (
|
||||
<TreeNode2Component
|
||||
key={node.label}
|
||||
className="dataResourceTree"
|
||||
node={node}
|
||||
treeNodeId={node.label}
|
||||
globalOpenIds={[...openItems].map((item) => item.toString())}
|
||||
/>
|
||||
<TreeNode2Component key={node.label} className="dataResourceTree" node={node} treeNodeId={node.label} />
|
||||
))}
|
||||
</Tree>
|
||||
</FluentProvider>
|
||||
|
||||
@@ -45,7 +45,7 @@ export const buildCollectionNode = (
|
||||
// push to most recent
|
||||
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
|
||||
},
|
||||
onExpanded: () => {
|
||||
onExpanded: async () => {
|
||||
// Rewritten version of expandCollapseCollection
|
||||
useSelectedNode.getState().setSelectedNode(collection);
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
@@ -53,9 +53,16 @@ export const buildCollectionNode = (
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
},
|
||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
||||
onCollapsed: () => {
|
||||
collection.collapseCollection();
|
||||
// useCommandBar.getState().setContextButtons([]);
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
},
|
||||
isExpanded: collection.isCollectionExpanded(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,15 +25,22 @@ export const useDatabaseTreeNodes = (container: Explorer, isNotebookEnabled: boo
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(container, database.id()),
|
||||
onExpanded: async () => {
|
||||
useSelectedNode.getState().setSelectedNode(database);
|
||||
if (databaseNode.children?.length === 0) {
|
||||
if (!databaseNode.children || databaseNode.children?.length === 0) {
|
||||
databaseNode.isLoading = true;
|
||||
}
|
||||
await database.expandDatabase();
|
||||
databaseNode.isLoading = false;
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
},
|
||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
|
||||
isExpanded: database.isDatabaseExpanded(),
|
||||
onCollapsed: () => {
|
||||
database.collapseDatabase();
|
||||
// useCommandBar.getState().setContextButtons([]);
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
},
|
||||
};
|
||||
|
||||
if (database.isDatabaseShared() && configContext.platform !== Platform.Fabric) {
|
||||
|
||||
Reference in New Issue
Block a user