mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 17:22:42 -05:00
Notebook Views Insert Cells Modal (#16836)
* Add html-to-image package * Add image card type
This commit is contained in:
@@ -396,7 +396,8 @@ export enum DeclarativeDataType {
|
||||
export enum CardType {
|
||||
VerticalButton = 'VerticalButton',
|
||||
Details = 'Details',
|
||||
ListItem = 'ListItem'
|
||||
ListItem = 'ListItem',
|
||||
Image = 'Image'
|
||||
}
|
||||
|
||||
export enum Orientation {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div #cardDiv role="radio" *ngIf="label" [class]="getClass() + ' horizontal'" (click)="onCardClick()"
|
||||
[attr.aria-checked]="selected" (mouseover)="onCardHoverChanged($event)" (mouseout)="onCardHoverChanged($event)"
|
||||
tabIndex="0" [style.width]="width" [style.height]="height">
|
||||
<ng-container *ngIf="isVerticalButton || isDetailsCard">
|
||||
<ng-container *ngIf="isVerticalButton || isDetailsCard || isImageCard">
|
||||
<span *ngIf="hasStatus" class="card-status">
|
||||
<div class="status-content" [style.backgroundColor]="statusColor"></div>
|
||||
</span>
|
||||
@@ -47,6 +47,15 @@
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="isImageCard">
|
||||
<div class="card-content">
|
||||
<div [class]="'card-image ' + iconClass"></div>
|
||||
<div *ngIf="label" class="card-label-overlay">
|
||||
<h4 class="card-label">{{label}}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isListItemCard">
|
||||
<div class="list-item-content">
|
||||
|
||||
@@ -18,8 +18,9 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces';
|
||||
import { IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CARD_OVERLAY_BACKGROUND, CARD_OVERLAY_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
|
||||
export interface ActionDescriptor {
|
||||
label: string;
|
||||
@@ -52,7 +53,8 @@ export interface CardDescriptionItem {
|
||||
export enum CardType {
|
||||
VerticalButton = 'VerticalButton',
|
||||
Details = 'Details',
|
||||
ListItem = 'ListItem'
|
||||
ListItem = 'ListItem',
|
||||
Image = 'Image'
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -119,6 +121,11 @@ export default class CardComponent extends ComponentWithIconBase<azdata.CardProp
|
||||
|
||||
public getClass(): string {
|
||||
let cardClass = this.isListItemCard ? 'model-card-list-item-legacy' : 'model-card-legacy';
|
||||
|
||||
if (this.cardType === 'Image') {
|
||||
cardClass += ' image-card';
|
||||
}
|
||||
|
||||
return (this.selectable && this.selected || this._hasFocus) ? `${cardClass} selected` :
|
||||
`${cardClass} unselected`;
|
||||
}
|
||||
@@ -151,7 +158,7 @@ export default class CardComponent extends ComponentWithIconBase<azdata.CardProp
|
||||
}
|
||||
|
||||
private get selectable(): boolean {
|
||||
return this.enabled && (this.cardType === 'VerticalButton' || this.cardType === 'ListItem');
|
||||
return this.enabled && (this.cardType === 'VerticalButton' || this.cardType === 'ListItem' || this.cardType === 'Image');
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
@@ -184,6 +191,10 @@ export default class CardComponent extends ComponentWithIconBase<azdata.CardProp
|
||||
return !this.cardType || this.cardType === 'ListItem';
|
||||
}
|
||||
|
||||
public get isImageCard(): boolean {
|
||||
return !this.cardType || this.cardType === 'Image';
|
||||
}
|
||||
|
||||
public get isVerticalButton(): boolean {
|
||||
return this.cardType === 'VerticalButton';
|
||||
}
|
||||
@@ -236,3 +247,23 @@ export default class CardComponent extends ComponentWithIconBase<azdata.CardProp
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const backgroundColor = theme.getColor(CARD_OVERLAY_BACKGROUND);
|
||||
const foregroundColor = theme.getColor(CARD_OVERLAY_FOREGROUND);
|
||||
if (backgroundColor) {
|
||||
collector.addRule(`
|
||||
.model-card-legacy .card-label-overlay {
|
||||
background-color: ${backgroundColor.toString()};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
if (foregroundColor) {
|
||||
collector.addRule(`
|
||||
.model-card-legacy.image-card .card-label {
|
||||
color: ${foregroundColor.toString()};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,6 +26,47 @@
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.model-card-legacy.image-card {
|
||||
height: auto;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.model-card-legacy.image-card .card-content {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.model-card-legacy.image-card .card-image {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-size: contain;
|
||||
background-position: initial;
|
||||
background-repeat: no-repeat;
|
||||
max-width: unset;
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
.model-card-legacy.image-card .card-label-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.model-card-legacy.image-card .card-label {
|
||||
margin: 0;
|
||||
padding: 0px 10px
|
||||
}
|
||||
|
||||
.model-card-legacy .card-vertical-button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -103,6 +144,7 @@
|
||||
border-width: 1px;
|
||||
border-color: rgb(0, 120, 215);
|
||||
border-style: solid;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.model-card-list-item-legacy .selection-indicator-container, .model-card-legacy .selection-indicator-container {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import {
|
||||
ApplicationRef, ComponentFactoryResolver, NgModule,
|
||||
Inject, forwardRef, Type
|
||||
} from '@angular/core';
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { providerIterator } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IBootstrapParams, ISelector } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
|
||||
import CardComponent from 'sql/workbench/browser/modelComponents/card.component';
|
||||
import DivContainer from 'sql/workbench/browser/modelComponents/divContainer.component';
|
||||
import { ModelComponentWrapper } from 'sql/workbench/browser/modelComponents/modelComponentWrapper.component';
|
||||
import { InsertCellsScreenshots } from 'sql/workbench/contrib/notebook/browser/notebookViews/insertCellsScreenshots.component';
|
||||
import ImageComponent from 'sql/workbench/browser/modelComponents/image.component';
|
||||
|
||||
// Insertcells main angular module
|
||||
export const InsertCellsModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CardComponent,
|
||||
DivContainer,
|
||||
ModelComponentWrapper,
|
||||
InsertCellsScreenshots,
|
||||
ImageComponent,
|
||||
],
|
||||
entryComponents: [InsertCellsScreenshots, CardComponent, ImageComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
BrowserModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: IBootstrapParams, useValue: params },
|
||||
{ provide: ISelector, useValue: selector },
|
||||
...providerIterator(instantiationService)
|
||||
]
|
||||
})
|
||||
class ModuleClass {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(ISelector) private selector: string
|
||||
) {
|
||||
}
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
const factory = this._resolver.resolveComponentFactory(InsertCellsScreenshots);
|
||||
(<any>factory).factory.selector = this.selector;
|
||||
appRef.bootstrap(factory);
|
||||
}
|
||||
}
|
||||
return ModuleClass;
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./insertCellsModal';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
@@ -13,22 +12,24 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { attachCheckboxStyler } from 'sql/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ServiceOptionType } from 'sql/platform/connection/common/interfaces';
|
||||
import { ServiceOption } from 'azdata';
|
||||
import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper';
|
||||
import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component';
|
||||
import { ComponentFactoryResolver, ViewContainerRef } from '@angular/core';
|
||||
import { NgModuleRef, ComponentFactoryResolver, ViewContainerRef } from '@angular/core';
|
||||
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
|
||||
import { inputBorder, inputValidationInfoBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
|
||||
import { InsertCellsModule } from 'sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal.module';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { truncate } from 'vs/base/common/strings';
|
||||
import { toJpeg } from 'html-to-image';
|
||||
import { IComponentEventArgs } from 'sql/platform/dashboard/browser/interfaces';
|
||||
import { Thumbnail } from 'sql/workbench/contrib/notebook/browser/notebookViews/insertCellsScreenshots.component';
|
||||
|
||||
type CellOption = {
|
||||
optionMetadata: ServiceOption,
|
||||
@@ -54,6 +55,10 @@ export class CellOptionsModel {
|
||||
});
|
||||
}
|
||||
|
||||
public get checkedOptions(): CellOption[] {
|
||||
return Object.values(this._optionsMap).filter(o => o.currentValue === true);
|
||||
}
|
||||
|
||||
private getDisplayValue(optionMetadata: ServiceOption, optionValue: string): boolean {
|
||||
let displayValue: boolean = false;
|
||||
switch (optionMetadata.valueType) {
|
||||
@@ -93,8 +98,8 @@ export class InsertCellsModal extends Modal {
|
||||
|
||||
private _submitButton: Button;
|
||||
private _cancelButton: Button;
|
||||
private _optionsMap: { [name: string]: Checkbox } = {};
|
||||
private _maxTitleLength: number = 20;
|
||||
private _moduleRef?: NgModuleRef<typeof InsertCellsModule>;
|
||||
|
||||
constructor(
|
||||
private onInsert: (cell: ICellModel) => void,
|
||||
@@ -107,6 +112,7 @@ export class InsertCellsModal extends Modal {
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
) {
|
||||
super(
|
||||
@@ -127,21 +133,7 @@ export class InsertCellsModal extends Modal {
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
const grid = DOM.$<HTMLDivElement>('div#insert-dialog-cell-grid');
|
||||
|
||||
grid.style.display = 'grid';
|
||||
grid.style.gridTemplateColumns = '1fr 1fr';
|
||||
grid.style.gap = '10px';
|
||||
grid.style.padding = '10px';
|
||||
grid.style.overflowY = 'auto';
|
||||
grid.style.maxHeight = 'calc(100% - 40px)';
|
||||
|
||||
const gridTitle = DOM.$<HTMLHeadElement>('h2.grid-title');
|
||||
gridTitle.title = localize("insertCellsModal.selectCells", "Select cell sources");
|
||||
|
||||
DOM.append(container, grid);
|
||||
|
||||
this.createOptions(grid)
|
||||
this.createOptions(container)
|
||||
.catch((e) => { this.setError(localize("insertCellsModal.thumbnailError", "Error: Unable to generate thumbnails.")); });
|
||||
}
|
||||
|
||||
@@ -153,59 +145,48 @@ export class InsertCellsModal extends Modal {
|
||||
const activeView = this._context.getActiveView();
|
||||
const cellsAvailableToInsert = activeView.hiddenCells;
|
||||
|
||||
cellsAvailableToInsert.forEach(async (cell) => {
|
||||
const optionWidget = this.createCheckBoxHelper(
|
||||
container,
|
||||
'<div class="loading-spinner-container"><div class="loading-spinner codicon in-progress"></div></div>',
|
||||
false,
|
||||
() => this.onOptionChecked(cell.cellGuid)
|
||||
);
|
||||
const thumbnails = await Promise.all(
|
||||
cellsAvailableToInsert.map(async (cell) => {
|
||||
return {
|
||||
id: cell.cellGuid,
|
||||
path: await this.generateScreenshot(cell),
|
||||
title: localize("insertCellsModal.cellTitle", "Cell {0}", Number.parseInt(cell.id) + 1)
|
||||
} as Thumbnail;
|
||||
})
|
||||
);
|
||||
|
||||
const img = await this.generateScreenshot(cell);
|
||||
const wrapper = DOM.$<HTMLDivElement>('div.thumnail-wrapper');
|
||||
const thumbnail = DOM.$<HTMLImageElement>('img.thumbnail');
|
||||
|
||||
thumbnail.src = img;
|
||||
thumbnail.style.maxWidth = '100%';
|
||||
DOM.append(wrapper, thumbnail);
|
||||
optionWidget.label = wrapper.outerHTML;
|
||||
|
||||
this._optionsMap[cell.cellGuid] = optionWidget;
|
||||
});
|
||||
this.bootstrapAngular(container, thumbnails);
|
||||
}
|
||||
|
||||
private createCheckBoxHelper(container: HTMLElement, label: string, isChecked: boolean, onCheck: (viaKeyboard: boolean) => void): Checkbox {
|
||||
const checkbox = new Checkbox(DOM.append(container, DOM.$('.dialog-input-section')), {
|
||||
label: label,
|
||||
checked: isChecked,
|
||||
onChange: onCheck,
|
||||
ariaLabel: label
|
||||
});
|
||||
this._register(attachCheckboxStyler(checkbox, this._themeService));
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
public onOptionChecked(optionName: string) {
|
||||
this.viewModel.setOptionValue(optionName, (<Checkbox>this._optionsMap[optionName]).checked);
|
||||
this.validate();
|
||||
public onOptionChecked(e: IComponentEventArgs) {
|
||||
if (e.args?.value) {
|
||||
let optionName: string = e.args.value;
|
||||
this.viewModel.setOptionValue(optionName, e.args.selected);
|
||||
this.validate();
|
||||
}
|
||||
}
|
||||
|
||||
public async generateScreenshot(cell: ICellModel, screenshotWidth: number = 300, screenshowHeight: number = 300, backgroundColor: string = '#ffffff'): Promise<string> {
|
||||
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(TextCellComponent);
|
||||
let component = this._containerRef.createComponent(componentFactory);
|
||||
try {
|
||||
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(TextCellComponent);
|
||||
let component = this._containerRef.createComponent(componentFactory);
|
||||
|
||||
component.instance.model = this._context.notebook as NotebookModel;
|
||||
component.instance.cellModel = cell;
|
||||
component.instance.model = this._context.notebook as NotebookModel;
|
||||
component.instance.cellModel = cell;
|
||||
|
||||
component.instance.handleContentChanged();
|
||||
component.instance.handleContentChanged();
|
||||
|
||||
const element: HTMLElement = component.instance.outputRef.nativeElement;
|
||||
const element: HTMLElement = component.instance.outputRef.nativeElement;
|
||||
|
||||
const scale = element.clientWidth / screenshotWidth;
|
||||
const canvasWidth = element.clientWidth / scale;
|
||||
const canvasHeight = element.clientHeight / scale;
|
||||
const scale = element.clientWidth / screenshotWidth;
|
||||
const canvasWidth = element.clientWidth / scale;
|
||||
const canvasHeight = element.clientHeight / scale;
|
||||
|
||||
return toJpeg(component.instance.outputRef.nativeElement, { quality: .6, canvasWidth, canvasHeight, backgroundColor });
|
||||
return toJpeg(component.instance.outputRef.nativeElement, { quality: .6, canvasWidth, canvasHeight, backgroundColor });
|
||||
} catch (e) {
|
||||
this.logService.error(`Error generating screenshot: ${e}`);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private getOptions(): ServiceOption[] {
|
||||
@@ -239,7 +220,7 @@ export class InsertCellsModal extends Modal {
|
||||
}
|
||||
|
||||
private validate() {
|
||||
if (Object.keys(this._optionsMap).length) {
|
||||
if (this.viewModel.checkedOptions.length) {
|
||||
this._submitButton.enabled = true;
|
||||
} else {
|
||||
this._submitButton.enabled = false;
|
||||
@@ -265,34 +246,25 @@ export class InsertCellsModal extends Modal {
|
||||
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
for (let key in this._optionsMap) {
|
||||
let widget = this._optionsMap[key];
|
||||
widget.dispose();
|
||||
delete this._optionsMap[key];
|
||||
|
||||
if (this._moduleRef) {
|
||||
this._moduleRef.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bootstrap params and perform the bootstrap
|
||||
*/
|
||||
private bootstrapAngular(bodyContainer: HTMLElement, thumbnails: Thumbnail[]) {
|
||||
this._instantiationService.invokeFunction<void, any[]>(bootstrapAngular,
|
||||
InsertCellsModule,
|
||||
bodyContainer,
|
||||
'insert-cells-screenshots-component',
|
||||
{
|
||||
thumbnails,
|
||||
onClick: (e: IComponentEventArgs) => { this.onOptionChecked(e); }
|
||||
},
|
||||
undefined,
|
||||
(moduleRef: NgModuleRef<typeof InsertCellsModule>) => this._moduleRef = moduleRef);
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const inputBorderColor = theme.getColor(inputBorder);
|
||||
if (inputBorderColor) {
|
||||
collector.addRule(`
|
||||
#insert-dialog-cell-grid input[type="checkbox"] + label {
|
||||
border: 2px solid;
|
||||
border-color: ${inputBorderColor.toString()};
|
||||
display: flex;
|
||||
height: 125px;
|
||||
overflow: hidden;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const inputActiveOptionBorderColor = theme.getColor(inputValidationInfoBorder);
|
||||
if (inputActiveOptionBorderColor) {
|
||||
collector.addRule(`
|
||||
#insert-dialog-cell-grid input[type="checkbox"]:checked + label {
|
||||
border-color: ${inputActiveOptionBorderColor.toString()};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./insertCellsScreenshots';
|
||||
import { Component, AfterViewInit, forwardRef, Inject, ComponentFactoryResolver, ViewContainerRef, ViewChild } from '@angular/core';
|
||||
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
|
||||
import { IComponentEventArgs } from 'sql/platform/dashboard/browser/interfaces';
|
||||
import CardComponent, { CardType } from 'sql/workbench/browser/modelComponents/card.component';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
|
||||
export interface LayoutRequestParams {
|
||||
modelViewId?: string;
|
||||
alwaysRefresh?: boolean;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
id: string,
|
||||
path: string,
|
||||
title: string
|
||||
}
|
||||
|
||||
export interface InsertCellsComponentParams extends IBootstrapParams {
|
||||
thumbnails: Thumbnail[],
|
||||
onClick: (e: IComponentEventArgs) => void
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'insert-cells-screenshots-component',
|
||||
template: '<div class="insert-cells-screenshot-grid"><ng-container #divContainer></ng-container></div>'
|
||||
})
|
||||
export class InsertCellsScreenshots implements AfterViewInit {
|
||||
@ViewChild('divContainer', { read: ViewContainerRef }) _containerRef: ViewContainerRef;
|
||||
|
||||
constructor(
|
||||
@Inject(IBootstrapParams) private _params: InsertCellsComponentParams,
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver
|
||||
) { }
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._params.thumbnails.forEach((thumbnail: Thumbnail, idx: number) => {
|
||||
const cellImageUri = URI.parse(thumbnail.path);
|
||||
|
||||
let cardComponentFactory = this._componentFactoryResolver.resolveComponentFactory(CardComponent);
|
||||
let cardComponent = this._containerRef.createComponent(cardComponentFactory);
|
||||
|
||||
cardComponent.instance.setProperties({ iconPath: cellImageUri, label: thumbnail.title, value: thumbnail.id, cardType: CardType.Image });
|
||||
|
||||
cardComponent.instance.enabled = true;
|
||||
cardComponent.instance.registerEventHandler(e => this._params.onClick(e));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.insert-cells-screenshot-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100% - 40px);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Component, Input, ViewChildren, QueryList, ChangeDetectorRef, forwardRef, Inject, ViewChild, ElementRef, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
|
||||
import { ICellModel, INotebookModel, ISingleNotebookEditOperation, NotebookContentChange } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import 'vs/css!./notebookViewsGrid';
|
||||
import { CodeCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/codeCell.component';
|
||||
import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component';
|
||||
import { ICellEditorProvider, INotebookParams, INotebookService, INotebookEditor, NotebookRange, INotebookSection, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
@@ -260,6 +261,10 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
|
||||
|
||||
let insertCellsAction = this._instantiationService.createInstance(InsertCellAction, this.insertCell.bind(this), this.views, this._containerRef, this._componentFactoryResolver);
|
||||
|
||||
// Hide the insert cell action button if no cells can be added
|
||||
insertCellsAction.enabled = this.activeView?.hiddenCells.length > 0;
|
||||
this.activeView.onCellVisibilityChanged(e => { insertCellsAction.enabled = this.activeView?.hiddenCells.length > 0; });
|
||||
|
||||
this._runAllCellsAction = this._instantiationService.createInstance(RunAllCellsAction, 'notebook.runAllCells', localize('runAllPreview', "Run all"), 'notebook-button masked-pseudo start-outline');
|
||||
|
||||
let spacerElement = document.createElement('li');
|
||||
|
||||
@@ -770,6 +770,7 @@ export class NotebookViewStub implements INotebookView {
|
||||
displayedCells: readonly ICellModel[];
|
||||
|
||||
onDeleted: vsEvent.Event<INotebookView>;
|
||||
onCellVisibilityChanged: vsEvent.Event<ICellModel>;
|
||||
initialize(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@@ -21,10 +21,12 @@ function cellCollides(c1: INotebookViewCell, c2: INotebookViewCell): boolean {
|
||||
|
||||
export class NotebookViewModel implements INotebookView {
|
||||
private _onDeleted = new Emitter<INotebookView>();
|
||||
private _onCellVisibilityChanged = new Emitter<ICellModel>();
|
||||
private _isNew: boolean = false;
|
||||
|
||||
public readonly guid: string;
|
||||
public readonly onDeleted = this._onDeleted.event;
|
||||
public readonly onCellVisibilityChanged = this._onCellVisibilityChanged.event;
|
||||
|
||||
constructor(
|
||||
protected _name: string,
|
||||
@@ -119,10 +121,12 @@ export class NotebookViewModel implements INotebookView {
|
||||
|
||||
public insertCell(cell: ICellModel) {
|
||||
this.updateCell(cell, this, { hidden: false });
|
||||
this._onCellVisibilityChanged.fire(cell);
|
||||
}
|
||||
|
||||
public hideCell(cell: ICellModel) {
|
||||
this.updateCell(cell, this, { hidden: true });
|
||||
this._onCellVisibilityChanged.fire(cell);
|
||||
}
|
||||
|
||||
public moveCell(cell: ICellModel, x: number, y: number) {
|
||||
|
||||
@@ -26,8 +26,9 @@ export interface INotebookViews {
|
||||
export interface INotebookView {
|
||||
readonly guid: string;
|
||||
readonly onDeleted: Event<INotebookView>;
|
||||
isNew: boolean;
|
||||
readonly onCellVisibilityChanged: Event<ICellModel>;
|
||||
|
||||
isNew: boolean;
|
||||
cells: Readonly<ICellModel[]>;
|
||||
hiddenCells: Readonly<ICellModel[]>;
|
||||
displayedCells: Readonly<ICellModel[]>;
|
||||
|
||||
Reference in New Issue
Block a user