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.
This commit is contained in:
Yurong He
2018-11-14 17:51:59 -08:00
committed by GitHub
parent db3bb82dbd
commit e607f68b3e
10 changed files with 174 additions and 19 deletions

View File

@@ -9,4 +9,6 @@
</div>
<div #editor class="editor" style="flex: 1 1 auto; overflow: hidden;">
</div>
<div #moreactions class="toolbar" style="flex: 0 0 auto; display: flex; flex-flow:column; width: 20px; min-height: 20px; max-height: 20px; padding-top: 10px; orientation: portrait">
</div>
</div>

View File

@@ -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<void>();
@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 = <HTMLElement>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 = <HTMLElement>this.toolbarElement.nativeElement;
toolbarEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
let moreactionsEl = <HTMLElement>this.moreactionsElement.nativeElement;
moreactionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
}

View File

@@ -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

View File

@@ -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<boolean> {
@@ -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<boolean> {
return new TPromise<boolean>((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<boolean> {
return new TPromise<boolean>((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
});
}
});
}
}

View File

@@ -6,7 +6,7 @@
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div class="notebook-code" style="flex: 0 0 auto;">
<code-component [cellModel]="cellModel"></code-component>
<code-component [cellModel]="cellModel" [model]="model"></code-component>
</div>
<div class="notebook-output" style="flex: 0 0 auto;">
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel">

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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 });
}

View File

@@ -10,7 +10,7 @@
<div class="scrollable" style="flex: 1 1 auto; position: relative">
<loading-spinner [loading]="isLoading"></loading-spinner>
<div class="notebook-cell" *ngFor="let cell of cells" (click)="selectCell(cell)" [class.active]="cell.active" (keydown)="onKeyDown($event)">
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell">
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model">
</code-cell-component>
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell">
</text-cell-component>

View File

@@ -79,6 +79,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
this.doLoad();
}
public get model(): NotebookModel {
return this._model;
}
public get modelRegistered(): Promise<NotebookModel> {
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();
}
}