This is an automated email from the ASF dual-hosted git repository. chanholee pushed a commit to branch branch-0.12 in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/branch-0.12 by this push: new 356bf8d6a5 [ZEPPELIN-6197] 6229] Fixed the shortcuts in the New UI so that they match the Classic UI and work correctly 356bf8d6a5 is described below commit 356bf8d6a5ccceeb0e2c3d6ab3d82e2858878f50 Author: YONGJAE LEE(이용재) <dev.yongjae...@gmail.com> AuthorDate: Sun Sep 7 13:03:34 2025 +0900 [ZEPPELIN-6197] 6229] Fixed the shortcuts in the New UI so that they match the Classic UI and work correctly ### What is this PR for? Unlike the Classic UI, the New UI either lacked implementations for several shortcuts or had shortcuts assigned differently from the Classic UI. I consider this a significant issue, and after addressing other related derivative issues, I am finally submitting it. #### d96ae5bde - Remove the top margin in the editor search [AS-IS] <img width="671" height="136" alt="스크린샷 2025-09-01 오후 11 26 14" src="https://github.com/user-attachments/assets/fcd0a107-4b22-42f2-978f-f0781c5fd675" /> [TO-BE] <img width="669" height="136" alt="스크린샷 2025-09-01 오후 11 26 23" src="https://github.com/user-attachments/assets/475c05e6-ffae-4410-920f-7b0a9e44388d" /> Fixed an issue where the top area increased by 33px when search was activated. <- [referece](https://github.com/microsoft/monaco-editor/blob/5a7ba61be909ae9e4889768a3453ebb0dec392e2/monaco-editor/typedoc/monaco.d.ts#L3473) #### 0d24eb420 - Handle focus on PARAGRAPH_MOVED receive When performing "Move Paragraph Up" or "Move Paragraph Down," the paragraph moves up or down after receiving `PARAGRAPH_MOVED`. During this process, focus was being skipped, so I added it. #### 1d3d294bc - Overriding shortcuts conflict with the editor In the New UI, we use the Monaco editor, where reserved shortcuts had to be explicitly overridden. I added shortcut functionality for four actions: "Toggle Editor", "Cut the Line", "Paste the Line", and "Search Inside the Code". #### 57f45e1e8 - Make action bar searchCode accessible to siblings There are `paragraph.component.ts` and `action-bar.component.ts` that share `notebook.component.ts` as their parent. In the Classic UI, "Find in Code" is a shortcut that activates the search button in the action-bar. Although this functionality is currently marked as [TODO](https://github.com/dididy/zeppelin/blob/fix/ZEPPELIN-6197/6229/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.ts#L220-L222)([ZEPPELIN-6279](https://issues.apache.org/jira/secu [...] #### b027a33 - Ensure shortcut keys work correctly <img width="605" height="1088" alt="image" src="https://github.com/user-attachments/assets/2c4bc264-07e5-4abe-93bb-a2656a86527b" /> All shortcut orders have been updated based on the Classic UI. There was logic to handle key bindings differently for macOS, but upon review, it was unnecessary and has been removed. On macOS, typing a combination of Alt and an English letter can produce a different character, so to ensure correct behavior, this resulting character combination is included in the shortcut handling. I added several actions that were not yet implemented. The distinction between command mode and edit mode for shortcuts was meaningless, so it was removed. The original implementation seemed intended to mimic a vi-like behavior, but this would have made the shortcut system different from the Classic UI. In the Classic UI, "Auto Completion" had a shortcut to trigger it manually. In the New UI, auto completion is triggered automatically when typing, so "Auto Completion" shortcut was unnecessary and has been removed in this PR. ### What type of PR is it? Bug Fix ### Todos ### What is the Jira issue? * [[ZEPPELIN-6197](https://issues.apache.org/jira/browse/ZEPPELIN-6197)] * [[ZEPPELIN-6229](https://issues.apache.org/jira/browse/ZEPPELIN-6229)] ### How should this be tested? ### Screenshots (if appropriate) ### Questions: * Does the license files need to update? N * Is there breaking changes for older versions? N * Does this needs documentation? N Closes #5064 from dididy/fix/ZEPPELIN-6197/6229. Signed-off-by: ChanHo Lee <chanho...@apache.org> (cherry picked from commit 65fd70fd3521f607eee066a7bf3ad5c71f9cd69b) Signed-off-by: ChanHo Lee <chanho...@apache.org> --- .../workspace/notebook/notebook.component.html | 2 + .../pages/workspace/notebook/notebook.component.ts | 1 + .../paragraph/code-editor/code-editor.component.ts | 82 +++++++++ .../paragraph/control/control.component.ts | 38 +++-- .../notebook/paragraph/paragraph.component.html | 1 + .../notebook/paragraph/paragraph.component.ts | 183 ++++++++++++--------- .../src/app/services/shortcut.service.ts | 91 +++++----- .../app/notebook/paragraph/paragraph-control.html | 6 +- 8 files changed, 260 insertions(+), 144 deletions(-) diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html index 9cca1743d2..301ff35e4a 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html @@ -23,6 +23,7 @@ [currentRevision]="currentRevision" (tableHideChange)="setAllParagraphTableHide($event)" (editorHideChange)="setAllParagraphEditorHide($event)" + #actionBar ></zeppelin-notebook-action-bar> <div class="flex-container"> <div @@ -87,6 +88,7 @@ (selected)="onParagraphSelect($event)" (triggerSaveParagraph)="saveParagraph($event)" (saveNoteTimer)="startSaveTimer()" + (searchCode)="actionBar.searchCode()" ></zeppelin-notebook-paragraph> </div> </div> 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 4bf9cae4f6..f7511cd75c 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 @@ -198,6 +198,7 @@ export class NotebookComponent extends MessageListenersManager implements OnInit // Call when next tick setTimeout(() => { scrollIntoViewIfNeeded(paragraphComponent.getElement()); + paragraphComponent.focusEditor(); }); } } 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 b19e91d1df..5cf9d2623f 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 @@ -57,6 +57,7 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro @Output() readonly textChanged = new EventEmitter<string>(); @Output() readonly editorBlur = new EventEmitter<void>(); @Output() readonly editorFocus = new EventEmitter<void>(); + @Output() readonly toggleEditorShow = new EventEmitter<void>(); private editor?: IStandaloneCodeEditor; private monacoDisposables: IDisposable[] = []; height = 18; @@ -108,6 +109,72 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro } } + // Handle Ctrl+Alt+E: Toggle editor show/hide + handleToggleEditorShow() { + this.toggleEditorShow.emit(); + } + + // Handle Ctrl+K: Cut current line to clipboard + async handleCutLine() { + if (!this.editor) { + return; + } + + const position = this.editor.getPosition(); + const model = this.editor.getModel(); + if (!position || !model) { + return; + } + + const lineNumber = position.lineNumber; + const lineContent = model.getLineContent(lineNumber); + + if (!lineContent) { + return; + } + + await navigator.clipboard.writeText(lineContent); + + this.editor.executeEdits('cut-line', [ + { + range: new monaco.Range(lineNumber, 1, lineNumber, lineContent.length + 1), + text: '', + forceMoveMarkers: true + } + ]); + } + + // Handle Ctrl+Y: Paste from clipboard at current position + async handlePasteFromClipboard() { + if (!this.editor) { + return; + } + + const text = await navigator.clipboard.readText(); + const position = this.editor.getPosition(); + if (position) { + this.editor.executeEdits('my-source', [ + { + range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), + text: text, + forceMoveMarkers: true + } + ]); + } + } + + // Handle Ctrl+S: Show find widget + handleShowFind() { + if (this.editor) { + this.editor.getAction('actions.find').run(); + + // Focus on the find widget input field + const findInput = document.querySelector('.find-widget .input') as HTMLInputElement; + findInput.focus(); + findInput.select(); + } + } + initializedEditor(editor: IEditor) { this.editor = editor as IStandaloneCodeEditor; this.editor.addCommand( @@ -119,6 +186,18 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro }, '!suggestWidgetVisible' ); + this.editor.addCommand(monaco.KeyMod.WinCtrl | monaco.KeyMod.Alt | monaco.KeyCode.KeyE, () => { + this.handleToggleEditorShow(); + }); + this.editor.addCommand(monaco.KeyMod.WinCtrl | monaco.KeyCode.KeyK, () => { + this.handleCutLine(); + }); + this.editor.addCommand(monaco.KeyMod.WinCtrl | monaco.KeyCode.KeyY, () => { + this.handlePasteFromClipboard(); + }); + this.editor.addCommand(monaco.KeyMod.WinCtrl | monaco.KeyCode.KeyS, () => { + this.handleShowFind(); + }); this.updateEditorOptions(this.editor); this.setParagraphMode(); @@ -161,6 +240,9 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro scrollbar: { handleMouseWheel: false, alwaysConsumeMouseWheel: false + }, + find: { + addExtraSpaceOnTop: false } }); } diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.ts index 3d39dcc1af..741c9288c0 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.ts @@ -85,6 +85,14 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { trigger(): void; }> = []; + formatShortcut(shortcut: string, isMac: boolean) { + if (isMac) { + return shortcut.replace('Alt', 'Option'); + } + + return shortcut; + } + updateListOfMenu() { this.listOfMenu = [ { @@ -93,7 +101,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'play-circle', trigger: () => this.trigger(this.runParagraph), - shortCut: this.isMac ? '⇧+⌘+Enter' : 'Shift+Ctrl+Enter' + shortCut: this.formatShortcut('Shift+Enter', this.isMac) }, { label: 'Run all above', @@ -101,7 +109,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'up-square', trigger: () => this.trigger(this.runAllAbove), - shortCut: this.isMac ? '⇧+⌘+Enter' : 'Shift+Ctrl+Enter' + shortCut: this.formatShortcut('Shift+Ctrl+Up', this.isMac) }, { label: 'Run all below', @@ -109,7 +117,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'down-square', trigger: () => this.trigger(this.runAllBelowAndCurrent), - shortCut: this.isMac ? '⇧+⌘+Enter' : 'Shift+Ctrl+Enter' + shortCut: this.formatShortcut('Shift+Ctrl+Down', this.isMac) }, { label: 'Link this paragraph', @@ -119,7 +127,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { trigger: () => { this.openSingleParagraph.emit(this.pid); }, - shortCut: this.isMac ? '⌥+⌘+T' : 'Alt+Ctrl+T' + shortCut: this.formatShortcut('Ctrl+Alt+W', this.isMac) }, { label: 'Clear output', @@ -127,7 +135,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'fire', trigger: () => this.clearParagraphOutput(), - shortCut: this.isMac ? '⌥+⌘+L' : 'Alt+Ctrl+L' + shortCut: this.formatShortcut('Ctrl+Alt+L', this.isMac) }, { label: 'Remove', @@ -135,23 +143,23 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'delete', trigger: () => this.onRemoveParagraph(), - shortCut: this.isMac ? '⇧+Del (Command)' : 'Shift+Del (Command)' + shortCut: this.formatShortcut('Ctrl+Alt+D', this.isMac) }, { - label: 'Move up', + label: 'Move paragraph up', show: !this.first, disabled: this.isEntireNoteRunning, icon: 'up', trigger: () => this.trigger(this.moveUp), - shortCut: `${this.isMac ? '⌘' : 'Ctrl'}+K (Command)` + shortCut: this.formatShortcut('Ctrl+Alt+K', this.isMac) }, { - label: 'Move down', + label: 'Move paragraph down', show: !this.last, disabled: this.isEntireNoteRunning, icon: 'down', trigger: () => this.trigger(this.moveDown), - shortCut: `${this.isMac ? '⌘' : 'Ctrl'}+J (Command)` + shortCut: this.formatShortcut('Ctrl+Alt+J', this.isMac) }, { label: 'Insert new', @@ -159,7 +167,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'plus', trigger: () => this.trigger(this.insertNew), - shortCut: `B (Command)` + shortCut: this.formatShortcut('Ctrl+Alt+B', this.isMac) }, { label: 'Clone paragraph', @@ -167,7 +175,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'copy', trigger: () => this.trigger(this.cloneParagraph), - shortCut: `C (Command)` + shortCut: this.formatShortcut('Shift+Ctrl+C', this.isMac) }, { label: this.title ? 'Hide Title' : 'Show Title', @@ -175,7 +183,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: false, icon: 'font-colors', trigger: () => this.toggleTitle(), - shortCut: `T (Command)` + shortCut: this.formatShortcut('Ctrl+Alt+T', this.isMac) }, { label: this.lineNumbers ? 'Hide line numbers' : 'Show line numbers', @@ -183,7 +191,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: false, icon: 'ordered-list', trigger: () => this.toggleLineNumbers(), - shortCut: `L (Command)` + shortCut: this.formatShortcut('Ctrl+Alt+M', this.isMac) }, { label: this.enabled ? 'Disable run' : 'Enable run', @@ -191,7 +199,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { disabled: this.isEntireNoteRunning, icon: 'api', trigger: () => this.toggleEnabled(), - shortCut: `R (Command)` + shortCut: this.formatShortcut('Ctrl+Alt+R', this.isMac) } ]; } diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.html index 5892aee551..63ecfa5639 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.html +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.html @@ -82,6 +82,7 @@ (editorBlur)="onEditorBlur()" (editorFocus)="onEditorFocus()" (textChanged)="textChanged($event)" + (toggleEditorShow)="toggleEditorShow()" ></zeppelin-notebook-paragraph-code-editor> <zeppelin-notebook-paragraph-progress *ngIf="paragraph.status === 'RUNNING'" 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 1d9cb4675f..d740b9317f 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 @@ -84,6 +84,7 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, @Output() readonly triggerSaveParagraph = new EventEmitter<string>(); @Output() readonly selected = new EventEmitter<string>(); @Output() readonly selectAtIndex = new EventEmitter<number>(); + @Output() readonly searchCode = new EventEmitter(); private destroy$ = new Subject(); private mode: Mode = 'command'; @@ -155,6 +156,11 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, } } + toggleEditorShow() { + this.setEditorHide(!this.paragraph.config.editorHide); + this.commitParagraph(); + } + saveParagraph() { const dirtyText = this.paragraph.text; if (dirtyText === undefined || dirtyText === this.originalText) { @@ -356,7 +362,7 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, this.cdr.markForCheck(); } - moveUpParagraph() { + moveCursorUp() { const newIndex = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id) - 1; if (newIndex < 0 || newIndex >= this.note.paragraphs.length) { return; @@ -369,7 +375,7 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, this.messageService.moveParagraph(this.paragraph.id, newIndex); } - moveDownParagraph() { + moveCursorDown() { const newIndex = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id) + 1; if (newIndex < 0 || newIndex >= this.note.paragraphs.length) { return; @@ -382,6 +388,28 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, this.messageService.moveParagraph(this.paragraph.id, newIndex); } + moveParagraphUp() { + const newIndex = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id) - 1; + if (newIndex < 0 || newIndex >= this.note.paragraphs.length) { + return; + } + this.messageService.moveParagraph(this.paragraph.id, newIndex); + } + + moveParagraphDown() { + const newIndex = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id) + 1; + if (newIndex < 0 || newIndex >= this.note.paragraphs.length) { + return; + } + this.messageService.moveParagraph(this.paragraph.id, newIndex); + } + + clearParagraphOutput() { + if (!this.isEntireNoteRunning) { + this.messageService.paragraphClearOutput(this.paragraph.id); + } + } + changeColWidth(needCommit: boolean, updateResult = true) { if (needCommit) { this.commitParagraph(); @@ -485,89 +513,15 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, return; // ignore shortcut to make input work } - if (this.mode === 'command') { - switch (action) { - case ParagraphActions.InsertAbove: - this.insertParagraph('above'); - break; - case ParagraphActions.InsertBelow: - this.insertParagraph('below'); - break; - case ParagraphActions.SwitchEditorShow: - this.setEditorHide(!this.paragraph.config.editorHide); - this.commitParagraph(); - break; - case ParagraphActions.SwitchOutputShow: - this.setTableHide(!this.paragraph.config.tableHide); - this.commitParagraph(); - break; - case ParagraphActions.SwitchTitleShow: - this.paragraph.config.title = !this.paragraph.config.title; - this.commitParagraph(); - break; - case ParagraphActions.SwitchLineNumber: - this.paragraph.config.lineNumbers = !this.paragraph.config.lineNumbers; - this.commitParagraph(); - break; - case ParagraphActions.MoveToUp: - event.preventDefault(); - this.moveUpParagraph(); - break; - case ParagraphActions.MoveToDown: - event.preventDefault(); - this.moveDownParagraph(); - break; - case ParagraphActions.SwitchEnable: - this.paragraph.config.enabled = !this.paragraph.config.enabled; - this.commitParagraph(); - break; - case ParagraphActions.ReduceWidth: - if (!this.paragraph.config.colWidth) { - throw new Error('colWidth is required'); - } - this.paragraph.config.colWidth = Math.max(1, this.paragraph.config.colWidth - 1); - this.cdr.markForCheck(); - this.changeColWidth(true); - break; - case ParagraphActions.IncreaseWidth: - if (!this.paragraph.config.colWidth) { - throw new Error('colWidth is required'); - } - this.paragraph.config.colWidth = Math.min(12, this.paragraph.config.colWidth + 1); - this.cdr.markForCheck(); - this.changeColWidth(true); - break; - case ParagraphActions.Delete: - this.removeParagraph(); - break; - case ParagraphActions.SelectAbove: - event.preventDefault(); - this.selectAtIndex.emit(this.index - 1); - break; - case ParagraphActions.SelectBelow: - event.preventDefault(); - this.selectAtIndex.emit(this.index + 1); - break; - default: - break; - } - } switch (action) { - case ParagraphActions.Link: - this.openSingleParagraph(this.paragraph.id); - break; - case ParagraphActions.EditMode: - if (this.mode === 'command') { - event.preventDefault(); - } - if (!this.paragraph.config.editorHide) { - this.switchMode('edit'); - } - break; case ParagraphActions.Run: event.preventDefault(); this.runParagraph(); break; + case ParagraphActions.RunAbove: + this.waitConfirmFromEdit = true; + this.runAllAbove(); + break; case ParagraphActions.RunBelow: this.waitConfirmFromEdit = true; this.runAllBelowAndCurrent(); @@ -576,6 +530,75 @@ export class NotebookParagraphComponent extends ParagraphBase implements OnInit, event.preventDefault(); this.cancelParagraph(); break; + case ParagraphActions.MoveCursorUp: + event.preventDefault(); + this.moveCursorUp(); + break; + case ParagraphActions.MoveCursorDown: + event.preventDefault(); + this.moveCursorDown(); + break; + case ParagraphActions.Delete: + this.removeParagraph(); + break; + case ParagraphActions.InsertAbove: + this.insertParagraph('above'); + break; + case ParagraphActions.InsertBelow: + this.insertParagraph('below'); + break; + case ParagraphActions.InsertCopyOfParagraphBelow: + this.cloneParagraph('below'); + break; + case ParagraphActions.MoveParagraphUp: + event.preventDefault(); + this.moveParagraphUp(); + break; + case ParagraphActions.MoveParagraphDown: + event.preventDefault(); + this.moveParagraphDown(); + break; + case ParagraphActions.SwitchEnable: + this.paragraph.config.enabled = !this.paragraph.config.enabled; + this.commitParagraph(); + break; + case ParagraphActions.SwitchOutputShow: + this.setTableHide(!this.paragraph.config.tableHide); + this.commitParagraph(); + break; + case ParagraphActions.SwitchLineNumber: + this.paragraph.config.lineNumbers = !this.paragraph.config.lineNumbers; + this.commitParagraph(); + break; + case ParagraphActions.SwitchTitleShow: + this.paragraph.config.title = !this.paragraph.config.title; + this.commitParagraph(); + break; + case ParagraphActions.Clear: + this.clearParagraphOutput(); + break; + case ParagraphActions.Link: + this.openSingleParagraph(this.paragraph.id); + break; + case ParagraphActions.ReduceWidth: + if (!this.paragraph.config.colWidth) { + throw new Error('colWidth is required'); + } + this.paragraph.config.colWidth = Math.max(1, this.paragraph.config.colWidth - 1); + this.cdr.markForCheck(); + this.changeColWidth(true); + break; + case ParagraphActions.IncreaseWidth: + if (!this.paragraph.config.colWidth) { + throw new Error('colWidth is required'); + } + this.paragraph.config.colWidth = Math.min(12, this.paragraph.config.colWidth + 1); + this.cdr.markForCheck(); + this.changeColWidth(true); + break; + case ParagraphActions.FindInCode: + this.searchCode.emit(); + break; default: break; } diff --git a/zeppelin-web-angular/src/app/services/shortcut.service.ts b/zeppelin-web-angular/src/app/services/shortcut.service.ts index 135de34388..f3cdb96620 100644 --- a/zeppelin-web-angular/src/app/services/shortcut.service.ts +++ b/zeppelin-web-angular/src/app/services/shortcut.service.ts @@ -16,56 +16,61 @@ import { EventManager } from '@angular/platform-browser'; import { Observable } from 'rxjs'; export enum ParagraphActions { - EditMode = 'Paragraph:EditMode', - CommandMode = 'Paragraph:CommandMode', Run = 'Paragraph:Run', + RunAbove = 'Paragraph:RunAbove', RunBelow = 'Paragraph:RunBelow', Cancel = 'Paragraph:Cancel', - Clear = 'Paragraph:Clear', - ReduceWidth = 'Paragraph:ReduceWidth', - IncreaseWidth = 'Paragraph:IncreaseWidth', + MoveCursorUp = 'Paragraph:MoveCursorUp', + MoveCursorDown = 'Paragraph:MoveCursorDown', Delete = 'Paragraph:Delete', - MoveToUp = 'Paragraph:MoveToUp', - MoveToDown = 'Paragraph:MoveToDown', - SelectAbove = 'Paragraph:SelectAbove', - SelectBelow = 'Paragraph:SelectBelow', InsertAbove = 'Paragraph:InsertAbove', InsertBelow = 'Paragraph:InsertBelow', + InsertCopyOfParagraphBelow = 'Paragraph:InsertCopyOfParagraphBelow', + MoveParagraphUp = 'Paragraph:MoveParagraphUp', + MoveParagraphDown = 'Paragraph:MoveParagraphDown', + SwitchEnable = 'Paragraph:SwitchEnable', + SwitchOutputShow = 'Paragraph:SwitchOutputShow', SwitchLineNumber = 'Paragraph:SwitchLineNumber', SwitchTitleShow = 'Paragraph:SwitchTitleShow', - SwitchOutputShow = 'Paragraph:SwitchOutputShow', - SwitchEditorShow = 'Paragraph:SwitchEditorShow', - SwitchEnable = 'Paragraph:SwitchEnable', - Link = 'Paragraph:Link' + Clear = 'Paragraph:Clear', + Link = 'Paragraph:Link', + ReduceWidth = 'Paragraph:ReduceWidth', + IncreaseWidth = 'Paragraph:IncreaseWidth', + FindInCode = 'Paragraph:FindInCode' } +// On macOS, pressing Option(Alt) + a letter produces a non-ASCII character +// Shortcuts must use this resulting character instead of the plain letter for macOS export const ShortcutsMap = { - [ParagraphActions.EditMode]: 'enter', - [ParagraphActions.CommandMode]: 'esc', - [ParagraphActions.Run]: 'shift.enter', - [ParagraphActions.RunBelow]: 'shift.ctrlCmd.enter', - [ParagraphActions.Cancel]: 'shift.ctrlCmd.c', - // Need register special character `¬` in MacOS - [ParagraphActions.Clear]: ['alt.ctrlCmd.l', 'alt.ctrlCmd.¬'], - // Need register special character `†` in MacOS - [ParagraphActions.Link]: ['alt.ctrlCmd.t', 'alt.ctrlCmd.†'], - // Need register special character `®` in MacOS - [ParagraphActions.SwitchEnable]: ['alt.ctrlCmd.r', 'alt.ctrlCmd.®'], - // Need register special character `–` in MacOS - [ParagraphActions.ReduceWidth]: ['alt.ctrlCmd.-', 'alt.ctrlCmd.–'], - // Need register special character `≠` in MacOS - [ParagraphActions.IncreaseWidth]: ['alt.ctrlCmd.+', 'alt.ctrlCmd.≠'], - [ParagraphActions.Delete]: 'shift.delete', - [ParagraphActions.MoveToUp]: ['ctrlCmd.k', 'ctrlCmd.arrowup', 'ctrlCmd.arrowleft'], - [ParagraphActions.MoveToDown]: ['ctrlCmd.j', 'ctrlCmd.arrowdown', 'ctrlCmd.arrowright'], - [ParagraphActions.SelectAbove]: ['k', 'arrowup', 'arrowleft'], - [ParagraphActions.SelectBelow]: ['j', 'arrowdown', 'arrowright'], - [ParagraphActions.SwitchLineNumber]: 'l', - [ParagraphActions.SwitchTitleShow]: 't', - [ParagraphActions.SwitchOutputShow]: 'o', - [ParagraphActions.SwitchEditorShow]: 'e', - [ParagraphActions.InsertAbove]: 'a', - [ParagraphActions.InsertBelow]: 'b' + [ParagraphActions.Run]: 'shift.enter', // Run paragraph + [ParagraphActions.RunAbove]: 'control.shift.arrowup', // Run all above paragraphs (exclusive) + [ParagraphActions.RunBelow]: 'control.shift.arrowdown', // Run all below paragraphs (inclusive) + [ParagraphActions.Cancel]: ['control.alt.c', 'control.alt.ç'], // Cancel + [ParagraphActions.MoveCursorUp]: 'control.p', // Move cursor Up + [ParagraphActions.MoveCursorDown]: 'control.n', // Move cursor Down + [ParagraphActions.Delete]: ['control.alt.d', 'control.alt.∂'], // Remove paragraph + [ParagraphActions.InsertAbove]: ['control.alt.a', 'control.alt.å'], // Insert new paragraph above + [ParagraphActions.InsertBelow]: ['control.alt.b', 'control.alt.∫'], // Insert new paragraph below + [ParagraphActions.InsertCopyOfParagraphBelow]: 'control.shift.c', // Insert copy of paragraph below + [ParagraphActions.MoveParagraphUp]: ['control.alt.k', 'control.alt.˚'], // Move paragraph Up + [ParagraphActions.MoveParagraphDown]: ['control.alt.j', 'control.alt.∆'], // Move paragraph Down + [ParagraphActions.SwitchEnable]: ['control.alt.r', 'control.alt.®'], // Enable/Disable run paragraph + [ParagraphActions.SwitchOutputShow]: ['control.alt.o', 'control.alt.ø'], // Toggle output + // Toggle editor - Shortcut logic is implemented in the editor component + [ParagraphActions.SwitchLineNumber]: ['control.alt.m', 'control.alt.µ'], // Toggle line number + [ParagraphActions.SwitchTitleShow]: ['control.alt.t', 'control.alt.†'], // Toggle title + [ParagraphActions.Clear]: ['control.alt.l', 'control.alt.¬'], // Clear output + [ParagraphActions.Link]: ['control.alt.w', 'control.alt.∑'], // Link this paragraph + [ParagraphActions.ReduceWidth]: 'control.shift._', // Reduce paragraph width + [ParagraphActions.IncreaseWidth]: 'control.shift.=', // Increase paragraph width + // Auto-completion - No longer needed; always applied now + // Cut the line - Shortcut logic is implemented in the editor component + // Paste the line - Shortcut logic is implemented in the editor component + // Search inside the code - Shortcut logic is implemented in the editor component + // Move cursor to the beginning - System shortcut + // Move cursor at the end - System shortcut + // TODO: Check after the search code is implemented in action-bar.component.ts + [ParagraphActions.FindInCode]: ['control.alt.f', 'control.alt.ƒ'] // Find in code }; export interface ShortcutEvent { @@ -78,10 +83,6 @@ export interface ShortcutOption { keybindings: string; } -function isMacOS() { - return navigator.platform.indexOf('Mac') > -1; -} - @Injectable({ providedIn: 'root' }) @@ -99,9 +100,7 @@ export class ShortcutService { bindShortcut(option: ShortcutOption): Observable<ShortcutEvent> { const host = option.scope || this.element; - // `ctrlCmd` is special symbol, will be replaced `meta` in MacOS, 'control' in Windows/Linux - const keybindings = option.keybindings.replace(/ctrlCmd/g, isMacOS() ? 'meta' : 'control'); - const eventName = `keydown.${keybindings}`; + const eventName = `keydown.${option.keybindings}`; // tslint:disable-next-line:ban-types let dispose: Function; return new Observable<ShortcutEvent>(observer => { diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html index 81d711155d..1621a17410 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html @@ -141,7 +141,7 @@ limitations under the License. <span class="icon-action-redo shortcut-icon" style="position: relative; transform: rotate(-90deg); left: -4px;"> </span> - <span class="shortcut-keys">Ctrl+Shift+Enter</span> + <span class="shortcut-keys">Ctrl+Shift+Up</span> Run all above </a> </li> @@ -150,7 +150,7 @@ limitations under the License. <span class="icon-action-undo shortcut-icon" style="position: relative; transform: rotate(-90deg); left: -4px;"> </span> - <span class="shortcut-keys">Ctrl+Shift+Enter</span> + <span class="shortcut-keys">Ctrl+Shift+Down</span> Run all below </a> </li> @@ -189,7 +189,7 @@ limitations under the License. <li> <a ng-click="toggleEnableDisable(paragraph)" ng-class="{'item-disable' : isNoteRunning}"> <span class="icon-control-play shortcut-icon"></span> - <span class="shortcut-keys">Ctrl+ {{ isMac ? 'Option' : 'Alt'}}+R</span> + <span class="shortcut-keys">Ctrl+{{ isMac ? 'Option' : 'Alt'}}+R</span> {{paragraph.config.enabled ? "Disable" : "Enable"}} run </a> </li>