diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts index f7511cd75ca..57e5b207495 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts @@ -128,7 +128,14 @@ export class NotebookComponent extends MessageListenersManager implements OnInit return; } const definedNote = this.note; - definedNote.paragraphs = definedNote.paragraphs.filter(p => p.id !== data.id); + const paragraphIndex = definedNote.paragraphs.findIndex(p => p.id === data.id); + definedNote.paragraphs = definedNote.paragraphs.filter((p, index) => index !== paragraphIndex); + const adjustedCursorIndex = + paragraphIndex === definedNote.paragraphs.length ? paragraphIndex - 1 : paragraphIndex + 1; + const targetParagraph = this.listOfNotebookParagraphComponent.find((_, index) => index === adjustedCursorIndex); + if (targetParagraph) { + targetParagraph.focusEditor(); + } this.cdr.markForCheck(); } @@ -142,15 +149,11 @@ export class NotebookComponent extends MessageListenersManager implements OnInit return; } const definedNote = this.note; - definedNote.paragraphs.splice(data.index, 0, data.paragraph).map(p => { - return { - ...p, - focus: p.id === data.paragraph.id - }; - }); - definedNote.paragraphs = [...definedNote.paragraphs]; + definedNote.paragraphs.splice(data.index, 0, data.paragraph); + const paragraphIndex = definedNote.paragraphs.findIndex(p => p.id === data.paragraph.id); + + definedNote.paragraphs[paragraphIndex].focus = true; this.cdr.markForCheck(); - // TODO(hsuanxyz) focus on paragraph } @MessageListener(OP.SAVE_NOTE_FORMS) diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts index 5cf9d2623f0..fdbc3f37d02 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts @@ -23,7 +23,7 @@ import { Output, SimpleChanges } from '@angular/core'; -import { editor as MonacoEditor, IDisposable, KeyCode } from 'monaco-editor'; +import { editor as MonacoEditor, IDisposable, IPosition, KeyCode, Position } from 'monaco-editor'; import { InterpreterBindingItem } from '@zeppelin/sdk'; import { CompletionService, MessageService } from '@zeppelin/services'; @@ -41,8 +41,7 @@ type IEditor = MonacoEditor.IEditor; changeDetection: ChangeDetectionStrategy.OnPush }) export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestroy, AfterViewInit { - // TODO(hsuanxyz): - // 1. cursor position + @Input() position: IPosition | null = null; @Input() readOnly = false; @Input() language = 'text'; @Input() paragraphControl!: NotebookParagraphControlComponent; @@ -83,7 +82,11 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro editor.onDidBlurEditorText(() => { this.editorBlur.emit(); }), - + editor.onDidChangeCursorPosition(e => { + this.ngZone.run(() => { + this.position = e.position; + }); + }), editor.onDidChangeModelContent(() => { this.ngZone.run(() => { const model = editor.getModel(); @@ -175,6 +178,35 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro } } + setCursorPosition({ lineNumber, column }: IPosition) { + if (this.editor) { + this.editor.setPosition({ lineNumber, column }); + } + } + + setRestorePosition() { + if (this.editor) { + const previousPosition = this.position ?? { lineNumber: 0, column: 0 }; + this.setCursorPosition(previousPosition); + this.editor.focus(); + } + } + + setCursorPositionToBeginning() { + if (this.editor) { + this.setCursorPosition({ lineNumber: 0, column: 0 }); + this.editor.focus(); + } + } + + setCursorPositionToEnd() { + if (this.editor) { + const lineNumber = this.editor.getModel()?.getLineCount() ?? 0; + const column = this.editor.getModel()?.getLineMaxColumn(lineNumber) ?? 0; + this.setCursorPosition({ lineNumber, column }); + } + } + initializedEditor(editor: IEditor) { this.editor = editor as IStandaloneCodeEditor; this.editor.addCommand( diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts index d740b9317f8..bdd6adfb059 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts @@ -64,7 +64,7 @@ type Mode = 'edit' | 'command'; }) export class NotebookParagraphComponent extends ParagraphBase implements OnInit, OnChanges, OnDestroy, AfterViewInit { @ViewChild(NotebookParagraphCodeEditorComponent, { static: false }) - notebookParagraphCodeEditorComponent!: NotebookParagraphCodeEditorComponent; + notebookParagraphCodeEditorComponent?: NotebookParagraphCodeEditorComponent; @ViewChildren(NotebookParagraphResultComponent) notebookParagraphResultComponents!: QueryList< NotebookParagraphResultComponent >; @@ -180,15 +180,22 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, nzContent: `All the paragraphs can't be deleted` }); } else { - this.nzModalService.confirm({ - nzTitle: 'Delete Paragraph', - nzContent: 'Do you want to delete this paragraph?', - nzOnOk: () => { - this.messageService.paragraphRemove(this.paragraph.id); - this.cdr.markForCheck(); - // TODO(hsuanxyz) moveFocusToNextParagraph - } - }); + this.nzModalService + .confirm({ + nzTitle: 'Delete Paragraph', + nzContent: 'Do you want to delete this paragraph?', + nzAutofocus: null, + nzOnOk: () => true + }) + .afterClose.pipe(takeUntil(this.destroy$)) + .subscribe(result => { + // In the modal, clicking "Cancel" makes result undefined. + // Clicking "OK" makes result defined and passes the condition below. + if (result) { + this.messageService.paragraphRemove(this.paragraph.id); + this.cdr.markForCheck(); + } + }); } } } @@ -206,14 +213,19 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, params: p.settings.params }; }); - this.nzModalService.confirm({ - nzTitle: 'Run all above?', - nzContent: 'Are you sure to run all above paragraphs?', - nzOnOk: () => { - this.messageService.runAllParagraphs(this.note.id, paragraphs); - } - }); - // TODO(hsuanxyz): save cursor + this.nzModalService + .confirm({ + nzTitle: 'Run all above?', + nzContent: 'Are you sure to run all above paragraphs?', + nzOnOk: () => { + this.messageService.runAllParagraphs(this.note.id, paragraphs); + } + }) + .afterClose.pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.waitConfirmFromEdit = false; + this.notebookParagraphCodeEditorComponent?.setRestorePosition(); + }); } doubleClickParagraph() { @@ -223,7 +235,9 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, if (this.paragraph.config.editorSetting.editOnDblClick && this.revisionView !== true) { this.paragraph.config.editorHide = false; this.paragraph.config.tableHide = true; - // TODO(hsuanxyz): focus editor + this.focusEditor(); + this.cdr.detectChanges(); + this.notebookParagraphCodeEditorComponent?.setCursorPositionToEnd(); } } @@ -251,8 +265,8 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, .afterClose.pipe(takeUntil(this.destroy$)) .subscribe(() => { this.waitConfirmFromEdit = false; + this.notebookParagraphCodeEditorComponent?.setRestorePosition(); }); - // TODO(hsuanxyz): save cursor } cloneParagraph(position: string = 'below', newText?: string) {