mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Enable keyboard navigation in notebooks (#18176)
This commit is contained in:
@@ -231,7 +231,6 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
|
|||||||
this._register(this._editorInput);
|
this._register(this._editorInput);
|
||||||
this._register(this._editorModel.onDidChangeContent(e => {
|
this._register(this._editorModel.onDidChangeContent(e => {
|
||||||
this.cellModel.modelContentChangedEvent = e;
|
this.cellModel.modelContentChangedEvent = e;
|
||||||
|
|
||||||
let originalSourceLength = this.cellModel.source.length;
|
let originalSourceLength = this.cellModel.source.length;
|
||||||
this.cellModel.source = this._editorModel.getValue();
|
this.cellModel.source = this._editorModel.getValue();
|
||||||
if (this._cellModel.isCollapsed && originalSourceLength !== this.cellModel.source.length) {
|
if (this._cellModel.isCollapsed && originalSourceLength !== this.cellModel.source.length) {
|
||||||
@@ -269,6 +268,10 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
this._layoutEmitter.fire();
|
this._layoutEmitter.fire();
|
||||||
}));
|
}));
|
||||||
|
this._register(this.cellModel.onCellModeChanged((isEditMode) => {
|
||||||
|
this.onCellModeChanged(isEditMode);
|
||||||
|
}));
|
||||||
|
|
||||||
this.layout();
|
this.layout();
|
||||||
|
|
||||||
if (this._cellModel.isCollapsed) {
|
if (this._cellModel.isCollapsed) {
|
||||||
@@ -421,4 +424,16 @@ export class CodeComponent extends CellView implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
this._editor.setHeightToScrollHeight(false, isCollapsed);
|
this._editor.setHeightToScrollHeight(false, isCollapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onCellModeChanged(isEditMode: boolean): void {
|
||||||
|
if (this.cellModel.id === this._activeCellId || this._activeCellId === '') {
|
||||||
|
if (isEditMode) {
|
||||||
|
this._editor.getContainer().contentEditable = 'true';
|
||||||
|
this._editor.getControl().focus();
|
||||||
|
} else {
|
||||||
|
this._editor.getContainer().contentEditable = 'false';
|
||||||
|
(document.activeElement as HTMLElement).blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { ICellEditorProvider } from 'sql/workbench/services/notebook/browser/not
|
|||||||
import { CodeComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/code.component';
|
import { CodeComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/code.component';
|
||||||
import { OutputAreaComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/outputArea.component';
|
import { OutputAreaComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/outputArea.component';
|
||||||
|
|
||||||
|
|
||||||
export const CODE_SELECTOR: string = 'code-cell-component';
|
export const CODE_SELECTOR: string = 'code-cell-component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -32,10 +31,9 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
this._activeCellId = value;
|
this._activeCellId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:keydown.escape', ['$event'])
|
// On click to edit code cell in notebook
|
||||||
handleKeyboardEvent() {
|
@HostListener('click', ['$event']) onClick() {
|
||||||
this.cellModel.active = false;
|
this.toggleEditMode();
|
||||||
this._model.updateActiveCell(undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _activeCellId: string;
|
private _activeCellId: string;
|
||||||
@@ -60,6 +58,11 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
this._register(this.cellModel.onOutputsChanged(() => {
|
this._register(this.cellModel.onOutputsChanged(() => {
|
||||||
this._changeRef.detectChanges();
|
this._changeRef.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
this._register(this.cellModel.onCellModeChanged(mode => {
|
||||||
|
if (mode !== this.cellModel.isEditMode) {
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}
|
||||||
|
}));
|
||||||
// Register request handler, cleanup on dispose of this component
|
// Register request handler, cleanup on dispose of this component
|
||||||
this.cellModel.setStdInHandler({ handle: (msg) => this.handleStdIn(msg) });
|
this.cellModel.setStdInHandler({ handle: (msg) => this.handleStdIn(msg) });
|
||||||
this._register({ dispose: () => this.cellModel.setStdInHandler(undefined) });
|
this._register({ dispose: () => this.cellModel.setStdInHandler(undefined) });
|
||||||
@@ -99,6 +102,11 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toggleEditMode(): void {
|
||||||
|
this.cellModel.isEditMode = !this.cellModel.isEditMode;
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
handleStdIn(msg: nb.IStdinMessage): void | Thenable<void> {
|
handleStdIn(msg: nb.IStdinMessage): void | Thenable<void> {
|
||||||
if (msg) {
|
if (msg) {
|
||||||
this.stdIn = msg;
|
this.stdIn = msg;
|
||||||
|
|||||||
@@ -53,15 +53,6 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
this._activeCellId = value;
|
this._activeCellId = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:keydown.escape', ['$event'])
|
|
||||||
handleKeyboardEvent() {
|
|
||||||
if (this.isEditMode) {
|
|
||||||
this.toggleEditMode(false);
|
|
||||||
}
|
|
||||||
this.cellModel.active = false;
|
|
||||||
this._model.updateActiveCell(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double click to edit text cell in notebook
|
// Double click to edit text cell in notebook
|
||||||
@HostListener('dblclick', ['$event']) onDblClick() {
|
@HostListener('dblclick', ['$event']) onDblClick() {
|
||||||
this.enableActiveCellEditOnDoubleClick();
|
this.enableActiveCellEditOnDoubleClick();
|
||||||
@@ -70,8 +61,8 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
@HostListener('document:keydown', ['$event'])
|
@HostListener('document:keydown', ['$event'])
|
||||||
onkeydown(e: KeyboardEvent) {
|
onkeydown(e: KeyboardEvent) {
|
||||||
if (DOM.getActiveElement() === this.output?.nativeElement && this.isActive() && this.cellModel?.currentMode === CellEditModes.WYSIWYG) {
|
if (DOM.getActiveElement() === this.output?.nativeElement && this.isActive() && this.cellModel?.currentMode === CellEditModes.WYSIWYG) {
|
||||||
// Select all text
|
|
||||||
const keyEvent = new StandardKeyboardEvent(e);
|
const keyEvent = new StandardKeyboardEvent(e);
|
||||||
|
// Select all text
|
||||||
if ((keyEvent.ctrlKey || keyEvent.metaKey) && keyEvent.keyCode === KeyCode.KEY_A) {
|
if ((keyEvent.ctrlKey || keyEvent.metaKey) && keyEvent.keyCode === KeyCode.KEY_A) {
|
||||||
preventDefaultAndExecCommand(e, 'selectAll');
|
preventDefaultAndExecCommand(e, 'selectAll');
|
||||||
} else if ((keyEvent.metaKey && keyEvent.shiftKey && keyEvent.keyCode === KeyCode.KEY_Z) || (keyEvent.ctrlKey && keyEvent.keyCode === KeyCode.KEY_Y) && !this.markdownMode) {
|
} else if ((keyEvent.metaKey && keyEvent.shiftKey && keyEvent.keyCode === KeyCode.KEY_Z) || (keyEvent.ctrlKey && keyEvent.keyCode === KeyCode.KEY_Y) && !this.markdownMode) {
|
||||||
@@ -557,7 +548,6 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
if (!this.isEditMode && this.doubleClickEditEnabled) {
|
if (!this.isEditMode && this.doubleClickEditEnabled) {
|
||||||
this.toggleEditMode(true);
|
this.toggleEditMode(true);
|
||||||
}
|
}
|
||||||
this.cellModel.active = true;
|
|
||||||
this._model.updateActiveCell(this.cellModel);
|
this._model.updateActiveCell(this.cellModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<cell-toolbar-component *ngIf="cell.active" [cellModel]="cell" [model]="model"></cell-toolbar-component>
|
<cell-toolbar-component *ngIf="cell.active" [cellModel]="cell" [model]="model"></cell-toolbar-component>
|
||||||
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
||||||
</code-cell-component>
|
</code-cell-component>
|
||||||
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId" (dblclick)="enableActiveCellIconOnDoubleClick()">
|
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId" (dblclick)="enableActiveCellEditIconOnDoubleClick()">
|
||||||
</text-cell-component>
|
</text-cell-component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ import { MaskedLabeledMenuItemActionItem } from 'sql/platform/actions/browser/me
|
|||||||
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
import { Emitter } from 'vs/base/common/event';
|
import { Emitter } from 'vs/base/common/event';
|
||||||
import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
|
import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
|
||||||
|
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||||
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||||
|
|
||||||
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
||||||
const PRIORITY = 105;
|
const PRIORITY = 105;
|
||||||
@@ -131,6 +133,40 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}));
|
}));
|
||||||
|
this._register(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||||
|
let event = new StandardKeyboardEvent(e);
|
||||||
|
if (this.isActive() && this.model.activeCell) {
|
||||||
|
if (!this.model.activeCell?.isEditMode) {
|
||||||
|
if (event.keyCode === KeyCode.DownArrow) {
|
||||||
|
let next = (this.findCellIndex(this.model.activeCell) + 1) % this.cells.length;
|
||||||
|
this.selectCell(this.cells[next]);
|
||||||
|
this.scrollToActiveCell();
|
||||||
|
} else if (event.keyCode === KeyCode.UpArrow) {
|
||||||
|
let index = this.findCellIndex(this.model.activeCell);
|
||||||
|
if (index === 0) {
|
||||||
|
index = this.cells.length;
|
||||||
|
}
|
||||||
|
this.selectCell(this.cells[--index]);
|
||||||
|
this.scrollToActiveCell();
|
||||||
|
} else if (event.keyCode === KeyCode.Escape) {
|
||||||
|
// unselects active cell and removes the focus from code cells
|
||||||
|
this.unselectActiveCell();
|
||||||
|
(document.activeElement as HTMLElement).blur();
|
||||||
|
}
|
||||||
|
else if (event.keyCode === KeyCode.Enter) {
|
||||||
|
// prevents adding a newline to the cell source
|
||||||
|
e.preventDefault();
|
||||||
|
// show edit toolbar
|
||||||
|
this.setActiveCellEditActionMode(true);
|
||||||
|
this.toggleEditMode();
|
||||||
|
}
|
||||||
|
} else if (event.keyCode === KeyCode.Escape) {
|
||||||
|
// first time hitting escape removes the cursor from code cell and changes toolbar in text cells and changes edit mode to false
|
||||||
|
this.toggleEditMode();
|
||||||
|
this.setActiveCellEditActionMode(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -248,6 +284,23 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scrollToActiveCell(): void {
|
||||||
|
// Get active cell from active notebook editor
|
||||||
|
const activeCellElement = document.querySelector(`.editor-group-container.active .notebook-cell.active`);
|
||||||
|
activeCellElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleEditMode(): void {
|
||||||
|
let selectedCell: TextCellComponent | CodeCellComponent = undefined;
|
||||||
|
if (this.model.activeCell.cellType !== CellTypes.Code) {
|
||||||
|
selectedCell = this.textCells.find(c => c.cellModel.id === this.activeCellId);
|
||||||
|
} else {
|
||||||
|
selectedCell = this.codeCells.find(c => c.cellModel.id === this.activeCellId);
|
||||||
|
}
|
||||||
|
selectedCell.toggleEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
//Saves scrollTop value on scroll change
|
//Saves scrollTop value on scroll change
|
||||||
public scrollHandler(event: Event) {
|
public scrollHandler(event: Event) {
|
||||||
this._scrollTop = (<HTMLElement>event.srcElement).scrollTop;
|
this._scrollTop = (<HTMLElement>event.srcElement).scrollTop;
|
||||||
@@ -261,16 +314,18 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
|
|
||||||
// Handles double click to edit icon change
|
// Handles double click to edit icon change
|
||||||
// See textcell.component.ts for changing edit behavior
|
// See textcell.component.ts for changing edit behavior
|
||||||
public enableActiveCellIconOnDoubleClick() {
|
public enableActiveCellEditIconOnDoubleClick() {
|
||||||
if (this.doubleClickEditEnabled) {
|
if (this.doubleClickEditEnabled) {
|
||||||
const toolbarComponent = (<CellToolbarComponent>this.cellToolbar.first);
|
this.setActiveCellEditActionMode(true);
|
||||||
const toolbarEditCellAction = toolbarComponent.getEditCellAction();
|
|
||||||
if (!toolbarEditCellAction.editMode) {
|
|
||||||
toolbarEditCellAction.editMode = !toolbarEditCellAction.editMode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setActiveCellEditActionMode(editMode: boolean) {
|
||||||
|
const toolbarComponent = (<CellToolbarComponent>this.cellToolbar.first);
|
||||||
|
const toolbarEditCellAction = toolbarComponent.getEditCellAction();
|
||||||
|
toolbarEditCellAction.editMode = editMode;
|
||||||
|
}
|
||||||
|
|
||||||
// Add cell based on cell type
|
// Add cell based on cell type
|
||||||
public addCell(cellType: CellType, index?: number, event?: Event) {
|
public addCell(cellType: CellType, index?: number, event?: Event) {
|
||||||
if (event) {
|
if (event) {
|
||||||
|
|||||||
@@ -769,9 +769,9 @@ suite('Cell Model', function (): void {
|
|||||||
cell.trustedMode = true;
|
cell.trustedMode = true;
|
||||||
assert.strictEqual(cell.trustedMode, true, 'Cell should be trusted after manually setting trustedMode');
|
assert.strictEqual(cell.trustedMode, true, 'Cell should be trusted after manually setting trustedMode');
|
||||||
|
|
||||||
assert.strictEqual(cell.isEditMode, true, 'Code cells should be editable by default');
|
assert.strictEqual(cell.isEditMode, false, 'Code cells should not be editable by default');
|
||||||
cell.isEditMode = false;
|
cell.isEditMode = true;
|
||||||
assert.strictEqual(cell.isEditMode, false, 'Cell should not be editable after manually setting isEditMode');
|
assert.strictEqual(cell.isEditMode, true, 'Cell should be editable after manually setting isEditMode');
|
||||||
|
|
||||||
cell.hover = true;
|
cell.hover = true;
|
||||||
assert.strictEqual(cell.hover, true, 'Cell should be hovered after manually setting hover=true');
|
assert.strictEqual(cell.hover, true, 'Cell should be hovered after manually setting hover=true');
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export class CellModel extends Disposable implements ICellModel {
|
|||||||
this._source = '';
|
this._source = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this._isEditMode = this._cellType !== CellTypes.Markdown;
|
this._isEditMode = false;
|
||||||
this._stdInVisible = false;
|
this._stdInVisible = false;
|
||||||
if (_options && _options.isTrusted) {
|
if (_options && _options.isTrusted) {
|
||||||
this._isTrusted = true;
|
this._isTrusted = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user