Skip to content

Commit 441cd1e

Browse files
author
Aasim Khan
committed
Making results grid have dynamic length
1 parent 609dc9a commit 441cd1e

File tree

4 files changed

+230
-118
lines changed

4 files changed

+230
-118
lines changed

src/reactviews/common/locConstants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ export class LocConstants {
521521
}
522522
return l10n.t("Save as INSERT INTO");
523523
},
524+
moreQueryActions: l10n.t("More Query Actions"),
524525
clickHereToHideThisPanel: l10n.t("Hide this panel"),
525526
queryPlan: (count: number) => {
526527
return l10n.t({

src/reactviews/pages/QueryResult/commandBar.tsx

Lines changed: 200 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,22 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Button, makeStyles, Toolbar, Tooltip } from "@fluentui/react-components";
7-
import { useContext } from "react";
6+
import {
7+
makeStyles,
8+
Menu,
9+
MenuItem,
10+
MenuList,
11+
MenuPopover,
12+
MenuTrigger,
13+
Overflow,
14+
OverflowItem,
15+
Toolbar,
16+
ToolbarButton,
17+
ToolbarButtonProps,
18+
useIsOverflowItemVisible,
19+
useOverflowMenu,
20+
} from "@fluentui/react-components";
21+
import { ReactElement, useContext } from "react";
822
import { QueryResultCommandsContext } from "./queryResultStateProvider";
923
import { useVscodeWebview2 } from "../../common/vscodeWebviewProvider2";
1024
import { useQueryResultSelector } from "./queryResultSelector";
@@ -21,19 +35,39 @@ import {
2135
ArrowMaximize16Filled,
2236
ArrowMinimize16Filled,
2337
DocumentTextRegular,
38+
MoreVertical20Filled,
2439
TableRegular,
2540
} from "@fluentui/react-icons";
2641
import { WebviewAction } from "../../../sharedInterfaces/webview";
42+
import { ACTIONBAR_WIDTH_PX } from "./table/table";
2743

2844
const useStyles = makeStyles({
45+
commandBarContainer: {
46+
width: `${ACTIONBAR_WIDTH_PX}px`,
47+
flexShrink: 0,
48+
overflow: "hidden",
49+
display: "flex",
50+
paddingRight: "10px",
51+
},
2952
commandBar: {
30-
width: "16px",
53+
width: "100%",
3154
},
3255
buttonImg: {
3356
display: "block",
3457
height: "16px",
3558
width: "16px",
3659
},
60+
toolbarButton: {
61+
width: "32px",
62+
height: "32px",
63+
minWidth: "32px",
64+
minHeight: "32px",
65+
padding: "4px",
66+
display: "inline-flex",
67+
justifyContent: "center",
68+
alignItems: "center",
69+
flexShrink: 0,
70+
},
3771
});
3872

3973
export interface CommandBarProps {
@@ -44,6 +78,75 @@ export interface CommandBarProps {
4478
isMaximized?: boolean;
4579
}
4680

81+
type ToolbarOverflowButtonProps = {
82+
overflowId: string;
83+
overflowGroupId?: string;
84+
} & ToolbarButtonProps;
85+
86+
type CommandBarAction = {
87+
id: string;
88+
groupId?: string;
89+
icon: ReactElement;
90+
menuIcon?: ReactElement;
91+
ariaLabel: string;
92+
title: string;
93+
menuLabel: string;
94+
onClick: () => void;
95+
disabled?: boolean;
96+
className?: string;
97+
};
98+
99+
const ToolbarOverflowButton = ({
100+
overflowId,
101+
overflowGroupId,
102+
className,
103+
...props
104+
}: ToolbarOverflowButtonProps & { className?: string }) => {
105+
const classes = useStyles();
106+
const mergedClassName = [classes.toolbarButton, className].filter(Boolean).join(" ");
107+
return (
108+
<OverflowItem id={overflowId} groupId={overflowGroupId}>
109+
<ToolbarButton appearance="subtle" {...props} className={mergedClassName} />
110+
</OverflowItem>
111+
);
112+
};
113+
114+
const CommandBarOverflowMenuItem = ({ action }: { action: CommandBarAction }) => {
115+
const isVisible = useIsOverflowItemVisible(action.id);
116+
if (isVisible) {
117+
return null;
118+
}
119+
return <MenuItem onClick={action.onClick}>{action.menuLabel}</MenuItem>;
120+
};
121+
122+
const CommandBarOverflowMenu = ({ actions }: { actions: CommandBarAction[] }) => {
123+
const { ref, isOverflowing } = useOverflowMenu<HTMLButtonElement>();
124+
if (!isOverflowing) {
125+
return null;
126+
}
127+
128+
return (
129+
<Menu>
130+
<MenuTrigger disableButtonEnhancement>
131+
<ToolbarButton
132+
ref={ref}
133+
appearance="subtle"
134+
icon={<MoreVertical20Filled />}
135+
aria-label={locConstants.queryResult.moreQueryActions}
136+
title={locConstants.queryResult.moreQueryActions}
137+
/>
138+
</MenuTrigger>
139+
<MenuPopover>
140+
<MenuList>
141+
{actions.map((action) => (
142+
<CommandBarOverflowMenuItem key={action.id} action={action} />
143+
))}
144+
</MenuList>
145+
</MenuPopover>
146+
</Menu>
147+
);
148+
};
149+
47150
const CommandBar = (props: CommandBarProps) => {
48151
const classes = useStyles();
49152
const { themeKind } = useVscodeWebview2<qr.QueryResultWebviewState, qr.QueryResultReducers>();
@@ -115,114 +218,104 @@ const CommandBar = (props: CommandBarProps) => {
115218
const saveAsExcelTooltip = locConstants.queryResult.saveAsExcel(saveAsExcelShortcut?.label);
116219
const saveAsInsertTooltip = locConstants.queryResult.saveAsInsert(saveAsInsertShortcut?.label);
117220

118-
if (props.viewMode === qr.QueryResultViewMode.Text) {
119-
return (
120-
<div className={classes.commandBar}>
121-
<Tooltip content={toggleToGridViewTooltip} relationship="label">
122-
<Button
123-
appearance="subtle"
124-
onClick={toggleViewMode}
125-
icon={<TableRegular />}
126-
title={toggleToGridViewTooltip}
127-
/>
128-
</Tooltip>
129-
</div>
221+
const isGridView = props.viewMode === qr.QueryResultViewMode.Grid;
222+
const hasAdditionalResults = hasMultipleResults();
223+
const toggleToGrid = props.viewMode === qr.QueryResultViewMode.Text;
224+
225+
const actions: CommandBarAction[] = [
226+
{
227+
id: "toggleViewMode",
228+
groupId: "viewMode",
229+
icon: toggleToGrid ? <TableRegular /> : <DocumentTextRegular />,
230+
ariaLabel: toggleToGrid ? toggleToGridViewTooltip : toggleToTextViewTooltip,
231+
title: toggleToGrid ? toggleToGridViewTooltip : toggleToTextViewTooltip,
232+
menuLabel: toggleToGrid ? toggleToGridViewTooltip : toggleToTextViewTooltip,
233+
onClick: toggleViewMode,
234+
},
235+
];
236+
237+
if (isGridView && hasAdditionalResults) {
238+
actions.push({
239+
id: "toggleMaximize",
240+
groupId: "viewMode",
241+
icon: props.isMaximized ? (
242+
<ArrowMinimize16Filled className={classes.buttonImg} />
243+
) : (
244+
<ArrowMaximize16Filled className={classes.buttonImg} />
245+
),
246+
ariaLabel: isMaximized ? restoreTooltip : maximizeTooltip,
247+
title: isMaximized ? restoreTooltip : maximizeTooltip,
248+
menuLabel: isMaximized ? restoreTooltip : maximizeTooltip,
249+
onClick: () => props.onToggleMaximize?.(),
250+
});
251+
}
252+
253+
if (isGridView) {
254+
actions.push(
255+
{
256+
id: "saveAsCsv",
257+
groupId: "export",
258+
icon: <img className={classes.buttonImg} src={saveAsCsvIcon(themeKind)} />,
259+
menuLabel: saveAsCsvTooltip,
260+
ariaLabel: saveAsCsvTooltip,
261+
title: saveAsCsvTooltip,
262+
onClick: () => saveResults("csv"),
263+
className: "codicon saveCsv",
264+
},
265+
{
266+
id: "saveAsJson",
267+
groupId: "export",
268+
icon: <img className={classes.buttonImg} src={saveAsJsonIcon(themeKind)} />,
269+
menuLabel: saveAsJsonTooltip,
270+
ariaLabel: saveAsJsonTooltip,
271+
title: saveAsJsonTooltip,
272+
onClick: () => saveResults("json"),
273+
className: "codicon saveJson",
274+
},
275+
{
276+
id: "saveAsExcel",
277+
groupId: "export",
278+
icon: <img className={classes.buttonImg} src={saveAsExcelIcon(themeKind)} />,
279+
menuLabel: saveAsExcelTooltip,
280+
ariaLabel: saveAsExcelTooltip,
281+
title: saveAsExcelTooltip,
282+
onClick: () => saveResults("excel"),
283+
className: "codicon saveExcel",
284+
},
285+
{
286+
id: "saveAsInsert",
287+
groupId: "export",
288+
icon: <img className={classes.buttonImg} src={saveAsInsertIcon(themeKind)} />,
289+
menuLabel: saveAsInsertTooltip,
290+
ariaLabel: saveAsInsertTooltip,
291+
title: saveAsInsertTooltip,
292+
onClick: () => saveResults("insert"),
293+
className: "codicon saveInsert",
294+
},
130295
);
131296
}
132297

133298
return (
134-
<Toolbar vertical className={classes.commandBar}>
135-
{/* View Mode Toggle */}
136-
<Tooltip
137-
content={
138-
props.viewMode === qr.QueryResultViewMode.Grid
139-
? toggleToTextViewTooltip
140-
: toggleToGridViewTooltip
141-
}
142-
relationship="label">
143-
<Button
144-
appearance="subtle"
145-
onClick={toggleViewMode}
146-
icon={
147-
props.viewMode === qr.QueryResultViewMode.Grid ? (
148-
<DocumentTextRegular />
149-
) : (
150-
<TableRegular />
151-
)
152-
}
153-
title={
154-
props.viewMode === qr.QueryResultViewMode.Grid
155-
? toggleToTextViewTooltip
156-
: toggleToGridViewTooltip
157-
}
158-
/>
159-
</Tooltip>
160-
161-
{hasMultipleResults() && props.viewMode === qr.QueryResultViewMode.Grid && (
162-
<Tooltip
163-
content={isMaximized ? restoreTooltip : maximizeTooltip}
164-
relationship="label">
165-
<Button
166-
appearance="subtle"
167-
onClick={() => {
168-
props.onToggleMaximize?.();
169-
}}
170-
icon={
171-
isMaximized ? (
172-
<ArrowMinimize16Filled className={classes.buttonImg} />
173-
) : (
174-
<ArrowMaximize16Filled className={classes.buttonImg} />
175-
)
176-
}
177-
title={isMaximized ? restoreTooltip : maximizeTooltip}></Button>
178-
</Tooltip>
179-
)}
180-
181-
<Tooltip content={saveAsCsvTooltip} relationship="label">
182-
<Button
183-
appearance="subtle"
184-
onClick={(_event) => {
185-
saveResults("csv");
186-
}}
187-
icon={<img className={classes.buttonImg} src={saveAsCsvIcon(themeKind)} />}
188-
className="codicon saveCsv"
189-
title={saveAsCsvTooltip}
190-
/>
191-
</Tooltip>
192-
<Tooltip content={saveAsJsonTooltip} relationship="label">
193-
<Button
194-
appearance="subtle"
195-
onClick={(_event) => {
196-
saveResults("json");
197-
}}
198-
icon={<img className={classes.buttonImg} src={saveAsJsonIcon(themeKind)} />}
199-
className="codicon saveJson"
200-
title={saveAsJsonTooltip}
201-
/>
202-
</Tooltip>
203-
<Tooltip content={saveAsExcelTooltip} relationship="label">
204-
<Button
205-
appearance="subtle"
206-
onClick={(_event) => {
207-
saveResults("excel");
208-
}}
209-
icon={<img className={classes.buttonImg} src={saveAsExcelIcon(themeKind)} />}
210-
className="codicon saveExcel"
211-
title={saveAsExcelTooltip}
212-
/>
213-
</Tooltip>
214-
<Tooltip content={saveAsInsertTooltip} relationship="label">
215-
<Button
216-
appearance="subtle"
217-
onClick={(_event) => {
218-
saveResults("insert");
219-
}}
220-
icon={<img className={classes.buttonImg} src={saveAsInsertIcon(themeKind)} />}
221-
className="codicon saveInsert"
222-
title={saveAsInsertTooltip}
223-
/>
224-
</Tooltip>
225-
</Toolbar>
299+
<div className={classes.commandBarContainer}>
300+
<Overflow overflowAxis="vertical" overflowDirection="end">
301+
<Toolbar vertical className={classes.commandBar} aria-label="Query result commands">
302+
{actions.map((action) => (
303+
<ToolbarOverflowButton
304+
key={action.id}
305+
overflowId={action.id}
306+
overflowGroupId={action.groupId}
307+
icon={action.icon}
308+
aria-label={action.ariaLabel}
309+
title={action.title}
310+
onClick={action.onClick}
311+
disabled={action.disabled}
312+
className={action.className}
313+
/>
314+
))}
315+
<CommandBarOverflowMenu actions={actions} />
316+
</Toolbar>
317+
</Overflow>
318+
</div>
226319
);
227320
};
228321

0 commit comments

Comments
 (0)