diff --git a/apps/wing-console/console/app/demo/main.w b/apps/wing-console/console/app/demo/main.w index 035874567ca..f6ca4555b7d 100644 --- a/apps/wing-console/console/app/demo/main.w +++ b/apps/wing-console/console/app/demo/main.w @@ -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( diff --git a/apps/wing-console/console/design-system/src/headless/tree-item.tsx b/apps/wing-console/console/design-system/src/headless/tree-item.tsx index e89df902931..e30b4891ead 100644 --- a/apps/wing-console/console/design-system/src/headless/tree-item.tsx +++ b/apps/wing-console/console/design-system/src/headless/tree-item.tsx @@ -121,6 +121,12 @@ export const TreeItem = ({ }); const canBeExpanded = !!children; + useEffect(() => { + if (selected) { + ref.current?.scrollIntoView(); + } + }, [selected, ref]); + return (
  • void; color?: string; icon?: string; + collapsed?: boolean; + onCollapse?: (value: boolean) => void; } const Wrapper: FunctionComponent> = 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
    > = memo(
    0 && "border-b border-slate-200 dark:border-slate-800", + "cursor-pointer", )} > > = memo( > {name} +
    +
    { + onCollapse(!collapsed); + }} + > + {collapsed && ( + + )} + {!collapsed && ( + + )} +
    +
    - - {children} + {!collapsed && children}
    ); }, @@ -113,6 +146,8 @@ interface ContainerNodeProps { resourceType?: string; highlight?: boolean; onClick?: () => void; + collapsed?: boolean; + onCollapse?: (value: boolean) => void; color?: string; icon?: string; } @@ -136,6 +171,8 @@ const ContainerNode: FunctionComponent> = fqn={props.resourceType!} highlight={props.highlight} onClick={props.onClick} + onCollapse={props.onCollapse} + collapsed={props.collapsed} color={props.color} icon={props.icon} > @@ -164,6 +201,8 @@ interface ConstructNodeProps { hasChildNodes?: boolean; onSelectedNodeIdChange: (id: string | undefined) => void; color?: string; + onCollapse: (value: boolean) => void; + collapsed: boolean; icon?: string; } @@ -179,6 +218,8 @@ const ConstructNode: FunctionComponent> = children, hasChildNodes, color, + onCollapse, + collapsed, icon, }) => { const select = useCallback( @@ -245,6 +286,7 @@ const ConstructNode: FunctionComponent> = /> {!hasChildNodes && ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
    > = > {name} + {collapsed && ( +
    { + if (collapsed) { + onCollapse(false); + } + }} + > + +
    + )}
    )} @@ -334,6 +393,8 @@ const ConstructNode: FunctionComponent> = resourceType={fqn} highlight={highlight} onClick={select} + onCollapse={onCollapse} + collapsed={collapsed} color={color} icon={icon} > @@ -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( @@ -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( (props) => { @@ -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 ( 0} + collapsed={collapsed} + onCollapse={(collapse) => { + if (collapse) { + onCollapse(node.path); + } else { + onExpand(node.path); + } + }} > {childNodes.map((child) => ( ); }, - [isNodeHidden, nodeInfo], + [isNodeHidden, nodeInfo, onCollapse, onExpand, expandedItems], ); const { theme } = useTheme(); diff --git a/apps/wing-console/console/ui/src/layout/default-layout.tsx b/apps/wing-console/console/ui/src/layout/default-layout.tsx index 1ad8578e8fa..9d5174937b5 100644 --- a/apps/wing-console/console/ui/src/layout/default-layout.tsx +++ b/apps/wing-console/console/ui/src/layout/default-layout.tsx @@ -77,6 +77,7 @@ export const DefaultLayout = ({ expandedItems, setExpandedItems, expand, + collapse, expandAll, collapseAll, theme, @@ -261,7 +262,7 @@ export const DefaultLayout = ({ return ( {panelComponent} @@ -300,6 +301,9 @@ export const DefaultLayout = ({ onSelectedNodeIdChange={setSelectedItemSingle} selectedEdgeId={selectedEdgeId} onSelectedEdgeIdChange={setSelectedEdgeId} + onExpand={expand} + onCollapse={collapse} + expandedItems={expandedItems} /> {!layout.rightPanel?.hide && ( diff --git a/apps/wing-console/console/ui/src/layout/use-layout.tsx b/apps/wing-console/console/ui/src/layout/use-layout.tsx index bd2e84b9caa..db27f0e4e52 100644 --- a/apps/wing-console/console/ui/src/layout/use-layout.tsx +++ b/apps/wing-console/console/ui/src/layout/use-layout.tsx @@ -21,6 +21,7 @@ export const useLayout = ({ cloudAppState }: UseLayoutProps) => { expandedItems, setExpandedItems, expand, + collapse, expandAll, collapseAll, } = useExplorer(); @@ -98,6 +99,7 @@ export const useLayout = ({ cloudAppState }: UseLayoutProps) => { expandedItems, setExpandedItems, expand, + collapse, expandAll, collapseAll, theme, diff --git a/apps/wing-console/console/ui/src/services/use-explorer.tsx b/apps/wing-console/console/ui/src/services/use-explorer.tsx index 357b8a6f1d6..de897acf893 100644 --- a/apps/wing-console/console/ui/src/services/use-explorer.tsx +++ b/apps/wing-console/console/ui/src/services/use-explorer.tsx @@ -37,6 +37,7 @@ export const useExplorer = () => { expandedItems, setExpandedItems, expand, + collapse, expandAll, collapseAll, } = useTreeMenuItems({ @@ -83,10 +84,6 @@ export const useExplorer = () => { setSelectedItems([selectedNode]); }, [selectedNode, setSelectedItems]); - useEffect(() => { - expandAll(); - }, [items, expandAll]); - return { items, selectedItems, @@ -94,6 +91,7 @@ export const useExplorer = () => { expandedItems, setExpandedItems, expand, + collapse, expandAll, collapseAll, }; diff --git a/apps/wing-console/console/ui/src/services/use-map.ts b/apps/wing-console/console/ui/src/services/use-map.ts index 54f13aa6147..1fb7009f98b 100644 --- a/apps/wing-console/console/ui/src/services/use-map.ts +++ b/apps/wing-console/console/ui/src/services/use-map.ts @@ -108,9 +108,11 @@ const getNodeInflights = ( })); }; -export interface UseMapOptions {} +export interface UseMapOptions { + expandedItems: string[]; +} -export const useMap = ({}: UseMapOptions = {}) => { +export const useMap = ({ expandedItems }: UseMapOptions) => { const query = trpc["app.map"].useQuery(); const { tree: rawTree, connections: rawConnections } = query.data ?? {}; @@ -160,9 +162,16 @@ export const useMap = ({}: UseMapOptions = {}) => { const hiddenMap = new Map(); const traverse = (node: ConstructTreeNode, forceHidden?: boolean) => { const hidden = forceHidden || node.display?.hidden || false; + hiddenMap.set(node.path, hidden); - for (const child of Object.values(node.children ?? {})) { - traverse(child, hidden); + + const children = Object.values(node.children ?? {}); + const canBeExpanded = + !!node.children && children.some((child) => !child.display?.hidden); + const collapsed = canBeExpanded && !expandedItems.includes(node.path); + + for (const child of children) { + traverse(child, hidden || collapsed); } }; const pseudoRoot = rawTree?.children?.["Default"]; @@ -170,7 +179,7 @@ export const useMap = ({}: UseMapOptions = {}) => { traverse(child!); } return hiddenMap; - }, [rawTree]); + }, [rawTree, expandedItems]); const isNodeHidden = useCallback( (path: string) => { diff --git a/apps/wing-console/console/ui/src/ui/map-controls.tsx b/apps/wing-console/console/ui/src/ui/map-controls.tsx index 7c724ac9e08..c7c30fdb77e 100644 --- a/apps/wing-console/console/ui/src/ui/map-controls.tsx +++ b/apps/wing-console/console/ui/src/ui/map-controls.tsx @@ -23,15 +23,15 @@ export const MapControls = ({
    - + - + - +
    diff --git a/apps/wing-console/console/ui/src/ui/resource-metadata.tsx b/apps/wing-console/console/ui/src/ui/resource-metadata.tsx index 0c155468164..fd552c35daa 100644 --- a/apps/wing-console/console/ui/src/ui/resource-metadata.tsx +++ b/apps/wing-console/console/ui/src/ui/resource-metadata.tsx @@ -1,10 +1,5 @@ +import { CubeIcon } from "@heroicons/react/20/solid"; import { - CubeIcon, - ArrowLeftOnRectangleIcon, - ArrowRightOnRectangleIcon, -} from "@heroicons/react/20/solid"; -import { - ArrowPathRoundedSquareIcon, CubeTransparentIcon, CursorArrowRaysIcon, } from "@heroicons/react/24/outline"; @@ -17,7 +12,6 @@ import { getResourceIconComponent, Attribute, ScrollableArea, - getResourceIconColors, } from "@wingconsole/design-system"; import type { NodeDisplay } from "@wingconsole/server"; import classNames from "classnames"; @@ -216,7 +210,7 @@ export const ResourceMetadata = memo( resourceGroup, connectionsGroups: connectionsGroupsArray, }; - }, [node, inbound, outbound]); + }, [node, inbound, outbound, icon]); const nodeLabel = useMemo(() => { const cloudResourceTypeName = node.type.split(".").at(-1) || ""; diff --git a/apps/wing-console/console/ui/src/ui/use-tree-menu-items.tsx b/apps/wing-console/console/ui/src/ui/use-tree-menu-items.tsx index 4dfefd92d16..1dc481ccf8e 100644 --- a/apps/wing-console/console/ui/src/ui/use-tree-menu-items.tsx +++ b/apps/wing-console/console/ui/src/ui/use-tree-menu-items.tsx @@ -22,6 +22,7 @@ export function useTreeMenuItems(options?: { const [expandedItems, setExpandedItems] = useState( options?.openMenuItemIds ?? [], ); + const toggle = useCallback((itemId: string) => { setExpandedItems(([...openedMenuItems]) => { const index = openedMenuItems.indexOf(itemId); @@ -70,6 +71,18 @@ export function useTreeMenuItems(options?: { }); }, []); + const collapse = useCallback((itemId: string) => { + setExpandedItems(([...openedMenuItems]) => { + const index = openedMenuItems.indexOf(itemId); + if (index === -1) { + return openedMenuItems; + } + + openedMenuItems.splice(index, 1); + return openedMenuItems; + }); + }, []); + return { items, setItems, @@ -81,5 +94,6 @@ export function useTreeMenuItems(options?: { expandAll, collapseAll, expand, + collapse, }; } diff --git a/apps/wing-console/console/ui/src/ui/zoom-pane.tsx b/apps/wing-console/console/ui/src/ui/zoom-pane.tsx index 439f886a676..07abaa1185b 100644 --- a/apps/wing-console/console/ui/src/ui/zoom-pane.tsx +++ b/apps/wing-console/console/ui/src/ui/zoom-pane.tsx @@ -395,8 +395,7 @@ export const ZoomPane = forwardRef((props, ref) => {
    -
    -
    +