diff --git a/src/sql/parts/notebook/notebook.component.html b/src/sql/parts/notebook/notebook.component.html new file mode 100644 index 0000000000..cb770cb285 --- /dev/null +++ b/src/sql/parts/notebook/notebook.component.html @@ -0,0 +1,16 @@ + +
+
+
+ PlaceHolder for Toolbar +
+
+
+ Place Holder for cell list +
+
diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts new file mode 100644 index 0000000000..484126f784 --- /dev/null +++ b/src/sql/parts/notebook/notebook.component.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Source EULA. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./notebook'; + +import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core'; + +import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; +import { AngularDisposable } from 'sql/base/common/lifecycle'; + +import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import * as themeColors from 'vs/workbench/common/theme'; + +export const NOTEBOOK_SELECTOR: string = 'notebook-component'; + +@Component({ + selector: NOTEBOOK_SELECTOR, + templateUrl: decodeURI(require.toUrl('./notebook.component.html')) +}) +export class NotebookComponent extends AngularDisposable implements OnInit { + @ViewChild('header', { read: ElementRef }) private header: ElementRef; + constructor( + @Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface, + @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, + @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService + ) { + super(); + } + + ngOnInit() { + this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this)); + this.updateTheme(this.themeService.getColorTheme()); + } + + private updateTheme(theme: IColorTheme): void { + let headerEl = this.header.nativeElement; + headerEl.style.borderBottomColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); + headerEl.style.borderBottomWidth = '1px'; + headerEl.style.borderBottomStyle = 'solid'; + } +} diff --git a/src/sql/parts/notebook/notebook.contribution.ts b/src/sql/parts/notebook/notebook.contribution.ts new file mode 100644 index 0000000000..69546dd9f3 --- /dev/null +++ b/src/sql/parts/notebook/notebook.contribution.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Action } from 'vs/base/common/actions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { TPromise } from 'vs/base/common/winjs.base'; +import * as nls from 'vs/nls'; + +import { NotebookInput, NotebookInputModel } from 'sql/parts/notebook/notebookInput'; +import { NotebookEditor } from 'sql/parts/notebook/notebookEditor'; + +/** + * todo: Will remove this code. + * This is the entry point to open the new Notebook + */ +export class OpenNotebookAction extends Action { + + public static ID = 'OpenNotebookAction'; + public static LABEL = nls.localize('OpenNotebookAction', 'Open Notebook editor'); + + constructor( + id: string, + label: string, + @IEditorService private _editorService: IEditorService + ) { + super(id, label); + } + + public run(): TPromise { + return new TPromise((resolve, reject) => { + let model = new NotebookInputModel('modelViewId', undefined, undefined); + let input = new NotebookInput('modelViewId', model); + this._editorService.openEditor(input, { pinned: true }); + }); + } +} + +// Model View editor registration +const viewModelEditorDescriptor = new EditorDescriptor( + NotebookEditor, + NotebookEditor.ID, + 'Notebook' +); + +Registry.as(EditorExtensions.Editors) + .registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(NotebookInput)]); + +// todo: Will remove this code. +// this is the entry point to open the new Notebook +let actionRegistry = Registry.as(Extensions.WorkbenchActions); +actionRegistry.registerWorkbenchAction( + new SyncActionDescriptor( + OpenNotebookAction, + OpenNotebookAction.ID, + OpenNotebookAction.LABEL + ), + OpenNotebookAction.LABEL +); \ No newline at end of file diff --git a/src/sql/parts/notebook/notebook.css b/src/sql/parts/notebook/notebook.css new file mode 100644 index 0000000000..b5cae2d434 --- /dev/null +++ b/src/sql/parts/notebook/notebook.css @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +/* +.notebookEditor .header .monaco-action-bar .action-label { + padding: 8px; +} + +.notebookEditor .header .monaco-action-bar .action-item { + margin-right: 5px; +} + +.notebookEditor .monaco-action-bar { + overflow: visible; +} +*/ \ No newline at end of file diff --git a/src/sql/parts/notebook/notebook.module.ts b/src/sql/parts/notebook/notebook.module.ts new file mode 100644 index 0000000000..94ba37d12d --- /dev/null +++ b/src/sql/parts/notebook/notebook.module.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Source EULA. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { CommonModule, APP_BASE_HREF } from '@angular/common'; +import { BrowserModule } from '@angular/platform-browser'; + + +import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry'; +import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive'; +import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService'; +import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; +import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component'; +import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component'; +import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component'; +import { NotebookComponent } from 'sql/parts/notebook/notebook.component'; + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export const NotebookModule = (params, selector: string, instantiationService: IInstantiationService): any => { + @NgModule({ + declarations: [ + Checkbox, + SelectBox, + EditableDropDown, + InputBox, + NotebookComponent, + ComponentHostDirective + ], + entryComponents: [NotebookComponent], + imports: [ + FormsModule, + CommonModule, + BrowserModule + ], + providers: [ + { provide: APP_BASE_HREF, useValue: '/' }, + CommonServiceInterface, + { provide: IBootstrapParams, useValue: params }, + { provide: ISelector, useValue: selector }, + ...providerIterator(instantiationService) + ] + }) + class ModuleClass { + + constructor( + @Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver, + @Inject(ISelector) private selector: string + ) { + } + + ngDoBootstrap(appRef: ApplicationRef) { + const factoryWrapper: any = this._resolver.resolveComponentFactory(NotebookComponent); + factoryWrapper.factory.selector = this.selector; + appRef.bootstrap(factoryWrapper); + } + } + + return ModuleClass; +}; diff --git a/src/sql/parts/notebook/notebookEditor.ts b/src/sql/parts/notebook/notebookEditor.ts new file mode 100644 index 0000000000..eb89f73fcf --- /dev/null +++ b/src/sql/parts/notebook/notebookEditor.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { TPromise } from 'vs/base/common/winjs.base'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import * as DOM from 'vs/base/browser/dom'; +import { $ } from 'vs/base/browser/builder'; +import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotebookInput } from 'sql/parts/notebook/notebookInput'; +import { NotebookModule } from 'sql/parts/notebook/notebook.module'; +import { NOTEBOOK_SELECTOR } from 'sql/parts/notebook/notebook.component'; + +export class NotebookEditor extends BaseEditor { + + public static ID: string = 'workbench.editor.notebookEditor'; + private _notebookContainer: HTMLElement; + protected _input: NotebookInput; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IInstantiationService private instantiationService: IInstantiationService, + ) { + super(NotebookEditor.ID, telemetryService, themeService); + } + + public get input(): NotebookInput { + return this._input; + } + + /** + * Called to create the editor in the parent element. + */ + public createEditor(parent: HTMLElement): void { + } + + /** + * Sets focus on this editor. Specifically, it sets the focus on the hosted text editor. + */ + public focus(): void { + } + + /** + * Updates the internal variable keeping track of the editor's size, and re-calculates the sash position. + * To be called when the container of this editor changes size. + */ + public layout(dimension: DOM.Dimension): void { + } + + public setInput(input: NotebookInput, options: EditorOptions): TPromise { + if (this.input && this.input.matches(input)) { + return TPromise.as(undefined); + } + + const parentElement = this.getContainer(); + + super.setInput(input, options, CancellationToken.None); + + $(parentElement).clearChildren(); + + if (!input.hasBootstrapped) { + let container = DOM.$('.notebookEditor'); + container.style.height = '100%'; + this._notebookContainer = DOM.append(parentElement, container); + this.input.container = this._notebookContainer; + return TPromise.wrap(this.bootstrapAngular(input)); + } else { + this._notebookContainer = DOM.append(parentElement, this.input.container); + return TPromise.wrap(null); + } + } + + /** + * Load the angular components and record for this input that we have done so + */ + private bootstrapAngular(input: NotebookInput): void { + // Get the bootstrap params and perform the bootstrap + input.hasBootstrapped = true; + bootstrapAngular(this.instantiationService, + NotebookModule, + this._notebookContainer, + NOTEBOOK_SELECTOR, + undefined, + undefined + ); + } +} + diff --git a/src/sql/parts/notebook/notebookInput.ts b/src/sql/parts/notebook/notebookInput.ts new file mode 100644 index 0000000000..a2d6b4dc87 --- /dev/null +++ b/src/sql/parts/notebook/notebookInput.ts @@ -0,0 +1,124 @@ +import { TPromise } from 'vs/base/common/winjs.base'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor'; + +import { Emitter, Event } from 'vs/base/common/event'; + +export type ModeViewSaveHandler = (handle: number) => Thenable; + +export class NotebookInputModel extends EditorModel { + private dirty: boolean; + private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + get onDidChangeDirty(): Event { return this._onDidChangeDirty.event; } + + constructor(public readonly modelViewId, private readonly handle: number, private saveHandler?: ModeViewSaveHandler) { + super(); + this.dirty = false; + } + + get isDirty(): boolean { + return this.dirty; + } + + public setDirty(dirty: boolean): void { + if (this.dirty === dirty) { + return; + } + + this.dirty = dirty; + this._onDidChangeDirty.fire(); + } + + save(): TPromise { + if (this.saveHandler) { + return TPromise.wrap(this.saveHandler(this.handle)); + } + return TPromise.wrap(true); + } +} +export class NotebookInput extends EditorInput { + + public static ID: string = 'workbench.editorinputs.notebookInput'; + + public hasBootstrapped = false; + // Holds the HTML content for the editor when the editor discards this input and loads another + private _parentContainer: HTMLElement; + + constructor(private _title: string, private _model: NotebookInputModel, + ) { + super(); + this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire()); + + } + + public get title(): string { + return this._title; + } + + public get modelViewId(): string { + return this._model.modelViewId; + } + + public getTypeId(): string { + return NotebookInput.ID; + } + + public resolve(refresh?: boolean): TPromise { + return undefined; + } + + public getName(): string { + return this._title; + } + + public dispose(): void { + this._disposeContainer(); + super.dispose(); + } + + private _disposeContainer() { + if (!this._parentContainer) { + return; + } + + let parentNode = this._parentContainer.parentNode; + if (parentNode) { + parentNode.removeChild(this._parentContainer); + this._parentContainer = null; + } + } + + set container(container: HTMLElement) { + this._disposeContainer(); + this._parentContainer = container; + } + + get container(): HTMLElement { + return this._parentContainer; + } + + /** + * An editor that is dirty will be asked to be saved once it closes. + */ + isDirty(): boolean { + return this._model.isDirty; + } + + /** + * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result. + */ + confirmSave(): TPromise { + // TODO #2530 support save on close / confirm save. This is significantly more work + // as we need to either integrate with textFileService (seems like this isn't viable) + // or register our own complimentary service that handles the lifecycle operations such + // as close all, auto save etc. + return TPromise.wrap(ConfirmResult.DONT_SAVE); + } + + /** + * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. + */ + save(): TPromise { + return this._model.save(); + } +} \ No newline at end of file diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 2faf5ef1a5..8e4307b613 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -189,6 +189,8 @@ import 'sql/parts/dashboard/dashboardConfig.contribution'; import 'sql/parts/modelComponents/components.contribution'; /* View Model Editor */ import 'sql/parts/modelComponents/modelEditor/modelViewEditor.contribution'; +/* Notebook Editor */ +import 'sql/parts/notebook/notebook.contribution'; /* Containers */ import 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution'; import 'sql/parts/dashboard/containers/dashboardControlHostContainer.contribution';