Add collapse/expand functionality to notebook code cells. (#7481)
2
src/sql/azdata.d.ts
vendored
@@ -4536,6 +4536,7 @@ declare module 'azdata' {
|
|||||||
export interface INotebookMetadata {
|
export interface INotebookMetadata {
|
||||||
kernelspec: IKernelInfo;
|
kernelspec: IKernelInfo;
|
||||||
language_info?: ILanguageInfo;
|
language_info?: ILanguageInfo;
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKernelInfo {
|
export interface IKernelInfo {
|
||||||
@@ -4567,6 +4568,7 @@ declare module 'azdata' {
|
|||||||
source: string | string[];
|
source: string | string[];
|
||||||
metadata?: {
|
metadata?: {
|
||||||
language?: string;
|
language?: string;
|
||||||
|
tags?: string[];
|
||||||
azdata_cell_guid?: string;
|
azdata_cell_guid?: string;
|
||||||
};
|
};
|
||||||
execution_count?: number;
|
execution_count?: number;
|
||||||
|
|||||||
@@ -631,6 +631,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
|
|||||||
case NotebookChangeType.CellOutputUpdated:
|
case NotebookChangeType.CellOutputUpdated:
|
||||||
case NotebookChangeType.CellSourceUpdated:
|
case NotebookChangeType.CellSourceUpdated:
|
||||||
case NotebookChangeType.DirtyStateChanged:
|
case NotebookChangeType.DirtyStateChanged:
|
||||||
|
case NotebookChangeType.CellInputVisibilityChanged:
|
||||||
case NotebookChangeType.CellOutputCleared:
|
case NotebookChangeType.CellOutputCleared:
|
||||||
return NotebookChangeKind.ContentUpdated;
|
return NotebookChangeKind.ContentUpdated;
|
||||||
case NotebookChangeType.KernelChanged:
|
case NotebookChangeType.KernelChanged:
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export class QueryTextEditor extends BaseTextEditor {
|
|||||||
return editorWidget.getScrollHeight();
|
return editorWidget.getScrollHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setHeightToScrollHeight(configChanged?: boolean): void {
|
public setHeightToScrollHeight(configChanged?: boolean, isEditorCollapsed?: boolean, ) {
|
||||||
let editorWidget = this.getControl() as ICodeEditor;
|
let editorWidget = this.getControl() as ICodeEditor;
|
||||||
let layoutInfo = editorWidget.getLayoutInfo();
|
let layoutInfo = editorWidget.getLayoutInfo();
|
||||||
if (!this._scrollbarHeight) {
|
if (!this._scrollbarHeight) {
|
||||||
@@ -138,7 +138,12 @@ export class QueryTextEditor extends BaseTextEditor {
|
|||||||
// Not ready yet
|
// Not ready yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let lineCount = editorWidgetModel.getLineCount();
|
let lineCount: number;
|
||||||
|
if (!!isEditorCollapsed) {
|
||||||
|
lineCount = 1;
|
||||||
|
} else {
|
||||||
|
lineCount = editorWidgetModel.getLineCount();
|
||||||
|
}
|
||||||
// Need to also keep track of lines that wrap; if we just keep into account line count, then the editor's height would not be
|
// Need to also keep track of lines that wrap; if we just keep into account line count, then the editor's height would not be
|
||||||
// tall enough and we would need to show a scrollbar. Unfortunately, it looks like there isn't any metadata saved in a ICodeEditor
|
// tall enough and we would need to show a scrollbar. Unfortunately, it looks like there isn't any metadata saved in a ICodeEditor
|
||||||
// around max column length for an editor (which we could leverage to see if we need to loop through every line to determine
|
// around max column length for an editor (which we could leverage to see if we need to loop through every line to determine
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ export class CellToggleMoreActions {
|
|||||||
instantiationService.createInstance(AddCellFromContextAction, 'markdownAfter', localize('markdownAfter', "Insert Text After"), CellTypes.Markdown, true),
|
instantiationService.createInstance(AddCellFromContextAction, 'markdownAfter', localize('markdownAfter', "Insert Text After"), CellTypes.Markdown, true),
|
||||||
instantiationService.createInstance(RunCellsAction, 'runAllBefore', localize('runAllBefore', "Run Cells Before"), false),
|
instantiationService.createInstance(RunCellsAction, 'runAllBefore', localize('runAllBefore', "Run Cells Before"), false),
|
||||||
instantiationService.createInstance(RunCellsAction, 'runAllAfter', localize('runAllAfter', "Run Cells After"), true),
|
instantiationService.createInstance(RunCellsAction, 'runAllAfter', localize('runAllAfter', "Run Cells After"), true),
|
||||||
instantiationService.createInstance(ClearCellOutputAction, 'clear', localize('clear', "Clear Output"))
|
instantiationService.createInstance(ClearCellOutputAction, 'clear', localize('clear', "Clear Output")),
|
||||||
|
instantiationService.createInstance(CollapseCellAction, 'collapseCell', localize('collapseCell', "Collapse Cell"), true),
|
||||||
|
instantiationService.createInstance(CollapseCellAction, 'expandCell', localize('expandCell', "Expand Cell"), false)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,3 +184,41 @@ export class RunCellsAction extends CellActionBase {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CollapseCellAction extends CellActionBase {
|
||||||
|
constructor(id: string,
|
||||||
|
label: string,
|
||||||
|
private collapseCell: boolean,
|
||||||
|
@INotificationService notificationService: INotificationService
|
||||||
|
) {
|
||||||
|
super(id, label, undefined, notificationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public canRun(context: CellContext): boolean {
|
||||||
|
return context.cell && context.cell.cellType === CellTypes.Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
async doRun(context: CellContext): Promise<void> {
|
||||||
|
try {
|
||||||
|
let cell = context.cell || context.model.activeCell;
|
||||||
|
if (cell) {
|
||||||
|
if (this.collapseCell) {
|
||||||
|
if (!cell.isCollapsed) {
|
||||||
|
cell.isCollapsed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (cell.isCollapsed) {
|
||||||
|
cell.isCollapsed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let message = getErrorMessage(error);
|
||||||
|
this.notificationService.notify({
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
<div style="width: 100%; height: 100%; display: flex; flex-flow: row" (mouseover)="hover=true" (mouseleave)="hover=false">
|
<div style="width: 100%; height: 100%; display: flex; flex-flow: row" (mouseover)="hover=true" (mouseleave)="hover=false">
|
||||||
<div #toolbar class="toolbar">
|
<div #toolbar class="toolbar">
|
||||||
</div>
|
</div>
|
||||||
<div #editor class="editor" style="flex: 1 1 auto; overflow: hidden;">
|
<div style="flex: 1 1 auto; flex-flow: column; overflow: hidden;">
|
||||||
|
<div #editor class="editor"></div>
|
||||||
|
<collapse-component *ngIf="cellModel.cellType === 'code'" [cellModel]="cellModel" [activeCellId]="activeCellId"></collapse-component>
|
||||||
</div>
|
</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 #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>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import * as notebookUtils from 'sql/workbench/parts/notebook/browser/models/note
|
|||||||
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
|
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
|
||||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
|
import { CollapseComponent } from 'sql/workbench/parts/notebook/browser/cellViews/collapse.component';
|
||||||
|
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
|
|
||||||
export const CODE_SELECTOR: string = 'code-component';
|
export const CODE_SELECTOR: string = 'code-component';
|
||||||
const MARKDOWN_CLASS = 'markdown';
|
const MARKDOWN_CLASS = 'markdown';
|
||||||
@@ -45,6 +47,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
@ViewChild('toolbar', { read: ElementRef }) private toolbarElement: ElementRef;
|
@ViewChild('toolbar', { read: ElementRef }) private toolbarElement: ElementRef;
|
||||||
@ViewChild('moreactions', { read: ElementRef }) private moreActionsElementRef: ElementRef;
|
@ViewChild('moreactions', { read: ElementRef }) private moreActionsElementRef: ElementRef;
|
||||||
@ViewChild('editor', { read: ElementRef }) private codeElement: ElementRef;
|
@ViewChild('editor', { read: ElementRef }) private codeElement: ElementRef;
|
||||||
|
@ViewChild(CollapseComponent) private collapseComponent: CollapseComponent;
|
||||||
|
|
||||||
public get cellModel(): ICellModel {
|
public get cellModel(): ICellModel {
|
||||||
return this._cellModel;
|
return this._cellModel;
|
||||||
@@ -81,7 +84,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
this.cellModel.hover = value;
|
this.cellModel.hover = value;
|
||||||
if (!this.isActive()) {
|
if (!this.isActive()) {
|
||||||
// Only make a change if we're not active, since this has priority
|
// Only make a change if we're not active, since this has priority
|
||||||
this.toggleMoreActionsButton(this.cellModel.hover);
|
this.toggleActionsVisibility(this.cellModel.hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +115,6 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
(() => this.layout()));
|
(() => this.layout()));
|
||||||
// Handle disconnect on removal of the cell, if it was the active cell
|
// Handle disconnect on removal of the cell, if it was the active cell
|
||||||
this._register({ dispose: () => this.updateConnectionState(false) });
|
this._register({ dispose: () => this.updateConnectionState(false) });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -129,7 +131,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
let changedProp = changes[propName];
|
let changedProp = changes[propName];
|
||||||
let isActive = this.cellModel.id === changedProp.currentValue;
|
let isActive = this.cellModel.id === changedProp.currentValue;
|
||||||
this.updateConnectionState(isActive);
|
this.updateConnectionState(isActive);
|
||||||
this.toggleMoreActionsButton(isActive);
|
this.toggleActionsVisibility(isActive);
|
||||||
if (this._editor) {
|
if (this._editor) {
|
||||||
this._editor.toggleEditorSelected(isActive);
|
this._editor.toggleEditorSelected(isActive);
|
||||||
}
|
}
|
||||||
@@ -191,21 +193,24 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
this._editor.setVisible(true);
|
this._editor.setVisible(true);
|
||||||
this._editor.setMinimumHeight(this._minimumHeight);
|
this._editor.setMinimumHeight(this._minimumHeight);
|
||||||
this._editor.setMaximumHeight(this._maximumHeight);
|
this._editor.setMaximumHeight(this._maximumHeight);
|
||||||
|
|
||||||
let uri = this.cellModel.cellUri;
|
let uri = this.cellModel.cellUri;
|
||||||
let cellModelSource: string;
|
let cellModelSource: string;
|
||||||
cellModelSource = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
|
cellModelSource = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source;
|
||||||
this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, this.cellModel.language, cellModelSource, '');
|
this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, this.cellModel.language, cellModelSource, '');
|
||||||
await this._editor.setInput(this._editorInput, undefined);
|
await this._editor.setInput(this._editorInput, undefined);
|
||||||
this.setFocusAndScroll();
|
this.setFocusAndScroll();
|
||||||
|
|
||||||
let untitledEditorModel: UntitledEditorModel = await this._editorInput.resolve();
|
let untitledEditorModel: UntitledEditorModel = await this._editorInput.resolve();
|
||||||
this._editorModel = untitledEditorModel.textEditorModel;
|
this._editorModel = untitledEditorModel.textEditorModel;
|
||||||
|
|
||||||
let isActive = this.cellModel.id === this._activeCellId;
|
let isActive = this.cellModel.id === this._activeCellId;
|
||||||
this._editor.toggleEditorSelected(isActive);
|
this._editor.toggleEditorSelected(isActive);
|
||||||
|
|
||||||
// For markdown cells, don't show line numbers unless we're using editor defaults
|
// For markdown cells, don't show line numbers unless we're using editor defaults
|
||||||
let overrideEditorSetting = this._configurationService.getValue<boolean>(OVERRIDE_EDITOR_THEMING_SETTING);
|
let overrideEditorSetting = this._configurationService.getValue<boolean>(OVERRIDE_EDITOR_THEMING_SETTING);
|
||||||
this._editor.hideLineNumbers = (overrideEditorSetting && this.cellModel.cellType === CellTypes.Markdown);
|
this._editor.hideLineNumbers = (overrideEditorSetting && this.cellModel.cellType === CellTypes.Markdown);
|
||||||
|
|
||||||
|
|
||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
// At this point, we may have been disposed (scenario: restoring markdown cell in preview mode).
|
// 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
|
// Exiting early to avoid warnings on registering already disposed items, which causes some churning
|
||||||
@@ -216,15 +221,21 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
this._register(this._editor);
|
this._register(this._editor);
|
||||||
this._register(this._editorInput);
|
this._register(this._editorInput);
|
||||||
this._register(this._editorModel.onDidChangeContent(e => {
|
this._register(this._editorModel.onDidChangeContent(e => {
|
||||||
this._editor.setHeightToScrollHeight();
|
|
||||||
this.cellModel.modelContentChangedEvent = e;
|
this.cellModel.modelContentChangedEvent = e;
|
||||||
|
|
||||||
|
let originalSourceLength = this.cellModel.source.length;
|
||||||
this.cellModel.source = this._editorModel.getValue();
|
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.onContentChanged.emit();
|
||||||
this.checkForLanguageMagics();
|
this.checkForLanguageMagics();
|
||||||
}));
|
}));
|
||||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||||
if (e.affectsConfiguration('editor.wordWrap') || e.affectsConfiguration('editor.fontSize')) {
|
if (e.affectsConfiguration('editor.wordWrap') || e.affectsConfiguration('editor.fontSize')) {
|
||||||
this._editor.setHeightToScrollHeight(true);
|
this._editor.setHeightToScrollHeight(true, this._cellModel.isCollapsed);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this._register(this.model.layoutChanged(() => this._layoutEmitter.fire(), this));
|
this._register(this.model.layoutChanged(() => this._layoutEmitter.fire(), this));
|
||||||
@@ -233,14 +244,22 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
this.setFocusAndScroll();
|
this.setFocusAndScroll();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
this._register(this.cellModel.onCollapseStateChanged(isCollapsed => {
|
||||||
|
this.onCellCollapse(isCollapsed);
|
||||||
|
}));
|
||||||
|
|
||||||
this.layout();
|
this.layout();
|
||||||
|
|
||||||
|
if (this._cellModel.isCollapsed) {
|
||||||
|
this.onCellCollapse(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public layout(): void {
|
public layout(): void {
|
||||||
this._editor.layout(new DOM.Dimension(
|
this._editor.layout(new DOM.Dimension(
|
||||||
DOM.getContentWidth(this.codeElement.nativeElement),
|
DOM.getContentWidth(this.codeElement.nativeElement),
|
||||||
DOM.getContentHeight(this.codeElement.nativeElement)));
|
DOM.getContentHeight(this.codeElement.nativeElement)));
|
||||||
this._editor.setHeightToScrollHeight();
|
this._editor.setHeightToScrollHeight(false, this._cellModel.isCollapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initActionBar() {
|
protected initActionBar() {
|
||||||
@@ -317,7 +336,29 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
|
|||||||
return this.cellModel && this.cellModel.id === this.activeCellId;
|
return this.cellModel && this.cellModel.id === this.activeCellId;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toggleMoreActionsButton(isActiveOrHovered: boolean) {
|
protected toggleActionsVisibility(isActiveOrHovered: boolean) {
|
||||||
this._cellToggleMoreActions.toggleVisible(!isActiveOrHovered);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ code-component .toolbarIconStop {
|
|||||||
code-component .editor {
|
code-component .editor {
|
||||||
padding: 5px 0px 5px 0px
|
padding: 5px 0px 5px 0px
|
||||||
}
|
}
|
||||||
|
|
||||||
/* overview ruler */
|
/* overview ruler */
|
||||||
code-component .monaco-editor .decorationsOverviewRuler {
|
code-component .monaco-editor .decorationsOverviewRuler {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -86,7 +87,34 @@ code-component .carbon-taskbar .codicon.hideIcon.execCountHundred {
|
|||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
code-component .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container
|
code-component .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container {
|
||||||
{
|
|
||||||
padding-left: 10px
|
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");
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
|
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 0 0 auto; width: 100%; height: 100%; display: block">
|
<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 *ngIf="cellModel.outputs && cellModel.outputs.length > 0 && !cellModel.isCollapsed" [cellModel]="cellModel" [activeCellId]="activeCellId">
|
||||||
</output-area-component>
|
</output-area-component>
|
||||||
<stdin-component *ngIf="isStdInVisible" [onSendInput]="inputDeferred" [stdIn]="stdIn" [cellModel]="cellModel"></stdin-component>
|
<stdin-component *ngIf="isStdInVisible" [onSendInput]="inputDeferred" [stdIn]="stdIn" [cellModel]="cellModel"></stdin-component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.cellModel) {
|
if (this.cellModel) {
|
||||||
|
this._register(this.cellModel.onCollapseStateChanged((state) => {
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}));
|
||||||
this._register(this.cellModel.onOutputsChanged(() => {
|
this._register(this.cellModel.onOutputsChanged(() => {
|
||||||
this._changeRef.detectChanges();
|
this._changeRef.detectChanges();
|
||||||
}));
|
}));
|
||||||
@@ -73,6 +76,7 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
get activeCellId(): string {
|
get activeCellId(): string {
|
||||||
return this._activeCellId;
|
return this._activeCellId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public layout() {
|
public layout() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, SimpleChange, OnChanges } from '@angular/core';
|
||||||
|
import { CellView } from 'sql/workbench/parts/notebook/browser/cellViews/interfaces';
|
||||||
|
import { ICellModel } from 'sql/workbench/parts/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(
|
||||||
|
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>opac_command_icons_bv</title>
|
||||||
|
<path d="M0,1H16V2H0ZM.148,6.148,2.5,3.8,4.852,6.148l-.7.7L3,5.711V15H2V5.711L.852,6.852ZM7,5V4h9V5Z" fill="#fff"/>
|
||||||
|
<rect width="16" height="16" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 293 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>opac_command_icons_bv</title>
|
||||||
|
<path d="M0,1H16V2H0ZM3,13.289l1.148-1.141.7.7L2.5,15.2.148,12.852l.7-.7L2,13.289V4H3ZM7,5V4h9V5ZM7,8V7h9V8Zm0,3V10h9v1Zm0,3V13h9v1Z" fill="#fff"/>
|
||||||
|
<rect width="16" height="16" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>opac_command_icons_bv</title>
|
||||||
|
<path d="M0,1H16V2H0ZM.148,6.148,2.5,3.8,4.852,6.148l-.7.7L3,5.711V15H2V5.711L.852,6.852ZM7,5V4h9V5Z"/>
|
||||||
|
<rect width="16" height="16" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 281 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>opac_command_icons_bv</title>
|
||||||
|
<path d="M0,1H16V2H0ZM3,13.289l1.148-1.141.7.7L2.5,15.2.148,12.852l.7-.7L2,13.289V4H3ZM7,5V4h9V5ZM7,8V7h9V8Zm0,3V10h9v1Zm0,3V13h9v1Z"/>
|
||||||
|
<rect width="16" height="16" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
@@ -24,8 +24,9 @@ import { generateUuid } from 'vs/base/common/uuid';
|
|||||||
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||||
let modelId = 0;
|
let modelId = 0;
|
||||||
|
|
||||||
|
|
||||||
export class CellModel implements ICellModel {
|
export class CellModel implements ICellModel {
|
||||||
|
public id: string;
|
||||||
|
|
||||||
private _cellType: nb.CellType;
|
private _cellType: nb.CellType;
|
||||||
private _source: string | string[];
|
private _source: string | string[];
|
||||||
private _language: string;
|
private _language: string;
|
||||||
@@ -41,15 +42,18 @@ export class CellModel implements ICellModel {
|
|||||||
private _hover: boolean;
|
private _hover: boolean;
|
||||||
private _executionCount: number | undefined;
|
private _executionCount: number | undefined;
|
||||||
private _cellUri: URI;
|
private _cellUri: URI;
|
||||||
public id: string;
|
|
||||||
private _connectionManagementService: IConnectionManagementService;
|
private _connectionManagementService: IConnectionManagementService;
|
||||||
private _stdInHandler: nb.MessageHandler<nb.IStdinMessage>;
|
private _stdInHandler: nb.MessageHandler<nb.IStdinMessage>;
|
||||||
private _onCellLoaded = new Emitter<string>();
|
private _onCellLoaded = new Emitter<string>();
|
||||||
private _loaded: boolean;
|
private _loaded: boolean;
|
||||||
private _stdInVisible: boolean;
|
private _stdInVisible: boolean;
|
||||||
private _metadata: { language?: string, cellGuid?: string; };
|
private _metadata: { language?: string; tags?: string[]; cellGuid?: string; };
|
||||||
|
private _isCollapsed: boolean;
|
||||||
|
private _onCollapseStateChanged = new Emitter<boolean>();
|
||||||
private _modelContentChangedEvent: IModelContentChangedEvent;
|
private _modelContentChangedEvent: IModelContentChangedEvent;
|
||||||
|
|
||||||
|
private readonly _hideInputTag = 'hide_input';
|
||||||
|
|
||||||
constructor(cellData: nb.ICellContents,
|
constructor(cellData: nb.ICellContents,
|
||||||
private _options: ICellModelOptions,
|
private _options: ICellModelOptions,
|
||||||
@optional(INotebookService) private _notebookService?: INotebookService
|
@optional(INotebookService) private _notebookService?: INotebookService
|
||||||
@@ -78,6 +82,10 @@ export class CellModel implements ICellModel {
|
|||||||
return other && other.id === this.id;
|
return other && other.id === this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get onCollapseStateChanged(): Event<boolean> {
|
||||||
|
return this._onCollapseStateChanged.event;
|
||||||
|
}
|
||||||
|
|
||||||
public get onOutputsChanged(): Event<IOutputChangedEvent> {
|
public get onOutputsChanged(): Event<IOutputChangedEvent> {
|
||||||
return this._onOutputsChanged.event;
|
return this._onOutputsChanged.event;
|
||||||
}
|
}
|
||||||
@@ -94,6 +102,38 @@ export class CellModel implements ICellModel {
|
|||||||
return this._future;
|
return this._future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isCollapsed() {
|
||||||
|
return this._isCollapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set isCollapsed(value: boolean) {
|
||||||
|
let stateChanged = this._isCollapsed !== value;
|
||||||
|
this._isCollapsed = value;
|
||||||
|
|
||||||
|
let tagIndex = -1;
|
||||||
|
if (this._metadata.tags) {
|
||||||
|
tagIndex = this._metadata.tags.findIndex(tag => tag === this._hideInputTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._isCollapsed) {
|
||||||
|
if (tagIndex === -1) {
|
||||||
|
if (!this._metadata.tags) {
|
||||||
|
this._metadata.tags = [];
|
||||||
|
}
|
||||||
|
this._metadata.tags.push(this._hideInputTag);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tagIndex > -1) {
|
||||||
|
this._metadata.tags.splice(tagIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateChanged) {
|
||||||
|
this._onCollapseStateChanged.fire(this._isCollapsed);
|
||||||
|
this.sendChangeToNotebook(NotebookChangeType.CellInputVisibilityChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public set isEditMode(isEditMode: boolean) {
|
public set isEditMode(isEditMode: boolean) {
|
||||||
this._isEditMode = isEditMode;
|
this._isEditMode = isEditMode;
|
||||||
this._onCellModeChanged.fire(this._isEditMode);
|
this._onCellModeChanged.fire(this._isEditMode);
|
||||||
@@ -255,6 +295,9 @@ export class CellModel implements ICellModel {
|
|||||||
this.notebookModel.updateActiveCell(this);
|
this.notebookModel.updateActiveCell(this);
|
||||||
this.active = true;
|
this.active = true;
|
||||||
}
|
}
|
||||||
|
if (this.isCollapsed) {
|
||||||
|
this.isCollapsed = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (connectionManagementService) {
|
if (connectionManagementService) {
|
||||||
this._connectionManagementService = connectionManagementService;
|
this._connectionManagementService = connectionManagementService;
|
||||||
@@ -540,14 +583,16 @@ export class CellModel implements ICellModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public toJSON(): nb.ICellContents {
|
public toJSON(): nb.ICellContents {
|
||||||
|
let metadata = this._metadata || {};
|
||||||
let cellJson: Partial<nb.ICellContents> = {
|
let cellJson: Partial<nb.ICellContents> = {
|
||||||
cell_type: this._cellType,
|
cell_type: this._cellType,
|
||||||
source: this._source,
|
source: this._source,
|
||||||
metadata: this._metadata || {}
|
metadata: metadata
|
||||||
};
|
};
|
||||||
cellJson.metadata.azdata_cell_guid = this._cellGuid;
|
cellJson.metadata.azdata_cell_guid = this._cellGuid;
|
||||||
if (this._cellType === CellTypes.Code) {
|
if (this._cellType === CellTypes.Code) {
|
||||||
cellJson.metadata.language = this._language;
|
cellJson.metadata.language = this._language;
|
||||||
|
cellJson.metadata.tags = metadata.tags;
|
||||||
cellJson.outputs = this._outputs;
|
cellJson.outputs = this._outputs;
|
||||||
cellJson.execution_count = this.executionCount ? this.executionCount : 0;
|
cellJson.execution_count = this.executionCount ? this.executionCount : 0;
|
||||||
}
|
}
|
||||||
@@ -561,7 +606,14 @@ export class CellModel implements ICellModel {
|
|||||||
this._cellType = cell.cell_type;
|
this._cellType = cell.cell_type;
|
||||||
this.executionCount = cell.execution_count;
|
this.executionCount = cell.execution_count;
|
||||||
this._source = this.getMultilineSource(cell.source);
|
this._source = this.getMultilineSource(cell.source);
|
||||||
this._metadata = cell.metadata;
|
this._metadata = cell.metadata || {};
|
||||||
|
|
||||||
|
if (this._metadata.tags && this._metadata.tags.includes(this._hideInputTag)) {
|
||||||
|
this._isCollapsed = true;
|
||||||
|
} else {
|
||||||
|
this._isCollapsed = false;
|
||||||
|
}
|
||||||
|
|
||||||
this._cellGuid = cell.metadata && cell.metadata.azdata_cell_guid ? cell.metadata.azdata_cell_guid : generateUuid();
|
this._cellGuid = cell.metadata && cell.metadata.azdata_cell_guid ? cell.metadata.azdata_cell_guid : generateUuid();
|
||||||
this.setLanguageFromContents(cell);
|
this.setLanguageFromContents(cell);
|
||||||
if (cell.outputs) {
|
if (cell.outputs) {
|
||||||
|
|||||||
@@ -488,6 +488,8 @@ export interface ICellModel {
|
|||||||
loaded: boolean;
|
loaded: boolean;
|
||||||
stdInVisible: boolean;
|
stdInVisible: boolean;
|
||||||
readonly onLoaded: Event<string>;
|
readonly onLoaded: Event<string>;
|
||||||
|
isCollapsed: boolean;
|
||||||
|
readonly onCollapseStateChanged: Event<boolean>;
|
||||||
modelContentChangedEvent: IModelContentChangedEvent;
|
modelContentChangedEvent: IModelContentChangedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
|
|
||||||
private _cells: ICellModel[];
|
private _cells: ICellModel[];
|
||||||
private _defaultLanguageInfo: nb.ILanguageInfo;
|
private _defaultLanguageInfo: nb.ILanguageInfo;
|
||||||
|
private _tags: string[];
|
||||||
private _language: string;
|
private _language: string;
|
||||||
private _onErrorEmitter = new Emitter<INotification>();
|
private _onErrorEmitter = new Emitter<INotification>();
|
||||||
private _savedKernelInfo: nb.IKernelInfo;
|
private _savedKernelInfo: nb.IKernelInfo;
|
||||||
@@ -557,6 +558,9 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get tags(): string[] {
|
||||||
|
return this._tags;
|
||||||
|
}
|
||||||
|
|
||||||
public get languageInfo(): nb.ILanguageInfo {
|
public get languageInfo(): nb.ILanguageInfo {
|
||||||
return this._defaultLanguageInfo;
|
return this._defaultLanguageInfo;
|
||||||
@@ -991,6 +995,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
// TODO update language and kernel when these change
|
// TODO update language and kernel when these change
|
||||||
metadata.kernelspec = this._savedKernelInfo;
|
metadata.kernelspec = this._savedKernelInfo;
|
||||||
metadata.language_info = this.languageInfo;
|
metadata.language_info = this.languageInfo;
|
||||||
|
metadata.tags = this._tags;
|
||||||
return {
|
return {
|
||||||
metadata,
|
metadata,
|
||||||
nbformat_minor: this._nbformatMinor,
|
nbformat_minor: this._nbformatMinor,
|
||||||
@@ -1007,6 +1012,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
switch (change) {
|
switch (change) {
|
||||||
case NotebookChangeType.CellOutputUpdated:
|
case NotebookChangeType.CellOutputUpdated:
|
||||||
case NotebookChangeType.CellSourceUpdated:
|
case NotebookChangeType.CellSourceUpdated:
|
||||||
|
case NotebookChangeType.CellInputVisibilityChanged:
|
||||||
changeInfo.isDirty = true;
|
changeInfo.isDirty = true;
|
||||||
changeInfo.modelContentChangedEvent = cell.modelContentChangedEvent;
|
changeInfo.modelContentChangedEvent = cell.modelContentChangedEvent;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import * as notebookUtils from 'sql/workbench/parts/notebook/browser/models/note
|
|||||||
import { Deferred } from 'sql/base/common/promise';
|
import { Deferred } from 'sql/base/common/promise';
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||||
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, RunAllCellsAction, ClearAllOutputsAction } from 'sql/workbench/parts/notebook/browser/notebookActions';
|
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, RunAllCellsAction, ClearAllOutputsAction, CollapseCellsAction } from 'sql/workbench/parts/notebook/browser/notebookActions';
|
||||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||||
import * as TaskUtilities from 'sql/workbench/browser/taskUtilities';
|
import * as TaskUtilities from 'sql/workbench/browser/taskUtilities';
|
||||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
@@ -427,6 +427,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted');
|
this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted');
|
||||||
this._trustedAction.enabled = false;
|
this._trustedAction.enabled = false;
|
||||||
|
|
||||||
|
let collapseCellsAction = this.instantiationService.createInstance(CollapseCellsAction, 'notebook.collapseCells');
|
||||||
|
|
||||||
let taskbar = <HTMLElement>this.toolbar.nativeElement;
|
let taskbar = <HTMLElement>this.toolbar.nativeElement;
|
||||||
this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) });
|
this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) });
|
||||||
this._actionBar.context = this;
|
this._actionBar.context = this;
|
||||||
@@ -437,7 +439,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
{ element: attachToContainer },
|
{ element: attachToContainer },
|
||||||
{ action: this._trustedAction },
|
{ action: this._trustedAction },
|
||||||
{ action: this._runAllCellsAction },
|
{ action: this._runAllCellsAction },
|
||||||
{ action: clearResultsButton }
|
{ action: clearResultsButton },
|
||||||
|
{ action: collapseCellsAction }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,48 +49,66 @@
|
|||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebookEditor .notebook-button.icon-add{
|
.notebookEditor .notebook-button.icon-add {
|
||||||
background-image: url("./media/light/add.svg");
|
background-image: url("./media/light/add.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .notebookEditor .notebook-button.icon-add,
|
.vs-dark .notebookEditor .notebook-button.icon-add,
|
||||||
.hc-black .notebookEditor .notebook-button.icon-add{
|
.hc-black .notebookEditor .notebook-button.icon-add {
|
||||||
background-image: url("./media/dark/add_inverse.svg");
|
background-image: url("./media/dark/add_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebookEditor .notebook-button.icon-run-cells{
|
.notebookEditor .notebook-button.icon-run-cells {
|
||||||
background-image: url("./media/light/run_cells.svg");
|
background-image: url("./media/light/run_cells.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .notebookEditor .notebook-button.icon-run-cells,
|
.vs-dark .notebookEditor .notebook-button.icon-run-cells,
|
||||||
.hc-black .notebookEditor .notebook-button.icon-run-cells{
|
.hc-black .notebookEditor .notebook-button.icon-run-cells {
|
||||||
background-image: url("./media/dark/run_cells_inverse.svg");
|
background-image: url("./media/dark/run_cells_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebookEditor .notebook-button.icon-trusted{
|
.notebookEditor .notebook-button.icon-trusted {
|
||||||
background-image: url("./media/light/trusted.svg");
|
background-image: url("./media/light/trusted.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .notebookEditor .notebook-button.icon-trusted,
|
.vs-dark .notebookEditor .notebook-button.icon-trusted,
|
||||||
.hc-black .notebookEditor .notebook-button.icon-trusted{
|
.hc-black .notebookEditor .notebook-button.icon-trusted {
|
||||||
background-image: url("./media/dark/trusted_inverse.svg");
|
background-image: url("./media/dark/trusted_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebookEditor .notebook-button.icon-notTrusted{
|
.notebookEditor .notebook-button.icon-notTrusted {
|
||||||
background-image: url("./media/light/nottrusted.svg");
|
background-image: url("./media/light/nottrusted.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .notebookEditor .notebook-button.icon-notTrusted,
|
.vs-dark .notebookEditor .notebook-button.icon-notTrusted,
|
||||||
.hc-black .notebookEditor .notebook-button.icon-notTrusted{
|
.hc-black .notebookEditor .notebook-button.icon-notTrusted {
|
||||||
background-image: url("./media/dark/nottrusted_inverse.svg");
|
background-image: url("./media/dark/nottrusted_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebookEditor .notebook-button.icon-clear-results{
|
.notebookEditor .notebook-button.icon-show-cells {
|
||||||
|
background-image: url("./media/light/show_code.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .notebookEditor .notebook-button.icon-show-cells,
|
||||||
|
.hc-black .notebookEditor .notebook-button.icon-show-cells {
|
||||||
|
background-image: url("./media/dark/show_code_inverse.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebookEditor .notebook-button.icon-hide-cells {
|
||||||
|
background-image: url("./media/light/hide_code.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .notebookEditor .notebook-button.icon-hide-cells,
|
||||||
|
.hc-black .notebookEditor .notebook-button.icon-hide-cells {
|
||||||
|
background-image: url("./media/dark/hide_code_inverse.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebookEditor .notebook-button.icon-clear-results {
|
||||||
background-image: url("./media/light/clear_results.svg");
|
background-image: url("./media/light/clear_results.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .notebookEditor .notebook-button.icon-clear-results,
|
.vs-dark .notebookEditor .notebook-button.icon-clear-results,
|
||||||
.hc-black .notebookEditor .notebook-button.icon-clear-results{
|
.hc-black .notebookEditor .notebook-button.icon-clear-results {
|
||||||
background-image: url("./media/dark/clear_results_inverse.svg");
|
background-image: url("./media/dark/clear_results_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
|||||||
import { LinkHandlerDirective } from 'sql/workbench/parts/notebook/browser/cellViews/linkHandler.directive';
|
import { LinkHandlerDirective } from 'sql/workbench/parts/notebook/browser/cellViews/linkHandler.directive';
|
||||||
import { IBootstrapParams, ISelector } from 'sql/platform/bootstrap/common/bootstrapParams';
|
import { IBootstrapParams, ISelector } from 'sql/platform/bootstrap/common/bootstrapParams';
|
||||||
import { ICellComponenetRegistry, Extensions as OutputComponentExtensions } from 'sql/platform/notebooks/common/outputRegistry';
|
import { ICellComponenetRegistry, Extensions as OutputComponentExtensions } from 'sql/platform/notebooks/common/outputRegistry';
|
||||||
|
import { CollapseComponent } from 'sql/workbench/parts/notebook/browser/cellViews/collapse.component';
|
||||||
|
|
||||||
const outputComponentRegistry = Registry.as<ICellComponenetRegistry>(OutputComponentExtensions.CellComponentContributions);
|
const outputComponentRegistry = Registry.as<ICellComponenetRegistry>(OutputComponentExtensions.CellComponentContributions);
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ export const NotebookModule = (params, selector: string, instantiationService: I
|
|||||||
OutputAreaComponent,
|
OutputAreaComponent,
|
||||||
OutputComponent,
|
OutputComponent,
|
||||||
StdInComponent,
|
StdInComponent,
|
||||||
|
CollapseComponent,
|
||||||
LinkHandlerDirective,
|
LinkHandlerDirective,
|
||||||
...outputComponents
|
...outputComponents
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -268,6 +268,47 @@ export class RunAllCellsAction extends Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CollapseCellsAction extends ToggleableAction {
|
||||||
|
private static readonly collapseCells = localize('collapseAllCells', "Collapse Cells");
|
||||||
|
private static readonly expandCells = localize('expandAllCells', "Expand Cells");
|
||||||
|
private static readonly baseClass = 'notebook-button';
|
||||||
|
private static readonly collapseCssClass = 'icon-hide-cells';
|
||||||
|
private static readonly expandCssClass = 'icon-show-cells';
|
||||||
|
|
||||||
|
constructor(id: string) {
|
||||||
|
super(id, {
|
||||||
|
baseClass: CollapseCellsAction.baseClass,
|
||||||
|
toggleOnLabel: CollapseCellsAction.expandCells,
|
||||||
|
toggleOnClass: CollapseCellsAction.expandCssClass,
|
||||||
|
toggleOffLabel: CollapseCellsAction.collapseCells,
|
||||||
|
toggleOffClass: CollapseCellsAction.collapseCssClass,
|
||||||
|
isOn: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isCollapsed(): boolean {
|
||||||
|
return this.state.isOn;
|
||||||
|
}
|
||||||
|
public set isCollapsed(value: boolean) {
|
||||||
|
this.toggle(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: NotebookComponent): Promise<boolean> {
|
||||||
|
let self = this;
|
||||||
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
self.isCollapsed = !self.isCollapsed;
|
||||||
|
context.cells.forEach(cell => {
|
||||||
|
cell.isCollapsed = self.isCollapsed;
|
||||||
|
});
|
||||||
|
resolve(true);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class KernelsDropdown extends SelectBox {
|
export class KernelsDropdown extends SelectBox {
|
||||||
private model: NotebookModel;
|
private model: NotebookModel;
|
||||||
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelReady: Promise<INotebookModel>) {
|
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelReady: Promise<INotebookModel>) {
|
||||||
|
|||||||
@@ -144,7 +144,8 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf
|
|||||||
collector.addRule(`
|
collector.addRule(`
|
||||||
.notebook-cell:not(.active) code-component .monaco-editor,
|
.notebook-cell:not(.active) code-component .monaco-editor,
|
||||||
.notebook-cell:not(.active) code-component .monaco-editor-background,
|
.notebook-cell:not(.active) code-component .monaco-editor-background,
|
||||||
.notebook-cell:not(.active) code-component .monaco-editor .inputarea.ime-input
|
.notebook-cell:not(.active) code-component .monaco-editor .inputarea.ime-input,
|
||||||
|
.notebook-cell.active .hide-component-button:hover
|
||||||
{
|
{
|
||||||
background-color: ${codeBackground};
|
background-color: ${codeBackground};
|
||||||
}`);
|
}`);
|
||||||
|
|||||||
@@ -45,5 +45,6 @@ export enum NotebookChangeType {
|
|||||||
TrustChanged,
|
TrustChanged,
|
||||||
Saved,
|
Saved,
|
||||||
CellExecuted,
|
CellExecuted,
|
||||||
|
CellInputVisibilityChanged,
|
||||||
CellOutputCleared
|
CellOutputCleared
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,6 +245,94 @@ suite('Cell Model', function (): void {
|
|||||||
should(JSON.stringify(cell.source)).equal(JSON.stringify(['']));
|
should(JSON.stringify(cell.source)).equal(JSON.stringify(['']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should parse metadata\'s hide_input tag correctly', async function (): Promise<void> {
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: '',
|
||||||
|
version: '',
|
||||||
|
mimetype: ''
|
||||||
|
});
|
||||||
|
let contents: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: ''
|
||||||
|
};
|
||||||
|
let model = factory.createCell(contents, { notebook: notebookModel, isTrusted: false });
|
||||||
|
|
||||||
|
should(model.isCollapsed).be.false();
|
||||||
|
model.isCollapsed = true;
|
||||||
|
should(model.isCollapsed).be.true();
|
||||||
|
model.isCollapsed = false;
|
||||||
|
should(model.isCollapsed).be.false();
|
||||||
|
|
||||||
|
let modelJson = model.toJSON();
|
||||||
|
should(modelJson.metadata.tags).not.be.undefined();
|
||||||
|
should(modelJson.metadata.tags).not.containEql('hide_input');
|
||||||
|
|
||||||
|
contents.metadata = {
|
||||||
|
tags: ['hide_input']
|
||||||
|
};
|
||||||
|
model = factory.createCell(contents, { notebook: notebookModel, isTrusted: false });
|
||||||
|
|
||||||
|
should(model.isCollapsed).be.true();
|
||||||
|
model.isCollapsed = false;
|
||||||
|
should(model.isCollapsed).be.false();
|
||||||
|
model.isCollapsed = true;
|
||||||
|
should(model.isCollapsed).be.true();
|
||||||
|
|
||||||
|
modelJson = model.toJSON();
|
||||||
|
should(modelJson.metadata.tags).not.be.undefined();
|
||||||
|
should(modelJson.metadata.tags).containEql('hide_input');
|
||||||
|
|
||||||
|
contents.metadata = {
|
||||||
|
tags: ['not_a_real_tag']
|
||||||
|
};
|
||||||
|
model = factory.createCell(contents, { notebook: notebookModel, isTrusted: false });
|
||||||
|
modelJson = model.toJSON();
|
||||||
|
should(modelJson.metadata.tags).not.be.undefined();
|
||||||
|
should(modelJson.metadata.tags).not.containEql('hide_input');
|
||||||
|
|
||||||
|
contents.metadata = {
|
||||||
|
tags: ['not_a_real_tag', 'hide_input']
|
||||||
|
};
|
||||||
|
model = factory.createCell(contents, { notebook: notebookModel, isTrusted: false });
|
||||||
|
modelJson = model.toJSON();
|
||||||
|
should(modelJson.metadata.tags).not.be.undefined();
|
||||||
|
should(modelJson.metadata.tags).containEql('hide_input');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should emit event after collapsing cell', async function (): Promise<void> {
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: '',
|
||||||
|
version: '',
|
||||||
|
mimetype: ''
|
||||||
|
});
|
||||||
|
let contents: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: ''
|
||||||
|
};
|
||||||
|
let model = factory.createCell(contents, { notebook: notebookModel, isTrusted: false });
|
||||||
|
should(model.isCollapsed).be.false();
|
||||||
|
|
||||||
|
let createCollapsePromise = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => reject(), 2000);
|
||||||
|
model.onCollapseStateChanged(isCollapsed => {
|
||||||
|
resolve(isCollapsed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
should(model.isCollapsed).be.false();
|
||||||
|
let collapsePromise = createCollapsePromise();
|
||||||
|
model.isCollapsed = true;
|
||||||
|
let isCollapsed = await collapsePromise;
|
||||||
|
should(isCollapsed).be.true();
|
||||||
|
|
||||||
|
collapsePromise = createCollapsePromise();
|
||||||
|
model.isCollapsed = false;
|
||||||
|
isCollapsed = await collapsePromise;
|
||||||
|
should(isCollapsed).be.false();
|
||||||
|
});
|
||||||
|
|
||||||
suite('Model Future handling', function (): void {
|
suite('Model Future handling', function (): void {
|
||||||
let future: TypeMoq.Mock<EmptyFuture>;
|
let future: TypeMoq.Mock<EmptyFuture>;
|
||||||
let cell: ICellModel;
|
let cell: ICellModel;
|
||||||
|
|||||||