Skip to content

Commit

Permalink
feat(console): allow collapsing and expanding map nodes (#6627)
Browse files Browse the repository at this point in the history
Resolves #6624
  • Loading branch information
polamoros authored Jun 5, 2024
1 parent cbb3043 commit fb0d00e
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 50 deletions.
2 changes: 2 additions & 0 deletions apps/wing-console/console/app/demo/main.w
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,12 @@ test "Add fixtures" {
class WidgetService {
data: cloud.Bucket;
counter: cloud.Counter;
bucket: myBucket;

new() {
this.data = new cloud.Bucket();
this.counter = new cloud.Counter();
this.bucket = new myBucket() as "MyInternalBucket";

// a field displays a labeled value, with optional refreshing
new ui.Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ export const TreeItem = ({
});
const canBeExpanded = !!children;

useEffect(() => {
if (selected) {
ref.current?.scrollIntoView();
}
}, [selected, ref]);

return (
<li
ref={ref}
Expand Down
136 changes: 109 additions & 27 deletions apps/wing-console/console/ui/src/features/map-view.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { CubeTransparentIcon } from "@heroicons/react/20/solid";
import { CubeIcon } from "@heroicons/react/24/outline";
import { BoltIcon } from "@heroicons/react/24/solid";
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
import {
ResourceIcon,
SpinnerLoader,
getResourceIconComponent,
useTheme,
} from "@wingconsole/design-system";
import type { ConstructTreeNode } from "@winglang/sdk/lib/core/index.js";
import clsx from "classnames";
import { type ElkPoint, type LayoutOptions } from "elkjs";
import type { FunctionComponent, PropsWithChildren } from "react";
import { memo, useCallback, useEffect, useId, useMemo } from "react";
import { memo, useCallback, useMemo } from "react";
import { useKeyPressEvent } from "react-use";

import { useMap } from "../services/use-map.js";
Expand Down Expand Up @@ -42,20 +39,32 @@ const baseLayoutOptions: LayoutOptions = {
"elk.algorithm": "org.eclipse.elk.layered",
"elk.layered.spacing.baseValue": `${SPACING_BASE_VALUE}`, // See https://eclipse.dev/elk/reference/options/org-eclipse-elk-layered-spacing-baseValue.html.
};

interface WrapperProps {
name: string;
fqn: string;
highlight?: boolean;
onClick?: () => void;
color?: string;
icon?: string;
collapsed?: boolean;
onCollapse?: (value: boolean) => void;
}

const Wrapper: FunctionComponent<PropsWithChildren<WrapperProps>> = memo(
({ name, fqn, highlight, onClick, children, color, icon }) => {
({
name,
fqn,
highlight,
onClick,
collapsed = false,
onCollapse = (value: boolean) => {},
children,
color,
icon,
}) => {
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
className={clsx(
"w-full h-full",
Expand All @@ -78,8 +87,8 @@ const Wrapper: FunctionComponent<PropsWithChildren<WrapperProps>> = memo(
<div
className={clsx(
"px-2.5 py-1 flex items-center gap-1.5",
// inflights.length > 0 &&
"border-b border-slate-200 dark:border-slate-800",
"cursor-pointer",
)}
>
<ResourceIcon
Expand All @@ -98,9 +107,33 @@ const Wrapper: FunctionComponent<PropsWithChildren<WrapperProps>> = memo(
>
{name}
</span>
<div className="flex grow justify-end">
<div
className="pl-1"
onClick={() => {
onCollapse(!collapsed);
}}
>
{collapsed && (
<ChevronRightIcon
className={clsx(
"size-4",
"hover:text-sky-600 dark:hover:text-sky-300 transition-colors",
)}
/>
)}
{!collapsed && (
<ChevronDownIcon
className={clsx(
"size-4",
"hover:text-sky-600 dark:hover:text-sky-300 transition-colors",
)}
/>
)}
</div>
</div>
</div>

{children}
{!collapsed && children}
</div>
);
},
Expand All @@ -113,6 +146,8 @@ interface ContainerNodeProps {
resourceType?: string;
highlight?: boolean;
onClick?: () => void;
collapsed?: boolean;
onCollapse?: (value: boolean) => void;
color?: string;
icon?: string;
}
Expand All @@ -136,6 +171,8 @@ const ContainerNode: FunctionComponent<PropsWithChildren<ContainerNodeProps>> =
fqn={props.resourceType!}
highlight={props.highlight}
onClick={props.onClick}
onCollapse={props.onCollapse}
collapsed={props.collapsed}
color={props.color}
icon={props.icon}
>
Expand Down Expand Up @@ -164,6 +201,8 @@ interface ConstructNodeProps {
hasChildNodes?: boolean;
onSelectedNodeIdChange: (id: string | undefined) => void;
color?: string;
onCollapse: (value: boolean) => void;
collapsed: boolean;
icon?: string;
}

Expand All @@ -179,6 +218,8 @@ const ConstructNode: FunctionComponent<PropsWithChildren<ConstructNodeProps>> =
children,
hasChildNodes,
color,
onCollapse,
collapsed,
icon,
}) => {
const select = useCallback(
Expand Down Expand Up @@ -245,6 +286,7 @@ const ConstructNode: FunctionComponent<PropsWithChildren<ConstructNodeProps>> =
/>

{!hasChildNodes && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
className={clsx(
"px-2.5 py-1 flex items-center gap-1.5",
Expand All @@ -268,6 +310,23 @@ const ConstructNode: FunctionComponent<PropsWithChildren<ConstructNodeProps>> =
>
{name}
</span>
{collapsed && (
<div
className="flex grow justify-end pl-1"
onClick={() => {
if (collapsed) {
onCollapse(false);
}
}}
>
<ChevronRightIcon
className={clsx(
"size-4",
"hover:text-sky-600 dark:hover:text-sky-300 transition-colors",
)}
/>
</div>
)}
</div>
)}

Expand Down Expand Up @@ -334,6 +393,8 @@ const ConstructNode: FunctionComponent<PropsWithChildren<ConstructNodeProps>> =
resourceType={fqn}
highlight={highlight}
onClick={select}
onCollapse={onCollapse}
collapsed={collapsed}
color={color}
icon={icon}
>
Expand Down Expand Up @@ -541,6 +602,9 @@ export interface MapViewV2Props {
onSelectedNodeIdChange: (id: string | undefined) => void;
selectedEdgeId?: string;
onSelectedEdgeIdChange?: (id: string | undefined) => void;
onExpand: (path: string) => void;
onCollapse: (path: string) => void;
expandedItems: string[];
}

export const MapView = memo(
Expand All @@ -549,8 +613,13 @@ export const MapView = memo(
onSelectedNodeIdChange,
selectedEdgeId,
onSelectedEdgeIdChange,
onExpand,
onCollapse,
expandedItems,
}: MapViewV2Props) => {
const { nodeInfo, isNodeHidden, rootNodes, edges } = useMap({});
const { nodeInfo, isNodeHidden, rootNodes, edges } = useMap({
expandedItems,
});

const RenderEdge = useCallback<EdgeComponent>(
(props) => {
Expand Down Expand Up @@ -581,40 +650,53 @@ export const MapView = memo(
}>
>(
(props) => {
if (isNodeHidden(props.constructTreeNode.path)) {
const node = props.constructTreeNode;
if (isNodeHidden(node.path)) {
return <></>;
}

const info = nodeInfo?.get(props.constructTreeNode.path);
const info = nodeInfo?.get(node.path);
if (!info) {
return <></>;
}

const childNodes = Object.values(
props.constructTreeNode.children ?? {},
).filter((node) => !isNodeHidden(node.path));
const childNodes = Object.values(node.children ?? {}).filter(
(node) => !isNodeHidden(node.path),
);

const fqn = props.constructTreeNode.constructInfo?.fqn;
const fqn = node.constructInfo?.fqn;

const cloudResourceType = fqn?.split(".").at(-1);

const name =
props.constructTreeNode.display?.title === cloudResourceType
? props.constructTreeNode.id
: props.constructTreeNode.display?.title ??
props.constructTreeNode.id;
node.display?.title === cloudResourceType
? node.id
: node.display?.title ?? node.id;

const children = Object.values(node.children ?? {});
const canBeExpanded =
!!node.children && children.some((child) => !child.display?.hidden);
const collapsed = canBeExpanded && !expandedItems.includes(node.path);

return (
<ConstructNode
id={props.constructTreeNode.path}
id={node.path}
name={name ?? ""}
fqn={fqn ?? ""}
color={props.constructTreeNode.display?.color}
icon={props.constructTreeNode.display?.icon}
color={node.display?.color}
icon={node.display?.icon}
inflights={info.type === "construct" ? info.inflights : []}
onSelectedNodeIdChange={props.onSelectedNodeIdChange}
highlight={props.selectedNodeId === props.constructTreeNode.path}
highlight={props.selectedNodeId === node.path}
hasChildNodes={childNodes.length > 0}
collapsed={collapsed}
onCollapse={(collapse) => {
if (collapse) {
onCollapse(node.path);
} else {
onExpand(node.path);
}
}}
>
{childNodes.map((child) => (
<RenderNode
Expand All @@ -627,7 +709,7 @@ export const MapView = memo(
</ConstructNode>
);
},
[isNodeHidden, nodeInfo],
[isNodeHidden, nodeInfo, onCollapse, onExpand, expandedItems],
);

const { theme } = useTheme();
Expand Down
6 changes: 5 additions & 1 deletion apps/wing-console/console/ui/src/layout/default-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const DefaultLayout = ({
expandedItems,
setExpandedItems,
expand,
collapse,
expandAll,
collapseAll,
theme,
Expand Down Expand Up @@ -261,7 +262,7 @@ export const DefaultLayout = ({
return (
<TopResizableWidget
key={component.type}
className="h-1/3"
className="h-1/5"
>
{panelComponent}
</TopResizableWidget>
Expand Down Expand Up @@ -300,6 +301,9 @@ export const DefaultLayout = ({
onSelectedNodeIdChange={setSelectedItemSingle}
selectedEdgeId={selectedEdgeId}
onSelectedEdgeIdChange={setSelectedEdgeId}
onExpand={expand}
onCollapse={collapse}
expandedItems={expandedItems}
/>
</div>
{!layout.rightPanel?.hide && (
Expand Down
2 changes: 2 additions & 0 deletions apps/wing-console/console/ui/src/layout/use-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const useLayout = ({ cloudAppState }: UseLayoutProps) => {
expandedItems,
setExpandedItems,
expand,
collapse,
expandAll,
collapseAll,
} = useExplorer();
Expand Down Expand Up @@ -98,6 +99,7 @@ export const useLayout = ({ cloudAppState }: UseLayoutProps) => {
expandedItems,
setExpandedItems,
expand,
collapse,
expandAll,
collapseAll,
theme,
Expand Down
6 changes: 2 additions & 4 deletions apps/wing-console/console/ui/src/services/use-explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const useExplorer = () => {
expandedItems,
setExpandedItems,
expand,
collapse,
expandAll,
collapseAll,
} = useTreeMenuItems({
Expand Down Expand Up @@ -83,17 +84,14 @@ export const useExplorer = () => {
setSelectedItems([selectedNode]);
}, [selectedNode, setSelectedItems]);

useEffect(() => {
expandAll();
}, [items, expandAll]);

return {
items,
selectedItems,
setSelectedItems: onSelectedItemsChange,
expandedItems,
setExpandedItems,
expand,
collapse,
expandAll,
collapseAll,
};
Expand Down
Loading

0 comments on commit fb0d00e

Please sign in to comment.