move code from parts to contrib (#8319)

This commit is contained in:
Anthony Dresser
2019-11-14 12:23:11 -08:00
committed by GitHub
parent 6438967202
commit 7a2c30e159
619 changed files with 848 additions and 848 deletions

View File

@@ -0,0 +1,16 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="width: 100%; height: 100%; display: flex; flex-flow: row" (mouseover)="hover=true" (mouseleave)="hover=false">
<div #toolbar class="toolbar">
</div>
<div style="flex: 1 1 auto; flex-flow: column; overflow: hidden;">
<div #editor class="editor"></div>
<collapse-component *ngIf="cellModel.cellType === 'code' && cellModel.source && cellModel.source.length > 1" [cellModel]="cellModel" [activeCellId]="activeCellId"></collapse-component>
</div>
<div #moreactions class="moreActions" style="flex: 0 0 auto; display: flex; flex-flow:column;width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
</div>
</div>

View File

@@ -0,0 +1,366 @@
/*---------------------------------------------------------------------------------------------
* 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!./code';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, Output, EventEmitter, OnChanges, SimpleChange, forwardRef, ChangeDetectorRef } from '@angular/core';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { QueryTextEditor } from 'sql/workbench/browser/modelComponents/queryTextEditor';
import { CellToggleMoreActions } from 'sql/workbench/contrib/notebook/browser/cellToggleMoreActions';
import { ICellModel, notebookConstants, CellExecutionState } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunCellAction, CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
import { NotebookModel } from 'sql/workbench/contrib/notebook/browser/models/notebookModel';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SimpleEditorProgressService } from 'vs/editor/standalone/browser/simpleServices';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextModel } from 'vs/editor/common/model';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import * as DOM from 'vs/base/browser/dom';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Event, Emitter } from 'vs/base/common/event';
import { CellTypes } from 'sql/workbench/contrib/notebook/common/models/contracts';
import { OVERRIDE_EDITOR_THEMING_SETTING } from 'sql/workbench/services/notebook/browser/notebookService';
import * as notebookUtils from 'sql/workbench/contrib/notebook/browser/models/notebookUtils';
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ILogService } from 'vs/platform/log/common/log';
import { CollapseComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/collapse.component';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export const CODE_SELECTOR: string = 'code-component';
const MARKDOWN_CLASS = 'markdown';
@Component({
selector: CODE_SELECTOR,
templateUrl: decodeURI(require.toUrl('./code.component.html'))
})
export class CodeComponent extends AngularDisposable implements OnInit, OnChanges {
@ViewChild('toolbar', { read: ElementRef }) private toolbarElement: ElementRef;
@ViewChild('moreactions', { read: ElementRef }) private moreActionsElementRef: ElementRef;
@ViewChild('editor', { read: ElementRef }) private codeElement: ElementRef;
@ViewChild(CollapseComponent) private collapseComponent: CollapseComponent;
public get cellModel(): ICellModel {
return this._cellModel;
}
@Input() public set cellModel(value: ICellModel) {
this._cellModel = value;
if (this.toolbarElement && value && value.cellType === CellTypes.Markdown) {
let nativeToolbar = <HTMLElement>this.toolbarElement.nativeElement;
DOM.addClass(nativeToolbar, MARKDOWN_CLASS);
}
}
@Output() public onContentChanged = new EventEmitter<void>();
@Input() set model(value: NotebookModel) {
this._model = value;
this._register(value.kernelChanged(() => {
// On kernel change, need to reevaluate the language for each cell
// Refresh based on the cell magic (since this is kernel-dependent) and then update using notebook language
this.checkForLanguageMagics();
this.updateLanguageMode();
}));
this._register(value.onValidConnectionSelected(() => {
this.updateConnectionState(this.isActive());
}));
}
@Input() set activeCellId(value: string) {
this._activeCellId = value;
}
@Input() set hover(value: boolean) {
this.cellModel.hover = value;
if (!this.isActive()) {
// Only make a change if we're not active, since this has priority
this.toggleActionsVisibility(this.cellModel.hover);
}
}
protected _actionBar: Taskbar;
private readonly _minimumHeight = 30;
private readonly _maximumHeight = 4000;
private _cellModel: ICellModel;
private _editor: QueryTextEditor;
private _editorInput: UntitledEditorInput;
private _editorModel: ITextModel;
private _model: NotebookModel;
private _activeCellId: string;
private _cellToggleMoreActions: CellToggleMoreActions;
private _layoutEmitter = new Emitter<void>();
constructor(
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IModelService) private _modelService: IModelService,
@Inject(IModeService) private _modeService: IModeService,
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(ILogService) private readonly logService: ILogService
) {
super();
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
this._register(Event.debounce(this._layoutEmitter.event, (l, e) => e, 250, /*leading=*/false)
(() => this.layout()));
// Handle disconnect on removal of the cell, if it was the active cell
this._register({ dispose: () => this.updateConnectionState(false) });
}
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
this.initActionBar();
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
this.updateLanguageMode();
this.updateModel();
for (let propName in changes) {
if (propName === 'activeCellId') {
let changedProp = changes[propName];
let isActive = this.cellModel.id === changedProp.currentValue;
this.updateConnectionState(isActive);
this.toggleActionsVisibility(isActive);
if (this._editor) {
this._editor.toggleEditorSelected(isActive);
}
break;
}
}
}
private updateConnectionState(shouldConnect: boolean) {
if (this.isSqlCodeCell()) {
let cellUri = this.cellModel.cellUri.toString();
let connectionService = this.connectionService;
if (!shouldConnect && connectionService && connectionService.isConnected(cellUri)) {
connectionService.disconnect(cellUri).catch(e => this.logService.error(e));
} else if (shouldConnect && this._model.activeConnection && this._model.activeConnection.id !== '-1') {
connectionService.connect(this._model.activeConnection, cellUri).catch(e => this.logService.error(e));
}
}
}
private get connectionService(): IConnectionManagementService {
return this._model && this._model.notebookOptions && this._model.notebookOptions.connectionService;
}
private isSqlCodeCell() {
return this._model
&& this._model.defaultKernel
&& this._model.defaultKernel.display_name === notebookConstants.SQL
&& this.cellModel.cellType === CellTypes.Code
&& this.cellModel.cellUri;
}
private get destroyed(): boolean {
return !!(this._changeRef['destroyed']);
}
ngAfterContentInit(): void {
if (this.destroyed) {
return;
}
this.createEditor();
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
this._layoutEmitter.fire();
}));
}
get model(): NotebookModel {
return this._model;
}
get activeCellId(): string {
return this._activeCellId;
}
private async createEditor(): Promise<void> {
let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleEditorProgressService()]));
this._editor = instantiationService.createInstance(QueryTextEditor);
this._editor.create(this.codeElement.nativeElement);
this._editor.setVisible(true);
this._editor.setMinimumHeight(this._minimumHeight);
this._editor.setMaximumHeight(this._maximumHeight);
let uri = this.cellModel.cellUri;
let cellModelSource: string;
cellModelSource = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, this.cellModel.language, cellModelSource, '');
await this._editor.setInput(this._editorInput, undefined);
this.setFocusAndScroll();
let untitledEditorModel: UntitledEditorModel = await this._editorInput.resolve();
this._editorModel = untitledEditorModel.textEditorModel;
let isActive = this.cellModel.id === this._activeCellId;
this._editor.toggleEditorSelected(isActive);
// For markdown cells, don't show line numbers unless we're using editor defaults
let overrideEditorSetting = this._configurationService.getValue<boolean>(OVERRIDE_EDITOR_THEMING_SETTING);
this._editor.hideLineNumbers = (overrideEditorSetting && this.cellModel.cellType === CellTypes.Markdown);
if (this.destroyed) {
// At this point, we may have been disposed (scenario: restoring markdown cell in preview mode).
// Exiting early to avoid warnings on registering already disposed items, which causes some churning
// due to re-disposing things.
// There's no negative impact as at this point the component isn't visible (it was removed from the DOM)
return;
}
this._register(this._editor);
this._register(this._editorInput);
this._register(this._editorModel.onDidChangeContent(e => {
this.cellModel.modelContentChangedEvent = e;
let originalSourceLength = this.cellModel.source.length;
this.cellModel.source = this._editorModel.getValue();
if (this._cellModel.isCollapsed && originalSourceLength !== this.cellModel.source.length) {
this._cellModel.isCollapsed = false;
}
this._editor.setHeightToScrollHeight(false, this._cellModel.isCollapsed);
this.onContentChanged.emit();
this.checkForLanguageMagics();
}));
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('editor.wordWrap') || e.affectsConfiguration('editor.fontSize')) {
this._editor.setHeightToScrollHeight(true, this._cellModel.isCollapsed);
}
}));
this._register(this.model.layoutChanged(() => this._layoutEmitter.fire(), this));
this._register(this.cellModel.onExecutionStateChange(event => {
if (event === CellExecutionState.Running && !this.cellModel.stdInVisible) {
this.setFocusAndScroll();
}
}));
this._register(this.cellModel.onCollapseStateChanged(isCollapsed => {
this.onCellCollapse(isCollapsed);
}));
this.layout();
if (this._cellModel.isCollapsed) {
this.onCellCollapse(true);
}
}
public layout(): void {
this._editor.layout(new DOM.Dimension(
DOM.getContentWidth(this.codeElement.nativeElement),
DOM.getContentHeight(this.codeElement.nativeElement)));
this._editor.setHeightToScrollHeight(false, this._cellModel.isCollapsed);
}
protected initActionBar() {
let context = new CellContext(this.model, this.cellModel);
let runCellAction = this._instantiationService.createInstance(RunCellAction, context);
let taskbar = <HTMLElement>this.toolbarElement.nativeElement;
this._actionBar = new Taskbar(taskbar);
this._actionBar.context = context;
this._actionBar.setContent([
{ action: runCellAction }
]);
this._cellToggleMoreActions.onInit(this.moreActionsElementRef, this.model, this.cellModel);
}
/// Editor Functions
private updateModel() {
if (this._editorModel) {
let cellModelSource: string;
cellModelSource = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
this._modelService.updateModel(this._editorModel, cellModelSource);
}
}
private checkForLanguageMagics(): void {
try {
if (!this.cellModel || this.cellModel.cellType !== CellTypes.Code) {
return;
}
if (this._editorModel && this._editor && this._editorModel.getLineCount() > 1) {
// Only try to match once we've typed past the first line
let magicName = notebookUtils.tryMatchCellMagic(this._editorModel.getLineContent(1));
if (magicName) {
let kernelName = this._model.clientSession && this._model.clientSession.kernel ? this._model.clientSession.kernel.name : undefined;
let magic = this._model.notebookOptions.cellMagicMapper.toLanguageMagic(magicName, kernelName);
if (magic && this.cellModel.language !== magic.language) {
this.cellModel.setOverrideLanguage(magic.language);
this.updateLanguageMode();
}
} else {
this.cellModel.setOverrideLanguage(undefined);
}
}
} catch (err) {
// No-op for now. Should we log?
}
}
private updateLanguageMode(): void {
if (this._editorModel && this._editor) {
let modeValue = this._modeService.create(this.cellModel.language);
this._modelService.setMode(this._editorModel, modeValue);
}
}
private updateTheme(theme: IColorTheme): void {
let toolbarEl = <HTMLElement>this.toolbarElement.nativeElement;
toolbarEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
let moreActionsEl = <HTMLElement>this.moreActionsElementRef.nativeElement;
moreActionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
private setFocusAndScroll(): void {
// If offsetParent is null, the element isn't visible
// In this case, we don't want a cell to grab focus for an editor that isn't in the foreground.
// In addition, ensure that the ownerDocument itself has focus for scenarios where ADS isn't in the foreground
let ownerDocument = this._editor.getContainer().ownerDocument;
if (this.cellModel.id === this._activeCellId && this._editor.getContainer().offsetParent && ownerDocument && ownerDocument.hasFocus()) {
this._editor.focus();
this._editor.getContainer().scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
protected isActive() {
return this.cellModel && this.cellModel.id === this.activeCellId;
}
protected toggleActionsVisibility(isActiveOrHovered: boolean) {
this._cellToggleMoreActions.toggleVisible(!isActiveOrHovered);
if (this.collapseComponent) {
this.collapseComponent.toggleIconVisibility(isActiveOrHovered);
}
}
private onCellCollapse(isCollapsed: boolean): void {
let editorWidget = this._editor.getControl() as ICodeEditor;
if (isCollapsed) {
let model = editorWidget.getModel();
let totalLines = model.getLineCount();
let endColumn = model.getLineMaxColumn(totalLines);
editorWidget.setHiddenAreas([{
startLineNumber: 2,
startColumn: 1,
endLineNumber: totalLines,
endColumn: endColumn
}]);
} else {
editorWidget.setHiddenAreas([]);
}
this._editor.setHeightToScrollHeight(false, isCollapsed);
}
}

View File

@@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
code-component {
height: 100%;
width: 100%;
display: block;
}
code-component .toolbar {
border-right-width: 1px;
flex: 0 0 auto;
display: flex;
flex-flow:column;
width: 40px;
min-height: 40px;
orientation: portrait
}
code-component .toolbar.markdown {
display: none;
}
code-component .toolbar .carbon-taskbar {
position: sticky;
top: 0px;
margin-top: 5px;
}
code-component .toolbarIconRun {
height: 20px;
background-image: url('./media/light/execute_cell.svg');
padding-bottom: 10px;
}
.vs-dark code-component .toolbarIconRun,
.hc-black code-component .toolbarIconRun {
background-image: url('./media/dark/execute_cell_inverse.svg');
}
code-component .toolbarIconRunError {
height: 20px;
background-image: url('./media/light/execute_cell_error.svg');
padding-bottom: 10px;
}
code-component .toolbarIconStop {
height: 20px;
background-image: url('./media/light/stop_cell_solidanimation.svg');
padding-bottom: 10px;
}
.vs-dark code-component .toolbarIconStop,
.hc-black code-component .toolbarIconStop {
background-image: url('./media/dark/stop_cell_solidanimation_inverse.svg');
}
code-component .editor {
padding: 5px 0px 5px 0px
}
/* overview ruler */
code-component .monaco-editor .decorationsOverviewRuler {
visibility: hidden;
}
code-component .carbon-taskbar .codicon {
background-size: 20px;
width: 40px;
}
code-component .carbon-taskbar .codicon.hideIcon {
width: 0px;
padding-left: 0px;
padding-top: 6px;
font-family: monospace;
font-size: 12px;
}
code-component .carbon-taskbar .codicon.hideIcon.execCountTen {
margin-left: -2px;
}
code-component .carbon-taskbar .codicon.hideIcon.execCountHundred {
margin-left: -6px;
}
code-component .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container {
padding-left: 10px
}
code-component .hide-component-button {
height: 16px;
width: 100%;
box-sizing: border-box;
border-width: 0px;
background-repeat: no-repeat;
background-position: center;
background-color: inherit;
}
code-component .hide-component-button.icon-hide-cell {
background-image: url("./media/light/chevron_up.svg");
}
code-component .hide-component-button.icon-show-cell {
background-image: url("./media/light/chevron_down.svg");
}
.vs-dark code-component .hide-component-button.icon-hide-cell,
.hc-black code-component .hide-component-button.icon-hide-cell {
background-image: url("./media/dark/chevron_up_inverse.svg");
}
.vs-dark code-component .hide-component-button.icon-show-cell,
.hc-black code-component .hide-component-button.icon-show-cell {
background-image: url("./media/dark/chevron_down_inverse.svg");
}

View File

@@ -0,0 +1,135 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Action } from 'vs/base/common/actions';
import { localize } from 'vs/nls';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as types from 'vs/base/common/types';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { NotebookModel } from 'sql/workbench/contrib/notebook/browser/models/notebookModel';
import { ICellModel, CellExecutionState } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { MultiStateAction, IMultiStateData } from 'sql/workbench/contrib/notebook/browser/notebookActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ILogService } from 'vs/platform/log/common/log';
import { getErrorMessage } from 'vs/base/common/errors';
let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again");
const emptyExecutionCountLabel = '[ ]';
function hasModelAndCell(context: CellContext, notificationService: INotificationService): boolean {
if (!context || !context.model) {
return false;
}
if (context.cell === undefined) {
notificationService.notify({
severity: Severity.Error,
message: notebookMoreActionMsg
});
return false;
}
return true;
}
export class CellContext {
constructor(public model: NotebookModel, private _cell?: ICellModel) {
}
public get cell(): ICellModel {
return this._cell ? this._cell : this.model.activeCell;
}
}
export abstract class CellActionBase extends Action {
constructor(id: string, label: string, icon: string, protected notificationService: INotificationService) {
super(id, label, icon);
}
public canRun(context: CellContext): boolean {
return true;
}
public run(context: CellContext): Promise<boolean> {
if (hasModelAndCell(context, this.notificationService)) {
return this.doRun(context).then(() => true);
}
return Promise.resolve(true);
}
abstract doRun(context: CellContext): Promise<void>;
}
export class RunCellAction extends MultiStateAction<CellExecutionState> {
public static ID = 'notebook.runCell';
public static LABEL = 'Run cell';
private _executionChangedDisposable: IDisposable;
private _context: CellContext;
constructor(context: CellContext, @INotificationService private notificationService: INotificationService,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IKeybindingService keybindingService: IKeybindingService,
@ILogService logService: ILogService
) {
super(RunCellAction.ID, new IMultiStateData<CellExecutionState>([
{ key: CellExecutionState.Hidden, value: { label: emptyExecutionCountLabel, className: '', tooltip: '', hideIcon: true } },
{ key: CellExecutionState.Stopped, value: { label: '', className: 'toolbarIconRun', tooltip: localize('runCell', "Run cell"), commandId: 'notebook.command.runactivecell' } },
{ key: CellExecutionState.Running, value: { label: '', className: 'toolbarIconStop', tooltip: localize('stopCell', "Cancel execution") } },
{ key: CellExecutionState.Error, value: { label: '', className: 'toolbarIconRunError', tooltip: localize('errorRunCell', "Error on last run. Click to run again") } },
], CellExecutionState.Hidden), keybindingService, logService);
this.ensureContextIsUpdated(context);
}
public run(context?: CellContext): Promise<boolean> {
return this.doRun(context).then(() => true);
}
public async doRun(context: CellContext): Promise<void> {
this.ensureContextIsUpdated(context);
if (!this._context) {
// TODO should we error?
return;
}
try {
await this._context.cell.runCell(this.notificationService, this.connectionManagementService);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.error(message);
}
}
private ensureContextIsUpdated(context: CellContext) {
if (context && context !== this._context) {
if (this._executionChangedDisposable) {
this._executionChangedDisposable.dispose();
}
this._context = context;
this.updateStateAndExecutionCount(context.cell.executionState);
this._executionChangedDisposable = this._context.cell.onExecutionStateChange((state) => {
this.updateStateAndExecutionCount(state);
});
}
}
private updateStateAndExecutionCount(state: CellExecutionState) {
let label = emptyExecutionCountLabel;
let className = '';
if (!types.isUndefinedOrNull(this._context.cell.executionCount)) {
label = `[${this._context.cell.executionCount}]`;
// Heuristic to try and align correctly independent of execution count length. Moving left margin
// back by a few px seems to make things "work" OK, but isn't a super clean solution
if (label.length === 4) {
className = 'execCountTen';
} else if (label.length > 4) {
className = 'execCountHundred';
}
}
this.states.updateStateData(CellExecutionState.Hidden, (data) => {
data.label = label;
data.className = className;
});
this.updateState(state);
}
}

View File

@@ -0,0 +1,16 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="width: 100%; height: 100%; display: flex; flex-flow: column">
<div style="flex: 0 0 auto;">
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
</div>
<div style="flex: 0 0 auto; width: 100%; height: 100%; display: block">
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel" [activeCellId]="activeCellId">
</output-area-component>
<stdin-component *ngIf="isStdInVisible" [onSendInput]="inputDeferred" [stdIn]="stdIn" [cellModel]="cellModel"></stdin-component>
</div>
</div>

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { nb } from 'azdata';
import { OnInit, Component, Input, Inject, forwardRef, ChangeDetectorRef, SimpleChange, OnChanges, HostListener } from '@angular/core';
import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { NotebookModel } from 'sql/workbench/contrib/notebook/browser/models/notebookModel';
import { Deferred } from 'sql/base/common/promise';
export const CODE_SELECTOR: string = 'code-cell-component';
@Component({
selector: CODE_SELECTOR,
templateUrl: decodeURI(require.toUrl('./codeCell.component.html'))
})
export class CodeCellComponent extends CellView implements OnInit, OnChanges {
@Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) {
this._model = value;
}
@Input() set activeCellId(value: string) {
this._activeCellId = value;
}
@HostListener('document:keydown.escape', ['$event'])
handleKeyboardEvent() {
this.cellModel.active = false;
this._model.updateActiveCell(undefined);
}
private _model: NotebookModel;
private _activeCellId: string;
public inputDeferred: Deferred<string>;
public stdIn: nb.IStdinMessage;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
) {
super();
}
ngOnInit() {
if (this.cellModel) {
this._register(this.cellModel.onCollapseStateChanged((state) => {
this._changeRef.detectChanges();
}));
this._register(this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
}));
// Register request handler, cleanup on dispose of this component
this.cellModel.setStdInHandler({ handle: (msg) => this.handleStdIn(msg) });
this._register({ dispose: () => this.cellModel.setStdInHandler(undefined) });
}
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
for (let propName in changes) {
if (propName === 'activeCellId') {
let changedProp = changes[propName];
this._activeCellId = changedProp.currentValue;
break;
}
}
}
get model(): NotebookModel {
return this._model;
}
get activeCellId(): string {
return this._activeCellId;
}
public layout() {
}
handleStdIn(msg: nb.IStdinMessage): void | Thenable<void> {
if (msg) {
this.stdIn = msg;
this.inputDeferred = new Deferred();
this.cellModel.stdInVisible = true;
this._changeRef.detectChanges();
return this.awaitStdIn();
}
}
private async awaitStdIn(): Promise<void> {
try {
let value = await this.inputDeferred.promise;
this.cellModel.future.sendInputReply({ value: value });
} catch (err) {
// Note: don't have a better way to handle completing input request. For now just canceling by sending empty string?
this.cellModel.future.sendInputReply({ value: '' });
} finally {
// Clean up so no matter what, the stdIn request goes away
this.stdIn = undefined;
this.inputDeferred = undefined;
this.cellModel.stdInVisible = false;
this._changeRef.detectChanges();
}
}
get isStdInVisible(): boolean {
return this.cellModel.stdInVisible;
}
}

View File

@@ -0,0 +1,10 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="width: 100%; height: fit-content; display: flex; flex-flow: column">
<button #collapseCellButton (click)="toggleCollapsed($event)" class="hide-component-button"></button>
<button #expandCellButton (click)="toggleCollapsed($event)" style="display:none" class="hide-component-button icon-show-cell"></button>
</div>

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* 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!./code';
import { OnInit, Component, Input, ElementRef, ViewChild, SimpleChange, OnChanges } from '@angular/core';
import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
export const COLLAPSE_SELECTOR: string = 'collapse-component';
@Component({
selector: COLLAPSE_SELECTOR,
templateUrl: decodeURI(require.toUrl('./collapse.component.html'))
})
export class CollapseComponent extends CellView implements OnInit, OnChanges {
@ViewChild('collapseCellButton', { read: ElementRef }) private collapseCellButtonElement: ElementRef;
@ViewChild('expandCellButton', { read: ElementRef }) private expandCellButtonElement: ElementRef;
@Input() cellModel: ICellModel;
@Input() activeCellId: string;
constructor() {
super();
}
ngOnInit() {
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
}
ngAfterContentInit() {
this._register(this.cellModel.onCollapseStateChanged(isCollapsed => {
this.handleCellCollapse(isCollapsed);
}));
this.handleCellCollapse(this.cellModel.isCollapsed);
if (this.activeCellId === this.cellModel.id) {
this.toggleIconVisibility(true);
}
}
private handleCellCollapse(isCollapsed: boolean): void {
let collapseButton = <HTMLElement>this.collapseCellButtonElement.nativeElement;
let expandButton = <HTMLElement>this.expandCellButtonElement.nativeElement;
if (isCollapsed) {
collapseButton.style.display = 'none';
expandButton.style.display = 'block';
} else {
collapseButton.style.display = 'block';
expandButton.style.display = 'none';
}
}
public toggleCollapsed(event?: Event): void {
if (event) {
event.stopPropagation();
}
this.cellModel.isCollapsed = !this.cellModel.isCollapsed;
}
public layout() {
}
public toggleIconVisibility(isActiveOrHovered: boolean) {
let collapseButton = <HTMLElement>this.collapseCellButtonElement.nativeElement;
let buttonClass = 'icon-hide-cell';
if (isActiveOrHovered) {
collapseButton.classList.add(buttonClass);
} else {
collapseButton.classList.remove(buttonClass);
}
}
}

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OnDestroy } from '@angular/core';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
export abstract class CellView extends AngularDisposable implements OnDestroy {
constructor() {
super();
}
public abstract layout(): void;
}

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Directive, Inject, HostListener, Input } from '@angular/core';
import { URI } from 'vs/base/common/uri';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { onUnexpectedError } from 'vs/base/common/errors';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
const knownSchemes = new Set(['http', 'https', 'file', 'mailto', 'data', 'azuredatastudio', 'azuredatastudio-insiders', 'vscode', 'vscode-insiders', 'vscode-resource']);
@Directive({
selector: '[link-handler]',
})
export class LinkHandlerDirective {
private workbenchFilePath: URI;
@Input() isTrusted: boolean;
@Input() notebookUri: URI;
constructor(
@Inject(IOpenerService) private readonly openerService: IOpenerService,
@Inject(INotebookService) private readonly notebookService: INotebookService
) {
this.workbenchFilePath = URI.parse(require.toUrl('vs/code/electron-browser/workbench/workbench.html'));
}
@HostListener('click', ['$event'])
onclick(event: MouseEvent): void {
// Note: this logic is taken from the VSCode handling of links in markdown
// Untrusted cells will not support commands or raw HTML tags
// Finally, we should consider supporting relative paths - created #5238 to track
let target: HTMLElement = event.target as HTMLElement;
if (target.tagName !== 'A') {
target = target.parentElement;
if (!target || target.tagName !== 'A') {
return;
}
}
try {
const href = target['href'];
if (href) {
this.handleLink(href);
}
} catch (err) {
onUnexpectedError(err);
} finally {
event.preventDefault();
}
}
private handleLink(content: string): void {
let uri: URI | undefined;
try {
uri = URI.parse(content);
} catch {
// ignore
}
if (uri && this.openerService && this.isSupportedLink(uri)) {
if (uri.fragment && uri.fragment.length > 0 && uri.path === this.workbenchFilePath.path) {
this.notebookService.navigateTo(this.notebookUri, uri.fragment);
} else {
this.openerService.open(uri).catch(onUnexpectedError);
}
}
}
private isSupportedLink(link: URI): boolean {
if (knownSchemes.has(link.scheme)) {
return true;
}
return !!this.isTrusted && link.scheme === 'command';
}
}

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.1484 3.64844L15.8516 4.35156L8 12.2031L0.148438 4.35156L0.851562 3.64844L8 10.7969L15.1484 3.64844Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 232 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.2734 11.9766L8 4.71094L0.726562 11.9766L0.0234375 11.2734L8 3.28906L15.9766 11.2734L15.2734 11.9766Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>execute_cell_inverse </title><circle class="cls-1" cx="8" cy="7.92" r="7.76"/><polygon points="10.7 8 6.67 11 6.67 5 10.7 8 10.7 8"/></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -0,0 +1,16 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs><style>.cls-1{fill:#c1d6e6;}.cls-2{fill:#0078d4;}.cls-3{fill:#fff;}</style></defs>
<title>stop_cell_solidanimation_inverse</title>
<path class="cls-1" d="M8,16a8,8,0,1,1,8-8A8,8,0,0,1,8,16ZM8,1a7,7,0,1,0,7,7A7,7,0,0,0,8,1Z"/>
<path class="cls-2" d="M8.51,0v1A7,7,0,0,1,15,8a6.87,6.87,0,0,1-1.07,3.7l.81.64A7.92,7.92,0,0,0,16,8,8,8,0,0,0,8.51,0Z">
<animateTransform attributeName="transform"
type="rotate"
from="0 8 8"
to="360 8 8"
begin="0s"
dur="1.5s"
repeatCount="indefinite"
/>
</path>
<circle class="cls-3" cx="8" cy="8" r="6.32"/>
<rect x="4.91" y="4.91" width="6.18" height="6.18"/></svg>

After

Width:  |  Height:  |  Size: 774 B

View File

@@ -0,0 +1,191 @@
/*
https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs2015.css
*/
/*
* Visual Studio 2015 dark style
* Author: Nicolas LLOBERA <nllobera@gmail.com>
*/
.notebook-preview .hljs-keyword,
.notebook-preview .hljs-literal,
.notebook-preview .hljs-symbol,
.notebook-preview .hljs-name {
color: #569CD6;
}
.notebook-preview .hljs-link {
color: #569CD6;
text-decoration: underline;
}
.notebook-preview .hljs-built_in,
.notebook-preview .hljs-type {
color: #4EC9B0;
}
.notebook-preview .hljs-number,
.notebook-preview .hljs-class {
color: #B8D7A3;
}
.notebook-preview .hljs-string,
.notebook-preview .hljs-meta-string {
color: #D69D85;
}
.notebook-preview .hljs-regexp,
.notebook-preview .hljs-template-tag {
color: #9A5334;
}
.notebook-preview .hljs-subst,
.notebook-preview .hljs-function,
.notebook-preview .hljs-title,
.notebook-preview .hljs-params,
.notebook-preview .hljs-formula {
color: #DCDCDC;
}
.notebook-preview pre code .hljs-subst,
.notebook-preview pre code .hljs-function,
.notebook-preview pre code .hljs-title,
.notebook-preview pre code .hljs-params,
.notebook-preview pre code .hljs-formula {
color: var(--vscode-editor-foreground);
}
.notebook-preview .hljs-comment,
.notebook-preview .hljs-quote {
color: #57A64A;
font-style: italic;
}
.notebook-preview .hljs-doctag {
color: #608B4E;
}
.notebook-preview .hljs-meta,
.notebook-preview .hljs-meta-keyword,
.notebook-preview .hljs-tag {
color: #9B9B9B;
}
.notebook-preview .hljs-variable,
.notebook-preview .hljs-template-variable {
color: #BD63C5;
}
.notebook-preview .hljs-attr,
.notebook-preview .hljs-attribute,
.notebook-preview .hljs-builtin-name {
color: #9CDCFE;
}
.notebook-preview .hljs-section {
color: gold;
}
.notebook-preview .hljs-emphasis {
font-style: italic;
}
.notebook-preview .hljs-strong {
font-weight: bold;
}
/*.hljs-code {
font-family:'Monospace';
}*/
.notebook-preview .hljs-bullet,
.notebook-preview .hljs-selector-tag,
.notebook-preview .hljs-selector-id,
.notebook-preview .hljs-selector-class,
.notebook-preview .hljs-selector-attr,
.notebook-preview .hljs-selector-pseudo {
color: #D7BA7D;
}
.notebook-preview .hljs-addition {
background-color: var(--vscode-diffEditor-insertedTextBackground, rgba(155, 185, 85, 0.2));
color: rgb(155, 185, 85);
display: inline-block;
width: 100%;
}
.notebook-preview .hljs-deletion {
background: var(--vscode-diffEditor-removedTextBackground, rgba(255, 0, 0, 0.2));
color: rgb(255, 0, 0);
display: inline-block;
width: 100%;
}
/*
From https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs.css
*/
/*
Visual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>
*/
.notebook-preview .hljs-function,
.notebook-preview .hljs-params {
color: inherit;
}
.notebook-preview .hljs-comment,
.notebook-preview .hljs-quote,
.notebook-preview .hljs-variable {
color: #008000;
}
.notebook-preview .hljs-keyword,
.notebook-preview .hljs-selector-tag,
.notebook-preview .hljs-built_in,
.notebook-preview .hljs-name,
.notebook-preview .hljs-tag {
color: #00f;
}
.notebook-preview .hljs-string,
.notebook-preview .hljs-title,
.notebook-preview .hljs-section,
.notebook-preview .hljs-attribute,
.notebook-preview .hljs-literal,
.notebook-preview .hljs-template-tag,
.notebook-preview .hljs-template-variable,
.notebook-preview .hljs-type {
color: #a31515;
}
.notebook-preview .hljs-selector-attr,
.notebook-preview .hljs-selector-pseudo,
.notebook-preview .hljs-meta {
color: #2b91af;
}
.notebook-preview .hljs-doctag {
color: #808080;
}
.notebook-preview .hljs-attr {
color: #f00;
}
.notebook-preview .hljs-symbol,
.notebook-preview .hljs-bullet,
.notebook-preview .hljs-link {
color: #00b0e8;
}
.notebook-preview .hljs-emphasis {
font-style: italic;
}
.notebook-preview .hljs-strong {
font-weight: bold;
}

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.1484 3.64844L15.8516 4.35156L8 12.2031L0.148438 4.35156L0.851562 3.64844L8 10.7969L15.1484 3.64844Z" fill="#4F4F4F"/>
</svg>

After

Width:  |  Height:  |  Size: 234 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.2734 11.9766L8 4.71094L0.726562 11.9766L0.0234375 11.2734L8 3.28906L15.9766 11.2734L15.2734 11.9766Z" fill="#4F4F4F"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>execute_cell</title><circle cx="8" cy="7.92" r="7.76"/><polygon class="cls-1" points="10.7 8 6.67 11 6.67 5 10.7 8 10.7 8"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}.cls-2{fill:#fff;}</style></defs><title>execute_cell_error</title><circle class="cls-1" cx="8" cy="7.92" r="7.76"/><polygon class="cls-2" points="10.7 8 6.67 11 6.67 5 10.7 8 10.7 8"/></svg>

After

Width:  |  Height:  |  Size: 317 B

View File

@@ -0,0 +1,16 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs><style>.cls-1{fill:#c1d6e6;}.cls-2{fill:#0078d4;}.cls-3{fill:#fff;}</style></defs>
<title>stop_cell_solidanimation</title>
<path class="cls-1" d="M8,16a8,8,0,1,1,8-8A8,8,0,0,1,8,16ZM8,1a7,7,0,1,0,7,7A7,7,0,0,0,8,1Z"/>
<path class="cls-2" d="M8.51,0v1A7,7,0,0,1,15,8a6.87,6.87,0,0,1-1.07,3.7l.81.64A7.92,7.92,0,0,0,16,8,8,8,0,0,0,8.51,0Z">
<animateTransform attributeName="transform"
type="rotate"
from="0 8 8"
to="360 8 8"
begin="0s"
dur="1.5s"
repeatCount="indefinite"
/>
</path>
<circle cx="8" cy="8" r="6.32"/>
<rect class="cls-3" x="4.91" y="4.91" width="6.18" height="6.18"/></svg>

After

Width:  |  Height:  |  Size: 766 B

View File

@@ -0,0 +1,231 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.notebook-preview {
font-size: 14px;
line-height: 22px;
word-wrap: break-word;
}
.notebook-preview #code-csp-warning {
position: fixed;
top: 0;
right: 0;
color: white;
margin: 16px;
text-align: center;
font-size: 12px;
font-family: sans-serif;
background-color:#444444;
cursor: pointer;
padding: 6px;
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
}
.notebook-preview #code-csp-warning:hover {
text-decoration: none;
background-color:#007acc;
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}
.notebook-preview .scrollBeyondLastLine {
margin-bottom: calc(100vh - 22px);
}
.notebook-preview .showEditorSelection .code-line {
position: relative;
}
.notebook-preview .showEditorSelection .code-active-line:before,
.notebook-preview .showEditorSelection .code-line:hover:before {
content: "";
display: block;
position: absolute;
top: 0;
left: -12px;
height: 100%;
}
.notebook-preview .showEditorSelection li.code-active-line:before,
.notebook-preview .showEditorSelection li.code-line:hover:before {
left: -30px;
}
.notebook-preview .showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(0, 0, 0, 0.15);
}
.notebook-preview .showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(0, 0, 0, 0.40);
}
.notebook-preview .showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
.vs-dark .notebook-preview .showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(255, 255, 255, 0.4);
}
.vs-dark .notebook-preview .showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(255, 255, 255, 0.60);
}
.vs-dark .notebook-preview .showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
.hc-black .notebook-preview .showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(255, 160, 0, 0.7);
}
.hc-black .notebook-preview .showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(255, 160, 0, 1);
}
.hc-black .notebook-preview .showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
.notebook-preview img {
max-width: 100%;
max-height: 100%;
}
.notebookEditor a {
text-decoration: none;
}
.notebookEditor a:hover {
text-decoration: underline;
}
.notebook-preview a:focus,
.notebook-preview input:focus,
.notebook-preview select:focus,
.notebook-preview textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
.notebook-preview hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
.notebook-preview h1 {
padding-bottom: 0.3em;
line-height: 1.2;
border-bottom-width: 1px;
border-bottom-style: solid;
}
.notebook-preview h1, .notebook-preview h2, .notebook-preview h3 {
font-weight: normal;
}
.notebook-preview h1 code,
.notebook-preview h2 code,
.notebook-preview h3 code,
.notebook-preview h4 code,
.notebook-preview h5 code,
.notebook-preview h6 code {
font-size: inherit;
line-height: auto;
}
.notebook-preview table {
border-collapse: collapse;
}
.notebook-preview table > thead > tr > th {
text-align: left;
border-bottom: 1px solid;
}
.notebook-preview table > thead > tr > th,
.notebook-preview table > thead > tr > td,
.notebook-preview table > tbody > tr > th,
.notebook-preview .notebook-preview table > tbody > tr > td {
padding: 5px 10px;
}
.notebook-preview table > tbody > tr + tr > td {
border-top: 1px solid;
}
.notebook-preview blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
}
.notebook-preview code {
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
font-size: 12px;
line-height: 19px;
}
.notebook-preview pre {
white-space: pre-wrap;
}
.notebook-preview .mac code {
font-size: 12px;
line-height: 18px;
}
.notebook-preview pre:not(.hljs),
.notebook-preview pre.hljs code > div {
padding: 16px;
border-radius: 3px;
overflow: auto;
}
/** Theming */
.notebook-preview pre code {
color: var(--vscode-editor-foreground);
}
.notebook-preview pre {
background-color: rgba(220, 220, 220, 0.4);
}
.vs-dark .notebook-preview pre {
background-color: rgba(10, 10, 10, 0.4);
}
.hc-black .notebook-preview pre {
background-color: rgb(0, 0, 0);
}
.hc-black .notebook-preview h1 {
border-color: rgb(0, 0, 0);
}
.notebook-preview table > thead > tr > th {
border-color: rgba(0, 0, 0, 0.69);
}
.vs-dark .notebook-preview table > thead > tr > th {
border-color: rgba(255, 255, 255, 0.69);
}
.notebook-preview h1,
.notebook-preview hr,
.notebook-preview table > tbody > tr + tr > td {
border-color: rgba(0, 0, 0, 0.18);
}
.vs-dark .notebook-preview h1,
.vs-dark .notebook-preview hr,
.vs-dark .notebook-preview table > tbody > tr + tr > td {
border-color: rgba(255, 255, 255, 0.18);
}

View File

@@ -0,0 +1,476 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------
| RenderedText
|----------------------------------------------------------------------------*/
output-component .jp-RenderedText {
text-align: left;
padding-left: var(--jp-code-padding);
font-size: var(--jp-code-font-size);
line-height: var(--jp-code-line-height);
font-family: var(--jp-code-font-family);
}
output-component .jp-RenderedText pre,
.jp-RenderedJavaScript pre,
output-component .jp-RenderedHTMLCommon pre {
color: var(--jp-content-font-color1);
border: none;
margin: 0px;
padding: 0px;
}
/* ansi_up creates classed spans for console foregrounds and backgrounds. */
output-component .jp-RenderedText pre .ansi-black-fg {
color: #3e424d;
}
output-component .jp-RenderedText pre .ansi-red-fg {
color: #e75c58;
}
output-component .jp-RenderedText pre .ansi-green-fg {
color: #00a250;
}
output-component .jp-RenderedText pre .ansi-yellow-fg {
color: #ddb62b;
}
output-component .jp-RenderedText pre .ansi-blue-fg {
color: #208ffb;
}
output-component .jp-RenderedText pre .ansi-magenta-fg {
color: #d160c4;
}
output-component .jp-RenderedText pre .ansi-cyan-fg {
color: #60c6c8;
}
output-component .jp-RenderedText pre .ansi-white-fg {
color: #c5c1b4;
}
output-component .jp-RenderedText pre .ansi-black-bg {
background-color: #3e424d;
}
output-component .jp-RenderedText pre .ansi-red-bg {
background-color: #e75c58;
}
output-component .jp-RenderedText pre .ansi-green-bg {
background-color: #00a250;
}
output-component .jp-RenderedText pre .ansi-yellow-bg {
background-color: #ddb62b;
}
output-component .jp-RenderedText pre .ansi-blue-bg {
background-color: #208ffb;
}
output-component .jp-RenderedText pre .ansi-magenta-bg {
background-color: #d160c4;
}
output-component .jp-RenderedText pre .ansi-cyan-bg {
background-color: #60c6c8;
}
output-component .jp-RenderedText pre .ansi-white-bg {
background-color: #c5c1b4;
}
output-component .jp-RenderedText pre .ansi-bright-black-fg {
color: #282c36;
}
output-component .jp-RenderedText pre .ansi-bright-red-fg {
color: #b22b31;
}
output-component .jp-RenderedText pre .ansi-bright-green-fg {
color: #007427;
}
output-component .jp-RenderedText pre .ansi-bright-yellow-fg {
color: #b27d12;
}
output-component .jp-RenderedText pre .ansi-bright-blue-fg {
color: #0065ca;
}
output-component .jp-RenderedText pre .ansi-bright-magenta-fg {
color: #a03196;
}
output-component .jp-RenderedText pre .ansi-bright-cyan-fg {
color: #258f8f;
}
output-component .jp-RenderedText pre .ansi-bright-white-fg {
color: #a1a6b2;
}
output-component .jp-RenderedText pre .ansi-bright-black-bg {
background-color: #282c36;
}
output-component .jp-RenderedText pre .ansi-bright-red-bg {
background-color: #b22b31;
}
output-component .jp-RenderedText pre .ansi-bright-green-bg {
background-color: #007427;
}
output-component .jp-RenderedText pre .ansi-bright-yellow-bg {
background-color: #b27d12;
}
output-component .jp-RenderedText pre .ansi-bright-blue-bg {
background-color: #0065ca;
}
output-component .jp-RenderedText pre .ansi-bright-magenta-bg {
background-color: #a03196;
}
output-component .jp-RenderedText pre .ansi-bright-cyan-bg {
background-color: #258f8f;
}
output-component .jp-RenderedText pre .ansi-bright-white-bg {
background-color: #a1a6b2;
}
output-component .jp-RenderedText[data-mime-type='application/vnd.jupyter.stderr'] {
background: var(--jp-rendermime-error-background);
padding-top: var(--jp-code-padding);
}
/*-----------------------------------------------------------------------------
| RenderedLatex
|----------------------------------------------------------------------------*/
.jp-RenderedLatex {
color: var(--jp-content-font-color1);
font-size: var(--jp-content-font-size1);
line-height: var(--jp-content-line-height);
}
/* Left-justify outputs.*/
.jp-OutputArea-output.jp-RenderedLatex {
text-align: left;
}
/*-----------------------------------------------------------------------------
| RenderedHTML
|----------------------------------------------------------------------------*/
output-component .jp-RenderedHTMLCommon {
color: var(--jp-content-font-color1);
font-family: var(--jp-content-font-family);
font-size: var(--jp-content-font-size1);
line-height: var(--jp-content-line-height);
/* Give a bit more R padding on Markdown text to keep line lengths reasonable */
padding-right: 20px;
}
output-component .jp-RenderedHTMLCommon em {
font-style: italic;
}
output-component .jp-RenderedHTMLCommon strong {
font-weight: bold;
}
output-component .jp-RenderedHTMLCommon u {
text-decoration: underline;
}
output-component .jp-RenderedHTMLCommon a:link {
text-decoration: none;
}
output-component .jp-RenderedHTMLCommon a:hover {
text-decoration: underline;
}
output-component .jp-RenderedHTMLCommon a:visited {
text-decoration: none;
}
/* Headings */
output-component .jp-RenderedHTMLCommon h1,
output-component .jp-RenderedHTMLCommon h2,
output-component .jp-RenderedHTMLCommon h3,
output-component .jp-RenderedHTMLCommon h4,
output-component .jp-RenderedHTMLCommon h5,
output-component .jp-RenderedHTMLCommon h6 {
line-height: var(--jp-content-heading-line-height);
font-weight: var(--jp-content-heading-font-weight);
font-style: normal;
margin: var(--jp-content-heading-margin-top) 0
var(--jp-content-heading-margin-bottom) 0;
}
output-component .jp-RenderedHTMLCommon h1:first-child,
output-component .jp-RenderedHTMLCommon h2:first-child,
output-component .jp-RenderedHTMLCommon h3:first-child,
output-component .jp-RenderedHTMLCommon h4:first-child,
output-component .jp-RenderedHTMLCommon h5:first-child,
output-component .jp-RenderedHTMLCommon h6:first-child {
margin-top: calc(0.5 * var(--jp-content-heading-margin-top));
}
output-component .jp-RenderedHTMLCommon h1:last-child,
output-component .jp-RenderedHTMLCommon h2:last-child,
output-component .jp-RenderedHTMLCommon h3:last-child,
output-component .jp-RenderedHTMLCommon h4:last-child,
output-component .jp-RenderedHTMLCommon h5:last-child,
output-component .jp-RenderedHTMLCommon h6:last-child {
margin-bottom: calc(0.5 * var(--jp-content-heading-margin-bottom));
}
output-component .jp-RenderedHTMLCommon h1 {
font-size: var(--jp-content-font-size5);
}
output-component .jp-RenderedHTMLCommon h2 {
font-size: var(--jp-content-font-size4);
}
output-component .jp-RenderedHTMLCommon h3 {
font-size: var(--jp-content-font-size3);
}
output-component .jp-RenderedHTMLCommon h4 {
font-size: var(--jp-content-font-size2);
}
output-component .jp-RenderedHTMLCommon h5 {
font-size: var(--jp-content-font-size1);
}
output-component .jp-RenderedHTMLCommon h6 {
font-size: var(--jp-content-font-size0);
}
/* Lists */
output-component .jp-RenderedHTMLCommon ul:not(.list-inline),
output-component .jp-RenderedHTMLCommon ol:not(.list-inline) {
padding-left: 2em;
}
output-component .jp-RenderedHTMLCommon ul {
list-style: disc;
}
output-component .jp-RenderedHTMLCommon ul ul {
list-style: square;
}
output-component .jp-RenderedHTMLCommon ul ul ul {
list-style: circle;
}
output-component .jp-RenderedHTMLCommon ol {
list-style: decimal;
}
output-component .jp-RenderedHTMLCommon ol ol {
list-style: upper-alpha;
}
output-component .jp-RenderedHTMLCommon ol ol ol {
list-style: lower-alpha;
}
output-component .jp-RenderedHTMLCommon ol ol ol ol {
list-style: lower-roman;
}
output-component .jp-RenderedHTMLCommon ol ol ol ol ol {
list-style: decimal;
}
output-component .jp-RenderedHTMLCommon ol,
output-component .jp-RenderedHTMLCommon ul {
margin-bottom: 1em;
}
output-component .jp-RenderedHTMLCommon ul ul,
output-component .jp-RenderedHTMLCommon ul ol,
output-component .jp-RenderedHTMLCommon ol ul,
output-component .jp-RenderedHTMLCommon ol ol {
margin-bottom: 0em;
}
output-component .jp-RenderedHTMLCommon hr {
color: var(--jp-border-color2);
background-color: var(--jp-border-color1);
margin-top: 1em;
margin-bottom: 1em;
}
output-component .jp-RenderedHTMLCommon > pre {
margin: 1.5em 2em;
}
output-component .jp-RenderedHTMLCommon pre,
output-component .jp-RenderedHTMLCommon code {
border: 0;
background-color: var(--jp-layout-color0);
color: var(--jp-content-font-color1);
font-family: var(--jp-code-font-family);
font-size: inherit;
line-height: var(--jp-code-line-height);
padding: 0;
}
output-component .jp-RenderedHTMLCommon p > code {
background-color: var(--jp-layout-color2);
padding: 1px 5px;
}
/* Tables */
output-component .jp-RenderedHTMLCommon table {
border-collapse: collapse;
border-spacing: 0;
border: none;
color: var(--jp-ui-font-color1);
font-size: 12px;
table-layout: auto;
margin-left: auto;
margin-right: auto;
}
output-component .jp-RenderedHTMLCommon thead {
border-bottom: var(--jp-border-width) solid var(--jp-border-color1);
vertical-align: bottom;
}
output-component .jp-RenderedHTMLCommon td,
output-component .jp-RenderedHTMLCommon th,
output-component .jp-RenderedHTMLCommon tr {
text-align: left;
vertical-align: middle;
padding: 0.5em 0.5em;
line-height: normal;
white-space: normal;
max-width: none;
border: none;
}
.jp-RenderedMarkdown.jp-RenderedHTMLCommon td,
.jp-RenderedMarkdown.jp-RenderedHTMLCommon th {
max-width: none;
}
output-component th {
font-weight: bold;
}
output-component .jp-RenderedHTMLCommon tbody tr:nth-child(odd) {
background: var(--jp-layout-color0);
}
output-component .jp-RenderedHTMLCommon tbody tr:nth-child(even) {
background: var(--jp-rendermime-table-row-background);
}
output-component .jp-RenderedHTMLCommon tbody tr:hover {
background: var(--jp-rendermime-table-row-hover-background);
}
output-component .jp-RenderedHTMLCommon table {
margin-bottom: 1em;
display: table-row;
}
output-component .jp-RenderedHTMLCommon p {
text-align: left;
margin: 0px;
}
output-component .jp-RenderedHTMLCommon p {
margin-bottom: 1em;
}
output-component .jp-RenderedHTMLCommon img {
-moz-force-broken-image-icon: 1;
}
/* Restrict to direct children as other images could be nested in other content. */
output-component .jp-RenderedHTMLCommon > img {
display: block;
margin-left: auto;
margin-right: auto;
margin-bottom: 1em;
}
/* Change color behind transparent images if they need it... */
[data-theme-light='false'] .jp-RenderedImage img.jp-needs-light-background {
background-color: var(--jp-inverse-layout-color1);
}
[data-theme-light='true'] .jp-RenderedImage img.jp-needs-dark-background {
background-color: var(--jp-inverse-layout-color1);
}
/* ...or leave it untouched if they don't */
[data-theme-light='false'] .jp-RenderedImage img.jp-needs-dark-background {
}
[data-theme-light='true'] .jp-RenderedImage img.jp-needs-light-background {
}
output-component .jp-RenderedHTMLCommon img,
.jp-RenderedImage img,
output-component .jp-RenderedHTMLCommon svg,
.jp-RenderedSVG svg {
max-width: 100%;
height: auto;
}
output-component .jp-RenderedHTMLCommon img.jp-mod-unconfined,
.jp-RenderedImage img.jp-mod-unconfined,
output-component .jp-RenderedHTMLCommon svg.jp-mod-unconfined,
.jp-RenderedSVG svg.jp-mod-unconfined {
max-width: none;
}
output-component .jp-RenderedHTMLCommon .alert {
margin-bottom: 1em;
}
output-component .jp-RenderedHTMLCommon blockquote {
margin: 1em 2em;
padding: 0 1em;
border-left: 5px solid var(--jp-border-color2);
}
a.jp-InternalAnchorLink {
visibility: hidden;
margin-left: 8px;
color: var(--md-blue-800);
}
h1:hover .jp-InternalAnchorLink,
h2:hover .jp-InternalAnchorLink,
h3:hover .jp-InternalAnchorLink,
h4:hover .jp-InternalAnchorLink,
h5:hover .jp-InternalAnchorLink,
h6:hover .jp-InternalAnchorLink {
visibility: visible;
}
/* Most direct children of .jp-RenderedHTMLCommon have a margin-bottom of 1.0.
* At the bottom of cells this is a bit too much as there is also spacing
* between cells. Going all the way to 0 gets too tight between markdown and
* code cells.
*/
output-component .jp-RenderedHTMLCommon > *:last-child {
margin-bottom: 0.5em;
}
/*-----------------------------------------------------------------------------
| RenderedPDF
|----------------------------------------------------------------------------*/
.jp-RenderedPDF {
font-size: var(--jp-ui-font-size1);
}
plotly-output .plotly-wrapper {
display: block;
overflow-y: hidden;
}
output-component .grid-panel .action-label.codicon {
min-width: 16px;
margin-right: 6px;
margin-bottom: 6px;
}

View File

@@ -0,0 +1,15 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div style="flex: 0 0 auto; user-select: none;">
<div #output class="output-userselect">
<ng-template component-host>
</ng-template>
<pre *ngIf="hasError" class="p-Widget jp-RenderedText">{{errorText}}</pre>
</div>
</div>
</div>

View File

@@ -0,0 +1,187 @@
/*---------------------------------------------------------------------------------------------
* 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!./code';
import 'vs/css!./media/output';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, SimpleChange, AfterViewInit, forwardRef, ChangeDetectorRef, ComponentRef, ComponentFactoryResolver } from '@angular/core';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { Event } from 'vs/base/common/event';
import { nb } from 'azdata';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import * as outputProcessor from 'sql/workbench/contrib/notebook/browser/models/outputProcessor';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import * as DOM from 'vs/base/browser/dom';
import { ComponentHostDirective } from 'sql/workbench/contrib/dashboard/browser/core/componentHost.directive';
import { Extensions, IMimeComponent, IMimeComponentRegistry } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import * as themeColors from 'vs/workbench/common/theme';
import { Registry } from 'vs/platform/registry/common/platform';
import { localize } from 'vs/nls';
import * as types from 'vs/base/common/types';
import { getErrorMessage } from 'vs/base/common/errors';
export const OUTPUT_SELECTOR: string = 'output-component';
const USER_SELECT_CLASS = 'actionselect';
const componentRegistry = <IMimeComponentRegistry>Registry.as(Extensions.MimeComponentContribution);
@Component({
selector: OUTPUT_SELECTOR,
templateUrl: decodeURI(require.toUrl('./output.component.html'))
})
export class OutputComponent extends AngularDisposable implements OnInit, AfterViewInit {
@ViewChild('output', { read: ElementRef }) private outputElement: ElementRef;
@ViewChild(ComponentHostDirective) componentHost: ComponentHostDirective;
@Input() cellOutput: nb.ICellOutput;
@Input() cellModel: ICellModel;
private _trusted: boolean;
private _initialized: boolean = false;
private _activeCellId: string;
private _componentInstance: IMimeComponent;
public errorText: string;
constructor(
@Inject(IThemeService) private _themeService: IThemeService,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeref: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _ref: ElementRef,
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver
) {
super();
}
ngOnInit() {
this._register(this._themeService.onThemeChange(event => this.updateTheme(event)));
this.loadComponent();
this.layout();
this._initialized = true;
this._register(Event.debounce(this.cellModel.notebookModel.layoutChanged, (l, e) => e, 50, /*leading=*/false)
(() => this.layout()));
}
ngAfterViewInit() {
this.updateTheme(this._themeService.getTheme());
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
for (let propName in changes) {
if (propName === 'activeCellId') {
this.toggleUserSelect(this.isActive());
break;
}
}
}
private toggleUserSelect(userSelect: boolean): void {
if (!this.nativeOutputElement) {
return;
}
if (userSelect) {
DOM.addClass(this.nativeOutputElement, USER_SELECT_CLASS);
} else {
DOM.removeClass(this.nativeOutputElement, USER_SELECT_CLASS);
}
}
private get nativeOutputElement() {
return this.outputElement ? this.outputElement.nativeElement : undefined;
}
public layout(): void {
if (this.componentInstance && this.componentInstance.layout) {
this.componentInstance.layout();
}
}
private get componentInstance(): IMimeComponent {
if (!this._componentInstance) {
this.loadComponent();
}
return this._componentInstance;
}
get trustedMode(): boolean {
return this._trusted;
}
@Input() set trustedMode(value: boolean) {
this._trusted = value;
if (this._initialized) {
this.layout();
}
}
@Input() set activeCellId(value: string) {
this._activeCellId = value;
}
get activeCellId(): string {
return this._activeCellId;
}
protected isActive() {
return this.cellModel && this.cellModel.id === this.activeCellId;
}
public hasError(): boolean {
return !types.isUndefinedOrNull(this.errorText);
}
private updateTheme(theme: ITheme): void {
let el = <HTMLElement>this._ref.nativeElement;
let backgroundColor = theme.getColor(colors.editorBackground, true);
let foregroundColor = theme.getColor(themeColors.SIDE_BAR_FOREGROUND, true);
if (backgroundColor) {
el.style.backgroundColor = backgroundColor.toString();
}
if (foregroundColor) {
el.style.color = foregroundColor.toString();
}
}
private loadComponent(): void {
let options = outputProcessor.getBundleOptions({ value: this.cellOutput, trusted: this.trustedMode });
options.themeService = this._themeService;
let mimeType = componentRegistry.getPreferredMimeType(
options.data,
options.trusted ? 'any' : 'ensure'
);
this.errorText = undefined;
if (!mimeType) {
this.errorText = localize('noMimeTypeFound', "No {0}renderer could be found for output. It has the following MIME types: {1}",
options.trusted ? '' : localize('safe', "safe "),
Object.keys(options.data).join(', '));
return;
}
let selector = componentRegistry.getCtorFromMimeType(mimeType);
if (!selector) {
this.errorText = localize('noSelectorFound', "No component could be found for selector {0}", mimeType);
return;
}
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(selector);
let viewContainerRef = this.componentHost.viewContainerRef;
viewContainerRef.clear();
let componentRef: ComponentRef<IMimeComponent>;
try {
componentRef = viewContainerRef.createComponent(componentFactory, 0);
this._componentInstance = componentRef.instance;
this._componentInstance.mimeType = mimeType;
this._componentInstance.cellModel = this.cellModel;
this._componentInstance.bundleOptions = options;
this._changeref.detectChanges();
let el = <HTMLElement>componentRef.location.nativeElement;
// set widget styles to conform to its box
el.style.overflow = 'hidden';
el.style.position = 'relative';
} catch (e) {
this.errorText = localize('componentRenderError', "Error rendering component: {0}", getErrorMessage(e));
return;
}
}
}

View File

@@ -0,0 +1,12 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div #outputarea link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-output" style="flex: 0 0 auto;">
<output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" [cellModel]="cellModel" [activeCellId]="activeCellId">
</output-component>
</div>
</div>

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* 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!./code';
import 'vs/css!./outputArea';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, ChangeDetectorRef } from '@angular/core';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import * as themeColors from 'vs/workbench/common/theme';
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { URI } from 'vs/base/common/uri';
export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
@Component({
selector: OUTPUT_AREA_SELECTOR,
templateUrl: decodeURI(require.toUrl('./outputArea.component.html'))
})
export class OutputAreaComponent extends AngularDisposable implements OnInit {
@ViewChild('outputarea', { read: ElementRef }) private outputArea: ElementRef;
@Input() cellModel: ICellModel;
private _activeCellId: string;
constructor(
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef
) {
super();
}
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
if (this.cellModel) {
this._register(this.cellModel.onOutputsChanged(e => {
if (!(this._changeRef['destroyed'])) {
this._changeRef.detectChanges();
if (e && e.shouldScroll) {
this.setFocusAndScroll(this.outputArea.nativeElement);
}
}
}));
}
}
public get isTrusted(): boolean {
return this.cellModel.trustedMode;
}
public get notebookUri(): URI {
return this.cellModel.notebookModel.notebookUri;
}
private setFocusAndScroll(node: HTMLElement): void {
// If offsetParent is null, the element isn't visible
// In this case, we don't want a cell to grab focus for an editor that isn't in the foreground
if (node && node.offsetParent) {
node.focus();
node.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
@Input() set activeCellId(value: string) {
this._activeCellId = value;
}
get activeCellId(): string {
return this._activeCellId;
}
private updateTheme(theme: IColorTheme): void {
let outputElement = <HTMLElement>this.outputArea.nativeElement;
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
output-area-component {
display: block;
}
output-area-component .notebook-output {
border-top-width: 0px;
user-select: text;
padding: 5px 20px 0px;
}
.output-userselect.actionselect {
user-select: text;
}
.output-userselect pre{
white-space: pre-wrap;
word-wrap: break-word;
}

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
placeholder-cell-component {
height: 50px;
width: 100%;
display: block;
box-shadow: 0px 4px 6px 0px rgba(0,0,0,0.14);
}
placeholder-cell-component .text {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
-webkit-margin-before: 0em;
-webkit-margin-after: 0em;
}

View File

@@ -0,0 +1,13 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div class="placeholder-cell-component" style="flex: 0 0 auto;">
<div class="placeholder-cell-component text">
<p>{{clickOn}} <a href="#" (click)="addCell('code', $event)">{{plusCode}}</a> {{or}} <a href="#" (click)="addCell('markdown', $event)">{{plusText}}</a> {{toAddCell}}</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* 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!./placeholder';
import { OnInit, Component, Input, Inject, forwardRef, ChangeDetectorRef, SimpleChange, OnChanges } from '@angular/core';
import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { NotebookModel } from 'sql/workbench/contrib/notebook/browser/models/notebookModel';
import { localize } from 'vs/nls';
import { CellType } from 'sql/workbench/contrib/notebook/common/models/contracts';
export const PLACEHOLDER_SELECTOR: string = 'placeholder-cell-component';
@Component({
selector: PLACEHOLDER_SELECTOR,
templateUrl: decodeURI(require.toUrl('./placeholderCell.component.html'))
})
export class PlaceholderCellComponent extends CellView implements OnInit, OnChanges {
@Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) {
this._model = value;
}
private _model: NotebookModel;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
) {
super();
}
ngOnInit() {
if (this.cellModel) {
this._register(this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
}));
}
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
}
get model(): NotebookModel {
return this._model;
}
get clickOn(): string {
return localize('clickOn', "Click on");
}
get plusCode(): string {
return localize('plusCode', "+ Code");
}
get or(): string {
return localize('or', "or");
}
get plusText(): string {
return localize('plusText', "+ Text");
}
get toAddCell(): string {
return localize('toAddCell', "to add a code or text cell");
}
public addCell(cellType: string, event?: Event): void {
if (event) {
event.stopPropagation();
}
let type: CellType = <CellType>cellType;
if (!type) {
type = 'code';
}
this._model.addCell(type);
}
public layout() {
}
}

View File

@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* 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!./stdin';
import {
Component, Input, Inject,
ViewChild, ElementRef, AfterViewInit, HostListener
} from '@angular/core';
import { nb } from 'azdata';
import { localize } from 'vs/nls';
import { IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { attachInputBoxStyler } from 'sql/platform/theme/common/styler';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { Deferred } from 'sql/base/common/promise';
import { ICellModel, CellExecutionState } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
export const STDIN_SELECTOR: string = 'stdin-component';
@Component({
selector: STDIN_SELECTOR,
template: `
<div class="prompt">{{prompt}}</div>
<div #input class="input"></div>
`
})
export class StdInComponent extends AngularDisposable implements AfterViewInit {
private _input: InputBox;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
@Input() stdIn: nb.IStdinMessage;
@Input() onSendInput: Deferred<string>;
@Input() cellModel: ICellModel;
constructor(
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService
) {
super();
}
ngAfterViewInit(): void {
let inputOptions: IInputOptions = {
placeholder: '',
ariaLabel: this.prompt
};
this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions);
if (this.password) {
this._input.inputElement.type = 'password';
}
this._register(this._input);
this._register(attachInputBoxStyler(this._input, this.themeService, {
inputValidationInfoBackground: inputBackground,
inputValidationInfoBorder: inputBorder,
}));
if (this.cellModel) {
this._register(this.cellModel.onExecutionStateChange((status) => this.handleExecutionChange(status)));
}
this._input.focus();
}
@HostListener('document:keydown', ['$event'])
public handleKeyboardInput(event: KeyboardEvent): void {
let e = new StandardKeyboardEvent(event);
switch (e.keyCode) {
case KeyCode.Enter:
// Indi
if (this.onSendInput) {
this.onSendInput.resolve(this._input.value);
}
e.stopPropagation();
break;
case KeyCode.Escape:
if (this.onSendInput) {
this.onSendInput.reject('');
}
e.stopPropagation();
break;
default:
// No-op
break;
}
}
handleExecutionChange(status: CellExecutionState): void {
if (status !== CellExecutionState.Running && this.onSendInput) {
this.onSendInput.reject('');
}
}
private get prompt(): string {
if (this.stdIn && this.stdIn.content && this.stdIn.content.prompt) {
return this.stdIn.content.prompt;
}
return localize('stdInLabel', "StdIn:");
}
private get password(): boolean {
return this.stdIn && this.stdIn.content && this.stdIn.content.password;
}
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
stdin-component {
display: flex;
flex-flow: row;
padding: 10px;
align-items: center;
}
stdin-component .prompt {
flex: 0 0 auto;
}
stdin-component .input {
flex: 1 1 auto;
padding-left: 10px;
}

View File

@@ -0,0 +1,18 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column" (mouseover)="hover=true" (mouseleave)="hover=false">
<div class="notebook-text" style="flex: 0 0 auto;">
<code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()" [model]="model" [activeCellId]="activeCellId">
</code-component>
</div>
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
<div #preview link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-preview" style="flex: 1 1 auto" (dblclick)="toggleEditMode()">
</div>
<div #moreactions class="moreActions" style="flex: 0 0 auto; display: flex; flex-flow:column;width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
</div>
</div>
</div>

View File

@@ -0,0 +1,262 @@
/*---------------------------------------------------------------------------------------------
* 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!./textCell';
import 'vs/css!./media/markdown';
import 'vs/css!./media/highlight';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnChanges, SimpleChange, HostListener } from '@angular/core';
import { localize } from 'vs/nls';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import * as DOM from 'vs/base/browser/dom';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { toDisposable } from 'vs/base/common/lifecycle';
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
import { NotebookMarkdownRenderer } from 'sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown';
import { CellView } from 'sql/workbench/contrib/notebook/browser/cellViews/interfaces';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { NotebookModel } from 'sql/workbench/contrib/notebook/browser/models/notebookModel';
import { ISanitizer, defaultSanitizer } from 'sql/workbench/contrib/notebook/browser/outputs/sanitizer';
import { CellToggleMoreActions } from 'sql/workbench/contrib/notebook/browser/cellToggleMoreActions';
import { useInProcMarkdown, convertVscodeResourceToFileInSubDirectories } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils';
export const TEXT_SELECTOR: string = 'text-cell-component';
const USER_SELECT_CLASS = 'actionselect';
@Component({
selector: TEXT_SELECTOR,
templateUrl: decodeURI(require.toUrl('./textCell.component.html'))
})
export class TextCellComponent extends CellView implements OnInit, OnChanges {
@ViewChild('preview', { read: ElementRef }) private output: ElementRef;
@ViewChild('moreactions', { read: ElementRef }) private moreActionsElementRef: ElementRef;
@Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) {
this._model = value;
}
@Input() set activeCellId(value: string) {
this._activeCellId = value;
}
@Input() set hover(value: boolean) {
this._hover = value;
if (!this.isActive()) {
// Only make a change if we're not active, since this has priority
this.updateMoreActions();
}
}
@HostListener('document:keydown.escape', ['$event'])
handleKeyboardEvent() {
if (this.isEditMode) {
this.toggleEditMode(false);
}
this.cellModel.active = false;
this._model.updateActiveCell(undefined);
}
private _content: string | string[];
private _lastTrustedMode: boolean;
private isEditMode: boolean;
private _sanitizer: ISanitizer;
private _model: NotebookModel;
private _activeCellId: string;
private readonly _onDidClickLink = this._register(new Emitter<URI>());
public readonly onDidClickLink = this._onDidClickLink.event;
private _cellToggleMoreActions: CellToggleMoreActions;
private _hover: boolean;
private markdownRenderer: NotebookMarkdownRenderer;
private markdownResult: IMarkdownRenderResult;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(ICommandService) private _commandService: ICommandService,
@Inject(IConfigurationService) private configurationService: IConfigurationService,
) {
super();
this.isEditMode = true;
this._cellToggleMoreActions = this._instantiationService.createInstance(CellToggleMoreActions);
this.markdownRenderer = this._instantiationService.createInstance(NotebookMarkdownRenderer);
this._register(toDisposable(() => {
if (this.markdownResult) {
this.markdownResult.dispose();
}
}));
}
//Gets sanitizer from ISanitizer interface
private get sanitizer(): ISanitizer {
if (this._sanitizer) {
return this._sanitizer;
}
return this._sanitizer = defaultSanitizer;
}
get model(): NotebookModel {
return this._model;
}
get activeCellId(): string {
return this._activeCellId;
}
private setLoading(isLoading: boolean): void {
this.cellModel.loaded = !isLoading;
this._changeRef.detectChanges();
}
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
this._cellToggleMoreActions.onInit(this.moreActionsElementRef, this.model, this.cellModel);
this.setFocusAndScroll();
this._register(this.cellModel.onOutputsChanged(e => {
this.updatePreview();
}));
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
for (let propName in changes) {
if (propName === 'activeCellId') {
let changedProp = changes[propName];
this._activeCellId = changedProp.currentValue;
this.toggleUserSelect(this.isActive());
// If the activeCellId is undefined (i.e. in an active cell update), don't unnecessarily set editMode to false;
// it will be set to true in a subsequent call to toggleEditMode()
if (changedProp.previousValue !== undefined) {
this.toggleEditMode(false);
}
break;
}
}
}
public get isTrusted(): boolean {
return this.model.trustedMode;
}
public get notebookUri(): URI {
return this.model.notebookUri;
}
/**
* Updates the preview of markdown component with latest changes
* If content is empty and in non-edit mode, default it to 'Double-click to edit'
* Sanitizes the data to be shown in markdown cell
*/
private updatePreview(): void {
let trustedChanged = this.cellModel && this._lastTrustedMode !== this.cellModel.trustedMode;
let cellModelSourceJoined = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
let contentJoined = Array.isArray(this._content) ? this._content.join('') : this._content;
let contentChanged = contentJoined !== cellModelSourceJoined || cellModelSourceJoined.length === 0;
if (trustedChanged || contentChanged) {
this._lastTrustedMode = this.cellModel.trustedMode;
if ((!cellModelSourceJoined) && !this.isEditMode) {
this._content = localize('doubleClickEdit', "Double-click to edit");
} else {
this._content = this.cellModel.source;
}
if (useInProcMarkdown(this.configurationService)) {
this.markdownRenderer.setNotebookURI(this.cellModel.notebookModel.notebookUri);
this.markdownResult = this.markdownRenderer.render({
isTrusted: true,
value: Array.isArray(this._content) ? this._content.join('') : this._content
});
this.markdownResult.element.innerHTML = this.sanitizeContent(this.markdownResult.element.innerHTML);
this.setLoading(false);
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.innerHTML = this.markdownResult.element.innerHTML;
} else {
this._commandService.executeCommand<string>('notebook.showPreview', this.cellModel.notebookModel.notebookUri, this._content).then((htmlcontent) => {
htmlcontent = convertVscodeResourceToFileInSubDirectories(htmlcontent, this.cellModel);
htmlcontent = this.sanitizeContent(htmlcontent);
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.innerHTML = htmlcontent;
this.setLoading(false);
});
}
}
}
//Sanitizes the content based on trusted mode of Cell Model
private sanitizeContent(content: string): string {
if (this.cellModel && !this.cellModel.trustedMode) {
content = this.sanitizer.sanitize(content);
}
return content;
}
// Todo: implement layout
public layout() {
}
private updateTheme(theme: IColorTheme): void {
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
let moreActionsEl = <HTMLElement>this.moreActionsElementRef.nativeElement;
moreActionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
public handleContentChanged(): void {
this.updatePreview();
}
public toggleEditMode(editMode?: boolean): void {
this.isEditMode = editMode !== undefined ? editMode : !this.isEditMode;
this.updateMoreActions();
this.updatePreview();
this._changeRef.detectChanges();
}
private updateMoreActions(): void {
if (!this.isEditMode && (this.isActive() || this._hover)) {
this.toggleMoreActionsButton(true);
}
else {
this.toggleMoreActionsButton(false);
}
}
private toggleUserSelect(userSelect: boolean): void {
if (!this.output) {
return;
}
if (userSelect) {
DOM.addClass(this.output.nativeElement, USER_SELECT_CLASS);
} else {
DOM.removeClass(this.output.nativeElement, USER_SELECT_CLASS);
}
}
private setFocusAndScroll(): void {
this.toggleEditMode(this.isActive());
if (this.output && this.output.nativeElement) {
(<HTMLElement>this.output.nativeElement).scrollTo({ behavior: 'smooth' });
}
}
protected isActive() {
return this.cellModel && this.cellModel.id === this.activeCellId;
}
protected toggleMoreActionsButton(isActiveOrHovered: boolean) {
this._cellToggleMoreActions.toggleVisible(!isActiveOrHovered);
}
}

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
text-cell-component {
display: block;
}
text-cell-component .notebook-preview {
user-select: none;
padding-left: 8px;
padding-right: 8px;
}
.notebook-preview.actionselect {
user-select: text;
}
text-cell-component table {
border-collapse: collapse;
border-spacing: 0;
border: none;
font-size: 12px;
table-layout: auto;
margin-left: auto;
margin-right: auto;
margin-bottom: 1em;
display: table-row;
}
text-cell-component thead {
vertical-align: bottom;
}
text-cell-component td,
text-cell-component th,
text-cell-component tr {
text-align: left;
vertical-align: middle;
padding: 0.5em 0.5em;
line-height: normal;
white-space: normal;
max-width: none;
border: none;
}
text-cell-component th {
font-weight: bold;
}