This is an automated email from the ASF dual-hosted git repository. zjffdu pushed a commit to branch web_angular in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/web_angular by this push: new 404852e [ZEPPELIN-4403] Support publishable for paragraph 404852e is described below commit 404852ecb39cf3b26b27cbcfda9d8838ddb051ea Author: Hsuan Lee <hsua...@gmail.com> AuthorDate: Fri Nov 29 17:16:34 2019 +0800 [ZEPPELIN-4403] Support publishable for paragraph ### What is this PR for? A few sentences describing the overall goals of the pull request's commits. First time? Check out the contributing guide - https://zeppelin.apache.org/contribution/contributions.html ### What type of PR is it? [Feature] ### Todos * ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-4403 ### How should this be tested? * First time? Setup Travis CI as described on https://zeppelin.apache.org/contribution/contributions.html#continuous-integration * Strongly recommended: add automated unit tests for any new or changed behavior * Outline any manual steps to test the PR here. ### Screenshots (if appropriate) ![publish](https://user-images.githubusercontent.com/22736418/69940879-4770d300-151e-11ea-9ed1-2925ce825e58.gif) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Hsuan Lee <hsua...@gmail.com> Closes #3530 from hsuanxyz/paragraph-publishable and squashes the following commits: a9b974c1d [Hsuan Lee] feat: support paragraph publishable --- .../{public-api.ts => paragraph-base/index.ts} | 4 +- .../src/app/core/paragraph-base/paragraph-base.ts | 315 +++++++++++++++++++++ .../app/core/{ => paragraph-base}/public-api.ts | 5 +- .../{public-api.ts => paragraph-base/published.ts} | 8 +- zeppelin-web-angular/src/app/core/public-api.ts | 1 + .../workspace/notebook/notebook-routing.module.ts | 4 - .../pages/workspace/notebook/notebook.component.ts | 13 +- .../pages/workspace/notebook/notebook.module.ts | 14 +- .../paragraph/code-editor/code-editor.component.ts | 14 - .../paragraph/control/control.component.ts | 17 +- .../notebook/paragraph/paragraph.component.html | 1 + .../notebook/paragraph/paragraph.component.ts | 305 ++------------------ .../published/paragraph/paragraph.component.html | 15 + .../published/paragraph/paragraph.component.less | 0 .../published/paragraph/paragraph.component.ts | 88 ++++++ .../published-ruoting.module.ts} | 17 +- .../pages/workspace/published/published.module.ts | 11 + .../dynamic-forms/dynamic-forms.component.html | 0 .../dynamic-forms/dynamic-forms.component.less | 0 .../dynamic-forms/dynamic-forms.component.ts | 0 .../workspace/share/index.ts} | 4 +- .../{core => pages/workspace/share}/public-api.ts | 4 +- .../result/result.component.html | 6 +- .../result/result.component.less | 0 .../paragraph => share}/result/result.component.ts | 4 + .../src/app/pages/workspace/share/share.module.ts | 56 ++++ .../pages/workspace/workspace-routing.module.ts | 4 + .../app/pages/workspace/workspace.component.html | 4 +- .../src/app/pages/workspace/workspace.component.ts | 11 +- .../src/app/services/shortcut.service.ts | 37 +-- 30 files changed, 581 insertions(+), 381 deletions(-) diff --git a/zeppelin-web-angular/src/app/core/public-api.ts b/zeppelin-web-angular/src/app/core/paragraph-base/index.ts similarity index 85% copy from zeppelin-web-angular/src/app/core/public-api.ts copy to zeppelin-web-angular/src/app/core/paragraph-base/index.ts index c514103..49e4740 100644 --- a/zeppelin-web-angular/src/app/core/public-api.ts +++ b/zeppelin-web-angular/src/app/core/paragraph-base/index.ts @@ -10,6 +10,4 @@ * limitations under the License. */ -export * from './message-listener'; -export * from './destroy-hook'; -export * from './copy-text'; +export * from './public-api'; diff --git a/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts new file mode 100644 index 0000000..f8f9a1b --- /dev/null +++ b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts @@ -0,0 +1,315 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ChangeDetectorRef, QueryList } from '@angular/core'; + +import { + AngularObjectRemove, + AngularObjectUpdate, + GraphConfig, + MessageReceiveDataTypeMap, + OP, + ParagraphConfig, + ParagraphEditorSetting, + ParagraphItem, + ParagraphIResultsMsgItem +} from '@zeppelin/sdk'; + +import { MessageService } from '@zeppelin/services/message.service'; +import { NgZService } from '@zeppelin/services/ng-z.service'; +import { NoteStatusService, ParagraphStatus } from '@zeppelin/services/note-status.service'; + +import DiffMatchPatch from 'diff-match-patch'; +import { isEmpty, isEqual } from 'lodash'; + +import { NotebookParagraphResultComponent } from '@zeppelin/pages/workspace/share/result/result.component'; +import { MessageListener, MessageListenersManager } from '../message-listener/message-listener'; + +export abstract class ParagraphBase extends MessageListenersManager { + paragraph: ParagraphItem; + dirtyText: string; + originalText: string; + isEntireNoteRunning = false; + revisionView = false; + diffMatchPatch = new DiffMatchPatch(); + isParagraphRunning = false; + results = []; + configs = {}; + progress = 0; + colWidthOption = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + editorSetting: ParagraphEditorSetting = {}; + + notebookParagraphResultComponents: QueryList<NotebookParagraphResultComponent>; + + constructor( + public messageService: MessageService, + protected noteStatusService: NoteStatusService, + protected ngZService: NgZService, + protected cdr: ChangeDetectorRef + ) { + super(messageService); + } + + abstract changeColWidth(needCommit: boolean, updateResult?: boolean): void; + + @MessageListener(OP.PROGRESS) + onProgress(data: MessageReceiveDataTypeMap[OP.PROGRESS]) { + if (data.id === this.paragraph.id) { + this.progress = data.progress; + this.cdr.markForCheck(); + } + } + + @MessageListener(OP.NOTE_RUNNING_STATUS) + noteRunningStatusChange(data: MessageReceiveDataTypeMap[OP.NOTE_RUNNING_STATUS]) { + this.isEntireNoteRunning = data.status; + this.cdr.markForCheck(); + } + + @MessageListener(OP.PARAS_INFO) + updateParaInfos(data: MessageReceiveDataTypeMap[OP.PARAS_INFO]) { + if (this.paragraph.id === data.id) { + this.paragraph.runtimeInfos = data.infos; + this.cdr.markForCheck(); + } + } + + @MessageListener(OP.EDITOR_SETTING) + getEditorSetting(data: MessageReceiveDataTypeMap[OP.EDITOR_SETTING]) { + if (this.paragraph.id === data.paragraphId) { + this.paragraph.config.editorSetting = { ...this.paragraph.config.editorSetting, ...data.editor }; + this.cdr.markForCheck(); + } + } + + @MessageListener(OP.PARAGRAPH) + paragraphData(data: MessageReceiveDataTypeMap[OP.PARAGRAPH]) { + const oldPara = this.paragraph; + const newPara = data.paragraph; + if (this.isUpdateRequired(oldPara, newPara)) { + this.updateParagraph(oldPara, newPara, () => { + if (newPara.results && newPara.results.msg) { + // tslint:disable-next-line:no-for-in-array + for (const i in newPara.results.msg) { + if (newPara.results.msg[i]) { + const newResult = newPara.results.msg ? newPara.results.msg[i] : new ParagraphIResultsMsgItem(); + const oldResult = + oldPara.results && oldPara.results.msg ? oldPara.results.msg[i] : new ParagraphIResultsMsgItem(); + const newConfig = newPara.config.results ? newPara.config.results[i] : { graph: new GraphConfig() }; + const oldConfig = oldPara.config.results ? oldPara.config.results[i] : { graph: new GraphConfig() }; + if (!isEqual(newResult, oldResult) || !isEqual(newConfig, oldConfig)) { + const resultComponent = this.notebookParagraphResultComponents.toArray()[i]; + if (resultComponent) { + resultComponent.updateResult(newConfig, newResult); + } + } + } + } + } + this.cdr.markForCheck(); + }); + this.cdr.markForCheck(); + } + } + + @MessageListener(OP.PATCH_PARAGRAPH) + patchParagraph(data: MessageReceiveDataTypeMap[OP.PATCH_PARAGRAPH]) { + if (data.paragraphId === this.paragraph.id) { + let patch = data.patch; + patch = this.diffMatchPatch.patch_fromText(patch); + if (!this.paragraph.text) { + this.paragraph.text = ''; + } + this.paragraph.text = this.diffMatchPatch.patch_apply(patch, this.paragraph.text)[0]; + this.originalText = this.paragraph.text; + this.cdr.markForCheck(); + } + } + + @MessageListener(OP.ANGULAR_OBJECT_UPDATE) + angularObjectUpdate(data: AngularObjectUpdate) { + if (data.paragraphId === this.paragraph.id) { + const { name, object } = data.angularObject; + this.ngZService.setContextValue(name, object, data.paragraphId, false); + } + } + + @MessageListener(OP.ANGULAR_OBJECT_REMOVE) + angularObjectRemove(data: AngularObjectRemove) { + if (data.paragraphId === this.paragraph.id) { + this.ngZService.unsetContextValue(data.name, data.paragraphId, false); + } + } + + updateParagraph(oldPara: ParagraphItem, newPara: ParagraphItem, updateCallback: () => void) { + // 1. can't update on revision view + if (!this.revisionView) { + // 2. get status, refreshed + const statusChanged = newPara.status !== oldPara.status; + const resultRefreshed = + newPara.dateFinished !== oldPara.dateFinished || + isEmpty(newPara.results) !== isEmpty(oldPara.results) || + newPara.status === ParagraphStatus.ERROR || + (newPara.status === ParagraphStatus.FINISHED && statusChanged); + + // 3. update texts managed by paragraph + this.updateAllScopeTexts(oldPara, newPara); + // 4. execute callback to update result + updateCallback(); + + // 5. update remaining paragraph objects + this.updateParagraphObjectWhenUpdated(newPara); + + // 6. handle scroll down by key properly if new paragraph is added + if (statusChanged || resultRefreshed) { + // when last paragraph runs, zeppelin automatically appends new paragraph. + // this broadcast will focus to the newly inserted paragraph + // TODO(hsuanxyz) + } + this.cdr.markForCheck(); + } + } + + isUpdateRequired(oldPara: ParagraphItem, newPara: ParagraphItem): boolean { + return ( + newPara.id === oldPara.id && + (newPara.dateCreated !== oldPara.dateCreated || + newPara.text !== oldPara.text || + newPara.dateFinished !== oldPara.dateFinished || + newPara.dateStarted !== oldPara.dateStarted || + newPara.dateUpdated !== oldPara.dateUpdated || + newPara.status !== oldPara.status || + newPara.jobName !== oldPara.jobName || + newPara.title !== oldPara.title || + isEmpty(newPara.results) !== isEmpty(oldPara.results) || + newPara.errorMessage !== oldPara.errorMessage || + !isEqual(newPara.settings, oldPara.settings) || + !isEqual(newPara.config, oldPara.config) || + !isEqual(newPara.runtimeInfos, oldPara.runtimeInfos)) + ); + } + + updateAllScopeTexts(oldPara: ParagraphItem, newPara: ParagraphItem) { + if (oldPara.text !== newPara.text) { + if (this.dirtyText) { + // check if editor has local update + if (this.dirtyText === newPara.text) { + // when local update is the same from remote, clear local update + this.paragraph.text = newPara.text; + this.dirtyText = undefined; + this.originalText = newPara.text; + } else { + // if there're local update, keep it. + this.paragraph.text = newPara.text; + } + } else { + this.paragraph.text = newPara.text; + this.originalText = newPara.text; + } + } + this.cdr.markForCheck(); + } + + updateParagraphObjectWhenUpdated(newPara: ParagraphItem) { + if (this.paragraph.config.colWidth !== newPara.config.colWidth) { + this.changeColWidth(false); + } + this.paragraph.aborted = newPara.aborted; + this.paragraph.user = newPara.user; + this.paragraph.dateUpdated = newPara.dateUpdated; + this.paragraph.dateCreated = newPara.dateCreated; + this.paragraph.dateFinished = newPara.dateFinished; + this.paragraph.dateStarted = newPara.dateStarted; + this.paragraph.errorMessage = newPara.errorMessage; + this.paragraph.jobName = newPara.jobName; + this.paragraph.title = newPara.title; + this.paragraph.lineNumbers = newPara.lineNumbers; + this.paragraph.status = newPara.status; + this.paragraph.fontSize = newPara.fontSize; + if (newPara.status !== ParagraphStatus.RUNNING) { + this.paragraph.results = newPara.results; + } + this.paragraph.settings = newPara.settings; + this.paragraph.runtimeInfos = newPara.runtimeInfos; + this.isParagraphRunning = this.noteStatusService.isParagraphRunning(newPara); + this.paragraph.config = newPara.config; + this.initializeDefault(this.paragraph.config); + this.setResults(); + this.cdr.markForCheck(); + } + + setResults() { + if (this.paragraph.results) { + this.results = this.paragraph.results.msg; + this.configs = this.paragraph.config.results; + } + if (!this.paragraph.config) { + this.paragraph.config = {}; + } + } + + initializeDefault(config: ParagraphConfig) { + const forms = this.paragraph.settings.forms; + + if (!config.colWidth) { + config.colWidth = 12; + } + + if (!config.fontSize) { + config.fontSize = 9; + } + + if (config.enabled === undefined) { + config.enabled = true; + } + + for (const idx in forms) { + if (forms[idx]) { + if (forms[idx].options) { + if (config.runOnSelectionChange === undefined) { + config.runOnSelectionChange = true; + } + } + } + } + + if (!config.results) { + config.results = {}; + } + + if (!config.editorSetting) { + config.editorSetting = {}; + } else if (config.editorSetting.editOnDblClick) { + this.editorSetting.isOutputHidden = config.editorSetting.editOnDblClick; + } + } + + runParagraphUsingSpell(paragraphText: string, magic: string, propagated: boolean) { + // TODO(hsuanxyz) + } + + runParagraphUsingBackendInterpreter(paragraphText: string) { + this.messageService.runParagraph( + this.paragraph.id, + this.paragraph.title, + paragraphText, + this.paragraph.config, + this.paragraph.settings.params + ); + } + + cancelParagraph() { + if (!this.isEntireNoteRunning) { + this.messageService.cancelParagraph(this.paragraph.id); + } + } +} diff --git a/zeppelin-web-angular/src/app/core/public-api.ts b/zeppelin-web-angular/src/app/core/paragraph-base/public-api.ts similarity index 85% copy from zeppelin-web-angular/src/app/core/public-api.ts copy to zeppelin-web-angular/src/app/core/paragraph-base/public-api.ts index c514103..a6ba532 100644 --- a/zeppelin-web-angular/src/app/core/public-api.ts +++ b/zeppelin-web-angular/src/app/core/paragraph-base/public-api.ts @@ -10,6 +10,5 @@ * limitations under the License. */ -export * from './message-listener'; -export * from './destroy-hook'; -export * from './copy-text'; +export * from './paragraph-base'; +export * from './published'; diff --git a/zeppelin-web-angular/src/app/core/public-api.ts b/zeppelin-web-angular/src/app/core/paragraph-base/published.ts similarity index 82% copy from zeppelin-web-angular/src/app/core/public-api.ts copy to zeppelin-web-angular/src/app/core/paragraph-base/published.ts index c514103..0f41577 100644 --- a/zeppelin-web-angular/src/app/core/public-api.ts +++ b/zeppelin-web-angular/src/app/core/paragraph-base/published.ts @@ -10,6 +10,8 @@ * limitations under the License. */ -export * from './message-listener'; -export * from './destroy-hook'; -export * from './copy-text'; +export const publishedSymbol = Symbol('published'); + +export interface Published { + readonly [publishedSymbol]: true; +} diff --git a/zeppelin-web-angular/src/app/core/public-api.ts b/zeppelin-web-angular/src/app/core/public-api.ts index c514103..3bcd355 100644 --- a/zeppelin-web-angular/src/app/core/public-api.ts +++ b/zeppelin-web-angular/src/app/core/public-api.ts @@ -13,3 +13,4 @@ export * from './message-listener'; export * from './destroy-hook'; export * from './copy-text'; +export * from './paragraph-base'; diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts index 6c177b6..321b788 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts @@ -21,10 +21,6 @@ const routes: Routes = [ component: NotebookComponent }, { - path: ':noteId/paragraph/:paragraphId', - component: NotebookComponent - }, - { path: ':noteId/revision/:revisionId', component: NotebookComponent } 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 97479a7..e0c17a1 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 @@ -21,7 +21,7 @@ import { } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { isNil } from 'lodash'; -import { Subject} from 'rxjs'; +import { Subject } from 'rxjs'; import { distinctUntilKeyChanged, takeUntil } from 'rxjs/operators'; import { MessageListener, MessageListenersManager } from '@zeppelin/core'; @@ -29,6 +29,7 @@ import { Permissions } from '@zeppelin/interfaces'; import { InterpreterBindingItem, MessageReceiveDataTypeMap, Note, OP, RevisionListItem } from '@zeppelin/sdk'; import { MessageService, + NgZService, NoteStatusService, NoteVarShareService, SecurityService, @@ -66,6 +67,7 @@ export class NotebookComponent extends MessageListenersManager implements OnInit if (isNil(note)) { this.router.navigate(['/']).then(); } else { + this.removeParagraphFromNgZ(); this.note = note; const { paragraphId } = this.activatedRoute.snapshot.params; if (paragraphId) { @@ -289,6 +291,7 @@ export class NotebookComponent extends MessageListenersManager implements OnInit private ticketService: TicketService, private securityService: SecurityService, private router: Router, + protected ngZService: NgZService ) { super(messageService); } @@ -317,6 +320,14 @@ export class NotebookComponent extends MessageListenersManager implements OnInit this.revisionView = !!this.activatedRoute.snapshot.params.revisionId; } + removeParagraphFromNgZ(): void { + if (this.note && Array.isArray(this.note.paragraphs)) { + this.note.paragraphs.forEach(p => { + this.ngZService.removeParagraph(p.id); + }); + } + } + ngOnDestroy(): void { super.ngOnDestroy(); this.killSaveTimer(); diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts index ccdd4de..0258bbe 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts @@ -18,7 +18,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NzButtonModule, - NzCheckboxModule, NzDividerModule, NzDropDownModule, NzFormModule, @@ -35,23 +34,20 @@ import { NzToolTipModule } from 'ng-zorro-antd'; import { NzCodeEditorModule } from 'ng-zorro-antd/code-editor'; -import { NzResizableModule } from 'ng-zorro-antd/resizable'; import { ShareModule } from '@zeppelin/share'; -import { VisualizationModule } from 'src/app/visualizations/visualization.module'; import { NotebookAddParagraphComponent } from './add-paragraph/add-paragraph.component'; import { NotebookInterpreterBindingComponent } from './interpreter-binding/interpreter-binding.component'; import { NotebookParagraphCodeEditorComponent } from './paragraph/code-editor/code-editor.component'; import { NotebookParagraphControlComponent } from './paragraph/control/control.component'; -import { NotebookParagraphDynamicFormsComponent } from './paragraph/dynamic-forms/dynamic-forms.component'; import { NotebookParagraphFooterComponent } from './paragraph/footer/footer.component'; import { NotebookParagraphComponent } from './paragraph/paragraph.component'; import { NotebookParagraphProgressComponent } from './paragraph/progress/progress.component'; -import { NotebookParagraphResultComponent } from './paragraph/result/result.component'; import { NotebookPermissionsComponent } from './permissions/permissions.component'; import { NotebookRevisionsComparatorComponent } from './revisions-comparator/revisions-comparator.component'; +import { WorkspaceShareModule } from '../../workspace/share/share.module'; import { NotebookActionBarComponent } from './action-bar/action-bar.component'; import { NotebookRoutingModule } from './notebook-routing.module'; import { NotebookComponent } from './notebook.component'; @@ -67,18 +63,16 @@ import { NotebookShareModule } from './share/share.module'; NotebookParagraphComponent, NotebookAddParagraphComponent, NotebookParagraphCodeEditorComponent, - NotebookParagraphResultComponent, NotebookParagraphProgressComponent, NotebookParagraphFooterComponent, - NotebookParagraphControlComponent, - NotebookParagraphDynamicFormsComponent + NotebookParagraphControlComponent ], imports: [ CommonModule, PortalModule, + WorkspaceShareModule, NotebookRoutingModule, ShareModule, - VisualizationModule, NotebookShareModule, NzButtonModule, NzIconModule, @@ -92,14 +86,12 @@ import { NotebookShareModule } from './share/share.module'; FormsModule, ReactiveFormsModule, NzDividerModule, - NzCheckboxModule, NzProgressModule, NzSwitchModule, NzSelectModule, NzGridModule, NzRadioModule, DragDropModule, - NzResizableModule, NzCodeEditorModule ] }) 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 916afef..8cf4bd7 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 @@ -104,20 +104,6 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro initializedEditor(editor: IEditor) { this.editor = editor as IStandaloneCodeEditor; - if (this.paragraphControl) { - this.paragraphControl.listOfMenu.forEach((item, index) => { - this.editor.addAction({ - id: item.icon, - label: item.label, - precondition: null, - keybindingContext: null, - contextMenuGroupId: 'navigation', - contextMenuOrder: index, - run: () => item.trigger() - }); - }); - } - this.editor.addCommand( monaco.KeyCode.Escape, () => { 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 bda003d..eacf2da 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 @@ -72,6 +72,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { @Output() readonly runAllBelowAndCurrent = new EventEmitter<void>(); @Output() readonly cloneParagraph = new EventEmitter<void>(); @Output() readonly removeParagraph = new EventEmitter<void>(); + @Output() readonly openSingleParagraph = new EventEmitter<string>(); fontSizeOption = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; dropdownVisible = false; isMac = navigator.appVersion.indexOf('Mac') !== -1; @@ -115,8 +116,10 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { show: true, disabled: false, icon: 'export', - trigger: () => this.goToSingleParagraph(), - shortCut: `Ctrl+${this.isMac ? 'Option' : 'Alt'}+W` + trigger: () => { + this.openSingleParagraph.emit(this.pid); + }, + shortCut: this.isMac ? '⌥+⌘+T' : 'Alt+Ctrl+T' }, { label: 'Clear output', @@ -225,13 +228,6 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { } } - goToSingleParagraph() { - // TODO(hsuanxyz) asIframe - const { noteId } = this.activatedRoute.snapshot.params; - const redirectToUrl = `${location.protocol}//${location.host}${location.pathname}#/notebook/${noteId}/paragraph/${this.pid}`; - window.open(redirectToUrl); - } - changeColWidth(colWidth: number) { this.colWidth = +colWidth; this.colWidthChange.emit(this.colWidth); @@ -269,8 +265,7 @@ export class NotebookParagraphControlComponent implements OnInit, OnChanges { private cdr: ChangeDetectorRef, private nzMessageService: NzMessageService, private activatedRoute: ActivatedRoute, - private messageService: MessageService, - private nzModalService: NzModalService + private messageService: MessageService ) {} ngOnInit() { 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 1c42f87..e286627 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 @@ -48,6 +48,7 @@ (editorHideChange)="commitParagraph()" (enabledChange)="commitParagraph()" (titleChange)="commitParagraph()" + (openSingleParagraph)="openSingleParagraph($event)" (runOnSelectionChangeChange)="commitParagraph()" (runParagraph)="runParagraph()" (moveUp)="moveUpParagraph()" 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 f5b61e8..246d09e 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 @@ -29,25 +29,10 @@ import { import { merge, Observable, Subject } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; -import DiffMatchPatch from 'diff-match-patch'; -import { isEmpty, isEqual } from 'lodash'; import { NzModalService } from 'ng-zorro-antd/modal'; -import { MessageListener, MessageListenersManager } from '@zeppelin/core'; -import { - AngularObjectRemove, - AngularObjectUpdate, - GraphConfig, - InterpreterBindingItem, - MessageReceiveDataTypeMap, - Note, - OP, - ParagraphConfig, - ParagraphConfigResult, - ParagraphEditorSetting, - ParagraphItem, - ParagraphIResultsMsgItem -} from '@zeppelin/sdk'; +import { ParagraphBase } from '@zeppelin/core'; +import { InterpreterBindingItem, Note, ParagraphConfigResult, ParagraphItem } from '@zeppelin/sdk'; import { HeliumService, MessageService, @@ -55,7 +40,6 @@ import { NoteStatusService, NoteVarShareService, ParagraphActions, - ParagraphStatus, ShortcutsMap, ShortcutService } from '@zeppelin/services'; @@ -63,8 +47,8 @@ import { SpellResult } from '@zeppelin/spell/spell-result'; import { NgTemplateAdapterService } from '@zeppelin/services/ng-template-adapter.service'; import { NzResizeEvent } from 'ng-zorro-antd/resizable'; +import { NotebookParagraphResultComponent } from '../../share/result/result.component'; import { NotebookParagraphCodeEditorComponent } from './code-editor/code-editor.component'; -import { NotebookParagraphResultComponent } from './result/result.component'; type Mode = 'edit' | 'command'; @@ -78,7 +62,7 @@ type Mode = 'edit' | 'command'; }, changeDetection: ChangeDetectionStrategy.OnPush }) -export class NotebookParagraphComponent extends MessageListenersManager implements OnInit, OnChanges, OnDestroy { +export class NotebookParagraphComponent extends ParagraphBase implements OnInit, OnChanges, OnDestroy { @ViewChild(NotebookParagraphCodeEditorComponent, { static: false }) notebookParagraphCodeEditorComponent: NotebookParagraphCodeEditorComponent; @ViewChildren(NotebookParagraphResultComponent) notebookParagraphResultComponents: QueryList< @@ -103,105 +87,6 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen private destroy$ = new Subject(); private mode: Mode = 'command'; waitConfirmFromEdit = false; - dirtyText: string; - originalText: string; - isEntireNoteRunning = false; - diffMatchPatch = new DiffMatchPatch(); - isParagraphRunning = false; - results = []; - configs = {}; - progress = 0; - colWidthOption = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; - editorSetting: ParagraphEditorSetting = {}; - - @MessageListener(OP.PROGRESS) - onProgress(data: MessageReceiveDataTypeMap[OP.PROGRESS]) { - if (data.id === this.paragraph.id) { - this.progress = data.progress; - this.cdr.markForCheck(); - } - } - - @MessageListener(OP.NOTE_RUNNING_STATUS) - noteRunningStatusChange(data: MessageReceiveDataTypeMap[OP.NOTE_RUNNING_STATUS]) { - this.isEntireNoteRunning = data.status; - this.cdr.markForCheck(); - } - - @MessageListener(OP.PARAS_INFO) - updateParaInfos(data: MessageReceiveDataTypeMap[OP.PARAS_INFO]) { - if (this.paragraph.id === data.id) { - this.paragraph.runtimeInfos = data.infos; - this.cdr.markForCheck(); - } - } - - @MessageListener(OP.EDITOR_SETTING) - getEditorSetting(data: MessageReceiveDataTypeMap[OP.EDITOR_SETTING]) { - if (this.paragraph.id === data.paragraphId) { - this.paragraph.config.editorSetting = { ...this.paragraph.config.editorSetting, ...data.editor }; - this.cdr.markForCheck(); - } - } - - @MessageListener(OP.PARAGRAPH) - paragraphData(data: MessageReceiveDataTypeMap[OP.PARAGRAPH]) { - const oldPara = this.paragraph; - const newPara = data.paragraph; - if (this.isUpdateRequired(oldPara, newPara)) { - this.updateParagraph(oldPara, newPara, () => { - if (newPara.results && newPara.results.msg) { - // tslint:disable-next-line:no-for-in-array - for (const i in newPara.results.msg) { - if (newPara.results.msg[i]) { - const newResult = newPara.results.msg ? newPara.results.msg[i] : new ParagraphIResultsMsgItem(); - const oldResult = - oldPara.results && oldPara.results.msg ? oldPara.results.msg[i] : new ParagraphIResultsMsgItem(); - const newConfig = newPara.config.results ? newPara.config.results[i] : { graph: new GraphConfig() }; - const oldConfig = oldPara.config.results ? oldPara.config.results[i] : { graph: new GraphConfig() }; - if (!isEqual(newResult, oldResult) || !isEqual(newConfig, oldConfig)) { - const resultComponent = this.notebookParagraphResultComponents.toArray()[i]; - if (resultComponent) { - resultComponent.updateResult(newConfig, newResult); - } - } - } - } - } - this.cdr.markForCheck(); - }); - this.cdr.markForCheck(); - } - } - - @MessageListener(OP.PATCH_PARAGRAPH) - patchParagraph(data: MessageReceiveDataTypeMap[OP.PATCH_PARAGRAPH]) { - if (data.paragraphId === this.paragraph.id) { - let patch = data.patch; - patch = this.diffMatchPatch.patch_fromText(patch); - if (!this.paragraph.text) { - this.paragraph.text = ''; - } - this.paragraph.text = this.diffMatchPatch.patch_apply(patch, this.paragraph.text)[0]; - this.originalText = this.paragraph.text; - this.cdr.markForCheck(); - } - } - - @MessageListener(OP.ANGULAR_OBJECT_UPDATE) - angularObjectUpdate(data: AngularObjectUpdate) { - if (data.paragraphId === this.paragraph.id) { - const { name, object } = data.angularObject; - this.ngZService.setContextValue(name, object, data.paragraphId, false); - } - } - - @MessageListener(OP.ANGULAR_OBJECT_REMOVE) - angularObjectRemove(data: AngularObjectRemove) { - if (data.paragraphId === this.paragraph.id) { - this.ngZService.unsetContextValue(data.name, data.paragraphId, false); - } - } switchMode(mode: Mode): void { if (mode === this.mode) { @@ -215,35 +100,6 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen } } - updateParagraph(oldPara: ParagraphItem, newPara: ParagraphItem, updateCallback: () => void) { - // 1. can't update on revision view - if (!this.revisionView) { - // 2. get status, refreshed - const statusChanged = newPara.status !== oldPara.status; - const resultRefreshed = - newPara.dateFinished !== oldPara.dateFinished || - isEmpty(newPara.results) !== isEmpty(oldPara.results) || - newPara.status === ParagraphStatus.ERROR || - (newPara.status === ParagraphStatus.FINISHED && statusChanged); - - // 3. update texts managed by paragraph - this.updateAllScopeTexts(oldPara, newPara); - // 4. execute callback to update result - updateCallback(); - - // 5. update remaining paragraph objects - this.updateParagraphObjectWhenUpdated(newPara); - - // 6. handle scroll down by key properly if new paragraph is added - if (statusChanged || resultRefreshed) { - // when last paragraph runs, zeppelin automatically appends new paragraph. - // this broadcast will focus to the newly inserted paragraph - // TODO(hsuanxyz) - } - this.cdr.markForCheck(); - } - } - textChanged(text: string) { this.dirtyText = text; this.paragraph.text = text; @@ -472,94 +328,6 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen } } - runParagraphUsingSpell(paragraphText: string, magic: string, propagated: boolean) { - // TODO(hsuanxyz) - } - - runParagraphUsingBackendInterpreter(paragraphText: string) { - this.messageService.runParagraph( - this.paragraph.id, - this.paragraph.title, - paragraphText, - this.paragraph.config, - this.paragraph.settings.params - ); - } - - cancelParagraph() { - if (!this.isEntireNoteRunning) { - this.messageService.cancelParagraph(this.paragraph.id); - } - } - - updateAllScopeTexts(oldPara: ParagraphItem, newPara: ParagraphItem) { - if (oldPara.text !== newPara.text) { - if (this.dirtyText) { - // check if editor has local update - if (this.dirtyText === newPara.text) { - // when local update is the same from remote, clear local update - this.paragraph.text = newPara.text; - this.dirtyText = undefined; - this.originalText = newPara.text; - } else { - // if there're local update, keep it. - this.paragraph.text = newPara.text; - } - } else { - this.paragraph.text = newPara.text; - this.originalText = newPara.text; - } - } - this.cdr.markForCheck(); - } - - updateParagraphObjectWhenUpdated(newPara: ParagraphItem) { - if (this.paragraph.config.colWidth !== newPara.config.colWidth) { - this.changeColWidth(false); - } - this.paragraph.aborted = newPara.aborted; - this.paragraph.user = newPara.user; - this.paragraph.dateUpdated = newPara.dateUpdated; - this.paragraph.dateCreated = newPara.dateCreated; - this.paragraph.dateFinished = newPara.dateFinished; - this.paragraph.dateStarted = newPara.dateStarted; - this.paragraph.errorMessage = newPara.errorMessage; - this.paragraph.jobName = newPara.jobName; - this.paragraph.title = newPara.title; - this.paragraph.lineNumbers = newPara.lineNumbers; - this.paragraph.status = newPara.status; - this.paragraph.fontSize = newPara.fontSize; - if (newPara.status !== ParagraphStatus.RUNNING) { - this.paragraph.results = newPara.results; - } - this.paragraph.settings = newPara.settings; - this.paragraph.runtimeInfos = newPara.runtimeInfos; - this.isParagraphRunning = this.noteStatusService.isParagraphRunning(newPara); - this.paragraph.config = newPara.config; - this.initializeDefault(this.paragraph.config); - this.setResults(); - this.cdr.markForCheck(); - } - - isUpdateRequired(oldPara: ParagraphItem, newPara: ParagraphItem): boolean { - return ( - newPara.id === oldPara.id && - (newPara.dateCreated !== oldPara.dateCreated || - newPara.text !== oldPara.text || - newPara.dateFinished !== oldPara.dateFinished || - newPara.dateStarted !== oldPara.dateStarted || - newPara.dateUpdated !== oldPara.dateUpdated || - newPara.status !== oldPara.status || - newPara.jobName !== oldPara.jobName || - newPara.title !== oldPara.title || - isEmpty(newPara.results) !== isEmpty(oldPara.results) || - newPara.errorMessage !== oldPara.errorMessage || - !isEqual(newPara.settings, oldPara.settings) || - !isEqual(newPara.config, oldPara.config) || - !isEqual(newPara.runtimeInfos, oldPara.runtimeInfos)) - ); - } - insertParagraph(position: string) { if (this.revisionView === true) { return; @@ -584,16 +352,6 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen this.cdr.markForCheck(); } - setResults() { - if (this.paragraph.results) { - this.results = this.paragraph.results.msg; - this.configs = this.paragraph.config.results; - } - if (!this.paragraph.config) { - this.paragraph.config = {}; - } - } - setTitle(title: string) { this.paragraph.title = title; this.commitParagraph(); @@ -611,42 +369,6 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen this.cdr.markForCheck(); } - initializeDefault(config: ParagraphConfig) { - const forms = this.paragraph.settings.forms; - - if (!config.colWidth) { - config.colWidth = 12; - } - - if (!config.fontSize) { - config.fontSize = 9; - } - - if (config.enabled === undefined) { - config.enabled = true; - } - - for (const idx in forms) { - if (forms[idx]) { - if (forms[idx].options) { - if (config.runOnSelectionChange === undefined) { - config.runOnSelectionChange = true; - } - } - } - } - - if (!config.results) { - config.results = {}; - } - - if (!config.editorSetting) { - config.editorSetting = {}; - } else if (config.editorSetting.editOnDblClick) { - this.editorSetting.isOutputHidden = config.editorSetting.editOnDblClick; - } - } - moveUpParagraph() { const newIndex = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id) - 1; if (newIndex < 0 || newIndex >= this.note.paragraphs.length) { @@ -709,23 +431,29 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen this.cdr.markForCheck(); } + openSingleParagraph(paragraphId: string): void { + const noteId = this.note.id; + const redirectToUrl = `${location.protocol}//${location.host}${location.pathname}#/notebook/${noteId}/paragraph/${paragraphId}`; + window.open(redirectToUrl); + } + trackByIndexFn(index: number) { return index; } constructor( + noteStatusService: NoteStatusService, + cdr: ChangeDetectorRef, + ngZService: NgZService, private heliumService: HeliumService, - private noteStatusService: NoteStatusService, public messageService: MessageService, private nzModalService: NzModalService, private noteVarShareService: NoteVarShareService, - private cdr: ChangeDetectorRef, - private ngZService: NgZService, private shortcutService: ShortcutService, private host: ElementRef, private ngTemplateAdapterService: NgTemplateAdapterService ) { - super(messageService); + super(messageService, noteStatusService, ngZService, cdr); } ngOnInit() { @@ -823,6 +551,9 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen } } switch (action) { + case ParagraphActions.Link: + this.openSingleParagraph(this.paragraph.id); + break; case ParagraphActions.EditMode: if (this.mode === 'command') { event.preventDefault(); @@ -847,7 +578,6 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen break; } }); - this.setResults(); this.originalText = this.paragraph.text; this.isEntireNoteRunning = this.noteStatusService.isEntireNoteRunning(this.note); @@ -894,6 +624,5 @@ export class NotebookParagraphComponent extends MessageListenersManager implemen ngOnDestroy(): void { super.ngOnDestroy(); - this.ngZService.removeParagraph(this.paragraph.id); } } diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html new file mode 100644 index 0000000..d69a0f4 --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html @@ -0,0 +1,15 @@ +<zeppelin-notebook-paragraph-dynamic-forms + *ngIf="paragraph" + [disable]="paragraph.status == 'RUNNING' || paragraph.status == 'PENDING'" + [paramDefs]="paragraph.settings.params" + [formDefs]="paragraph.settings.forms" + [runOnChange]="paragraph.config.runOnSelectionChange" + (formChange)="runParagraph()"> +</zeppelin-notebook-paragraph-dynamic-forms> +<zeppelin-notebook-paragraph-result *ngFor="let result of results; index as i; trackBy: trackByIndexFn" + [id]="paragraph.id" + [published]="true" + [currentCol]="paragraph.config.colWidth" + [config]="configs[i]" + [result]="result"> +</zeppelin-notebook-paragraph-result> diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.less b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.less new file mode 100644 index 0000000..e69de29 diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts new file mode 100644 index 0000000..12e10de --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts @@ -0,0 +1,88 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { MessageListener, ParagraphBase } from '@zeppelin/core'; +import { publishedSymbol, Published } from '@zeppelin/core/paragraph-base/published'; +import { NotebookParagraphResultComponent } from '@zeppelin/pages/workspace/share/result/result.component'; +import { MessageReceiveDataTypeMap, Note, OP } from '@zeppelin/sdk'; +import { HeliumService, MessageService, NgZService, NoteStatusService } from '@zeppelin/services'; +import { SpellResult } from '@zeppelin/spell/spell-result'; +import { isNil } from 'lodash'; + +@Component({ + selector: 'zeppelin-publish-paragraph', + templateUrl: './paragraph.component.html', + styleUrls: ['./paragraph.component.less'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PublishedParagraphComponent extends ParagraphBase implements Published, OnInit { + readonly [publishedSymbol] = true; + + noteId: string; + paragraphId: string; + + @ViewChildren(NotebookParagraphResultComponent) notebookParagraphResultComponents: QueryList< + NotebookParagraphResultComponent + >; + + constructor( + public messageService: MessageService, + noteStatusService: NoteStatusService, + ngZService: NgZService, + cdr: ChangeDetectorRef, + private activatedRoute: ActivatedRoute, + private heliumService: HeliumService + ) { + super(messageService, noteStatusService, ngZService, cdr); + this.activatedRoute.params.subscribe(params => { + this.noteId = params.noteId; + this.paragraphId = params.paragraphId; + this.messageService.getNote(this.noteId); + }); + } + + ngOnInit() {} + + @MessageListener(OP.NOTE) + getNote(data: MessageReceiveDataTypeMap[OP.NOTE]) { + const note = data.note; + if (!isNil(note)) { + this.paragraph = (note as Note['note']).paragraphs.find(p => p.id === this.paragraphId); + if (this.paragraph) { + this.setResults(); + this.originalText = this.paragraph.text; + this.initializeDefault(this.paragraph.config); + } + } + this.cdr.markForCheck(); + } + + trackByIndexFn(index: number) { + return index; + } + + setResults() { + if (this.paragraph.results) { + this.results = this.paragraph.results.msg; + this.configs = this.paragraph.config.results; + } + if (!this.paragraph.config) { + this.paragraph.config = {}; + } + } + + changeColWidth(needCommit: boolean, updateResult?: boolean): void { + // noop + } + + runParagraph(): void { + const text = this.paragraph.text; + if (text && !this.isParagraphRunning) { + const magic = SpellResult.extractMagic(this.paragraph.text); + if (this.heliumService.getSpellByMagic(magic)) { + this.runParagraphUsingSpell(text, magic, false); + } else { + this.runParagraphUsingBackendInterpreter(text); + } + } + } +} diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/published/published-ruoting.module.ts similarity index 70% copy from zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts copy to zeppelin-web-angular/src/app/pages/workspace/published/published-ruoting.module.ts index 6c177b6..eaf001f 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/published/published-ruoting.module.ts @@ -12,21 +12,12 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; - -import { NotebookComponent } from './notebook.component'; +import { PublishedParagraphComponent } from './paragraph/paragraph.component'; const routes: Routes = [ { - path: ':noteId', - component: NotebookComponent - }, - { - path: ':noteId/paragraph/:paragraphId', - component: NotebookComponent - }, - { - path: ':noteId/revision/:revisionId', - component: NotebookComponent + path: ':paragraphId', + component: PublishedParagraphComponent } ]; @@ -34,4 +25,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class NotebookRoutingModule {} +export class PublishedRoutingModule {} diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts b/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts new file mode 100644 index 0000000..f6474d9 --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { WorkspaceShareModule } from '../../workspace/share/share.module'; +import { PublishedParagraphComponent } from './paragraph/paragraph.component'; +import { PublishedRoutingModule } from './published-ruoting.module'; + +@NgModule({ + declarations: [PublishedParagraphComponent], + imports: [CommonModule, WorkspaceShareModule, PublishedRoutingModule] +}) +export class PublishedModule {} diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/dynamic-forms/dynamic-forms.component.html b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.html similarity index 100% rename from zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/dynamic-forms/dynamic-forms.component.html rename to zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.html diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/dynamic-forms/dynamic-forms.component.less b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.less similarity index 100% rename from zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/dynamic-forms/dynamic-forms.component.less rename to zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.less diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/dynamic-forms/dynamic-forms.component.ts b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.ts similarity index 100% rename from zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/dynamic-forms/dynamic-forms.component.ts rename to zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.ts diff --git a/zeppelin-web-angular/src/app/core/public-api.ts b/zeppelin-web-angular/src/app/pages/workspace/share/index.ts similarity index 85% copy from zeppelin-web-angular/src/app/core/public-api.ts copy to zeppelin-web-angular/src/app/pages/workspace/share/index.ts index c514103..49e4740 100644 --- a/zeppelin-web-angular/src/app/core/public-api.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/share/index.ts @@ -10,6 +10,4 @@ * limitations under the License. */ -export * from './message-listener'; -export * from './destroy-hook'; -export * from './copy-text'; +export * from './public-api'; diff --git a/zeppelin-web-angular/src/app/core/public-api.ts b/zeppelin-web-angular/src/app/pages/workspace/share/public-api.ts similarity index 85% copy from zeppelin-web-angular/src/app/core/public-api.ts copy to zeppelin-web-angular/src/app/pages/workspace/share/public-api.ts index c514103..e865360 100644 --- a/zeppelin-web-angular/src/app/core/public-api.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/share/public-api.ts @@ -10,6 +10,4 @@ * limitations under the License. */ -export * from './message-listener'; -export * from './destroy-hook'; -export * from './copy-text'; +export * from './share.module'; diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.html b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html similarity index 95% rename from zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.html rename to zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html index fe34a37..0b246b0 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.html +++ b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html @@ -10,7 +10,7 @@ ~ limitations under the License. --> -<div class="setting-bar" *ngIf="result.type === datasetType.TABLE"> +<div class="setting-bar" *ngIf="result.type === datasetType.TABLE && !published"> <div class="visualization-selector"> <nz-radio-group [(ngModel)]="config?.graph.mode" (ngModelChange)="switchMode($event)" nzButtonStyle="solid"> <label *ngFor="let item of visualizations" @@ -54,7 +54,7 @@ [nzGridColumnCount]="12" [nzMinColumn]="1" nzBounds="window"> - <nz-resize-handle nzDirection="bottomRight"> + <nz-resize-handle nzDirection="bottomRight" *ngIf="!published"> <zeppelin-resize-handle></zeppelin-resize-handle> </nz-resize-handle> <ng-template cdkPortalOutlet></ng-template> @@ -65,7 +65,7 @@ zeppelinRunScripts [scriptsContent]="innerHTML" [innerHTML]="innerHTML"></div> - <div *ngSwitchCase="datasetType.TEXT" class="text-plain"><pre>{{plainText}}</pre></div> + <div *ngSwitchCase="datasetType.TEXT" class="text-plain"><pre [innerHTML]="plainText"></pre></div> <div *ngSwitchCase="datasetType.IMG" class="img"><img [src]="imgData" alt="img"></div> </ng-container> <div *ngIf="angularComponent"> diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.less b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.less similarity index 100% rename from zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.less rename to zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.less diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.ts b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts similarity index 99% rename from zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.ts rename to zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts index 742a9fb..86d94cc 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts @@ -68,6 +68,7 @@ export class NotebookParagraphResultComponent implements OnInit, AfterViewInit, @Input() result: ParagraphIResultsMsgItem; @Input() config: ParagraphConfigResult; @Input() id: string; + @Input() published = false; @Input() currentCol = 12; @Output() readonly configChange = new EventEmitter<ParagraphConfigResult>(); @Output() readonly sizeChange = new EventEmitter<NzResizeEvent>(); @@ -223,6 +224,9 @@ export class NotebookParagraphResultComponent implements OnInit, AfterViewInit, break; } this.cdr.markForCheck(); + if (this.published) { + this.cdr.detectChanges(); + } } renderHTML(): void { diff --git a/zeppelin-web-angular/src/app/pages/workspace/share/share.module.ts b/zeppelin-web-angular/src/app/pages/workspace/share/share.module.ts new file mode 100644 index 0000000..4c7ef1d --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/share/share.module.ts @@ -0,0 +1,56 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PortalModule } from '@angular/cdk/portal'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; + +import { + NzButtonModule, + NzCheckboxModule, + NzDropDownModule, + NzIconModule, + NzRadioModule, + NzSelectModule, + NzSwitchModule, + NzToolTipModule +} from 'ng-zorro-antd'; +import { NzResizableModule } from 'ng-zorro-antd/resizable'; + +import { ShareModule } from '@zeppelin/share'; +import { VisualizationModule } from '@zeppelin/visualizations/visualization.module'; + +import { NotebookParagraphDynamicFormsComponent } from './dynamic-forms/dynamic-forms.component'; +import { NotebookParagraphResultComponent } from './result/result.component'; + +@NgModule({ + exports: [NotebookParagraphResultComponent, NotebookParagraphDynamicFormsComponent], + declarations: [NotebookParagraphResultComponent, NotebookParagraphDynamicFormsComponent], + imports: [ + CommonModule, + ShareModule, + PortalModule, + VisualizationModule, + FormsModule, + NzButtonModule, + NzDropDownModule, + NzRadioModule, + NzResizableModule, + NzToolTipModule, + NzIconModule, + NzCheckboxModule, + NzSelectModule, + NzSwitchModule + ] +}) +export class WorkspaceShareModule {} diff --git a/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts index 0340a8d..9315443 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts @@ -31,6 +31,10 @@ const routes: Routes = [ loadChildren: () => import('@zeppelin/pages/workspace/notebook/notebook.module').then(m => m.NotebookModule) }, { + path: 'notebook/:noteId/paragraph', + loadChildren: () => import('@zeppelin/pages/workspace/published/published.module').then(m => m.PublishedModule) + }, + { path: 'jobmanager', loadChildren: () => import('@zeppelin/pages/workspace/job-manager/job-manager.module').then(m => m.JobManagerModule) diff --git a/zeppelin-web-angular/src/app/pages/workspace/workspace.component.html b/zeppelin-web-angular/src/app/pages/workspace/workspace.component.html index 6bcae47..c41cf78 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/workspace.component.html +++ b/zeppelin-web-angular/src/app/pages/workspace/workspace.component.html @@ -11,7 +11,7 @@ --> <div class="content" [class.blur]="!websocketConnected"> - <zeppelin-header></zeppelin-header> - <router-outlet></router-outlet> + <zeppelin-header *ngIf="!publishMode"></zeppelin-header> + <router-outlet (activate)="onActivate($event)"></router-outlet> </div> <zeppelin-spin *ngIf="!websocketConnected" [transparent]="true">Connecting WebSocket ...</zeppelin-spin> diff --git a/zeppelin-web-angular/src/app/pages/workspace/workspace.component.ts b/zeppelin-web-angular/src/app/pages/workspace/workspace.component.ts index 03c5b9b..c89d287e 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/workspace.component.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/workspace.component.ts @@ -12,10 +12,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { filter, map, startWith, takeUntil, tap } from 'rxjs/operators'; +import { ActivatedRoute, NavigationEnd, Route, Router } from '@angular/router'; +import { publishedSymbol, Published } from '@zeppelin/core/paragraph-base/published'; import { HeliumManagerService } from '@zeppelin/helium-manager'; import { MessageService } from '@zeppelin/services'; +import { log } from 'ng-zorro-antd'; @Component({ selector: 'zeppelin-workspace', @@ -26,6 +29,7 @@ import { MessageService } from '@zeppelin/services'; export class WorkspaceComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); websocketConnected = false; + publishMode = false; constructor( public messageService: MessageService, @@ -33,6 +37,11 @@ export class WorkspaceComponent implements OnInit, OnDestroy { private heliumManagerService: HeliumManagerService ) {} + onActivate(e) { + this.publishMode = e && e[publishedSymbol]; + this.cdr.markForCheck(); + } + ngOnInit() { this.messageService.connectedStatus$.pipe(takeUntil(this.destroy$)).subscribe(data => { this.websocketConnected = data; diff --git a/zeppelin-web-angular/src/app/services/shortcut.service.ts b/zeppelin-web-angular/src/app/services/shortcut.service.ts index b6d6a3a..4b2a626 100644 --- a/zeppelin-web-angular/src/app/services/shortcut.service.ts +++ b/zeppelin-web-angular/src/app/services/shortcut.service.ts @@ -1,7 +1,7 @@ -import {DOCUMENT} from "@angular/common"; -import {Inject, Injectable} from '@angular/core'; -import {EventManager} from "@angular/platform-browser"; -import {Observable} from "rxjs"; +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; +import { EventManager } from '@angular/platform-browser'; +import { Observable } from 'rxjs'; export enum ParagraphActions { EditMode = 'Paragraph:EditMode', @@ -23,7 +23,8 @@ export enum ParagraphActions { SwitchTitleShow = 'Paragraph:SwitchTitleShow', SwitchOutputShow = 'Paragraph:SwitchOutputShow', SwitchEditorShow = 'Paragraph:SwitchEditorShow', - SwitchEnable = 'Paragraph:SwitchEnable' + SwitchEnable = 'Paragraph:SwitchEnable', + Link = 'Paragraph:Link' } export const ShortcutsMap = { @@ -34,6 +35,8 @@ export const ShortcutsMap = { [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 @@ -54,28 +57,27 @@ export const ShortcutsMap = { }; export interface ShortcutEvent { - event: KeyboardEvent + event: KeyboardEvent; keybindings: string; } export interface ShortcutOption { - scope?: HTMLElement, - keybindings: string + scope?: HTMLElement; + keybindings: string; } function isMacOS() { - return navigator.platform.indexOf('Mac') > -1 + return navigator.platform.indexOf('Mac') > -1; } @Injectable({ providedIn: 'root' }) export class ShortcutService { - private element: HTMLElement; - constructor(private eventManager: EventManager, - @Inject(DOCUMENT) _document: any) { + // tslint:disable-next-line:no-any + constructor(private eventManager: EventManager, @Inject(DOCUMENT) _document: any) { this.element = _document; } @@ -86,9 +88,9 @@ 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 event = `keydown.${keybindings}`; + const keybindings = option.keybindings.replace(/ctrlCmd/g, isMacOS() ? 'meta' : 'control'); + const eventName = `keydown.${keybindings}`; + // tslint:disable-next-line:ban-types let dispose: Function; return new Observable<ShortcutEvent>(observer => { const handler = event => { @@ -98,12 +100,11 @@ export class ShortcutService { }); }; - dispose = this.eventManager.addEventListener(host, event, handler); + dispose = this.eventManager.addEventListener(host, eventName, handler); return () => { dispose(); }; - }) + }); } - }