From e607f68b3e84265ce76afb11080ca3572cdac7d7 Mon Sep 17 00:00:00 2001 From: Yurong He <43652751+YurongHe@users.noreply.github.com> Date: Wed, 14 Nov 2018 17:51:59 -0800 Subject: [PATCH] Add more actions to cell (#3217) * Added toggle more actions to cell * Resolve PR comments -- Added INotificationService for notification msg * Reduced ToggleMoreAction to smaller size. So the dropdown could be displayed closer to it instead of at the buttom of the cell. --- .../notebook/cellViews/code.component.html | 2 + .../notebook/cellViews/code.component.ts | 41 +++++++++- src/sql/parts/notebook/cellViews/code.css | 5 ++ .../parts/notebook/cellViews/codeActions.ts | 79 ++++++++++++++++++- .../cellViews/codeCell.component.html | 2 +- .../notebook/cellViews/codeCell.component.ts | 12 +++ .../parts/notebook/models/modelInterfaces.ts | 4 +- .../parts/notebook/models/notebookModel.ts | 41 +++++++--- .../parts/notebook/notebook.component.html | 2 +- src/sql/parts/notebook/notebook.component.ts | 5 ++ 10 files changed, 174 insertions(+), 19 deletions(-) diff --git a/src/sql/parts/notebook/cellViews/code.component.html b/src/sql/parts/notebook/cellViews/code.component.html index f7b12f94ce..cff7e38d03 100644 --- a/src/sql/parts/notebook/cellViews/code.component.html +++ b/src/sql/parts/notebook/cellViews/code.component.html @@ -9,4 +9,6 @@
+
+
diff --git a/src/sql/parts/notebook/cellViews/code.component.ts b/src/sql/parts/notebook/cellViews/code.component.ts index 25ecc4869d..1fbff0f35d 100644 --- a/src/sql/parts/notebook/cellViews/code.component.ts +++ b/src/sql/parts/notebook/cellViews/code.component.ts @@ -19,6 +19,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITextModel } from 'vs/editor/common/model'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import URI from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { Schemas } from 'vs/base/common/network'; import * as DOM from 'vs/base/browser/dom'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -26,7 +29,11 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; -import { RunCellAction } from 'sql/parts/notebook/cellViews/codeActions'; +import { RunCellAction, DeleteCellAction, AddCellAction } from 'sql/parts/notebook/cellViews/codeActions'; +import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; +import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions'; +import { CellTypes } from 'sql/parts/notebook/models/contracts'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export const CODE_SELECTOR: string = 'code-component'; @@ -36,16 +43,24 @@ export const CODE_SELECTOR: string = 'code-component'; }) export class CodeComponent extends AngularDisposable implements OnInit { @ViewChild('toolbar', { read: ElementRef }) private toolbarElement: ElementRef; + @ViewChild('moreactions', { read: ElementRef }) private moreactionsElement: ElementRef; @ViewChild('editor', { read: ElementRef }) private codeElement: ElementRef; @Input() cellModel: ICellModel; + @Output() public onContentChanged = new EventEmitter(); + @Input() set model(value: NotebookModel) { + this._model = value; + } + protected _actionBar: Taskbar; + protected _moreActions: ActionBar; private readonly _minimumHeight = 30; private _editor: QueryTextEditor; private _editorInput: UntitledEditorInput; private _editorModel: ITextModel; private _uri: string; + private _model: NotebookModel; constructor( @Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface, @@ -55,7 +70,8 @@ export class CodeComponent extends AngularDisposable implements OnInit { @Inject(IModelService) private _modelService: IModelService, @Inject(IModeService) private _modeService: IModeService, @Inject(IContextMenuService) private contextMenuService: IContextMenuService, - @Inject(IContextViewService) private contextViewService: IContextViewService + @Inject(IContextViewService) private contextViewService: IContextViewService, + @Inject(INotificationService) private notificationService: INotificationService, ) { super(); } @@ -78,6 +94,10 @@ export class CodeComponent extends AngularDisposable implements OnInit { })); } + get model(): NotebookModel { + return this._model; + } + private createEditor(): void { let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()])); this._editor = instantiationService.createInstance(QueryTextEditor); @@ -119,6 +139,19 @@ export class CodeComponent extends AngularDisposable implements OnInit { this._actionBar.setContent([ { action: runCellAction } ]); + + let moreActionsElement = this.moreactionsElement.nativeElement; + this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL }); + this._moreActions.context = { target: moreActionsElement }; + + let actions: Action[] = []; + actions.push(this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false, this.notificationService)); + actions.push(this._instantiationService.createInstance(AddCellAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true, this.notificationService)); + actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false, this.notificationService)); + actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true, this.notificationService)); + actions.push(this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete'), CellTypes.Code, false, this.notificationService)); + + this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, actions, this.model, this.contextMenuService), { icon: true, label: false }); } private createUri(): URI { @@ -146,5 +179,9 @@ export class CodeComponent extends AngularDisposable implements OnInit { private updateTheme(theme: IColorTheme): void { let toolbarEl = this.toolbarElement.nativeElement; toolbarEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); + + let moreactionsEl = this.moreactionsElement.nativeElement; + moreactionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); } + } diff --git a/src/sql/parts/notebook/cellViews/code.css b/src/sql/parts/notebook/cellViews/code.css index 8785f88552..577d98b999 100644 --- a/src/sql/parts/notebook/cellViews/code.css +++ b/src/sql/parts/notebook/cellViews/code.css @@ -30,6 +30,11 @@ code-component .carbon-taskbar .icon { width: 40px; } +code-component .action-label.icon.toggle-more { + height: 20px; + width: 20px; +} + code-component .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container { padding-left: 10px diff --git a/src/sql/parts/notebook/cellViews/codeActions.ts b/src/sql/parts/notebook/cellViews/codeActions.ts index 292bd506e8..364ad449ad 100644 --- a/src/sql/parts/notebook/cellViews/codeActions.ts +++ b/src/sql/parts/notebook/cellViews/codeActions.ts @@ -6,7 +6,14 @@ import { Action } from 'vs/base/common/actions'; import { TPromise } from 'vs/base/common/winjs.base'; import { localize } from 'vs/nls'; +import { CellType } from 'sql/parts/notebook/models/contracts'; +import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; +import { getErrorMessage } from 'sql/parts/notebook/notebookUtils'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { NOTFOUND } from 'dns'; +import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService'; +let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again"); export class RunCellAction extends Action { public static ID = 'jobaction.notebookRunCell'; public static LABEL = 'Run cell'; @@ -14,7 +21,7 @@ export class RunCellAction extends Action { constructor( ) { super(RunCellAction.ID, '', 'toolbarIconRun'); - this.tooltip = localize('runCell','Run cell'); + this.tooltip = localize('runCell', 'Run cell'); } public run(context: any): TPromise { @@ -26,4 +33,74 @@ export class RunCellAction extends Action { } }); } +} + +export class AddCellAction extends Action { + constructor( + id: string, label: string, private cellType: CellType, private isAfter: boolean, private notificationService: INotificationService + ) { + super(id, label); + } + public run(model: NotebookModel): TPromise { + return new TPromise((resolve, reject) => { + try { + if (!model) { + return; + } + if (model.activeCell === undefined) { + this.notificationService.notify({ + severity: Severity.Error, + message: notebookMoreActionMsg + }); + } + else { + let index = model.cells.findIndex((cell) => cell.id === model.activeCell.id); + if (index !== undefined && this.isAfter) { + index += 1; + } + model.addCell(this.cellType, index); + } + } catch (error) { + let message = getErrorMessage(error); + + this.notificationService.notify({ + severity: Severity.Error, + message: message + }); + } + }); + } +} + +export class DeleteCellAction extends Action { + constructor( + id: string, label: string, private cellType: CellType, private isAfter: boolean, private notificationService: INotificationService + ) { + super(id, label); + } + public run(model: NotebookModel): TPromise { + return new TPromise((resolve, reject) => { + try { + if (!model) { + return; + } + if (model.activeCell === undefined) { + this.notificationService.notify({ + severity: Severity.Error, + message: notebookMoreActionMsg + }); + } + else { + model.deleteCell(model.activeCell); + } + } catch (error) { + let message = getErrorMessage(error); + + this.notificationService.notify({ + severity: Severity.Error, + message: message + }); + } + }); + } } \ No newline at end of file diff --git a/src/sql/parts/notebook/cellViews/codeCell.component.html b/src/sql/parts/notebook/cellViews/codeCell.component.html index 1351e5ef2f..6c3fef99ca 100644 --- a/src/sql/parts/notebook/cellViews/codeCell.component.html +++ b/src/sql/parts/notebook/cellViews/codeCell.component.html @@ -6,7 +6,7 @@ -->
- +
diff --git a/src/sql/parts/notebook/cellViews/codeCell.component.ts b/src/sql/parts/notebook/cellViews/codeCell.component.ts index c15f3cc42d..700f6f273f 100644 --- a/src/sql/parts/notebook/cellViews/codeCell.component.ts +++ b/src/sql/parts/notebook/cellViews/codeCell.component.ts @@ -12,6 +12,7 @@ import { CellView } from 'sql/parts/notebook/cellViews/interfaces'; import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as themeColors from 'vs/workbench/common/theme'; import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; +import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; export const CODE_SELECTOR: string = 'code-cell-component'; @@ -21,7 +22,13 @@ export const CODE_SELECTOR: string = 'code-cell-component'; templateUrl: decodeURI(require.toUrl('./codeCell.component.html')) }) export class CodeCellComponent extends CellView implements OnInit { + private _model: NotebookModel; + @Input() cellModel: ICellModel; + @Input() set model(value: NotebookModel) { + this._model = value; + } + constructor( @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService @@ -41,4 +48,9 @@ export class CodeCellComponent extends CellView implements OnInit { private updateTheme(theme: IColorTheme): void { } + + get model(): NotebookModel { + return this._model; + } + } diff --git a/src/sql/parts/notebook/models/modelInterfaces.ts b/src/sql/parts/notebook/models/modelInterfaces.ts index 93d68fee70..17baf6f61d 100644 --- a/src/sql/parts/notebook/models/modelInterfaces.ts +++ b/src/sql/parts/notebook/models/modelInterfaces.ts @@ -304,9 +304,9 @@ export interface INotebookModel { changeContext(host: string): void; /** - * Adds a cell to the end of the model + * Adds a cell to the index of the model */ - addCell(cellType: CellType): void; + addCell(cellType: CellType, index?: number): void; /** * Deletes a cell diff --git a/src/sql/parts/notebook/models/notebookModel.ts b/src/sql/parts/notebook/models/notebookModel.ts index 17520a2899..cd2d08f745 100644 --- a/src/sql/parts/notebook/models/notebookModel.ts +++ b/src/sql/parts/notebook/models/notebookModel.ts @@ -79,6 +79,7 @@ export class NotebookModel extends Disposable implements INotebookModel { private readonly _nbformatMinor: number = nbversion.MINOR_VERSION; private _hadoopConnection: NotebookConnection; private _defaultKernel: nb.IKernelSpec; + private _activeCell: ICellModel; constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) { super(); @@ -207,17 +208,25 @@ export class NotebookModel extends Disposable implements INotebookModel { } } - public addCell(cellType: CellType): void { - if (this.inErrorState || !this._cells) { - return; - } - let cell = this.createCell(cellType); - this._cells.push(cell); - this._contentChangedEmitter.fire({ - changeType: NotebookChangeType.CellsAdded, - cells: [cell] - }); - } + public addCell(cellType: CellType, index?: number): void { + if (this.inErrorState || !this._cells) { + return; + } + let cell = this.createCell(cellType); + + if (index !== undefined && index !== null && index >= 0 && index < this._cells.length) { + this._cells.splice(index, 0, cell); + } else { + this._cells.push(cell); + index = undefined; + } + + this._contentChangedEmitter.fire({ + changeType: NotebookChangeType.CellsAdded, + cells: [cell], + cellIndex: index + }); + } private createCell(cellType: CellType): ICellModel { let singleCell: nb.ICell = { @@ -229,7 +238,7 @@ export class NotebookModel extends Disposable implements INotebookModel { return this.notebookOptions.factory.createCell(singleCell, { notebook: this, isTrusted: true }); } - deleteCell(cellModel: CellModel): void { + deleteCell(cellModel: ICellModel): void { if (this.inErrorState || !this._cells) { return; } @@ -246,6 +255,14 @@ export class NotebookModel extends Disposable implements INotebookModel { } } + public get activeCell(): ICellModel { + return this._activeCell; + } + + public set activeCell(value: ICellModel) { + this._activeCell = value; + } + private notifyError(error: string): void { this.onErrorEmitter.fire({ message: error, severity: Severity.Error }); } diff --git a/src/sql/parts/notebook/notebook.component.html b/src/sql/parts/notebook/notebook.component.html index af172b0de0..274c3ed078 100644 --- a/src/sql/parts/notebook/notebook.component.html +++ b/src/sql/parts/notebook/notebook.component.html @@ -10,7 +10,7 @@
- + diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index 6e4a67037e..5c05c767e6 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -79,6 +79,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit { this.doLoad(); } + public get model(): NotebookModel { + return this._model; + } + public get modelRegistered(): Promise { return this._modelRegisteredDeferred.promise; } @@ -99,6 +103,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit { } this._activeCell = cell; this._activeCell.active = true; + this._model.activeCell = this._activeCell; this._changeRef.detectChanges(); } }