React Query Result Toolbar (#18093)
* add query results toolbar buttons * wire up rpc calls * cleanup * fix theming * add loc, other pr comments * add locfile * remove null checks * pr comments, rename file * address more pr comments
This commit is contained in:
Родитель
1421218db8
Коммит
c5cafa027b
|
@ -94,6 +94,9 @@
|
|||
"Move Up": "Move Up",
|
||||
"Move Down": "Move Down",
|
||||
"Delete": "Delete",
|
||||
"Save as CSV": "Save as CSV",
|
||||
"Save as JSON": "Save as JSON",
|
||||
"Save as Excel": "Save as Excel",
|
||||
"Results": "Results",
|
||||
"Messages": "Messages",
|
||||
"Timestamp": "Timestamp",
|
||||
|
@ -571,9 +574,6 @@
|
|||
"A SQL editor must have focus before executing this command": "A SQL editor must have focus before executing this command",
|
||||
"Maximize": "Maximize",
|
||||
"Restore": "Restore",
|
||||
"Save as CSV": "Save as CSV",
|
||||
"Save as JSON": "Save as JSON",
|
||||
"Save as Excel": "Save as Excel",
|
||||
"CSV": "CSV",
|
||||
"JSON": "JSON",
|
||||
"Excel": "Excel",
|
||||
|
|
|
@ -26,13 +26,6 @@ export enum ContentType {
|
|||
LocalizedTexts = 12,
|
||||
}
|
||||
|
||||
export interface ISlickRange {
|
||||
fromCell: number;
|
||||
fromRow: number;
|
||||
toCell: number;
|
||||
toRow: number;
|
||||
}
|
||||
|
||||
export enum AuthenticationTypes {
|
||||
Integrated = 1,
|
||||
SqlLogin = 2,
|
||||
|
@ -290,13 +283,6 @@ export interface ISelectionData {
|
|||
endColumn: number;
|
||||
}
|
||||
|
||||
export interface ISlickRange {
|
||||
fromCell: number;
|
||||
fromRow: number;
|
||||
toCell: number;
|
||||
toRow: number;
|
||||
}
|
||||
|
||||
export enum FieldType {
|
||||
String = 0,
|
||||
Boolean = 1,
|
||||
|
|
|
@ -47,6 +47,15 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
|
|||
message.selectionData,
|
||||
);
|
||||
});
|
||||
this.registerRequestHandler("saveResults", async (message) => {
|
||||
return await this._sqlOutputContentProvider.saveResultsRequestHandler(
|
||||
message.uri,
|
||||
message.batchId,
|
||||
message.resultId,
|
||||
message.format,
|
||||
message.selection,
|
||||
);
|
||||
});
|
||||
this.registerReducer("setResultTab", async (state, payload) => {
|
||||
state.tabStates.resultPaneTab = payload.tabId;
|
||||
return state;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 4.552V13c-.028.825-.593 2-2.035 2h-8C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 3.586L8.414 3H9v.586zM12 6h-2v7h2V6zm-6 7V7.414L5.414 8H4v5h2zm1 0h2V4.414l-1 1V6h-.586L7 6.414V13z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8 5.414V6h-.586L8 5.414zM13 5v8s-.035 1-1.035 1h-8S3 14 3 13V8h1v5h2V7.414l1-1V13h2V4.414L9.414 4 9 3.586V3h-.586l-1-1h2.227L13 5zm-1 1h-2v7h2V6z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
После Ширина: | Высота: | Размер: 940 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 4.552V13c-.028.825-.593 2-2.035 2h-8C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 3.586L8.414 3H9v.586zM12 6h-2v7h2V6zm-6 7V7.414L5.414 8H4v5h2zm1 0h2V4.414l-1 1V6h-.586L7 6.414V13z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8 5.414V6h-.586L8 5.414zM13 5v8s-.035 1-1.035 1h-8S3 14 3 13V8h1v5h2V7.414l1-1V13h2V4.414L9.414 4 9 3.586V3h-.586l-1-1h2.227L13 5zm-1 1h-2v7h2V6z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
После Ширина: | Высота: | Размер: 940 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M16 7v8H3.964C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552V7h2z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 6h3v1H7v6H4V8h1.414L9 4.414V6zm0-3h-.586L9 3.586V3zm0 10h1v-1H9v1zm0-2h1v-1H9v1zm2 2h1v-1h-1v1zm2-3v1h1v-1h-1zm-2 1h1v-1h-1v1zm2 2h1v-1h-1v1z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8.414 3l-1-1h2.227L13 5v2h-1V6H9V4.414L9.414 4 9 3.586V3h-.586zM4 8H3v5c0 1 .964 1 .964 1H7v-1H4V8zm11 0v6H8V8h7zm-5 4H9v1h1v-1zm0-2H9v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1z" id="iconBg"/><g id="colorAction"><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z"/></g></svg>
|
После Ширина: | Высота: | Размер: 1022 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M16 7v8H3.964C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552V7h2z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 6h3v1H7v6H4V8h1.414L9 4.414V6zm0-3h-.586L9 3.586V3zm0 10h1v-1H9v1zm0-2h1v-1H9v1zm2 2h1v-1h-1v1zm2-3v1h1v-1h-1zm-2 1h1v-1h-1v1zm2 2h1v-1h-1v1z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8.414 3l-1-1h2.227L13 5v2h-1V6H9V4.414L9.414 4 9 3.586V3h-.586zM4 8H3v5c0 1 .964 1 .964 1H7v-1H4V8zm11 0v6H8V8h7zm-5 4H9v1h1v-1zm0-2H9v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1z" id="iconBg"/><g id="colorAction"><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z"/></g></svg>
|
После Ширина: | Высота: | Размер: 1022 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 8.38v2.258s-.992-.001-.997-.003c-.012.052-.003.14-.003.281v1.579c0 1.271-.37 2.185-1.054 2.746-.638.522-1.576.759-2.822.759H10v-3.247s1.014-.001 1.037-.003c.004-.046-.037-.117-.037-.214V11.08c0-.943.222-1.606.539-2.072C11.223 8.527 11 7.843 11 6.869V5.468c0-.087.102-.286.094-.325-.02-.002.063-.143.03-.143H10V2h1.124c1.251 0 2.193.265 2.832.81C14.633 3.387 15 4.3 15 5.522V7h.919L16 8.38zM9.414 4l-4-4H.586l2 2H0v4h2v.586L1.586 7H1v.586L.586 8H1v1.638L1.329 11H2v1.536c0 1.247.495 2.149 1.19 2.711.641.517 1.697.753 2.937.753H7v-3.247s-1.011-.001-1.033-.003c-.008-.053.033-.127.033-.228v-1.401c0-.962-.224-1.637-.542-2.111.256-.378.444-.89.511-1.564L9.414 4z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M15 8.38v1.258c-.697 0-1.046.426-1.046 1.278v1.579c0 .961-.223 1.625-.666 1.989-.445.364-1.166.547-2.164.547v-1.278c.383 0 .661-.092.834-.277s.26-.498.26-.94V11.08c0-1.089.349-1.771 1.046-2.044v-.027c-.697-.287-1.046-1-1.046-2.14V5.468c0-.793-.364-1.189-1.094-1.189V3c.993 0 1.714.19 2.16.571s.67 1.031.67 1.952v1.565c0 .861.349 1.292 1.046 1.292zm-9.967 4.142v-1.401c0-1.117-.351-1.816-1.053-2.099v-.027c.429-.175.71-.519.877-.995H3.049c-.173.247-.436.38-.805.38v1.258c.692 0 1.039.419 1.039 1.258v1.641c0 .934.226 1.584.677 1.948s1.174.547 2.167.547v-1.278c-.388 0-.666-.093-.838-.28-.17-.188-.256-.505-.256-.952z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
После Ширина: | Высота: | Размер: 1.7 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 8.38v2.258s-.992-.001-.997-.003c-.012.052-.003.14-.003.281v1.579c0 1.271-.37 2.185-1.054 2.746-.638.522-1.576.759-2.822.759H10v-3.247s1.014-.001 1.037-.003c.004-.046-.037-.117-.037-.214V11.08c0-.943.222-1.606.539-2.072C11.223 8.527 11 7.843 11 6.869V5.468c0-.087.102-.286.094-.325-.02-.002.063-.143.03-.143H10V2h1.124c1.251 0 2.193.265 2.832.81C14.633 3.387 15 4.3 15 5.522V7h.919L16 8.38zM9.414 4l-4-4H.586l2 2H0v4h2v.586L1.586 7H1v.586L.586 8H1v1.638L1.329 11H2v1.536c0 1.247.495 2.149 1.19 2.711.641.517 1.697.753 2.937.753H7v-3.247s-1.011-.001-1.033-.003c-.008-.053.033-.127.033-.228v-1.401c0-.962-.224-1.637-.542-2.111.256-.378.444-.89.511-1.564L9.414 4z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M15 8.38v1.258c-.697 0-1.046.426-1.046 1.278v1.579c0 .961-.223 1.625-.666 1.989-.445.364-1.166.547-2.164.547v-1.278c.383 0 .661-.092.834-.277s.26-.498.26-.94V11.08c0-1.089.349-1.771 1.046-2.044v-.027c-.697-.287-1.046-1-1.046-2.14V5.468c0-.793-.364-1.189-1.094-1.189V3c.993 0 1.714.19 2.16.571s.67 1.031.67 1.952v1.565c0 .861.349 1.292 1.046 1.292zm-9.967 4.142v-1.401c0-1.117-.351-1.816-1.053-2.099v-.027c.429-.175.71-.519.877-.995H3.049c-.173.247-.436.38-.805.38v1.258c.692 0 1.039.419 1.039 1.258v1.641c0 .934.226 1.584.677 1.948s1.174.547 2.167.547v-1.278c-.388 0-.666-.093-.838-.28-.17-.188-.256-.505-.256-.952z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
После Ширина: | Высота: | Размер: 1.7 KiB |
|
@ -0,0 +1,97 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Button, makeStyles } from "@fluentui/react-components";
|
||||
import { useContext } from "react";
|
||||
import { QueryResultContext } from "./queryResultStateProvider";
|
||||
import { useVscodeWebview } from "../../common/vscodeWebviewProvider";
|
||||
import * as qr from "../../../sharedInterfaces/queryResult";
|
||||
import * as l10n from "@vscode/l10n";
|
||||
import {
|
||||
saveAsCsvIcon,
|
||||
saveAsExcelIcon,
|
||||
saveAsJsonIcon,
|
||||
} from "./queryResultUtils";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
commandBar: {
|
||||
display: "flex",
|
||||
flexDirection: "column" /* Align buttons vertically */,
|
||||
},
|
||||
buttonImg: {
|
||||
display: "block",
|
||||
height: "16px",
|
||||
width: "16px",
|
||||
},
|
||||
});
|
||||
|
||||
const CommandBar = () => {
|
||||
const context = useContext(QueryResultContext);
|
||||
if (context === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const queryResultState = context.state;
|
||||
const webViewState = useVscodeWebview<
|
||||
qr.QueryResultWebviewState,
|
||||
qr.QueryResultReducers
|
||||
>();
|
||||
const classes = useStyles();
|
||||
|
||||
const saveResults = (buttonLabel: string) => {
|
||||
webViewState.extensionRpc.call("saveResults", {
|
||||
uri: queryResultState?.uri,
|
||||
batchId: queryResultState?.resultSetSummary?.batchId,
|
||||
resultId: queryResultState?.resultSetSummary?.id,
|
||||
format: buttonLabel,
|
||||
selection: queryResultState?.resultSetSummary?.rowCount, //TODO: do for only user selection
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.commandBar}>
|
||||
<Button
|
||||
onClick={(_event) => {
|
||||
saveResults("csv");
|
||||
}}
|
||||
icon={
|
||||
<img
|
||||
className={classes.buttonImg}
|
||||
src={saveAsCsvIcon(context.theme)}
|
||||
/>
|
||||
}
|
||||
className="codicon saveCsv"
|
||||
title={l10n.t("Save as CSV")}
|
||||
/>
|
||||
<Button
|
||||
onClick={(_event) => {
|
||||
saveResults("json");
|
||||
}}
|
||||
icon={
|
||||
<img
|
||||
className={classes.buttonImg}
|
||||
src={saveAsJsonIcon(context.theme)}
|
||||
/>
|
||||
}
|
||||
className="codicon saveJson"
|
||||
title={l10n.t("Save as JSON")}
|
||||
/>
|
||||
<Button
|
||||
onClick={(_event) => {
|
||||
saveResults("excel");
|
||||
}}
|
||||
icon={
|
||||
<img
|
||||
className={classes.buttonImg}
|
||||
src={saveAsExcelIcon(context.theme)}
|
||||
/>
|
||||
}
|
||||
className="codicon saveExcel"
|
||||
title={l10n.t("Save as Excel")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommandBar;
|
|
@ -28,6 +28,7 @@ import * as qr from "../../../sharedInterfaces/queryResult";
|
|||
import { useVscodeWebview } from "../../common/vscodeWebviewProvider";
|
||||
import SlickGrid, { SlickGridHandle } from "./slickgrid";
|
||||
import * as l10n from "@vscode/l10n";
|
||||
import CommandBar from "./commandBar";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
|
@ -58,6 +59,7 @@ const useStyles = makeStyles({
|
|||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
},
|
||||
queryResultPaneOpenButton: {
|
||||
position: "absolute",
|
||||
|
@ -288,6 +290,7 @@ export const QueryResultPane = () => {
|
|||
ref={gridRef}
|
||||
resultSetSummary={metadata.resultSetSummary}
|
||||
/>
|
||||
<CommandBar />
|
||||
</div>
|
||||
)}
|
||||
{metadata.tabStates!.resultPaneTab ===
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
import { ReactNode, createContext } from "react";
|
||||
import * as qr from "../../../sharedInterfaces/queryResult";
|
||||
import { useVscodeWebview } from "../../common/vscodeWebviewProvider";
|
||||
import { Theme } from "@fluentui/react-components";
|
||||
|
||||
export interface QueryResultState {
|
||||
provider: qr.QueryResultReactProvider;
|
||||
state: qr.QueryResultWebviewState;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const QueryResultContext = createContext<QueryResultState | undefined>(
|
||||
|
@ -41,6 +43,7 @@ const QueryResultStateProvider: React.FC<QueryResultContextProps> = ({
|
|||
},
|
||||
},
|
||||
state: webViewState?.state as qr.QueryResultWebviewState,
|
||||
theme: webViewState?.theme,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Theme, webLightTheme } from "@fluentui/react-components";
|
||||
|
||||
export const saveAsCsvIcon = (theme: Theme) => {
|
||||
return theme === webLightTheme
|
||||
? require("../../media/saveCsv.svg")
|
||||
: require("../../media/saveCsv_inverse.svg");
|
||||
};
|
||||
|
||||
export const saveAsJsonIcon = (theme: Theme) => {
|
||||
return theme === webLightTheme
|
||||
? require("../../media/saveJson.svg")
|
||||
: require("../../media/saveJson_inverse.svg");
|
||||
};
|
||||
|
||||
export const saveAsExcelIcon = (theme: Theme) => {
|
||||
return theme === webLightTheme
|
||||
? require("../../media/saveExcel.svg")
|
||||
: require("../../media/saveExcel_inverse.svg");
|
||||
};
|
|
@ -26,6 +26,8 @@ function getDefaultOptions<T extends Slick.SlickData>(): Slick.GridOptions<T> {
|
|||
} as Slick.GridOptions<T>;
|
||||
}
|
||||
|
||||
const ACTIONBAR_WIDTH = 36; //px
|
||||
|
||||
export class Table<T extends Slick.SlickData> implements IThemable {
|
||||
protected styleElement: HTMLStyleElement;
|
||||
protected idPrefix: string;
|
||||
|
@ -71,7 +73,7 @@ export class Table<T extends Slick.SlickData> implements IThemable {
|
|||
// this._tableContainer.className = //TODO: class name for styles
|
||||
let gridParent = document.getElementById("grid-parent");
|
||||
if (gridParent) {
|
||||
this._tableContainer.style.width = `${gridParent?.clientWidth.toString()}px`;
|
||||
this._tableContainer.style.width = `${(gridParent?.clientWidth - ACTIONBAR_WIDTH).toString()}px`;
|
||||
const height = gridParent?.clientHeight - 5;
|
||||
this._tableContainer.style.height = `${height.toString()}px`;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче