mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-20 03:05:39 +00:00
Refactor ResourceTree to use FluentUI 9 Tree component
This commit is contained in:
parent
f8ff0626d9
commit
fa865f99c8
33018
package-lock.json
generated
33018
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@
|
|||||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
"@fluentui/react": "8.14.3",
|
"@fluentui/react": "8.14.3",
|
||||||
|
"@fluentui/react-components": "9.30.1",
|
||||||
"@jupyterlab/services": "6.0.2",
|
"@jupyterlab/services": "6.0.2",
|
||||||
"@jupyterlab/terminal": "3.0.3",
|
"@jupyterlab/terminal": "3.0.3",
|
||||||
"@microsoft/applicationinsights-web": "2.6.1",
|
"@microsoft/applicationinsights-web": "2.6.1",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import { TreeComponent, TreeNode, TreeNodeComponent } from "./TreeComponent";
|
import React from "react";
|
||||||
|
import { TreeComponent, TreeNode, TreeNodeComponent_old } from "./TreeComponent";
|
||||||
|
|
||||||
const buildChildren = (): TreeNode[] => {
|
const buildChildren = (): TreeNode[] => {
|
||||||
const grandChild11: TreeNode = {
|
const grandChild11: TreeNode = {
|
||||||
@ -98,7 +98,7 @@ describe("TreeNodeComponent", () => {
|
|||||||
generation: 12,
|
generation: 12,
|
||||||
paddingLeft: 23,
|
paddingLeft: 23,
|
||||||
};
|
};
|
||||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
const wrapper = shallow(<TreeNodeComponent_old {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ describe("TreeNodeComponent", () => {
|
|||||||
generation: 2,
|
generation: 2,
|
||||||
paddingLeft: 9,
|
paddingLeft: 9,
|
||||||
};
|
};
|
||||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
const wrapper = shallow(<TreeNodeComponent_old {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ describe("TreeNodeComponent", () => {
|
|||||||
generation: 2,
|
generation: 2,
|
||||||
paddingLeft: 9,
|
paddingLeft: 9,
|
||||||
};
|
};
|
||||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
const wrapper = shallow(<TreeNodeComponent_old {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ describe("TreeNodeComponent", () => {
|
|||||||
generation: 12,
|
generation: 12,
|
||||||
paddingLeft: 23,
|
paddingLeft: 23,
|
||||||
};
|
};
|
||||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
const wrapper = shallow(<TreeNodeComponent_old {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ describe("TreeNodeComponent", () => {
|
|||||||
generation: 2,
|
generation: 2,
|
||||||
paddingLeft: 9,
|
paddingLeft: 9,
|
||||||
};
|
};
|
||||||
const wrapper = shallow(<TreeNodeComponent {...props} />);
|
const wrapper = shallow(<TreeNodeComponent_old {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
IContextualMenuItemProps,
|
IContextualMenuItemProps,
|
||||||
IContextualMenuProps,
|
IContextualMenuProps,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
|
import { Button, Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, Spinner, Tree, TreeItem, TreeItemLayout } from "@fluentui/react-components";
|
||||||
|
import { MoreHorizontal20Regular } from "@fluentui/react-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import AnimateHeight from "react-animate-height";
|
import AnimateHeight from "react-animate-height";
|
||||||
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
@ -20,7 +22,6 @@ import TriangleRightIcon from "../../../../images/Triangle-right.svg";
|
|||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|
||||||
export interface TreeNodeMenuItem {
|
export interface TreeNodeMenuItem {
|
||||||
label: string;
|
label: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@ -58,13 +59,90 @@ export interface TreeComponentProps {
|
|||||||
export class TreeComponent extends React.Component<TreeComponentProps> {
|
export class TreeComponent extends React.Component<TreeComponentProps> {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div style={this.props.style} className={`treeComponent ${this.props.className}`} role="tree">
|
|
||||||
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
|
<Tree>
|
||||||
</div>
|
<TreeNodeComponent node={this.props.rootNode} />
|
||||||
|
</Tree>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TreeNodeComponent: React.FC<{ node: TreeNode, className?: string }> = ({ node }): JSX.Element => {
|
||||||
|
const { children } = node;
|
||||||
|
const defaultOpenItems = node.isExpanded ? children?.map((child: TreeNode) => child.label) : undefined;
|
||||||
|
|
||||||
|
const getSortedChildren = (treeNode: TreeNode): TreeNode[] => {
|
||||||
|
if (!treeNode || !treeNode.children) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compareFct = (a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label);
|
||||||
|
|
||||||
|
let unsortedChildren;
|
||||||
|
if (treeNode.isLeavesParentsSeparate) {
|
||||||
|
// Separate parents and leave
|
||||||
|
const parents: TreeNode[] = treeNode.children.filter((node) => node.children);
|
||||||
|
const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children);
|
||||||
|
|
||||||
|
if (treeNode.isAlphaSorted) {
|
||||||
|
parents.sort(compareFct);
|
||||||
|
leaves.sort(compareFct);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsortedChildren = parents.concat(leaves);
|
||||||
|
} else {
|
||||||
|
unsortedChildren = treeNode.isAlphaSorted ? treeNode.children.sort(compareFct) : treeNode.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unsortedChildren;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TreeItem key={node.label} value={node.label} itemType={node.children?.length > 0 || node.isLoading ? "branch" : "leaf"}>
|
||||||
|
<TreeItemLayout
|
||||||
|
className={node.className}
|
||||||
|
onClick={() => node.onClick && node.onClick(false)}
|
||||||
|
actions={node.contextMenu &&
|
||||||
|
<Menu>
|
||||||
|
<MenuTrigger disableButtonEnhancement>
|
||||||
|
<Button aria-label="More options" appearance="subtle" icon={<MoreHorizontal20Regular />} />
|
||||||
|
</MenuTrigger>
|
||||||
|
<MenuPopover>
|
||||||
|
<MenuList>{node.contextMenu.map(menuItem => (
|
||||||
|
<MenuItem disabled={menuItem.isDisabled} key={menuItem.label} onClick={menuItem.onClick}>{menuItem.label}</MenuItem>
|
||||||
|
))}</MenuList>
|
||||||
|
</MenuPopover>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
expandIcon={node.isLoading ? <Spinner size="tiny" /> : undefined}
|
||||||
|
>{node.label}</TreeItemLayout>
|
||||||
|
{!node.isLoading && node.children?.length > 0 && <Tree defaultOpenItems={defaultOpenItems}>
|
||||||
|
{getSortedChildren(node).map((childNode: TreeNode) => (
|
||||||
|
<TreeNodeComponent key={childNode.id} node={childNode} />
|
||||||
|
))}
|
||||||
|
</Tree>}
|
||||||
|
</TreeItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
/* Tree node is a react component */
|
/* Tree node is a react component */
|
||||||
interface TreeNodeComponentProps {
|
interface TreeNodeComponentProps {
|
||||||
node: TreeNode;
|
node: TreeNode;
|
||||||
@ -76,7 +154,7 @@ interface TreeNodeComponentState {
|
|||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
isMenuShowing: boolean;
|
isMenuShowing: boolean;
|
||||||
}
|
}
|
||||||
export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, TreeNodeComponentState> {
|
export class TreeNodeComponent_old extends React.Component<TreeNodeComponentProps, TreeNodeComponentState> {
|
||||||
private static readonly paddingPerGenerationPx = 16;
|
private static readonly paddingPerGenerationPx = 16;
|
||||||
private static readonly iconOffset = 22;
|
private static readonly iconOffset = 22;
|
||||||
private static readonly transitionDurationMS = 200;
|
private static readonly transitionDurationMS = 200;
|
||||||
@ -97,9 +175,9 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
// Only call when expand has actually changed
|
// Only call when expand has actually changed
|
||||||
if (this.state.isExpanded !== prevState.isExpanded) {
|
if (this.state.isExpanded !== prevState.isExpanded) {
|
||||||
if (this.state.isExpanded) {
|
if (this.state.isExpanded) {
|
||||||
this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, TreeNodeComponent.callbackDelayMS);
|
this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, TreeNodeComponent_old.callbackDelayMS);
|
||||||
} else {
|
} else {
|
||||||
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, TreeNodeComponent.callbackDelayMS);
|
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, TreeNodeComponent_old.callbackDelayMS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.props.node.isExpanded !== this.isExpanded) {
|
if (this.props.node.isExpanded !== this.isExpanded) {
|
||||||
@ -145,13 +223,13 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderNode(node: TreeNode, generation: number): JSX.Element {
|
private renderNode(node: TreeNode, generation: number): JSX.Element {
|
||||||
let paddingLeft = generation * TreeNodeComponent.paddingPerGenerationPx;
|
let paddingLeft = generation * TreeNodeComponent_old.paddingPerGenerationPx;
|
||||||
let additionalOffsetPx = 15;
|
let additionalOffsetPx = 15;
|
||||||
|
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
const childrenWithSubChildren = node.children.filter((child: TreeNode) => !!child.children);
|
const childrenWithSubChildren = node.children.filter((child: TreeNode) => !!child.children);
|
||||||
if (childrenWithSubChildren.length > 0) {
|
if (childrenWithSubChildren.length > 0) {
|
||||||
additionalOffsetPx = TreeNodeComponent.iconOffset;
|
additionalOffsetPx = TreeNodeComponent_old.iconOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,10 +237,10 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
const showSelected =
|
const showSelected =
|
||||||
this.props.node.isSelected &&
|
this.props.node.isSelected &&
|
||||||
this.props.node.isSelected() &&
|
this.props.node.isSelected() &&
|
||||||
!TreeNodeComponent.isAnyDescendantSelected(this.props.node);
|
!TreeNodeComponent_old.isAnyDescendantSelected(this.props.node);
|
||||||
|
|
||||||
const headerStyle: React.CSSProperties = { paddingLeft: this.props.paddingLeft };
|
const headerStyle: React.CSSProperties = { paddingLeft: this.props.paddingLeft };
|
||||||
if (TreeNodeComponent.isNodeHeaderBlank(node)) {
|
if (TreeNodeComponent_old.isNodeHeaderBlank(node)) {
|
||||||
headerStyle.height = 0;
|
headerStyle.height = 0;
|
||||||
headerStyle.padding = 0;
|
headerStyle.padding = 0;
|
||||||
}
|
}
|
||||||
@ -194,10 +272,10 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
<img className="loadingIcon" src={LoadingIndicator_3Squares} hidden={!this.props.node.isLoading} />
|
<img className="loadingIcon" src={LoadingIndicator_3Squares} hidden={!this.props.node.isLoading} />
|
||||||
</div>
|
</div>
|
||||||
{node.children && (
|
{node.children && (
|
||||||
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
|
<AnimateHeight duration={TreeNodeComponent_old.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}>
|
||||||
<div className="nodeChildren" data-test={node.label} role="group">
|
<div className="nodeChildren" data-test={node.label} role="group">
|
||||||
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => (
|
{TreeNodeComponent_old.getSortedChildren(node).map((childNode: TreeNode) => (
|
||||||
<TreeNodeComponent
|
<TreeNodeComponent_old
|
||||||
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}
|
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}
|
||||||
node={childNode}
|
node={childNode}
|
||||||
generation={generation + 1}
|
generation={generation + 1}
|
||||||
@ -220,7 +298,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
node.children &&
|
node.children &&
|
||||||
node.children.reduce(
|
node.children.reduce(
|
||||||
(previous: boolean, child: TreeNode) =>
|
(previous: boolean, child: TreeNode) =>
|
||||||
previous || (child.isSelected && child.isSelected()) || TreeNodeComponent.isAnyDescendantSelected(child),
|
previous || (child.isSelected && child.isSelected()) || TreeNodeComponent_old.isAnyDescendantSelected(child),
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -231,7 +309,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onRightClick = (): void => {
|
private onRightClick = (): void => {
|
||||||
this.contextMenuRef.current.firstChild.dispatchEvent(TreeNodeComponent.createClickEvent());
|
this.contextMenuRef.current.firstChild.dispatchEvent(TreeNodeComponent_old.createClickEvent());
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderContextMenuButton(node: TreeNode): JSX.Element {
|
private renderContextMenuButton(node: TreeNode): JSX.Element {
|
||||||
@ -264,7 +342,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
<div
|
<div
|
||||||
data-test={`treeComponentMenuItemContainer`}
|
data-test={`treeComponentMenuItemContainer`}
|
||||||
className="treeComponentMenuItemContainer"
|
className="treeComponentMenuItemContainer"
|
||||||
onContextMenu={(e) => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())}
|
onContextMenu={(e) => e.target.dispatchEvent(TreeNodeComponent_old.createClickEvent())}
|
||||||
>
|
>
|
||||||
{props.item.onRenderIcon()}
|
{props.item.onRenderIcon()}
|
||||||
<span
|
<span
|
||||||
@ -318,7 +396,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
if (node.children) {
|
if (node.children) {
|
||||||
const isExpanded = !this.state.isExpanded;
|
const isExpanded = !this.state.isExpanded;
|
||||||
// Prevent collapsing if node header is blank
|
// Prevent collapsing if node header is blank
|
||||||
if (!(TreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) {
|
if (!(TreeNodeComponent_old.isNodeHeaderBlank(node) && !isExpanded)) {
|
||||||
this.setState({ isExpanded });
|
this.setState({ isExpanded });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
||||||
import { SampleDataTree } from "Explorer/Tree/SampleDataTree";
|
import { BrandVariants, FluentProvider, Theme, Tree, createLightTheme } from "@fluentui/react-components";
|
||||||
|
import { buildSampleDataTree } from "Explorer/Tree/SampleDataTree";
|
||||||
import { getItemName } from "Utils/APITypeUtils";
|
import { getItemName } from "Utils/APITypeUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import shallow from "zustand/shallow";
|
import shallow from "zustand/shallow";
|
||||||
@ -26,9 +27,8 @@ import * as GitHubUtils from "../../Utils/GitHubUtils";
|
|||||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
import { useTabs } from "../../hooks/useTabs";
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
|
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
|
||||||
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
|
|
||||||
import { useDialog } from "../Controls/Dialog";
|
import { useDialog } from "../Controls/Dialog";
|
||||||
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
|
import { TreeNode, TreeNodeComponent, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||||
@ -50,6 +50,30 @@ interface ResourceTreeProps {
|
|||||||
container: Explorer;
|
container: Explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const cosmosdb: BrandVariants = {
|
||||||
|
10: "#020305",
|
||||||
|
20: "#111723",
|
||||||
|
30: "#16263D",
|
||||||
|
40: "#193253",
|
||||||
|
50: "#1B3F6A",
|
||||||
|
60: "#1B4C82",
|
||||||
|
70: "#18599B",
|
||||||
|
80: "#1267B4",
|
||||||
|
90: "#3174C2",
|
||||||
|
100: "#4F82C8",
|
||||||
|
110: "#6790CF",
|
||||||
|
120: "#7D9ED5",
|
||||||
|
130: "#92ACDC",
|
||||||
|
140: "#A6BAE2",
|
||||||
|
150: "#BAC9E9",
|
||||||
|
160: "#CDD8EF"
|
||||||
|
};
|
||||||
|
|
||||||
|
const lightTheme: Theme = {
|
||||||
|
...createLightTheme(cosmosdb),
|
||||||
|
};
|
||||||
|
|
||||||
export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: ResourceTreeProps): JSX.Element => {
|
export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: ResourceTreeProps): JSX.Element => {
|
||||||
const databases = useDatabases((state) => state.databases);
|
const databases = useDatabases((state) => state.databases);
|
||||||
const {
|
const {
|
||||||
@ -118,7 +142,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
|
|
||||||
const buildNotebooksTree = (): TreeNode => {
|
const buildNotebooksTree = (): TreeNode => {
|
||||||
const notebooksTree: TreeNode = {
|
const notebooksTree: TreeNode = {
|
||||||
label: undefined,
|
id: "notebooks",
|
||||||
|
label: "NOTEBOOKS",
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
@ -502,7 +527,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: undefined,
|
id: "data",
|
||||||
|
label: "DATA",
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
children: databaseTreeNodes,
|
children: databaseTreeNodes,
|
||||||
};
|
};
|
||||||
@ -768,56 +794,30 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
const isSampleDataEnabled = userContext.sampleDataConnectionInfo && userContext.apiType === "SQL";
|
const isSampleDataEnabled = userContext.sampleDataConnectionInfo && userContext.apiType === "SQL";
|
||||||
const sampleDataResourceTokenCollection = useDatabases((state) => state.sampleDataResourceTokenCollection);
|
const sampleDataResourceTokenCollection = useDatabases((state) => state.sampleDataResourceTokenCollection);
|
||||||
|
|
||||||
return (
|
const treeNodes = React.useMemo(() => {
|
||||||
<>
|
if (!isNotebookEnabled && !isSampleDataEnabled) {
|
||||||
{!isNotebookEnabled && !isSampleDataEnabled && (
|
return dataRootNode.children;
|
||||||
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
|
}
|
||||||
)}
|
|
||||||
{isNotebookEnabled && !isSampleDataEnabled && (
|
|
||||||
<>
|
|
||||||
<AccordionComponent>
|
|
||||||
<AccordionItemComponent title={"DATA"} isExpanded={!gitHubNotebooksContentRoot}>
|
|
||||||
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
<AccordionItemComponent title={"NOTEBOOKS"}>
|
|
||||||
<TreeComponent className="notebookResourceTree" rootNode={buildNotebooksTree()} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
</AccordionComponent>
|
|
||||||
|
|
||||||
{buildGalleryCallout()}
|
const nodes: TreeNode[] = [dataRootNode];
|
||||||
</>
|
if (isSampleDataEnabled) {
|
||||||
)}
|
nodes.push(buildSampleDataTree(sampleDataResourceTokenCollection));
|
||||||
{!isNotebookEnabled && isSampleDataEnabled && (
|
}
|
||||||
<>
|
|
||||||
<AccordionComponent>
|
|
||||||
<AccordionItemComponent title={"MY DATA"} isExpanded={!gitHubNotebooksContentRoot}>
|
|
||||||
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
<AccordionItemComponent title={"SAMPLE DATA"} containerStyles={{ display: "table" }}>
|
|
||||||
<SampleDataTree sampleDataResourceTokenCollection={sampleDataResourceTokenCollection} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
</AccordionComponent>
|
|
||||||
|
|
||||||
{buildGalleryCallout()}
|
if (isNotebookEnabled) {
|
||||||
</>
|
nodes.push(buildNotebooksTree());
|
||||||
)}
|
}
|
||||||
{isNotebookEnabled && isSampleDataEnabled && (
|
|
||||||
<>
|
|
||||||
<AccordionComponent>
|
|
||||||
<AccordionItemComponent title={"MY DATA"} isExpanded={!gitHubNotebooksContentRoot}>
|
|
||||||
<TreeComponent className="dataResourceTree" rootNode={dataRootNode} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
<AccordionItemComponent title={"SAMPLE DATA"} containerStyles={{ display: "table" }}>
|
|
||||||
<SampleDataTree sampleDataResourceTokenCollection={sampleDataResourceTokenCollection} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
<AccordionItemComponent title={"NOTEBOOKS"}>
|
|
||||||
<TreeComponent className="notebookResourceTree" rootNode={buildNotebooksTree()} />
|
|
||||||
</AccordionItemComponent>
|
|
||||||
</AccordionComponent>
|
|
||||||
|
|
||||||
{buildGalleryCallout()}
|
return nodes;
|
||||||
</>
|
}, [isNotebookEnabled, isSampleDataEnabled]);
|
||||||
)}
|
|
||||||
</>
|
return (<>
|
||||||
);
|
<FluentProvider theme={lightTheme}>
|
||||||
|
<Tree openItems={treeNodes.map(node => node.label)}>
|
||||||
|
{treeNodes.map(node => <TreeNodeComponent key={node.id} className="dataResourceTree" node={node} />)}
|
||||||
|
</Tree>
|
||||||
|
</FluentProvider>
|
||||||
|
{(isNotebookEnabled || isSampleDataEnabled) && buildGalleryCallout()}
|
||||||
|
</>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
83
src/Explorer/Tree/SampleDataTree.ts
Normal file
83
src/Explorer/Tree/SampleDataTree.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
|
import TabsBase from "Explorer/Tabs/TabsBase";
|
||||||
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
|
import { useTabs } from "hooks/useTabs";
|
||||||
|
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
||||||
|
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
|
||||||
|
import { TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
||||||
|
|
||||||
|
export const buildSampleDataTree = (sampleDataResourceTokenCollection: ViewModels.CollectionBase): TreeNode => {
|
||||||
|
const updatedSampleTree: TreeNode = {
|
||||||
|
label: sampleDataResourceTokenCollection.databaseId,
|
||||||
|
isExpanded: false,
|
||||||
|
iconSrc: CosmosDBIcon,
|
||||||
|
className: "databaseHeader",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: sampleDataResourceTokenCollection.id(),
|
||||||
|
iconSrc: CollectionIcon,
|
||||||
|
isExpanded: false,
|
||||||
|
className: "collectionHeader",
|
||||||
|
contextMenu: ResourceTreeContextMenuButtonFactory.createSampleCollectionContextMenuButton(),
|
||||||
|
onClick: () => {
|
||||||
|
useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection);
|
||||||
|
useCommandBar.getState().setContextButtons([]);
|
||||||
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
|
(tab: TabsBase) =>
|
||||||
|
tab.collection?.id() === sampleDataResourceTokenCollection.id() &&
|
||||||
|
tab.collection.databaseId === sampleDataResourceTokenCollection.databaseId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isSelected: () =>
|
||||||
|
useSelectedNode
|
||||||
|
.getState()
|
||||||
|
.isDataNodeSelected(sampleDataResourceTokenCollection.databaseId, sampleDataResourceTokenCollection.id()),
|
||||||
|
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: "Items",
|
||||||
|
onClick: () => sampleDataResourceTokenCollection.onDocumentDBDocumentsClick(),
|
||||||
|
contextMenu: ResourceTreeContextMenuButtonFactory.createSampleCollectionContextMenuButton(),
|
||||||
|
isSelected: () =>
|
||||||
|
useSelectedNode
|
||||||
|
.getState()
|
||||||
|
.isDataNodeSelected(
|
||||||
|
sampleDataResourceTokenCollection.databaseId,
|
||||||
|
sampleDataResourceTokenCollection.id(),
|
||||||
|
[ViewModels.CollectionTabKind.Documents]
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO handle case non-initialized
|
||||||
|
return updatedSampleTree;
|
||||||
|
|
||||||
|
// export const SampleDataTree = ({
|
||||||
|
// sampleDataResourceTokenCollection,
|
||||||
|
// }: {
|
||||||
|
// sampleDataResourceTokenCollection: ViewModels.CollectionBase;
|
||||||
|
// }): JSX.Element => {
|
||||||
|
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// id: "sampleData",
|
||||||
|
// label: "SAMPLE DATA",
|
||||||
|
// isExpanded: true,
|
||||||
|
// children: [updatedSampleTree],
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <TreeNodeComponent
|
||||||
|
// className="dataResourceTree"
|
||||||
|
// node={sampleDataResourceTokenCollection ? buildSampleDataTree() : { label: "Sample data not initialized." }}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
};
|
@ -1,78 +0,0 @@
|
|||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
|
||||||
import TabsBase from "Explorer/Tabs/TabsBase";
|
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
|
||||||
import { useTabs } from "hooks/useTabs";
|
|
||||||
import React from "react";
|
|
||||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
|
||||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
|
|
||||||
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
|
||||||
|
|
||||||
export const SampleDataTree = ({
|
|
||||||
sampleDataResourceTokenCollection,
|
|
||||||
}: {
|
|
||||||
sampleDataResourceTokenCollection: ViewModels.CollectionBase;
|
|
||||||
}): JSX.Element => {
|
|
||||||
const buildSampleDataTree = (): TreeNode => {
|
|
||||||
const updatedSampleTree: TreeNode = {
|
|
||||||
label: sampleDataResourceTokenCollection.databaseId,
|
|
||||||
isExpanded: false,
|
|
||||||
iconSrc: CosmosDBIcon,
|
|
||||||
className: "databaseHeader",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: sampleDataResourceTokenCollection.id(),
|
|
||||||
iconSrc: CollectionIcon,
|
|
||||||
isExpanded: false,
|
|
||||||
className: "collectionHeader",
|
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createSampleCollectionContextMenuButton(),
|
|
||||||
onClick: () => {
|
|
||||||
useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection);
|
|
||||||
useCommandBar.getState().setContextButtons([]);
|
|
||||||
useTabs
|
|
||||||
.getState()
|
|
||||||
.refreshActiveTab(
|
|
||||||
(tab: TabsBase) =>
|
|
||||||
tab.collection?.id() === sampleDataResourceTokenCollection.id() &&
|
|
||||||
tab.collection.databaseId === sampleDataResourceTokenCollection.databaseId
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isSelected: () =>
|
|
||||||
useSelectedNode
|
|
||||||
.getState()
|
|
||||||
.isDataNodeSelected(sampleDataResourceTokenCollection.databaseId, sampleDataResourceTokenCollection.id()),
|
|
||||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(sampleDataResourceTokenCollection),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: "Items",
|
|
||||||
onClick: () => sampleDataResourceTokenCollection.onDocumentDBDocumentsClick(),
|
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createSampleCollectionContextMenuButton(),
|
|
||||||
isSelected: () =>
|
|
||||||
useSelectedNode
|
|
||||||
.getState()
|
|
||||||
.isDataNodeSelected(
|
|
||||||
sampleDataResourceTokenCollection.databaseId,
|
|
||||||
sampleDataResourceTokenCollection.id(),
|
|
||||||
[ViewModels.CollectionTabKind.Documents]
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: undefined,
|
|
||||||
isExpanded: true,
|
|
||||||
children: [updatedSampleTree],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TreeComponent
|
|
||||||
className="dataResourceTree"
|
|
||||||
rootNode={sampleDataResourceTokenCollection ? buildSampleDataTree() : { label: "Sample data not initialized." }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user