Add collapse/expand functionality to notebook code cells. (#7481)

This commit is contained in:
Cory Rivera
2019-10-03 16:50:47 -07:00
committed by GitHub
parent 080d9bbaa6
commit 6b29fd05bd
29 changed files with 492 additions and 34 deletions

View File

@@ -7,7 +7,9 @@
<div style="width: 100%; height: 100%; display: flex; flex-flow: row" (mouseover)="hover=true" (mouseleave)="hover=false">
<div #toolbar class="toolbar">
</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 #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>

View File

@@ -33,6 +33,8 @@ import * as notebookUtils from 'sql/workbench/parts/notebook/browser/models/note
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/parts/notebook/browser/cellViews/collapse.component';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export const CODE_SELECTOR: string = 'code-component';
const MARKDOWN_CLASS = 'markdown';
@@ -45,6 +47,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
@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;
@@ -81,7 +84,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
this.cellModel.hover = value;
if (!this.isActive()) {
// 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()));
// Handle disconnect on removal of the cell, if it was the active cell
this._register({ dispose: () => this.updateConnectionState(false) });
}
ngOnInit() {
@@ -129,7 +131,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
let changedProp = changes[propName];
let isActive = this.cellModel.id === changedProp.currentValue;
this.updateConnectionState(isActive);
this.toggleMoreActionsButton(isActive);
this.toggleActionsVisibility(isActive);
if (this._editor) {
this._editor.toggleEditorSelected(isActive);
}
@@ -191,21 +193,24 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
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
@@ -216,15 +221,21 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
this._register(this._editor);
this._register(this._editorInput);
this._register(this._editorModel.onDidChangeContent(e => {
this._editor.setHeightToScrollHeight();
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._editor.setHeightToScrollHeight(true, this._cellModel.isCollapsed);
}
}));
this._register(this.model.layoutChanged(() => this._layoutEmitter.fire(), this));
@@ -233,14 +244,22 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
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();
this._editor.setHeightToScrollHeight(false, this._cellModel.isCollapsed);
}
protected initActionBar() {
@@ -317,7 +336,29 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
return this.cellModel && this.cellModel.id === this.activeCellId;
}
protected toggleMoreActionsButton(isActiveOrHovered: boolean) {
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

@@ -60,6 +60,7 @@ code-component .toolbarIconStop {
code-component .editor {
padding: 5px 0px 5px 0px
}
/* overview ruler */
code-component .monaco-editor .decorationsOverviewRuler {
visibility: hidden;
@@ -86,7 +87,34 @@ code-component .carbon-taskbar .codicon.hideIcon.execCountHundred {
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
}
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

@@ -9,8 +9,8 @@
<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 *ngIf="cellModel.outputs && cellModel.outputs.length > 0 && !cellModel.isCollapsed" [cellModel]="cellModel" [activeCellId]="activeCellId">
</output-area-component>
<stdin-component *ngIf="isStdInVisible" [onSendInput]="inputDeferred" [stdIn]="stdIn" [cellModel]="cellModel"></stdin-component>
</div>
</div>
</div>

View File

@@ -47,6 +47,9 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
ngOnInit() {
if (this.cellModel) {
this._register(this.cellModel.onCollapseStateChanged((state) => {
this._changeRef.detectChanges();
}));
this._register(this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
}));
@@ -73,6 +76,7 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
get activeCellId(): string {
return this._activeCellId;
}
public layout() {
}

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,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);
}
}
}

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,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