From 1b8aa90bbc9d8954215d27949a7a1329b0f51e98 Mon Sep 17 00:00:00 2001 From: "Li, Huiqing" Date: Tue, 8 Nov 2022 16:39:29 -0800 Subject: [PATCH 1/5] expand multiCommands, route to previous expanded command when collapsing the last command --- .../Explore/Panels/Command/Command.tsx | 4 +- .../Panels/Command/CommandContainer.tsx | 48 ++++++++++++++----- .../Explore/Panels/Command/Commands.tsx | 17 ++++++- .../Explore/components/NavBreadcrumbs.tsx | 3 +- 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx b/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx index 50bed3f5..0ffc57b8 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx @@ -54,7 +54,9 @@ export const Command = observer( )} - {!isCollapsed && showPath && } + {!isCollapsed && showPath && ( + + )} {command?.info.commandType} diff --git a/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx b/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx index 8d0733b0..6d4ca9f1 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx @@ -21,6 +21,9 @@ type CommandContainerProps = ComponentProps<'div'> & { setCommand?: (cmd: any) => any; hideCommentButton?: boolean; showPath?: boolean; + expandedCommandIDs?: string[]; + addExplandedCommandID?: (commandId: string) => void; + removeExplandedCommandID?: (commandId: string) => void; }; export const CommandContainer = observer( @@ -32,6 +35,9 @@ export const CommandContainer = observer( setCommand, hideCommentButton = false, showPath = false, + expandedCommandIDs = [], + addExplandedCommandID, + removeExplandedCommandID, ...props }) => { const store = useStore(); @@ -39,23 +45,39 @@ export const CommandContainer = observer( get active() { return store.router.params.activeItem === 'command' && store.router.params.activeItemId === state.commandId; }, + get expanded() { + return expandedCommandIDs.includes(state.commandId); + }, setCollapsed() { - store.router.updateRoute({ - path: store.router.currentRoute, - params: { - activeItem: state.active ? undefined : 'command', - activeItemId: state.active ? undefined : state.commandId, - }, - }); + if (!state.expanded) { + addExplandedCommandID?.(state.commandId); + store.router.updateRoute({ + path: store.router.currentRoute, + params: { + activeItem: 'command', + activeItemId: state.commandId, + }, + }); + } else if (expandedCommandIDs?.length >= 1) { + if (expandedCommandIDs[expandedCommandIDs.length - 1] === state.commandId) { + store.router.updateRoute({ + path: store.router.currentRoute, + params: { + activeItem: expandedCommandIDs.length > 1 ? 'command' : undefined, + activeItemId: + expandedCommandIDs.length > 1 ? (expandedCommandIDs[expandedCommandIDs.length - 2] as UUID) : undefined, + }, + }); + } + removeExplandedCommandID?.(state.commandId); + } }, localCommand: undefined as undefined | CommandModel, get commandId(): UUID | undefined { return (command?.id ?? commandId!) as UUID; }, get command(): CommandModel | undefined { - return state.commandId || command?.id - ? store.graphqlStore.commands.get((state.commandId || command?.id)!) - : undefined; + return state.commandId ? store.graphqlStore.commands.get(state.commandId!) : undefined; }, get skeletonClass() { return state?.command?.inputText ? undefined : Classes.SKELETON; @@ -83,7 +105,7 @@ export const CommandContainer = observer( interactiveRowStyle, gridFillStyle, { height: initialCommandRowHeight }, - state.active ? activeCommandInfoRowStyle : undefined, + state.expanded || state.active ? activeCommandInfoRowStyle : undefined, ]} onClick={state.setCollapsed} onMouseEnter={() => store.campaign?.interactionState.onHover(state.command?.beacon?.current?.hierarchy || {})} @@ -93,7 +115,7 @@ export const CommandContainer = observer( store={store} commandId={state.commandId} skeletonClass={state.skeletonClass} - collapsed={!state.active} + collapsed={!state.expanded} className={state.skeletonClass} command={state.command} showPath={showPath} @@ -115,7 +137,7 @@ export const CommandContainer = observer( /> )} - {state.active && } + {(state.expanded || state.active) && } ); } diff --git a/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx b/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx index a373e2cd..68080b83 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx @@ -23,6 +23,13 @@ export const Commands = observer(({ sort, showPath = true }) => { startIndex: 0, endIndex: 0, }, + expandedCommandIDs: store.router.params.activeItemId ? [store.router.params.activeItemId] : ([] as string[]), + addExplandedCommandID(commandId: string) { + this.expandedCommandIDs.push(commandId); + }, + removeExplandedCommandID(commandId: string) { + this.expandedCommandIDs.splice(this.expandedCommandIDs.indexOf(commandId), 1); + }, scrollToCommand(commandId: string, commandIds: string[], behavior: ScrollBehavior = 'smooth') { const commandIndex = commandIds.findIndex((id) => commandId === id); if (commandIndex > -1) { @@ -106,7 +113,15 @@ export const Commands = observer(({ sort, showPath = true }) => { No Commands ) : ( data?.commandIds?.map((commandId) => ( - + )) )} diff --git a/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx b/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx index b3d622a9..84fdb69c 100644 --- a/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx +++ b/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx @@ -13,6 +13,7 @@ type NavBreadcrumbsProps = Omit & BreadcrumbsStyledProps & { /** if specified, show a nav from the command, otherwise display nav relative to the current route */ command?: CommandModel; + commandId?: string | undefined /* handle multi-expanded commands */; // from: ServerModel | HostModel | BeaconModelType | CommandModelType // instead of command /** fire when the component navigates */ onNavigate?: (event: MouseEvent) => void; @@ -22,7 +23,7 @@ type NavBreadcrumbsProps = Omit & showCurrent?: boolean; }; export const NavBreadcrumbs = observer( - ({ command, onNavigate = () => {}, hideRoot = false, showCurrent = false, ...props }) => { + ({ command, commandId, onNavigate = () => {}, hideRoot = false, showCurrent = false, ...props }) => { // TODO: maybe state.breadCrumbs and state.commandBreadCrumbs could be combined? const store = useStore(); From 1cb8efe8f4101adafcfd8dc3a15be7a05ec99768 Mon Sep 17 00:00:00 2001 From: "Li, Huiqing" Date: Tue, 8 Nov 2022 16:44:22 -0800 Subject: [PATCH 2/5] update commandBreadcrumb beaconSelect to support expanding multi commands --- .../client/src/store/graphql/BeaconModel.ts | 26 +++++++++++++------ .../Explore/Panels/Command/Command.tsx | 4 +-- .../Explore/components/NavBreadcrumbs.tsx | 6 ++--- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/applications/client/src/store/graphql/BeaconModel.ts b/applications/client/src/store/graphql/BeaconModel.ts index 3dc3808b..d4c4d935 100644 --- a/applications/client/src/store/graphql/BeaconModel.ts +++ b/applications/client/src/store/graphql/BeaconModel.ts @@ -3,7 +3,7 @@ import { routes } from '@redeye/client/store'; import { TimeStatus } from '@redeye/client/types/timeline'; import { computed } from 'mobx'; import { ExtendedModel, getRoot, model, modelAction } from 'mobx-keystone'; -import type { UUID } from '../../types'; +import type { CurrentItem, UUID } from '../../types'; import { CampaignViews, Tabs } from '../../types'; import { BeaconModelBase } from './BeaconModel.base'; import type { OperatorModel } from './OperatorModel'; @@ -66,19 +66,29 @@ export class BeaconModel extends ExtendedModel(BeaconModelBase, {}) { return operators; } - @modelAction select() { + @modelAction select(activeItem?: CurrentItem, activeItemId?: UUID) { const appStore = getRoot(this); const notPrimary = this.id !== appStore.campaign?.interactionState.selectedBeacon?.id || appStore.router.params.view !== CampaignViews.EXPLORE; appStore.router.updateRoute({ path: routes[CampaignViews.EXPLORE], - params: { - view: CampaignViews.EXPLORE, - tab: notPrimary ? Tabs.COMMANDS : Tabs.BEACONS, - currentItem: notPrimary ? 'beacon' : 'all', - currentItemId: notPrimary ? (this.id as UUID) : undefined, - }, + params: + activeItem && activeItemId + ? { + view: CampaignViews.EXPLORE, + tab: notPrimary ? Tabs.COMMANDS : Tabs.BEACONS, + currentItem: notPrimary ? 'beacon' : 'all', + currentItemId: notPrimary ? (this.id as UUID) : undefined, + activeItem: notPrimary ? activeItem : undefined, + activeItemId: notPrimary ? (activeItemId as UUID) : undefined, + } + : { + view: CampaignViews.EXPLORE, + tab: notPrimary ? Tabs.COMMANDS : Tabs.BEACONS, + currentItem: notPrimary ? 'beacon' : 'all', + currentItemId: notPrimary ? (this.id as UUID) : undefined, + }, }); } diff --git a/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx b/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx index 0ffc57b8..50bed3f5 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Command/Command.tsx @@ -54,9 +54,7 @@ export const Command = observer( )} - {!isCollapsed && showPath && ( - - )} + {!isCollapsed && showPath && } {command?.info.commandType} diff --git a/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx b/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx index 84fdb69c..3ea0aa29 100644 --- a/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx +++ b/applications/client/src/views/Campaign/Explore/components/NavBreadcrumbs.tsx @@ -8,12 +8,12 @@ import { observer } from 'mobx-react-lite'; import type { MouseEvent } from 'react'; import { useEffect } from 'react'; import { CampaignViews, Tabs } from '../../../../types'; +import type { UUID } from '../../../../types'; type NavBreadcrumbsProps = Omit & BreadcrumbsStyledProps & { /** if specified, show a nav from the command, otherwise display nav relative to the current route */ command?: CommandModel; - commandId?: string | undefined /* handle multi-expanded commands */; // from: ServerModel | HostModel | BeaconModelType | CommandModelType // instead of command /** fire when the component navigates */ onNavigate?: (event: MouseEvent) => void; @@ -23,7 +23,7 @@ type NavBreadcrumbsProps = Omit & showCurrent?: boolean; }; export const NavBreadcrumbs = observer( - ({ command, commandId, onNavigate = () => {}, hideRoot = false, showCurrent = false, ...props }) => { + ({ command, onNavigate = () => {}, hideRoot = false, showCurrent = false, ...props }) => { // TODO: maybe state.breadCrumbs and state.commandBreadCrumbs could be combined? const store = useStore(); @@ -147,7 +147,7 @@ export const NavBreadcrumbs = observer( onClick: async (e) => { e.stopPropagation(); await onNavigate(e); - this.command?.beacon?.current?.select(); + this.command?.beacon?.current?.select('command', this.command?.id as UUID); }, }, { From 81e7fb4a42a752221bad6ac3758856c235ea3994 Mon Sep 17 00:00:00 2001 From: "Li, Huiqing" Date: Tue, 8 Nov 2022 16:49:05 -0800 Subject: [PATCH 3/5] be able to expand multi commands, with collapse all button --- .../src/views/Campaign/Explore/Explore.tsx | 1 + .../Panels/Command/CommandContainer.tsx | 6 ++-- .../Explore/Panels/Command/Commands.tsx | 14 ++++++-- .../Explore/components/ControlBar.tsx | 32 +++++++++++++++++-- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/applications/client/src/views/Campaign/Explore/Explore.tsx b/applications/client/src/views/Campaign/Explore/Explore.tsx index 2ab83722..cb42abe9 100644 --- a/applications/client/src/views/Campaign/Explore/Explore.tsx +++ b/applications/client/src/views/Campaign/Explore/Explore.tsx @@ -133,6 +133,7 @@ export const Explore = observer(({ ...props }) => { isAscending={store.campaign.sort.direction === SortDirection.ASC} toggleIsAscending={() => store.campaign.toggleIsAscending()} filter={state.filter} + isCollapsibleOnly={store.router?.params.tab === Tabs.COMMANDS} /> {store.router?.params.tab === Tabs.COMMANDS && state.infoPanelType !== InfoType.OVERVIEW && diff --git a/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx b/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx index 6d4ca9f1..dab67f76 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Command/CommandContainer.tsx @@ -22,7 +22,6 @@ type CommandContainerProps = ComponentProps<'div'> & { hideCommentButton?: boolean; showPath?: boolean; expandedCommandIDs?: string[]; - addExplandedCommandID?: (commandId: string) => void; removeExplandedCommandID?: (commandId: string) => void; }; @@ -36,7 +35,6 @@ export const CommandContainer = observer( hideCommentButton = false, showPath = false, expandedCommandIDs = [], - addExplandedCommandID, removeExplandedCommandID, ...props }) => { @@ -46,11 +44,11 @@ export const CommandContainer = observer( return store.router.params.activeItem === 'command' && store.router.params.activeItemId === state.commandId; }, get expanded() { - return expandedCommandIDs.includes(state.commandId); + return store.router.params.activeItem === 'command' && expandedCommandIDs.includes(state.commandId); }, setCollapsed() { if (!state.expanded) { - addExplandedCommandID?.(state.commandId); + expandedCommandIDs.push(state.commandId); store.router.updateRoute({ path: store.router.currentRoute, params: { diff --git a/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx b/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx index 68080b83..be9dc90b 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx @@ -7,6 +7,7 @@ import { observer } from 'mobx-react-lite'; import type { ComponentProps } from 'react'; import { useEffect, useRef } from 'react'; import type { VirtuosoHandle } from 'react-virtuoso'; +import { observable } from 'mobx'; type CommandsProps = ComponentProps<'div'> & { showPath?: boolean; @@ -23,12 +24,14 @@ export const Commands = observer(({ sort, showPath = true }) => { startIndex: 0, endIndex: 0, }, - expandedCommandIDs: store.router.params.activeItemId ? [store.router.params.activeItemId] : ([] as string[]), + expandedCommandIDs: store.router.params.activeItemId + ? observable.array([store.router.params.activeItemId]) + : observable.array([]), addExplandedCommandID(commandId: string) { this.expandedCommandIDs.push(commandId); }, removeExplandedCommandID(commandId: string) { - this.expandedCommandIDs.splice(this.expandedCommandIDs.indexOf(commandId), 1); + this.expandedCommandIDs.remove(commandId); }, scrollToCommand(commandId: string, commandIds: string[], behavior: ScrollBehavior = 'smooth') { const commandIndex = commandIds.findIndex((id) => commandId === id); @@ -101,6 +104,12 @@ export const Commands = observer(({ sort, showPath = true }) => { } }, [store.campaign.commentStore.commentsOpen]); + useEffect(() => { + if (store.router.params.activeItem !== 'command') { + state.expandedCommandIDs.clear(); + } + }, [store.router.params.activeItem]); + return ( state.update('visibleRange', visibleRange)} @@ -119,7 +128,6 @@ export const Commands = observer(({ sort, showPath = true }) => { data-command-id={commandId} showPath={showPath} expandedCommandIDs={state.expandedCommandIDs} - addExplandedCommandID={state.addExplandedCommandID} removeExplandedCommandID={state.removeExplandedCommandID} /> )) diff --git a/applications/client/src/views/Campaign/Explore/components/ControlBar.tsx b/applications/client/src/views/Campaign/Explore/components/ControlBar.tsx index 5cc65abf..94eac183 100644 --- a/applications/client/src/views/Campaign/Explore/components/ControlBar.tsx +++ b/applications/client/src/views/Campaign/Explore/components/ControlBar.tsx @@ -1,6 +1,6 @@ import { Alignment, Button, Intent, MenuItem } from '@blueprintjs/core'; import type { ItemRenderer } from '@blueprintjs/select'; -import { CaretDown16, CaretUp16, ChevronSort16, Minimize16 } from '@carbon/icons-react'; +import { CaretDown16, CaretUp16, ChevronSort16, CollapseCategories16, Minimize16 } from '@carbon/icons-react'; import { css } from '@emotion/react'; import type { DropdownItem } from '@redeye/client/components'; import { CarbonIcon, createSorter, customIconPaths, Dropdown } from '@redeye/client/components'; @@ -19,6 +19,7 @@ type ControlBarProps = ComponentProps<'div'> & { sortBy?: SortOption | null; setSortBy: (sortBy: SortOption) => void; isCollapsible?: boolean; + isCollapsibleOnly?: boolean; isAscending: boolean; toggleIsAscending: () => void; }; @@ -43,7 +44,17 @@ const renderSort: ItemRenderer<{ key: string; label: string }> = (item, { handle }; export const ControlBar = observer( - ({ type, sortBy, setSortBy, filter, isCollapsible = false, isAscending, toggleIsAscending, ...props }) => { + ({ + type, + sortBy, + setSortBy, + filter, + isCollapsible = false, + isCollapsibleOnly = false, + isAscending, + toggleIsAscending, + ...props + }) => { const store = useStore(); // TODO: add state for Select.activeItem(s) and sort order? const [expandAll, setExpandAll] = useState(true); @@ -90,6 +101,23 @@ export const ControlBar = observer( minimal /> )} + {isCollapsibleOnly && ( +