Add html-to-image library (#16845)

For use by the Notebook Views insert cells modal
This commit is contained in:
Daniel Grajeda
2021-08-20 18:18:23 -06:00
committed by GitHub
parent 1490e75302
commit ed280cb003
15 changed files with 69 additions and 9 deletions

View File

@@ -743,6 +743,7 @@
"plotly.js",
"angular2-grid",
"html-query-plan",
"html-to-image",
"turndown",
"gridstack",
"mark.js",

View File

@@ -78,6 +78,7 @@
"graceful-fs": "4.2.3",
"gridstack": "^3.1.3",
"html-query-plan": "git://github.com/kburtram/html-query-plan.git#2.6",
"html-to-image": "^1.6.2",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.8",

View File

@@ -20,6 +20,7 @@
"graceful-fs": "4.2.3",
"gridstack": "^3.1.3",
"html-query-plan": "git://github.com/kburtram/html-query-plan.git#2.6",
"html-to-image": "^1.6.2",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.8",

View File

@@ -16,6 +16,7 @@
"chart.js": "^2.9.4",
"gridstack": "^3.1.3",
"html-query-plan": "git://github.com/kburtram/html-query-plan.git#2.6",
"html-to-image": "^1.6.2",
"iconv-lite-umd": "0.6.8",
"jquery": "3.5.0",
"jschardet": "2.3.0",

View File

@@ -180,6 +180,11 @@ has-flag@^3.0.0:
version "2.5.0"
resolved "git://github.com/kburtram/html-query-plan.git#c524feb824e4960897ad875a37af068376a2b4a3"
html-to-image@^1.6.2:
version "1.7.0"
resolved "https://registry.yarnpkg.com/html-to-image/-/html-to-image-1.7.0.tgz#4ca93bb90c0b9392edaafbfd5d94e8f0d666e18b"
integrity sha512-6egK8mOXMw82nLjj5g3ohERuzrTglgR9+Q6A2cqa7UiuSSKHuFxpABZJSfZztj0EdLC6tAePZJAhjPr4bbU9tw==
htmlparser2@^3.9.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"

View File

@@ -402,6 +402,11 @@ has-flag@^3.0.0:
version "2.5.0"
resolved "git://github.com/kburtram/html-query-plan.git#c524feb824e4960897ad875a37af068376a2b4a3"
html-to-image@^1.6.2:
version "1.7.0"
resolved "https://registry.yarnpkg.com/html-to-image/-/html-to-image-1.7.0.tgz#4ca93bb90c0b9392edaafbfd5d94e8f0d666e18b"
integrity sha512-6egK8mOXMw82nLjj5g3ohERuzrTglgR9+Q6A2cqa7UiuSSKHuFxpABZJSfZztj0EdLC6tAePZJAhjPr4bbU9tw==
htmlparser2@^3.9.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"

View File

@@ -16,6 +16,7 @@ define(['require', 'exports'], function (require) {
require.__$__nodeRequire('slickgrid/slick.dataview');
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
require.__$__nodeRequire('gridstack/dist/h5/gridstack-dd-native');
require.__$__nodeRequire('html-to-image/dist/html-to-image.js');
require.__$__nodeRequire('reflect-metadata');
require.__$__nodeRequire('zone.js/dist/zone');
require.__$__nodeRequire('zone.js/dist/zone-error');

View File

@@ -19,12 +19,16 @@ import { attachCheckboxStyler } from 'sql/platform/theme/common/styler';
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 { 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 { localize } from 'vs/nls';
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { truncate } from 'vs/base/common/strings';
import { toJpeg } from 'html-to-image';
type CellOption = {
optionMetadata: ServiceOption,
@@ -95,6 +99,8 @@ export class InsertCellsModal extends Modal {
constructor(
private onInsert: (cell: ICellModel) => void,
private _context: NotebookViewsExtension,
private _containerRef: ViewContainerRef,
private _componentFactoryResolver: ComponentFactoryResolver,
@ILogService logService: ILogService,
@IThemeService themeService: IThemeService,
@ILayoutService layoutService: ILayoutService,
@@ -155,7 +161,14 @@ export class InsertCellsModal extends Modal {
() => this.onOptionChecked(cell.cellGuid)
);
optionWidget.label = cell.cellGuid;
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;
});
@@ -177,6 +190,24 @@ export class InsertCellsModal extends Modal {
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);
component.instance.model = this._context.notebook as NotebookModel;
component.instance.cellModel = cell;
component.instance.handleContentChanged();
const element: HTMLElement = component.instance.outputRef.nativeElement;
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 });
}
private getOptions(): ServiceOption[] {
const activeView = this._context.getActiveView();
const cellsAvailableToInsert = activeView.hiddenCells;

View File

@@ -2,9 +2,8 @@
* 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!./notebookViewsGrid';
import { Component, Input, ViewChildren, QueryList, ChangeDetectorRef, forwardRef, Inject, ViewChild, ElementRef } from '@angular/core';
import { ICellModel, INotebookModel, ISingleNotebookEditOperation, NotebookContentChange, } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
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 { 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';
@@ -76,7 +75,9 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
@Inject(INotebookService) private _notebookService: INotebookService,
@Inject(IConnectionManagementService) private _connectionManagementService: IConnectionManagementService,
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
@Inject(IEditorService) private _editorService: IEditorService
@Inject(IEditorService) private _editorService: IEditorService,
@Inject(ViewContainerRef) private _containerRef: ViewContainerRef,
@Inject(ComponentFactoryResolver) private _componentFactoryResolver: ComponentFactoryResolver,
) {
super();
this._register(this._configurationService.onDidChangeConfiguration(e => {
@@ -257,7 +258,7 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
titleElement.style.marginRight = '25px';
titleElement.style.minHeight = '25px';
let insertCellsAction = this._instantiationService.createInstance(InsertCellAction, this.insertCell.bind(this), this.views);
let insertCellsAction = this._instantiationService.createInstance(InsertCellAction, this.insertCell.bind(this), this.views, this._containerRef, this._componentFactoryResolver);
this._runAllCellsAction = this._instantiationService.createInstance(RunAllCellsAction, 'notebook.runAllCells', localize('runAllPreview', "Run all"), 'notebook-button masked-pseudo start-outline');

View File

@@ -9,6 +9,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { localize } from 'vs/nls';
import { InsertCellsModal } from 'sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ComponentFactoryResolver, ViewContainerRef } from '@angular/core';
import { IDisposable } from 'vs/base/common/lifecycle';
import { CellExecutionState, ICellModel, ViewMode } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { CellActionBase, CellContext, IMultiStateData, MultiStateAction } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
@@ -91,13 +92,15 @@ export class InsertCellAction extends Action {
constructor(
private onInsert: (cell: ICellModel) => void,
private _context: NotebookViewsExtension,
private _containerRef: ViewContainerRef,
private _componentFactoryResolver: ComponentFactoryResolver,
@IInstantiationService private _instantiationService: IInstantiationService,
) {
super(InsertCellAction.ID, InsertCellAction.LABEL, InsertCellAction.ICON);
}
override async run(): Promise<void> {
const optionsModal = this._instantiationService.createInstance(InsertCellsModal, this.onInsert, this._context);
const optionsModal = this._instantiationService.createInstance(InsertCellsModal, this.onInsert, this._context, this._containerRef, this._componentFactoryResolver);
optionsModal.render();
optionsModal.open();
}

View File

@@ -133,6 +133,8 @@ suite('Notebook Views Actions', function (): void {
let insertCellsModal = TypeMoq.Mock.ofType(InsertCellsModal, TypeMoq.MockBehavior.Strict,
(cell: ICellModel) => { }, // onInsert
notebookViews, // _context
undefined, // _containerRef
undefined, // _componentFactoryResolver
undefined, // logService
undefined, // themeService
undefined, // layoutService
@@ -151,9 +153,9 @@ suite('Notebook Views Actions', function (): void {
});
const instantiationService = new InstantiationService();
sinon.stub(instantiationService, 'createInstance').withArgs(InsertCellsModal, sinon.match.any, sinon.match.any).returns(insertCellsModal.object);
sinon.stub(instantiationService, 'createInstance').withArgs(InsertCellsModal, sinon.match.any, sinon.match.any, sinon.match.any, sinon.match.any).returns(insertCellsModal.object);
const insertCellAction = new InsertCellAction((cell: ICellModel) => { }, notebookViews, instantiationService);
const insertCellAction = new InsertCellAction((cell: ICellModel) => { }, notebookViews, undefined, undefined, instantiationService);
await insertCellAction.run();
assert.ok(rendered);

View File

@@ -60,6 +60,7 @@
'angular2-slickgrid': `${window.location.origin}/static/remote/web/node_modules/angular2-slickgrid/out/bundles/angular2-slickgrid.umd.js`,
'chart.js': `${window.location.origin}/static/remote/web/node_modules/chart.js/dist/Chart.bundle.min.js`,
'html-query-plan': `${window.location.origin}/static/remote/web/node_modules/html-query-plan/dist/index.min.js`,
'html-to-image': `${window.location.origin}/static/remote/web/node_modules/html-to-image/dist/html-to-image.js`,
'ng2-charts': `${window.location.origin}/static/remote/web/node_modules/ng2-charts/bundles/ng2-charts.umd.js`,
'rxjs/Observable': `${window.location.origin}/static/remote/web/node_modules/rxjs/bundles/Rx.min.js?0`,
'rxjs/observable/merge': `${window.location.origin}/static/remote/web/node_modules/rxjs/bundles/Rx.min.js?1`,

View File

@@ -60,6 +60,7 @@
'angular2-slickgrid': `${window.location.origin}/static/node_modules/angular2-slickgrid/out/bundles/angular2-slickgrid.umd.js`,
'chart.js': `${window.location.origin}/static/node_modules/chart.js/dist/Chart.bundle.min.js`,
'html-query-plan': `${window.location.origin}/static/node_modules/html-query-plan/dist/index.min.js`,
'html-to-image': `${window.location.origin}/static/node_modules/html-to-image/dist/html-to-image.js`,
'ng2-charts': `${window.location.origin}/static/node_modules/ng2-charts/bundles/ng2-charts.umd.js`,
'rxjs/Observable': `${window.location.origin}/static/node_modules/rxjs/bundles/Rx.min.js?0`,
'rxjs/observable/merge': `${window.location.origin}/static/node_modules/rxjs/bundles/Rx.min.js?1`,

View File

@@ -104,6 +104,7 @@ function initLoader(opts) {
'@angular/router',
'angular2-grid',
'gridstack/dist/h5/gridstack-dd-native',
'html-to-image',
'ng2-charts',
'rxjs/add/observable/of',
'rxjs/add/observable/fromPromise',

View File

@@ -5289,6 +5289,11 @@ html-escaper@^2.0.0:
version "2.5.0"
resolved "git://github.com/kburtram/html-query-plan.git#c524feb824e4960897ad875a37af068376a2b4a3"
html-to-image@^1.6.2:
version "1.7.0"
resolved "https://registry.yarnpkg.com/html-to-image/-/html-to-image-1.7.0.tgz#4ca93bb90c0b9392edaafbfd5d94e8f0d666e18b"
integrity sha512-6egK8mOXMw82nLjj5g3ohERuzrTglgR9+Q6A2cqa7UiuSSKHuFxpABZJSfZztj0EdLC6tAePZJAhjPr4bbU9tw==
"htmlparser2@>= 3.7.3 < 4.0.0":
version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"