Skip to content

Commit 6151b0f

Browse files
authored
Add action menu button to zoomable plots (#4398)
1 parent a087321 commit 6151b0f

File tree

7 files changed

+148
-17
lines changed

7 files changed

+148
-17
lines changed

webview/src/plots/components/App.test.tsx

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,8 +1409,9 @@ describe('App', () => {
14091409
})
14101410

14111411
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
1412-
1413-
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByRole('button')
1412+
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByLabelText(
1413+
'Open Plot in Popup'
1414+
)
14141415

14151416
fireEvent.click(plot)
14161417

@@ -1424,7 +1425,9 @@ describe('App', () => {
14241425

14251426
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
14261427

1427-
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByRole('button')
1428+
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByLabelText(
1429+
'Open Plot in Popup'
1430+
)
14281431

14291432
fireEvent.click(plot)
14301433

@@ -1471,13 +1474,63 @@ describe('App', () => {
14711474

14721475
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
14731476

1474-
const plot = within(screen.getAllByTestId(/^plot-/)[0]).getByRole('button')
1477+
const plot = within(screen.getAllByTestId(/^plot-/)[0]).getByLabelText(
1478+
'Open Plot in Popup'
1479+
)
14751480

14761481
fireEvent.click(plot)
14771482

14781483
expect(screen.getByTestId('modal')).toBeInTheDocument()
14791484
})
14801485

1486+
it('should open a modal with the plot zoomed in and actions menu open when clicking on a plot actions button', async () => {
1487+
renderAppWithOptionalData({
1488+
custom: customPlotsFixture
1489+
})
1490+
1491+
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
1492+
1493+
const plotActionsButton = within(
1494+
screen.getAllByTestId(/^plot-/)[0]
1495+
).getByLabelText('See Plot Export Options')
1496+
1497+
fireEvent.click(plotActionsButton)
1498+
1499+
expect(screen.getByTestId('modal')).toBeInTheDocument()
1500+
await waitFor(
1501+
() => {
1502+
expect(screen.getByTitle('Click to view actions')).toHaveAttribute(
1503+
'open'
1504+
)
1505+
},
1506+
{ timeout: 1000 }
1507+
)
1508+
})
1509+
1510+
it('should open a modal with the plot zoomed in and actions menu open when key pressing on a plot actions button', async () => {
1511+
renderAppWithOptionalData({
1512+
custom: customPlotsFixture
1513+
})
1514+
1515+
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
1516+
1517+
const plotActionsButton = within(
1518+
screen.getAllByTestId(/^plot-/)[0]
1519+
).getByLabelText('See Plot Export Options')
1520+
1521+
fireEvent.keyDown(plotActionsButton, { key: 'Enter' })
1522+
1523+
expect(screen.getByTestId('modal')).toBeInTheDocument()
1524+
await waitFor(
1525+
() => {
1526+
expect(screen.getByTitle('Click to view actions')).toHaveAttribute(
1527+
'open'
1528+
)
1529+
},
1530+
{ timeout: 1000 }
1531+
)
1532+
})
1533+
14811534
it('should not open a modal with the plot zoomed in when clicking a comparison table plot', () => {
14821535
renderAppWithOptionalData({
14831536
comparison: comparisonTableFixture,
@@ -1500,7 +1553,9 @@ describe('App', () => {
15001553
template: complexTemplatePlotsFixture
15011554
})
15021555

1503-
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByRole('button')
1556+
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByLabelText(
1557+
'Open Plot in Popup'
1558+
)
15041559

15051560
fireEvent.click(plot)
15061561
fireEvent.click(screen.getByTestId('modal'))
@@ -1518,7 +1573,9 @@ describe('App', () => {
15181573
template: complexTemplatePlotsFixture
15191574
})
15201575

1521-
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByRole('button')
1576+
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByLabelText(
1577+
'Open Plot in Popup'
1578+
)
15221579

15231580
fireEvent.click(plot)
15241581
fireEvent.click(screen.getByTestId('modal-content'))
@@ -1533,7 +1590,9 @@ describe('App', () => {
15331590

15341591
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
15351592

1536-
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByRole('button')
1593+
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByLabelText(
1594+
'Open Plot in Popup'
1595+
)
15371596

15381597
fireEvent.click(plot)
15391598

@@ -1558,7 +1617,9 @@ describe('App', () => {
15581617

15591618
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
15601619

1561-
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByRole('button')
1620+
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByLabelText(
1621+
'Open Plot in Popup'
1622+
)
15621623

15631624
fireEvent.click(plot)
15641625

@@ -1583,7 +1644,9 @@ describe('App', () => {
15831644

15841645
expect(screen.queryByTestId('modal')).not.toBeInTheDocument()
15851646

1586-
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByRole('button')
1647+
const plot = within(screen.getAllByTestId(/^plot_/)[0]).getByLabelText(
1648+
'Open Plot in Popup'
1649+
)
15871650

15881651
fireEvent.click(plot)
15891652

@@ -2341,7 +2404,8 @@ describe('App', () => {
23412404

23422405
const smoothPlot = within(
23432406
screen.getByTestId(`plot_${smoothId}`)
2344-
).getByRole('button')
2407+
).getByLabelText('Open Plot in Popup')
2408+
23452409
fireEvent.click(smoothPlot)
23462410

23472411
const popup = screen.getByTestId('zoomed-in-plot')
@@ -2382,7 +2446,8 @@ describe('App', () => {
23822446

23832447
const smoothPlot = within(
23842448
screen.getByTestId(`plot_${smoothId}`)
2385-
).getByRole('button')
2449+
).getByLabelText('Open Plot in Popup')
2450+
23862451
fireEvent.click(smoothPlot)
23872452

23882453
const popup = screen.getByTestId('zoomed-in-plot')

webview/src/plots/components/Plots.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const PlotsContent = () => {
5959
isTemplatePlot={zoomedInPlot.isTemplatePlot}
6060
id={zoomedInPlot.id}
6161
props={zoomedInPlot.plot}
62+
openActionsMenu={zoomedInPlot.openActionsMenu}
6263
/>
6364
</Modal>
6465
)

webview/src/plots/components/ZoomablePlot.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { config } from './constants'
1111
import { zoomPlot } from '../util/messages'
1212
import { useGetPlot } from '../hooks/useGetPlot'
1313
import { GripIcon } from '../../shared/components/dragDrop/GripIcon'
14+
import { Ellipsis } from '../../shared/components/icons'
1415

1516
interface ZoomablePlotProps {
1617
spec?: VisualizationSpec
@@ -57,9 +58,12 @@ export const ZoomablePlot: React.FC<ZoomablePlotProps> = ({
5758
)
5859
}, [data, spec, dispatch, id, isTemplatePlot])
5960

60-
const handleOnClick = () => {
61+
const handleOnClick = (openActionsMenu?: boolean) => {
6162
zoomPlot()
62-
return dispatch(setZoomedInPlot({ id, isTemplatePlot, plot: plotProps }))
63+
64+
return dispatch(
65+
setZoomedInPlot({ id, isTemplatePlot, openActionsMenu, plot: plotProps })
66+
)
6367
}
6468

6569
if (!data && !spec) {
@@ -73,8 +77,29 @@ export const ZoomablePlot: React.FC<ZoomablePlotProps> = ({
7377
}
7478

7579
return (
76-
<button className={styles.zoomablePlot} onClick={handleOnClick}>
80+
<button
81+
className={styles.zoomablePlot}
82+
onClick={() => handleOnClick()}
83+
aria-label="Open Plot in Popup"
84+
>
7785
<GripIcon className={styles.plotGripIcon} />
86+
<span
87+
className={styles.plotActions}
88+
onClick={event => {
89+
event.stopPropagation()
90+
handleOnClick(true)
91+
}}
92+
onKeyDown={event => {
93+
if (event.key === 'Enter') {
94+
handleOnClick(true)
95+
}
96+
}}
97+
role="button"
98+
tabIndex={0}
99+
aria-label="See Plot Export Options"
100+
>
101+
<Ellipsis />
102+
</span>
78103
{currentPlotProps.current &&
79104
(isTemplatePlot ? (
80105
<TemplateVegaLite

webview/src/plots/components/ZoomedInPlot.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type ZoomedInPlotProps = {
1717
id: string
1818
props: VegaLiteProps
1919
isTemplatePlot: boolean
20+
openActionsMenu?: boolean
2021
}
2122

2223
const appendActionToVega = (
@@ -36,7 +37,8 @@ const appendActionToVega = (
3637
export const ZoomedInPlot: React.FC<ZoomedInPlotProps> = ({
3738
id,
3839
props,
39-
isTemplatePlot
40+
isTemplatePlot,
41+
openActionsMenu
4042
}: ZoomedInPlotProps) => {
4143
const zoomedInPlotRef = useRef<HTMLDivElement>(null)
4244

@@ -55,9 +57,19 @@ export const ZoomedInPlot: React.FC<ZoomedInPlotProps> = ({
5557
if (!actions) {
5658
return
5759
}
60+
5861
appendActionToVega('JSON', actions, () => exportPlotDataAsJson(id))
5962
appendActionToVega('CSV', actions, () => exportPlotDataAsCsv(id))
6063
appendActionToVega('TSV', actions, () => exportPlotDataAsTsv(id))
64+
65+
if (openActionsMenu) {
66+
setTimeout(() => {
67+
const actionsDetails = actions.parentElement as HTMLDetailsElement
68+
if (actionsDetails) {
69+
actionsDetails.open = true
70+
}
71+
}, 500)
72+
}
6173
}
6274

6375
const vegaLiteProps = {

webview/src/plots/components/styles.module.scss

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,33 @@ $gap: 20px;
5858
}
5959
}
6060

61+
.plotActions {
62+
z-index: 500;
63+
width: 25px;
64+
height: 25px;
65+
border-radius: 50%;
66+
background-color: $fg-color;
67+
position: absolute;
68+
top: 10px;
69+
right: 10px;
70+
display: flex;
71+
align-items: center;
72+
justify-content: center;
73+
cursor: pointer;
74+
opacity: 0.5;
75+
transition: opacity ease-in-out 0.2s;
76+
77+
svg {
78+
fill: var(--vscode-editor-background);
79+
width: 18px;
80+
height: 18px;
81+
}
82+
83+
&:hover {
84+
opacity: 1;
85+
}
86+
}
87+
6188
.ratioSmaller .plot {
6289
aspect-ratio: 2 / 1;
6390
}

webview/src/plots/components/webviewSlice.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type ZoomedInPlotState = {
77
plot: VegaProps | undefined
88
id: string
99
isTemplatePlot: boolean
10+
openActionsMenu?: boolean
1011
refresh?: boolean
1112
}
1213
export interface WebviewState {

webview/src/stories/Plots.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ VirtualizedPlots.parameters = CHROMATIC_VIEWPORTS_WITH_DELAY
245245
export const ZoomedInPlot = Template.bind({})
246246
ZoomedInPlot.play = async ({ canvasElement }) => {
247247
const plots = await within(canvasElement).findAllByTestId(/^plot_/)
248-
const plot = await within(plots[0]).findByRole('button')
248+
const plot = await within(plots[0]).findByLabelText('Open Plot in Popup')
249249

250250
return userEvent.click(plot)
251251
}
@@ -256,7 +256,7 @@ MultiviewZoomedInPlot.play = async ({ canvasElement }) => {
256256
'plots-section_template-multi_1'
257257
)
258258
await within(plot).findByRole('graphics-document')
259-
const plotButton = await within(plot).findByRole('button')
259+
const plotButton = await within(plot).findByLabelText('Open Plot in Popup')
260260

261261
return userEvent.click(plotButton)
262262
}

0 commit comments

Comments
 (0)