From febc2954e5d0e565eae4c011006651d19a66207e Mon Sep 17 00:00:00 2001 From: "Li, Huiqing" Date: Fri, 23 Dec 2022 09:58:16 -0800 Subject: [PATCH 1/3] Timeline bars onHover and onClick labels via popover --- .../src/views/Campaign/Timeline/BarLabels.tsx | 67 +++++++++++ .../src/views/Campaign/Timeline/Bars.tsx | 110 +++++++++++++----- packages/client/ui-styles/src/tokens.ts | 1 + 3 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 applications/client/src/views/Campaign/Timeline/BarLabels.tsx diff --git a/applications/client/src/views/Campaign/Timeline/BarLabels.tsx b/applications/client/src/views/Campaign/Timeline/BarLabels.tsx new file mode 100644 index 00000000..9a8d7404 --- /dev/null +++ b/applications/client/src/views/Campaign/Timeline/BarLabels.tsx @@ -0,0 +1,67 @@ +import { css } from '@emotion/react'; +import { dateFormat, dateTimeFormat } from '@redeye/client/components'; +import { useStore } from '@redeye/client/store'; +import { Txt, FlexSplitter } from '@redeye/ui-styles'; +import { observer } from 'mobx-react-lite'; +import type { ComponentProps } from 'react'; +import type { IBar } from './TimelineChart'; + +type BarLabelsProps = ComponentProps<'div'> & { + bar: IBar; + dateFormatter: string | undefined; +}; + +export const BarLabelDate = observer(({ bar, dateFormatter }) => { + const store = useStore(); + const dateStart = store.settings.momentTz(bar?.start).format(dateFormatter); + const dateEnd = store.settings.momentTz(bar?.end).format(dateFormatter); + const sameDate = dateStart.split(' ')[0] === dateEnd.split(' ')[0]; + + return sameDate && dateFormatter === dateFormat ? ( + + {dateStart} + + ) : sameDate && dateFormatter === dateTimeFormat ? ( + {`${dateStart} - ${dateEnd.split(' ')[1]}`} + ) : ( + {`${dateStart} - ${dateEnd}`} + ); +}); + +export const BarLabelOnHover = observer(({ bar, dateFormatter }) => ( +
+ + + + Beacons + + + {bar?.beaconCount} + +
+)); + +export const BarLabelOnClick = observer(({ bar, dateFormatter }) => ( +
+ + + + Beacons + + + {bar?.beaconCount} + + + + Active Beacons + + + {bar?.activeBeaconCount} + +
+)); + +const barLabelStyles = css` + padding: 0.4rem; + min-width: 140px; +`; diff --git a/applications/client/src/views/Campaign/Timeline/Bars.tsx b/applications/client/src/views/Campaign/Timeline/Bars.tsx index 23ef1e91..f3b611a8 100644 --- a/applications/client/src/views/Campaign/Timeline/Bars.tsx +++ b/applications/client/src/views/Campaign/Timeline/Bars.tsx @@ -1,10 +1,12 @@ +import { Popover2, Popover2InteractionKind } from '@blueprintjs/popover2'; import { css } from '@emotion/react'; +import { createState, durationFormatter } from '@redeye/client/components'; import { Tokens } from '@redeye/ui-styles'; import { max, scaleLinear } from 'd3'; import { observer } from 'mobx-react-lite'; import type { ComponentProps } from 'react'; -import { Fragment } from 'react'; import { animated } from 'react-spring'; +import { BarLabelOnHover, BarLabelOnClick } from './BarLabels'; import { TIMELINE_BG_COLOR } from './timeline-static-vars'; import type { IBar, IDimensions, TimeScale } from './TimelineChart'; @@ -17,9 +19,15 @@ type BarsProps = ComponentProps<'div'> & { scrubberTime: Date | null; }; -export const Bars = observer(({ xScale, bars, start, dimensions, scrubberTime }) => { +export const Bars = observer(({ xScale, bars, start, end, dimensions, scrubberTime }) => { const yMax = max(bars.map((bar) => bar.beaconCount)) ?? 0; const yScale = scaleLinear([0, yMax], [0, dimensions.height]); + const state = createState({ + interaction: Popover2InteractionKind.HOVER as Popover2InteractionKind, + setInteraction(interaction: Popover2InteractionKind) { + this.interaction = interaction; + }, + }); return ( @@ -28,32 +36,70 @@ export const Bars = observer(({ xScale, bars, start, dimensions, scru const width = xScale(bar.end) - x; return ( - - {/* Dead & Future Beacon Bar */} - - {/* Active Beacon Bar */} - - {/* Selected Beacon Bar */} - - + + ) : ( + + ) + ) : undefined + } + placement="bottom" + renderTarget={({ isOpen, ref, ...targetProps }) => ( + { + e.preventDefault(); + state.setInteraction(Popover2InteractionKind.CLICK); + }} + onMouseOut={() => state.setInteraction(Popover2InteractionKind.HOVER)} + > + {/* Interaction Beacon Bar for color */} + {bar.beaconCount && ( + + )} + {/* Dead & Future Beacon Bar */} + + {/* Active Beacon Bar */} + + {/* Selected Beacon Bar */} + {!!bar.beaconCount && ( + + )} + {/* Interaction Beacon Bar for Functionality */} + + + )} + /> ); })} @@ -81,3 +127,11 @@ const aliveBarStyles = css` const selectedBarStyles = css` fill: ${Tokens.CoreTokens.BeaconSelected}; `; + +const interactionBarStyles = (hover: boolean) => css` + fill: ${hover ? Tokens.CoreTokens.BeaconInteracted : 'transparent'}; +`; + +const interactionBarFnStyles = css` + fill: transparent; +`; diff --git a/packages/client/ui-styles/src/tokens.ts b/packages/client/ui-styles/src/tokens.ts index dd13a82c..d8322d63 100644 --- a/packages/client/ui-styles/src/tokens.ts +++ b/packages/client/ui-styles/src/tokens.ts @@ -24,6 +24,7 @@ const CoreTokens = { BeaconAlive: BpTokens.Colors.Gray2, BeaconFuture: BpTokens.Colors.Black, BeaconSelected: BpTokens.Colors.White, + BeaconInteracted: BpTokens.Colors.Black, FontWeightBold: '700', FontWeightNormal: '400', From 31c480242d55da199e7c14fea83f250d53a221c8 Mon Sep 17 00:00:00 2001 From: "Li, Huiqing" Date: Tue, 27 Dec 2022 16:20:16 -0800 Subject: [PATCH 2/3] bar onHover/onClick state to ctrl labels, update route when clicking beacons --- .../client/src/store/campaign/timeline.ts | 5 + .../src/views/Campaign/Timeline/BarLabels.tsx | 107 +++++++++++++----- .../src/views/Campaign/Timeline/Bars.tsx | 24 ++-- .../views/Campaign/Timeline/TimelineChart.tsx | 2 + 4 files changed, 100 insertions(+), 38 deletions(-) diff --git a/applications/client/src/store/campaign/timeline.ts b/applications/client/src/store/campaign/timeline.ts index f3ca2a99..3952853a 100644 --- a/applications/client/src/store/campaign/timeline.ts +++ b/applications/client/src/store/campaign/timeline.ts @@ -116,6 +116,11 @@ export class TimelineStore extends ExtendedModel(() => ({ pair?.beaconId && selectedBeaconIds.includes(pair?.beaconId) ? sum + (pair?.commandCount ?? 0) : sum, 0 ), + beaconCommands: bucket?.beaconCommandCountPair.reduce( + (commands: Array>, pair) => [...commands, [pair?.beaconId, pair?.commandCount]], + [] + ), + beaconNumbers: bucket?.beaconCommandCountPair.length, })) ?? [] ); } diff --git a/applications/client/src/views/Campaign/Timeline/BarLabels.tsx b/applications/client/src/views/Campaign/Timeline/BarLabels.tsx index 9a8d7404..9bcb23ef 100644 --- a/applications/client/src/views/Campaign/Timeline/BarLabels.tsx +++ b/applications/client/src/views/Campaign/Timeline/BarLabels.tsx @@ -1,7 +1,9 @@ import { css } from '@emotion/react'; -import { dateFormat, dateTimeFormat } from '@redeye/client/components'; -import { useStore } from '@redeye/client/store'; -import { Txt, FlexSplitter } from '@redeye/ui-styles'; +import { dateFormat, dateTimeFormat, Flex } from '@redeye/client/components'; +import { routes, useStore } from '@redeye/client/store'; +import { CampaignViews, Tabs } from '@redeye/client/types'; +import type { UUID } from '@redeye/client/types'; +import { Txt, FlexSplitter, Tokens } from '@redeye/ui-styles'; import { observer } from 'mobx-react-lite'; import type { ComponentProps } from 'react'; import type { IBar } from './TimelineChart'; @@ -29,39 +31,82 @@ export const BarLabelDate = observer(({ bar, dateFormatter }) => }); export const BarLabelOnHover = observer(({ bar, dateFormatter }) => ( -
+
- - Beacons - - - {bar?.beaconCount} - + + + Beacons + + + {bar?.beaconNumbers} + + + + Total commands + + + {bar?.beaconCount} + + + + Active Beacon commands + + + {bar?.activeBeaconCount} +
)); -export const BarLabelOnClick = observer(({ bar, dateFormatter }) => ( -
- - - - Beacons - - - {bar?.beaconCount} - - - - Active Beacons - - - {bar?.activeBeaconCount} - -
-)); +export const BarLabelOnClick = observer(({ bar, dateFormatter }) => { + const store = useStore(); + const routeToBeacon = (beaconId: string) => { + store.router.updateRoute({ + path: routes[CampaignViews.EXPLORE], + params: { + view: CampaignViews.EXPLORE, + currentItem: 'beacon', + currentItemId: beaconId as UUID, + tab: Tabs.COMMANDS, + activeItem: undefined, + activeItemId: undefined, + }, + }); + }; + return ( +
+ + + + + Beacons + + + + Commands + + + {bar.beaconCommands.map((beaconCommand) => ( + routeToBeacon(beaconCommand[0] as string)}> + + {beaconCommand[0]} + + + {beaconCommand[1]} + + ))} +
+ ); +}); -const barLabelStyles = css` +const barPopoverStyles = css` padding: 0.4rem; - min-width: 140px; + min-width: 200px; +`; + +const barPopoverRowStyles = css` + &:hover { + cursor: pointer; + background: ${Tokens.Components.MinimalButtonBackgroundColorHover}; + } `; diff --git a/applications/client/src/views/Campaign/Timeline/Bars.tsx b/applications/client/src/views/Campaign/Timeline/Bars.tsx index f3b611a8..9ab795c3 100644 --- a/applications/client/src/views/Campaign/Timeline/Bars.tsx +++ b/applications/client/src/views/Campaign/Timeline/Bars.tsx @@ -23,9 +23,9 @@ export const Bars = observer(({ xScale, bars, start, end, dimensions, const yMax = max(bars.map((bar) => bar.beaconCount)) ?? 0; const yScale = scaleLinear([0, yMax], [0, dimensions.height]); const state = createState({ - interaction: Popover2InteractionKind.HOVER as Popover2InteractionKind, - setInteraction(interaction: Popover2InteractionKind) { - this.interaction = interaction; + isHover: true as boolean, + toggleIsHover() { + this.isHover = !this.isHover; }, }); @@ -41,7 +41,7 @@ export const Bars = observer(({ xScale, bars, start, end, dimensions, interactionKind={Popover2InteractionKind.HOVER} content={ bar.beaconCount ? ( - state.interaction === Popover2InteractionKind.HOVER ? ( + state.isHover ? ( ) : ( @@ -49,15 +49,24 @@ export const Bars = observer(({ xScale, bars, start, end, dimensions, ) : undefined } placement="bottom" + modifiers={{ + arrow: { enabled: false }, + offset: { + enabled: true, + options: { + offset: [0, 20], + }, + }, + }} renderTarget={({ isOpen, ref, ...targetProps }) => ( { e.preventDefault(); - state.setInteraction(Popover2InteractionKind.CLICK); + state.toggleIsHover(); }} - onMouseOut={() => state.setInteraction(Popover2InteractionKind.HOVER)} + // onMouseOut={() => state.toggleIsHover()} > {/* Interaction Beacon Bar for color */} {bar.beaconCount && ( @@ -96,7 +105,7 @@ export const Bars = observer(({ xScale, bars, start, end, dimensions, /> )} {/* Interaction Beacon Bar for Functionality */} - + )} /> @@ -130,6 +139,7 @@ const selectedBarStyles = css` const interactionBarStyles = (hover: boolean) => css` fill: ${hover ? Tokens.CoreTokens.BeaconInteracted : 'transparent'}; + opacity: 0.3; `; const interactionBarFnStyles = css` diff --git a/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx b/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx index 88e560f5..e71515da 100644 --- a/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx +++ b/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx @@ -21,6 +21,8 @@ export interface IBar { beaconCount: number; activeBeaconCount: number; selectedBeaconCount: number; + beaconNumbers: number; + beaconCommands: Array>; } export interface IDimensions { From 658200a2f14a5dd8f474870d66ee7e273c918c14 Mon Sep 17 00:00:00 2001 From: "Li, Huiqing" Date: Tue, 10 Jan 2023 10:54:38 -0800 Subject: [PATCH 3/3] click to show beacon list with router update when clicking listed beacon --- .../client/src/store/campaign/timeline.ts | 8 ++++- .../Panels/Command/CommandContainer.tsx | 6 ++-- .../Explore/Panels/Command/Commands.tsx | 4 +-- .../src/views/Campaign/Timeline/BarLabels.tsx | 32 ++++++++++++------- .../src/views/Campaign/Timeline/Bars.tsx | 24 +++++++------- .../views/Campaign/Timeline/TimelineChart.tsx | 2 +- 6 files changed, 46 insertions(+), 30 deletions(-) diff --git a/applications/client/src/store/campaign/timeline.ts b/applications/client/src/store/campaign/timeline.ts index 3952853a..4f7bccb7 100644 --- a/applications/client/src/store/campaign/timeline.ts +++ b/applications/client/src/store/campaign/timeline.ts @@ -117,7 +117,13 @@ export class TimelineStore extends ExtendedModel(() => ({ 0 ), beaconCommands: bucket?.beaconCommandCountPair.reduce( - (commands: Array>, pair) => [...commands, [pair?.beaconId, pair?.commandCount]], + (commands: Array>, pair) => [ + ...commands, + { + beaconId: pair?.beaconId, + commandCount: pair?.commandCount, + }, + ], [] ), beaconNumbers: bucket?.beaconCommandCountPair.length, 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 dab67f76..c93b899c 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,7 @@ type CommandContainerProps = ComponentProps<'div'> & { hideCommentButton?: boolean; showPath?: boolean; expandedCommandIDs?: string[]; - removeExplandedCommandID?: (commandId: string) => void; + removeExpandedCommandID?: (commandId: string) => void; }; export const CommandContainer = observer( @@ -35,7 +35,7 @@ export const CommandContainer = observer( hideCommentButton = false, showPath = false, expandedCommandIDs = [], - removeExplandedCommandID, + removeExpandedCommandID, ...props }) => { const store = useStore(); @@ -67,7 +67,7 @@ export const CommandContainer = observer( }, }); } - removeExplandedCommandID?.(state.commandId); + removeExpandedCommandID?.(state.commandId); } }, localCommand: undefined as undefined | CommandModel, 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 de6456fb..5909a319 100644 --- a/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx +++ b/applications/client/src/views/Campaign/Explore/Panels/Command/Commands.tsx @@ -27,7 +27,7 @@ export const Commands = observer(({ sort, showPath = true }) => { expandedCommandIDs: store.router.params.activeItemId ? observable.array([store.router.params.activeItemId]) : observable.array([]), - removeExplandedCommandID(commandId: string) { + removeExpandedCommandID(commandId: string) { this.expandedCommandIDs.remove(commandId); }, scrollToCommand(commandId: string, commandIds: string[], behavior: ScrollBehavior = 'smooth') { @@ -125,7 +125,7 @@ export const Commands = observer(({ sort, showPath = true }) => { data-command-id={commandId} showPath={showPath} expandedCommandIDs={state.expandedCommandIDs} - removeExplandedCommandID={state.removeExplandedCommandID} + removeExpandedCommandID={state.removeExpandedCommandID} /> )) )} diff --git a/applications/client/src/views/Campaign/Timeline/BarLabels.tsx b/applications/client/src/views/Campaign/Timeline/BarLabels.tsx index 9bcb23ef..826d4d8d 100644 --- a/applications/client/src/views/Campaign/Timeline/BarLabels.tsx +++ b/applications/client/src/views/Campaign/Timeline/BarLabels.tsx @@ -34,22 +34,22 @@ export const BarLabelOnHover = observer(({ bar, dateFormatter })
- - + + Beacons {bar?.beaconNumbers} - + Total commands {bar?.beaconCount} - + Active Beacon commands @@ -58,7 +58,7 @@ export const BarLabelOnHover = observer(({ bar, dateFormatter })
)); -export const BarLabelOnClick = observer(({ bar, dateFormatter }) => { +export const BarLabelBeaconList = observer(({ bar, dateFormatter }) => { const store = useStore(); const routeToBeacon = (beaconId: string) => { store.router.updateRoute({ @@ -77,7 +77,7 @@ export const BarLabelOnClick = observer(({ bar, dateFormatter })
- + Beacons @@ -87,12 +87,19 @@ export const BarLabelOnClick = observer(({ bar, dateFormatter }) {bar.beaconCommands.map((beaconCommand) => ( - routeToBeacon(beaconCommand[0] as string)}> - - {beaconCommand[0]} + routeToBeacon(beaconCommand.beaconId as string)} + > + + {store.graphqlStore.beacons.get(beaconCommand.beaconId as string)?.displayName} + + + {store.graphqlStore.beacons.get(beaconCommand.beaconId as string)?.meta[0].maybeCurrent?.username} - {beaconCommand[1]} + {beaconCommand.commandCount} ))}
@@ -101,7 +108,10 @@ export const BarLabelOnClick = observer(({ bar, dateFormatter }) const barPopoverStyles = css` padding: 0.4rem; - min-width: 200px; +`; + +const marginStyles = (num: number) => css` + margin-right: ${num}rem; `; const barPopoverRowStyles = css` diff --git a/applications/client/src/views/Campaign/Timeline/Bars.tsx b/applications/client/src/views/Campaign/Timeline/Bars.tsx index 9ab795c3..9a4f5c5f 100644 --- a/applications/client/src/views/Campaign/Timeline/Bars.tsx +++ b/applications/client/src/views/Campaign/Timeline/Bars.tsx @@ -6,7 +6,7 @@ import { max, scaleLinear } from 'd3'; import { observer } from 'mobx-react-lite'; import type { ComponentProps } from 'react'; import { animated } from 'react-spring'; -import { BarLabelOnHover, BarLabelOnClick } from './BarLabels'; +import { BarLabelOnHover, BarLabelBeaconList } from './BarLabels'; import { TIMELINE_BG_COLOR } from './timeline-static-vars'; import type { IBar, IDimensions, TimeScale } from './TimelineChart'; @@ -44,7 +44,7 @@ export const Bars = observer(({ xScale, bars, start, end, dimensions, state.isHover ? ( ) : ( - + ) ) : undefined } @@ -66,7 +66,6 @@ export const Bars = observer(({ xScale, bars, start, end, dimensions, e.preventDefault(); state.toggleIsHover(); }} - // onMouseOut={() => state.toggleIsHover()} > {/* Interaction Beacon Bar for color */} {bar.beaconCount && ( @@ -95,17 +94,17 @@ export const Bars = observer(({ xScale, bars, start, end, dimensions, css={[baseBarStyles, aliveBarStyles]} /> {/* Selected Beacon Bar */} + + {/* Interaction Beacon Bar for Functionality */} {!!bar.beaconCount && ( - + )} - {/* Interaction Beacon Bar for Functionality */} - )} /> @@ -144,4 +143,5 @@ const interactionBarStyles = (hover: boolean) => css` const interactionBarFnStyles = css` fill: transparent; + cursor: pointer; `; diff --git a/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx b/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx index e71515da..ec26eb9e 100644 --- a/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx +++ b/applications/client/src/views/Campaign/Timeline/TimelineChart.tsx @@ -22,7 +22,7 @@ export interface IBar { activeBeaconCount: number; selectedBeaconCount: number; beaconNumbers: number; - beaconCommands: Array>; + beaconCommands: Array>; } export interface IDimensions {