mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-21 09:35:38 -05:00
Adding button plugin to table component (#10918)
* Added delete plugin to table component
This commit is contained in:
10
extensions/notebook/resources/dark/delete_inverse.svg
Normal file
10
extensions/notebook/resources/dark/delete_inverse.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M14 3H13V14.5C13 14.7083 12.9609 14.9036 12.8828 15.0859C12.8047 15.2682 12.6979 15.4271 12.5625 15.5625C12.4271 15.6979 12.2682 15.8047 12.0859 15.8828C11.9036 15.9609 11.7083 16 11.5 16H3.5C3.29167 16 3.09635 15.9609 2.91406 15.8828C2.73177 15.8047 2.57292 15.6979 2.4375 15.5625C2.30208 15.4271 2.19531 15.2682 2.11719 15.0859C2.03906 14.9036 2 14.7083 2 14.5V3H1V2H5V1C5 0.859375 5.02604 0.729167 5.07812 0.609375C5.13021 0.489583 5.20052 0.385417 5.28906 0.296875C5.38281 0.203125 5.48958 0.130208 5.60938 0.078125C5.72917 0.0260417 5.85938 0 6 0H9C9.14062 0 9.27083 0.0260417 9.39062 0.078125C9.51042 0.130208 9.61458 0.203125 9.70312 0.296875C9.79688 0.385417 9.86979 0.489583 9.92188 0.609375C9.97396 0.729167 10 0.859375 10 1V2H14V3ZM6 2H9V1H6V2ZM12 3H3V14.5C3 14.6354 3.04948 14.7526 3.14844 14.8516C3.2474 14.9505 3.36458 15 3.5 15H11.5C11.6354 15 11.7526 14.9505 11.8516 14.8516C11.9505 14.7526 12 14.6354 12 14.5V3ZM6 13H5V5H6V13ZM8 13H7V5H8V13ZM10 13H9V5H10V13Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
extensions/notebook/resources/light/delete.svg
Normal file
3
extensions/notebook/resources/light/delete.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="24" height="24">
|
||||
<path d="M1792 384h-128v1472q0 40-15 75t-41 61-61 41-75 15H448q-40 0-75-15t-61-41-41-61-15-75V384H128V256h512V128q0-27 10-50t27-40 41-28 50-10h384q27 0 50 10t40 27 28 41 10 50v128h512v128zM768 256h384V128H768v128zm768 128H384v1472q0 26 19 45t45 19h1024q26 0 45-19t19-45V384zM768 1664H640V640h128v1024zm256 0H896V640h128v1024zm256 0h-128V640h128v1024z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 452 B |
25
extensions/notebook/src/common/iconHelper.ts
Normal file
25
extensions/notebook/src/common/iconHelper.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface IconPath {
|
||||
dark: string;
|
||||
light: string;
|
||||
}
|
||||
|
||||
export class IconPathHelper {
|
||||
private static extensionContext: vscode.ExtensionContext;
|
||||
|
||||
public static delete: IconPath;
|
||||
|
||||
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
|
||||
IconPathHelper.extensionContext = extensionContext;
|
||||
IconPathHelper.delete = {
|
||||
dark: IconPathHelper.extensionContext.asAbsolutePath('resources/dark/delete_inverse.svg'),
|
||||
light: IconPathHelper.extensionContext.asAbsolutePath('resources/light/delete.svg')
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { JupyterServerInstallation, PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
|
||||
import * as utils from '../../common/utils';
|
||||
import { ManagePackagesDialog } from './managePackagesDialog';
|
||||
import CodeAdapter from '../../prompts/adapter';
|
||||
import { IQuestion, confirm } from '../../prompts/question';
|
||||
import { IconPathHelper } from '../../common/iconHelper';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -27,6 +29,7 @@ export class InstalledPackagesTab {
|
||||
private uninstallPackageButton: azdata.ButtonComponent;
|
||||
private view: azdata.ModelView | undefined;
|
||||
private formBuilder: azdata.FormBuilder;
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
|
||||
constructor(private dialog: ManagePackagesDialog, private jupyterInstallation: JupyterServerInstallation) {
|
||||
this.prompter = new CodeAdapter();
|
||||
@@ -35,6 +38,13 @@ export class InstalledPackagesTab {
|
||||
|
||||
this.installedPkgTab.registerContent(async view => {
|
||||
this.view = view;
|
||||
|
||||
// Dispose the resources
|
||||
this.disposables.push(view.onClosed(() => {
|
||||
this.disposables.forEach(d => {
|
||||
try { d.dispose(); } catch { }
|
||||
});
|
||||
}));
|
||||
let dropdownValues = this.dialog.model.getPackageTypes().map(x => {
|
||||
return {
|
||||
name: x.providerId,
|
||||
@@ -67,20 +77,39 @@ export class InstalledPackagesTab {
|
||||
this.installedPackagesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
localize('managePackages.pkgNameColumn', "Name"),
|
||||
localize('managePackages.newPkgVersionColumn', "Version")
|
||||
{
|
||||
value: localize('managePackages.pkgNameColumn', "Name"),
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: localize('managePackages.newPkgVersionColumn', "Version"),
|
||||
type: azdata.ColumnType.text
|
||||
},
|
||||
{
|
||||
value: localize('managePackages.deleteColumn', "Delete"),
|
||||
type: azdata.ColumnType.button,
|
||||
options: {
|
||||
icon: IconPathHelper.delete
|
||||
}
|
||||
}
|
||||
],
|
||||
data: [[]],
|
||||
height: '600px',
|
||||
width: '400px'
|
||||
}).component();
|
||||
this.disposables.push(this.installedPackagesTable.onCellAction(async (rowState) => {
|
||||
let buttonState = <azdata.ICellActionEventArgs>rowState;
|
||||
if (buttonState) {
|
||||
await this.doUninstallPackage([rowState.row]);
|
||||
}
|
||||
}));
|
||||
|
||||
this.uninstallPackageButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: localize('managePackages.uninstallButtonText', "Uninstall selected packages"),
|
||||
width: '200px'
|
||||
}).component();
|
||||
this.uninstallPackageButton.onDidClick(() => this.doUninstallPackage());
|
||||
this.uninstallPackageButton.onDidClick(() => this.doUninstallPackage(this.installedPackagesTable.selectedRows));
|
||||
|
||||
this.formBuilder = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
@@ -214,8 +243,7 @@ export class InstalledPackagesTab {
|
||||
}
|
||||
}
|
||||
|
||||
private async doUninstallPackage(): Promise<void> {
|
||||
let rowNums = this.installedPackagesTable.selectedRows;
|
||||
private async doUninstallPackage(rowNums: number[]): Promise<void> {
|
||||
if (!rowNums || rowNums.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||
import { InstalledPackagesTab } from './installedPackagesTab';
|
||||
@@ -19,7 +20,7 @@ export class ManagePackagesDialog {
|
||||
private addNewPkgTab: AddNewPackageTab;
|
||||
|
||||
constructor(
|
||||
private _managePackageDialogModel: ManagePackagesDialogModel) {
|
||||
private _managePackageDialogModel: ManagePackagesDialogModel, private _extensionContext: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +59,10 @@ export class ManagePackagesDialog {
|
||||
return this._managePackageDialogModel;
|
||||
}
|
||||
|
||||
public get extensionContext(): vscode.ExtensionContext {
|
||||
return this._extensionContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current provider id
|
||||
* @param providerId Provider Id
|
||||
|
||||
@@ -30,6 +30,7 @@ import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvid
|
||||
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
|
||||
import { PyPiClient } from './pypiClient';
|
||||
import { ConfigurePythonDialog } from '../dialog/configurePython/configurePythonDialog';
|
||||
import { IconPathHelper } from '../common/iconHelper';
|
||||
|
||||
let untitledCounter = 0;
|
||||
|
||||
@@ -66,6 +67,7 @@ export class JupyterController implements vscode.Disposable {
|
||||
this.extensionContext.extensionPath,
|
||||
this.outputChannel);
|
||||
await this._jupyterInstallation.configurePackagePaths();
|
||||
IconPathHelper.setExtensionContext(this.extensionContext);
|
||||
|
||||
// Add command/task handlers
|
||||
azdata.tasks.registerTask(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
|
||||
@@ -212,7 +214,7 @@ export class JupyterController implements vscode.Disposable {
|
||||
let model = new ManagePackagesDialogModel(this._jupyterInstallation, this._packageManageProviders, options);
|
||||
|
||||
await model.init();
|
||||
let packagesDialog = new ManagePackagesDialog(model);
|
||||
let packagesDialog = new ManagePackagesDialog(model, this.extensionContext);
|
||||
packagesDialog.showDialog();
|
||||
} catch (error) {
|
||||
let message = utils.getErrorMessage(error);
|
||||
|
||||
@@ -110,7 +110,10 @@ describe('Manage Package Dialog', () => {
|
||||
let packageManageProviders = new Map<string, IPackageManageProvider>();
|
||||
packageManageProviders.set(LocalCondaPackageManageProvider.ProviderId, new LocalCondaPackageManageProvider(undefined));
|
||||
let model = TypeMoq.Mock.ofInstance(new ManagePackagesDialogModel(undefined, packageManageProviders));
|
||||
let dialog = TypeMoq.Mock.ofInstance(new ManagePackagesDialog(model.object));
|
||||
const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockExtensionContext.setup(x => x.asAbsolutePath(TypeMoq.It.isAny())).returns(() => '');
|
||||
|
||||
let dialog = TypeMoq.Mock.ofInstance(new ManagePackagesDialog(model.object, mockExtensionContext.object));
|
||||
dialog.setup(x => x.model).returns(() => model.object);
|
||||
|
||||
let onClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
|
||||
8
src/sql/azdata.proposed.d.ts
vendored
8
src/sql/azdata.proposed.d.ts
vendored
@@ -441,6 +441,14 @@ declare module 'azdata' {
|
||||
targetLocation?: string;
|
||||
}
|
||||
|
||||
export interface ButtonColumnOption {
|
||||
icon?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
|
||||
}
|
||||
|
||||
export interface ButtonCell extends TableCell {
|
||||
columnName: string;
|
||||
}
|
||||
|
||||
export namespace sqlAssessment {
|
||||
|
||||
export enum SqlAssessmentTargetType {
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface ButtonColumnOptions {
|
||||
export interface ButtonClickEventArgs<T extends Slick.SlickData> {
|
||||
item: T;
|
||||
position: { x: number, y: number };
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export class ButtonColumn<T extends Slick.SlickData> implements Slick.Plugin<T> {
|
||||
@@ -57,8 +59,10 @@ export class ButtonColumn<T extends Slick.SlickData> implements Slick.Plugin<T>
|
||||
private handleActiveCellChanged(args: Slick.OnActiveCellChangedEventArgs<T>): void {
|
||||
if (this.isCurrentColumn(args.cell)) {
|
||||
const cellElement = this._grid.getActiveCellNode();
|
||||
const button = cellElement.children[0] as HTMLButtonElement;
|
||||
button.focus();
|
||||
if (cellElement && cellElement.children) {
|
||||
const button = cellElement.children[0] as HTMLButtonElement;
|
||||
button.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +96,8 @@ export class ButtonColumn<T extends Slick.SlickData> implements Slick.Plugin<T>
|
||||
const activeCellPosition = this._grid.getActiveCellPosition();
|
||||
if (activeCell && activeCellPosition) {
|
||||
this._onClick.fire({
|
||||
row: activeCell.row,
|
||||
column: activeCell.cell,
|
||||
item: this._grid.getDataItem(activeCell.row),
|
||||
position: {
|
||||
x: (activeCellPosition.left + activeCellPosition.right) / 2,
|
||||
@@ -102,7 +108,7 @@ export class ButtonColumn<T extends Slick.SlickData> implements Slick.Plugin<T>
|
||||
}
|
||||
|
||||
private isCurrentColumn(columnIndex: number): boolean {
|
||||
return this._grid.getColumns()[columnIndex].id === this.definition.id;
|
||||
return this._grid?.getColumns()[columnIndex]?.id === this.definition.id;
|
||||
}
|
||||
|
||||
private formatter(row: number, cell: number, value: any, columnDef: Slick.Column<T>, dataContext: T): string {
|
||||
|
||||
@@ -239,7 +239,7 @@ export class CheckboxSelectColumn<T extends Slick.SlickData> implements Slick.Pl
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefinition(): Slick.Column<T> {
|
||||
public get definition(): Slick.Column<T> {
|
||||
return {
|
||||
id: this._options.columnId,
|
||||
name: this._options.title || strings.format(checkboxTemplate, '', ''),
|
||||
|
||||
@@ -20,4 +20,8 @@
|
||||
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modelview-table-button-icon {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import { slickGridDataItemColumnValueWithNoData, textFormatter } from 'sql/base/
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces';
|
||||
import { convertSizeToNumber } from 'sql/base/browser/dom';
|
||||
import { ButtonColumn, ButtonClickEventArgs } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin';
|
||||
import { createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils';
|
||||
|
||||
export enum ColumnSizingMode {
|
||||
ForceFit = 0, // all columns will be sized to fit in viewable space, no horiz scroll bar
|
||||
@@ -47,8 +49,12 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
||||
private _tableData: TableDataView<Slick.SlickData>;
|
||||
private _tableColumns;
|
||||
private _checkboxColumns: CheckboxSelectColumn<{}>[] = [];
|
||||
private _buttonsColumns: ButtonColumn<{}>[] = [];
|
||||
private _pluginsRegisterStatus: boolean[] = [];
|
||||
private _onCheckBoxChanged = new Emitter<ICheckboxCellActionEventArgs>();
|
||||
private _onButtonClicked = new Emitter<ButtonClickEventArgs<{}>>();
|
||||
public readonly onCheckBoxChanged: vsEvent<ICheckboxCellActionEventArgs> = this._onCheckBoxChanged.event;
|
||||
public readonly onButtonClicked: vsEvent<ButtonClickEventArgs<{}>> = this._onButtonClicked.event;
|
||||
|
||||
@ViewChild('table', { read: ElementRef }) private _inputContainer: ElementRef;
|
||||
constructor(
|
||||
@@ -72,6 +78,9 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
||||
if (col.type && col.type === 1) {
|
||||
this.createCheckBoxPlugin(col, index);
|
||||
}
|
||||
else if (col.type && col.type === 2) {
|
||||
this.createButtonPlugin(col);
|
||||
}
|
||||
else if (col.value) {
|
||||
mycolumns.push(<Slick.Column<any>>{
|
||||
name: col.value,
|
||||
@@ -238,7 +247,8 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
||||
this._table.setSelectedRows(this.selectedRows);
|
||||
}
|
||||
|
||||
Object.keys(this._checkboxColumns).forEach(col => this.registerCheckboxPlugin(this._checkboxColumns[col]));
|
||||
Object.keys(this._checkboxColumns).forEach(col => this.registerPlugins(col, this._checkboxColumns[col]));
|
||||
Object.keys(this._buttonsColumns).forEach(col => this.registerPlugins(col, this._buttonsColumns[col]));
|
||||
|
||||
if (this.ariaRowCount === -1) {
|
||||
this._table.removeAriaRowCount();
|
||||
@@ -309,11 +319,41 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
||||
}
|
||||
}
|
||||
|
||||
private registerCheckboxPlugin(checkboxSelectColumn: CheckboxSelectColumn<{}>): void {
|
||||
this._tableColumns.splice(checkboxSelectColumn.index, 0, checkboxSelectColumn.getColumnDefinition());
|
||||
this._table.registerPlugin(checkboxSelectColumn);
|
||||
private createButtonPlugin(col: any) {
|
||||
let name = col.value;
|
||||
if (!this._buttonsColumns[col.value]) {
|
||||
this._buttonsColumns[col.value] = new ButtonColumn({
|
||||
title: col.title,
|
||||
iconCssClass: 'modelview-table-button-icon ' + (col.options ? createIconCssClass(col.options.icon) : '')
|
||||
});
|
||||
|
||||
this._register(this._buttonsColumns[col.value].onClick((state) => {
|
||||
this.fireEvent({
|
||||
eventType: ComponentEventType.onCellAction,
|
||||
args: {
|
||||
row: state.row,
|
||||
column: state.column,
|
||||
name: name
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private registerPlugins(col: string, plugin: CheckboxSelectColumn<{}> | ButtonColumn<{}>): void {
|
||||
|
||||
const index = 'index' in plugin ? plugin.index : this.columns?.findIndex(x => x === col || ('value' in x && x['value'] === col));
|
||||
if (index >= 0) {
|
||||
this._tableColumns.splice(index, 0, plugin.definition);
|
||||
if (!(col in this._pluginsRegisterStatus) || !this._pluginsRegisterStatus[col]) {
|
||||
this._table.registerPlugin(plugin);
|
||||
this._pluginsRegisterStatus[col] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this._table.columns = this._tableColumns;
|
||||
this._table.autosizeColumns();
|
||||
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
@@ -335,7 +375,7 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
||||
this.setPropertyFromUI<azdata.TableComponentProperties, any[][]>((props, value) => props.data = value, newValue);
|
||||
}
|
||||
|
||||
public get columns(): string[] {
|
||||
public get columns(): string[] | azdata.TableColumn[] {
|
||||
return this.getPropertyOrDefault<azdata.TableComponentProperties, string[]>((props) => props.columns, []);
|
||||
}
|
||||
|
||||
@@ -343,8 +383,8 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
||||
return this.getPropertyOrDefault<azdata.TableComponentProperties, number | string>((props) => props.fontSize, '');
|
||||
}
|
||||
|
||||
public set columns(newValue: string[]) {
|
||||
this.setPropertyFromUI<azdata.TableComponentProperties, string[]>((props, value) => props.columns = value, newValue);
|
||||
public set columns(newValue: string[] | azdata.TableColumn[]) {
|
||||
this.setPropertyFromUI<azdata.TableComponentProperties, string[] | azdata.TableColumn[]>((props, value) => props.columns = value, newValue);
|
||||
}
|
||||
|
||||
public get selectedRows(): number[] {
|
||||
|
||||
@@ -830,7 +830,7 @@ export class RestoreDialog extends Modal {
|
||||
});
|
||||
|
||||
const checkboxSelectColumn = new CheckboxSelectColumn({ title: this._restoreLabel, toolTip: this._restoreLabel, width: 15 });
|
||||
this._restorePlanColumn.unshift(checkboxSelectColumn.getColumnDefinition());
|
||||
this._restorePlanColumn.unshift(checkboxSelectColumn.definition);
|
||||
this._restorePlanTable.columns = this._restorePlanColumn;
|
||||
this._restorePlanTable.registerPlugin(checkboxSelectColumn);
|
||||
this._restorePlanTable.autosizeColumns();
|
||||
|
||||
Reference in New Issue
Block a user