move code from parts to contrib (#8319)

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

View File

@@ -0,0 +1,105 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import * as widgets from './widgets';
import { IRenderMime } from '../models/renderMimeInterfaces';
/**
* A mime renderer factory for raw html.
*/
export const htmlRendererFactory: IRenderMime.IRendererFactory = {
safe: true,
mimeTypes: ['text/html'],
defaultRank: 50,
createRenderer: options => new widgets.RenderedHTML(options)
};
/**
* A mime renderer factory for images.
*/
export const imageRendererFactory: IRenderMime.IRendererFactory = {
safe: true,
mimeTypes: ['image/bmp', 'image/png', 'image/jpeg', 'image/gif'],
defaultRank: 90,
createRenderer: options => new widgets.RenderedImage(options)
};
// /**
// * A mime renderer factory for LaTeX.
// */
// export const latexRendererFactory: IRenderMime.IRendererFactory = {
// safe: true,
// mimeTypes: ['text/latex'],
// defaultRank: 70,
// createRenderer: options => new widgets.RenderedLatex(options)
// };
/**
* A mime renderer factory for svg.
*/
export const svgRendererFactory: IRenderMime.IRendererFactory = {
safe: false,
mimeTypes: ['image/svg+xml'],
defaultRank: 80,
createRenderer: options => new widgets.RenderedSVG(options)
};
/**
* A mime renderer factory for plain and jupyter console text data.
*/
export const textRendererFactory: IRenderMime.IRendererFactory = {
safe: true,
mimeTypes: [
'text/plain',
'application/vnd.jupyter.stdout',
'application/vnd.jupyter.stderr'
],
defaultRank: 120,
createRenderer: options => new widgets.RenderedText(options)
};
/**
* A placeholder factory for deprecated rendered JavaScript.
*/
export const javaScriptRendererFactory: IRenderMime.IRendererFactory = {
safe: false,
mimeTypes: ['text/javascript', 'application/javascript'],
defaultRank: 110,
createRenderer: options => new widgets.RenderedJavaScript(options)
};
export const dataResourceRendererFactory: IRenderMime.IRendererFactory = {
safe: true,
mimeTypes: [
'application/vnd.dataresource+json',
'application/vnd.dataresource'
],
defaultRank: 40,
createRenderer: options => new widgets.RenderedDataResource(options)
};
export const ipywidgetFactory: IRenderMime.IRendererFactory = {
safe: false,
mimeTypes: [
'application/vnd.jupyter.widget-view',
'application/vnd.jupyter.widget-view+json'
],
defaultRank: 45,
createRenderer: options => new widgets.RenderedIPyWidget(options)
};
/**
* The standard factories provided by the rendermime package.
*/
export const standardRendererFactories: ReadonlyArray<IRenderMime.IRendererFactory> = [
htmlRendererFactory,
// latexRendererFactory,
svgRendererFactory,
imageRendererFactory,
javaScriptRendererFactory,
textRendererFactory,
dataResourceRendererFactory,
ipywidgetFactory
];

View File

@@ -0,0 +1,378 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OnInit, Component, Input, Inject, ViewChild, ElementRef } from '@angular/core';
import * as azdata from 'azdata';
import { IGridDataProvider, getResultsString } from 'sql/platform/query/common/gridDataProvider';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { getEolString, shouldIncludeHeaders, shouldRemoveNewLines } from 'sql/platform/query/common/queryRunner';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { localize } from 'vs/nls';
import { IAction } from 'vs/base/common/actions';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { IMimeComponent } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { MimeModel } from 'sql/workbench/contrib/notebook/browser/models/mimemodel';
import { GridTableState } from 'sql/workbench/contrib/query/common/gridPanelState';
import { GridTableBase } from 'sql/workbench/contrib/query/browser/gridPanel';
import { getErrorMessage } from 'vs/base/common/errors';
import { ISerializationService, SerializeDataParams } from 'sql/platform/serialization/common/serializationService';
import { SaveResultAction } from 'sql/workbench/contrib/query/browser/actions';
import { ResultSerializer, SaveResultsResponse } from 'sql/workbench/contrib/query/common/resultSerializer';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { values } from 'vs/base/common/collections';
import { assign } from 'vs/base/common/objects';
@Component({
selector: GridOutputComponent.SELECTOR,
template: `<div #output class="notebook-cellTable" (mouseover)="hover=true" (mouseleave)="hover=false"></div>`
})
export class GridOutputComponent extends AngularDisposable implements IMimeComponent, OnInit {
public static readonly SELECTOR: string = 'grid-output';
@ViewChild('output', { read: ElementRef }) private output: ElementRef;
private _initialized: boolean = false;
private _cellModel: ICellModel;
private _bundleOptions: MimeModel.IOptions;
private _table: DataResourceTable;
private _hover: boolean;
constructor(
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IThemeService) private readonly themeService: IThemeService
) {
super();
}
@Input() set bundleOptions(value: MimeModel.IOptions) {
this._bundleOptions = value;
if (this._initialized) {
this.renderGrid();
}
}
@Input() mimeType: string;
get cellModel(): ICellModel {
return this._cellModel;
}
@Input() set cellModel(value: ICellModel) {
this._cellModel = value;
if (this._initialized) {
this.renderGrid();
}
}
@Input() set hover(value: boolean) {
// only reaction on hover changes
if (this._hover !== value) {
this.toggleActionbar(value);
this._hover = value;
}
}
ngOnInit() {
this.renderGrid();
}
renderGrid(): void {
if (!this._bundleOptions || !this._cellModel || !this.mimeType) {
return;
}
if (!this._table) {
let source = <IDataResource><any>this._bundleOptions.data[this.mimeType];
let state = new GridTableState(0, 0);
this._table = this.instantiationService.createInstance(DataResourceTable, source, this.cellModel.notebookModel.notebookUri.toString(), state);
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.appendChild(this._table.element);
this._register(attachTableStyler(this._table, this.themeService));
this.layout();
// By default, do not show the actions
this.toggleActionbar(false);
this._table.onAdd();
this._initialized = true;
}
}
layout(): void {
if (this._table) {
let maxSize = Math.min(this._table.maximumSize, 500);
this._table.layout(maxSize, undefined, ActionsOrientation.HORIZONTAL);
}
}
private toggleActionbar(visible: boolean) {
let outputElement = <HTMLElement>this.output.nativeElement;
let actionsContainers: HTMLElement[] = Array.prototype.slice.call(outputElement.getElementsByClassName('actions-container'));
if (actionsContainers && actionsContainers.length) {
if (visible) {
actionsContainers.forEach(container => container.style.visibility = 'visible');
} else {
actionsContainers.forEach(container => container.style.visibility = 'hidden');
}
}
}
}
class DataResourceTable extends GridTableBase<any> {
private _gridDataProvider: IGridDataProvider;
constructor(source: IDataResource,
documentUri: string,
state: GridTableState,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService editorService: IEditorService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IConfigurationService configurationService: IConfigurationService,
@ISerializationService private _serializationService: ISerializationService
) {
super(state, createResultSet(source), contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
this._gridDataProvider = this.instantiationService.createInstance(DataResourceDataProvider, source, this.resultSet, documentUri);
}
get gridDataProvider(): IGridDataProvider {
return this._gridDataProvider;
}
protected getCurrentActions(): IAction[] {
return this.getContextActions();
}
protected getContextActions(): IAction[] {
if (!this._serializationService.hasProvider()) {
return [];
}
return [
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
];
}
public get maximumSize(): number {
// Overriding action bar size calculation for now.
// When we add this back in, we should update this calculation
return Math.max(this.maxSize, /* ACTIONBAR_HEIGHT + BOTTOM_PADDING */ 0);
}
}
class DataResourceDataProvider implements IGridDataProvider {
private rows: azdata.DbCellValue[][];
constructor(source: IDataResource,
private resultSet: azdata.ResultSetSummary,
private documentUri: string,
@INotificationService private _notificationService: INotificationService,
@IClipboardService private _clipboardService: IClipboardService,
@IConfigurationService private _configurationService: IConfigurationService,
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService,
@ISerializationService private _serializationService: ISerializationService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
this.transformSource(source);
}
private transformSource(source: IDataResource): void {
this.rows = source.data.map(row => {
let rowData: azdata.DbCellValue[] = [];
Object.keys(row).forEach((val, index) => {
let displayValue = String(values(row)[index]);
// Since the columns[0] represents the row number, start at 1
rowData.push({
displayValue: displayValue,
isNull: false,
invariantCultureDisplayValue: displayValue
});
});
return rowData;
});
}
getRowData(rowStart: number, numberOfRows: number): Thenable<azdata.QueryExecuteSubsetResult> {
let rowEnd = rowStart + numberOfRows;
if (rowEnd > this.rows.length) {
rowEnd = this.rows.length;
}
let resultSubset: azdata.QueryExecuteSubsetResult = {
message: undefined,
resultSubset: {
rowCount: rowEnd - rowStart,
rows: this.rows.slice(rowStart, rowEnd)
}
};
return Promise.resolve(resultSubset);
}
async copyResults(selection: Slick.Range[], includeHeaders?: boolean): Promise<void> {
return this.copyResultsAsync(selection, includeHeaders);
}
private async copyResultsAsync(selection: Slick.Range[], includeHeaders?: boolean): Promise<void> {
try {
let results = await getResultsString(this, selection, includeHeaders);
this._clipboardService.writeText(results);
} catch (error) {
this._notificationService.error(localize('copyFailed', "Copy failed with error {0}", getErrorMessage(error)));
}
}
getEolString(): string {
return getEolString(this._textResourcePropertiesService, this.documentUri);
}
shouldIncludeHeaders(includeHeaders: boolean): boolean {
return shouldIncludeHeaders(includeHeaders, this._configurationService);
}
shouldRemoveNewLines(): boolean {
return shouldRemoveNewLines(this._configurationService);
}
getColumnHeaders(range: Slick.Range): string[] {
let headers: string[] = this.resultSet.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
return info.columnName;
});
return headers;
}
get canSerialize(): boolean {
return this._serializationService.hasProvider();
}
serializeResults(format: SaveFormat, selection: Slick.Range[]): Thenable<void> {
let serializer = this._instantiationService.createInstance(ResultSerializer);
return serializer.handleSerialization(this.documentUri, format, (filePath) => this.doSerialize(serializer, filePath, format, selection));
}
private doSerialize(serializer: ResultSerializer, filePath: string, format: SaveFormat, selection: Slick.Range[]): Promise<SaveResultsResponse | undefined> {
if (!this.canSerialize) {
return Promise.resolve(undefined);
}
// TODO implement selection support
let columns = this.resultSet.columnInfo;
let rowLength = this.rows.length;
let minRow = 0;
let maxRow = this.rows.length;
let singleSelection = selection && selection.length > 0 ? selection[0] : undefined;
if (singleSelection && this.isSelected(singleSelection)) {
rowLength = singleSelection.toRow - singleSelection.fromRow + 1;
minRow = singleSelection.fromRow;
maxRow = singleSelection.toRow + 1;
columns = columns.slice(singleSelection.fromCell, singleSelection.toCell + 1);
}
let getRows: ((index: number, rowCount: number) => azdata.DbCellValue[][]) = (index, rowCount) => {
// Offset for selections by adding the selection startRow to the index
index = index + minRow;
if (rowLength === 0 || index < 0 || index >= maxRow) {
return [];
}
let endIndex = index + rowCount;
if (endIndex > maxRow) {
endIndex = maxRow;
}
let result = this.rows.slice(index, endIndex).map(row => {
if (this.isSelected(singleSelection)) {
return row.slice(singleSelection.fromCell, singleSelection.toCell + 1);
}
return row;
});
return result;
};
let serializeRequestParams: SerializeDataParams = <SerializeDataParams>assign(serializer.getBasicSaveParameters(format), <Partial<SerializeDataParams>>{
saveFormat: format,
columns: columns,
filePath: filePath,
getRowRange: (rowStart, numberOfRows) => getRows(rowStart, numberOfRows),
rowCount: rowLength
});
return this._serializationService.serializeResults(serializeRequestParams);
}
/**
* Check if a range of cells were selected.
*/
private isSelected(selection: Slick.Range): boolean {
return (selection && !((selection.fromCell === selection.toCell) && (selection.fromRow === selection.toRow)));
}
}
function createResultSet(source: IDataResource): azdata.ResultSetSummary {
let columnInfo: azdata.IDbColumn[] = source.schema.fields.map(field => {
let column = new SimpleDbColumn(field.name);
if (field.type) {
switch (field.type) {
case 'xml':
column.isXml = true;
break;
case 'json':
column.isJson = true;
break;
default:
// Only handling a few cases for now
break;
}
}
return column;
});
let summary: azdata.ResultSetSummary = {
batchId: 0,
id: 0,
complete: true,
rowCount: source.data.length,
columnInfo: columnInfo
};
return summary;
}
class SimpleDbColumn implements azdata.IDbColumn {
constructor(columnName: string) {
this.columnName = columnName;
}
allowDBNull?: boolean;
baseCatalogName: string;
baseColumnName: string;
baseSchemaName: string;
baseServerName: string;
baseTableName: string;
columnName: string;
columnOrdinal?: number;
columnSize?: number;
isAliased?: boolean;
isAutoIncrement?: boolean;
isExpression?: boolean;
isHidden?: boolean;
isIdentity?: boolean;
isKey?: boolean;
isBytes?: boolean;
isChars?: boolean;
isSqlVariant?: boolean;
isUdt?: boolean;
dataType: string;
isXml?: boolean;
isJson?: boolean;
isLong?: boolean;
isReadOnly?: boolean;
isUnique?: boolean;
numericPrecision?: number;
numericScale?: number;
udtAssemblyQualifiedName: string;
dataTypeName: string;
}

View File

@@ -0,0 +1,5 @@
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
<div class="codicon in-progress" *ngIf="loading === true"></div>
<div #output link-handler [isTrusted]="isTrusted" [notebookUri]="notebookUri" class="notebook-preview" style="flex: 1 1 auto">
</div>
</div>

View File

@@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* 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!../cellViews/textCell';
import 'vs/css!../cellViews/media/markdown';
import 'vs/css!../cellViews/media/highlight';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ISanitizer, defaultSanitizer } from 'sql/workbench/contrib/notebook/browser/outputs/sanitizer';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { IMimeComponent } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { NotebookMarkdownRenderer } from 'sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown';
import { MimeModel } from 'sql/workbench/contrib/notebook/browser/models/mimemodel';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { useInProcMarkdown, convertVscodeResourceToFileInSubDirectories } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils';
import { URI } from 'vs/base/common/uri';
@Component({
selector: MarkdownOutputComponent.SELECTOR,
templateUrl: decodeURI(require.toUrl('./markdownOutput.component.html'))
})
export class MarkdownOutputComponent extends AngularDisposable implements IMimeComponent, OnInit {
public static readonly SELECTOR: string = 'markdown-output';
@ViewChild('output', { read: ElementRef }) private output: ElementRef;
private _sanitizer: ISanitizer;
private _lastTrustedMode: boolean;
private _bundleOptions: MimeModel.IOptions;
private _initialized: boolean = false;
public loading: boolean = false;
private _cellModel: ICellModel;
private _markdownRenderer: NotebookMarkdownRenderer;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(ICommandService) private _commandService: ICommandService,
@Inject(INotebookService) private _notebookService: INotebookService,
@Inject(IConfigurationService) private _configurationService: IConfigurationService,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService
) {
super();
this._sanitizer = this._notebookService.getMimeRegistry().sanitizer;
this._markdownRenderer = this._instantiationService.createInstance(NotebookMarkdownRenderer);
}
@Input() set bundleOptions(value: MimeModel.IOptions) {
this._bundleOptions = value;
if (this._initialized) {
this.updatePreview();
}
}
@Input() mimeType: string;
get cellModel(): ICellModel {
return this._cellModel;
}
@Input() set cellModel(value: ICellModel) {
this._cellModel = value;
if (this._initialized) {
this.updatePreview();
}
}
public get isTrusted(): boolean {
return this._bundleOptions && this._bundleOptions.trusted;
}
public get notebookUri(): URI {
return this.cellModel.notebookModel.notebookUri;
}
//Gets sanitizer from ISanitizer interface
private get sanitizer(): ISanitizer {
if (this._sanitizer) {
return this._sanitizer;
}
return this._sanitizer = defaultSanitizer;
}
private setLoading(isLoading: boolean): void {
this.loading = isLoading;
this._changeRef.detectChanges();
}
ngOnInit() {
this.updatePreview();
}
/**
* Updates the preview of markdown component with latest changes
* If content is empty and in non-edit mode, default it to 'Double-click to edit'
* Sanitizes the data to be shown in markdown cell
*/
private updatePreview() {
if (!this._bundleOptions || !this._cellModel) {
return;
}
let trustedChanged = this._bundleOptions && this._lastTrustedMode !== this.isTrusted;
if (trustedChanged || !this._initialized) {
this._lastTrustedMode = this.isTrusted;
let content = this._bundleOptions.data['text/markdown'];
if (useInProcMarkdown(this._configurationService)) {
this._markdownRenderer.setNotebookURI(this.cellModel.notebookModel.notebookUri);
let markdownResult = this._markdownRenderer.render({
isTrusted: this.cellModel.trustedMode,
value: content.toString()
});
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.innerHTML = markdownResult.element.innerHTML;
} else {
if (!content) {
} else {
this._commandService.executeCommand<string>('notebook.showPreview', this._cellModel.notebookModel.notebookUri, content).then((htmlcontent) => {
htmlcontent = convertVscodeResourceToFileInSubDirectories(htmlcontent, this._cellModel);
htmlcontent = this.sanitizeContent(htmlcontent);
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.innerHTML = htmlcontent;
this.setLoading(false);
});
}
}
this._initialized = true;
}
}
//Sanitizes the content based on trusted mode of Cell Model
private sanitizeContent(content: string): string {
if (this.isTrusted) {
content = this.sanitizer.sanitize(content);
}
return content;
}
public layout() {
// Do we need to update on layout changed?
}
public handleContentChanged(): void {
this.updatePreview();
}
}

View File

@@ -0,0 +1,195 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Type } from '@angular/core';
import * as platform from 'vs/platform/registry/common/platform';
import { ReadonlyJSONObject } from 'sql/workbench/contrib/notebook/common/models/jsonext';
import { MimeModel } from 'sql/workbench/contrib/notebook/browser/models/mimemodel';
import * as types from 'vs/base/common/types';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { values } from 'vs/base/common/collections';
export type FactoryIdentifier = string;
export const Extensions = {
MimeComponentContribution: 'notebook.contributions.mimecomponents'
};
export interface IMimeComponent {
bundleOptions: MimeModel.IOptions;
mimeType: string;
cellModel?: ICellModel;
layout(): void;
}
export interface IMimeComponentDefinition {
/**
* Whether the component is a "safe" component.
*
* #### Notes
* A "safe" component produces renderer widgets which can render
* untrusted model data in a usable way. *All* renderers must
* handle untrusted data safely, but some may simply failover
* with a "Run cell to view output" message. A "safe" renderer
* is an indication that its sanitized output will be useful.
*/
readonly safe: boolean;
/**
* The mime types handled by this component.
*/
readonly mimeTypes: ReadonlyArray<string>;
/**
* The angular selector for this component
*/
readonly selector: string;
/**
* The default rank of the factory. If not given, defaults to 100.
*/
readonly rank?: number;
readonly ctor: Type<IMimeComponent>;
}
export type SafetyLevel = 'ensure' | 'prefer' | 'any';
type RankPair = { readonly id: number; readonly rank: number };
/**
* A type alias for a mapping of mime type -> rank pair.
*/
type RankMap = { [key: string]: RankPair };
/**
* A type alias for a mapping of mime type -> ordered factories.
*/
export type ComponentMap = { [key: string]: IMimeComponentDefinition };
export interface IMimeComponentRegistry {
/**
* Add a MIME component to the registry.
*
* @param componentDefinition - The definition of this component including
* the constructor to initialize it, supported `mimeTypes`, and `rank` order
* of preference vs. other mime types.
* If no `rank` is given, it will default to 100.
*
* #### Notes
* The renderer will replace an existing renderer for the given
* mimeType.
*/
registerComponentType(componentDefinition: IMimeComponentDefinition): void;
/**
* Find the preferred mime type for a mime bundle.
*
* @param bundle - The bundle of mime data.
*
* @param safe - How to consider safe/unsafe factories. If 'ensure',
* it will only consider safe factories. If 'any', any factory will be
* considered. If 'prefer', unsafe factories will be considered, but
* only after the safe options have been exhausted.
*
* @returns The preferred mime type from the available factories,
* or `undefined` if the mime type cannot be rendered.
*/
getPreferredMimeType(bundle: ReadonlyJSONObject, safe: SafetyLevel): string;
getCtorFromMimeType(mimeType: string): Type<IMimeComponent>;
getAllCtors(): Array<Type<IMimeComponent>>;
getAllMimeTypes(): Array<string>;
}
class MimeComponentRegistry implements IMimeComponentRegistry {
private _id = 0;
private _ranks: RankMap = {};
private _types: string[] | null = null;
private _componentDefinitions: ComponentMap = {};
registerComponentType(componentDefinition: IMimeComponentDefinition): void {
let rank = !types.isUndefinedOrNull(componentDefinition.rank) ? componentDefinition.rank : 100;
for (let mt of componentDefinition.mimeTypes) {
this._componentDefinitions[mt] = componentDefinition;
this._ranks[mt] = { rank, id: this._id++ };
}
this._types = null;
}
public getPreferredMimeType(bundle: ReadonlyJSONObject, safe: SafetyLevel = 'ensure'): string | undefined {
// Try to find a safe factory first, if preferred.
if (safe === 'ensure' || safe === 'prefer') {
for (let mt of this.mimeTypes) {
if (mt in bundle && this._componentDefinitions[mt].safe) {
return mt;
}
}
}
if (safe !== 'ensure') {
// Otherwise, search for the best factory among all factories.
for (let mt of this.mimeTypes) {
if (mt in bundle) {
return mt;
}
}
}
// Otherwise, no matching mime type exists.
return undefined;
}
public getCtorFromMimeType(mimeType: string): Type<IMimeComponent> {
let componentDescriptor = this._componentDefinitions[mimeType];
return componentDescriptor ? componentDescriptor.ctor : undefined;
}
public getAllCtors(): Array<Type<IMimeComponent>> {
let addedCtors = [];
let ctors = values(this._componentDefinitions)
.map((c: IMimeComponentDefinition) => c.ctor)
.filter(ctor => {
let shouldAdd = !addedCtors.some((ctor2) => ctor === ctor2);
if (shouldAdd) {
addedCtors.push(ctor);
}
return shouldAdd;
});
return ctors;
}
public getAllMimeTypes(): Array<string> {
return Object.keys(this._componentDefinitions);
}
/**
* The ordered list of mimeTypes.
*/
get mimeTypes(): ReadonlyArray<string> {
return this._types || (this._types = sortedTypes(this._ranks));
}
}
const componentRegistry = new MimeComponentRegistry();
platform.Registry.add(Extensions.MimeComponentContribution, componentRegistry);
export function registerComponentType(componentDefinition: IMimeComponentDefinition): void {
componentRegistry.registerComponentType(componentDefinition);
}
/**
* Get the mime types in the map, ordered by rank.
*/
function sortedTypes(map: RankMap): string[] {
return Object.keys(map).sort((a, b) => {
let p1 = map[a];
let p2 = map[b];
if (p1.rank !== p2.rank) {
return p1.rank - p2.rank;
}
return p1.id - p2.id;
});
}

View File

@@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IMimeComponent } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { ElementRef, forwardRef, Inject, Component, OnInit, Input } from '@angular/core';
import { MimeModel } from 'sql/workbench/contrib/notebook/browser/models/mimemodel';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { RenderMimeRegistry } from 'sql/workbench/contrib/notebook/browser/outputs/registry';
import { localize } from 'vs/nls';
@Component({
selector: MimeRendererComponent.SELECTOR,
template: ``
})
export class MimeRendererComponent extends AngularDisposable implements IMimeComponent, OnInit {
public static readonly SELECTOR = 'mime-output';
private _bundleOptions: MimeModel.IOptions;
private registry: RenderMimeRegistry;
private _initialized: boolean = false;
constructor(
@Inject(forwardRef(() => ElementRef)) private el: ElementRef,
@Inject(INotebookService) private _notebookService: INotebookService,
) {
super();
this.registry = this._notebookService.getMimeRegistry();
}
@Input() set bundleOptions(value: MimeModel.IOptions) {
this._bundleOptions = value;
if (this._initialized) {
this.renderOutput();
}
}
@Input() mimeType: string;
ngOnInit(): void {
this.renderOutput();
this._initialized = true;
}
layout(): void {
// Re-layout the output when layout is requested
this.renderOutput();
}
private renderOutput(): void {
// TODO handle safe/unsafe mapping
this.createRenderedMimetype(this._bundleOptions, this.el.nativeElement);
}
protected createRenderedMimetype(options: MimeModel.IOptions, node: HTMLElement): void {
if (this.mimeType) {
let renderer = this.registry.createRenderer(this.mimeType);
renderer.node = node;
let model = new MimeModel(options);
renderer.renderModel(model).catch(error => {
// Manually append error message to output
renderer.node.innerHTML = `<pre>Javascript Error: ${error.message}</pre>`;
// Remove mime-type-specific CSS classes
renderer.node.className = 'p-Widget jp-RenderedText';
renderer.node.setAttribute(
'data-mime-type',
'application/vnd.jupyter.stderr'
);
});
} else {
node.innerHTML = localize('noRendererFound',
"No {0} renderer could be found for output. It has the following MIME types: {1}",
options.trusted ? '' : localize('safe', "(safe) "),
Object.keys(options.data).join(', '));
}
}
}

View File

@@ -0,0 +1,244 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { IMarkdownString, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
import * as marked from 'vs/base/common/marked/marked';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { revive } from 'vs/base/common/marshalling';
import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
// Based off of HtmlContentRenderer
export class NotebookMarkdownRenderer {
private _notebookURI: URI;
private _baseUrls: string[] = [];
constructor() {
}
render(markdown: IMarkdownString): IMarkdownRenderResult {
const element: HTMLElement = markdown ? this.renderMarkdown(markdown, undefined) : document.createElement('span');
return {
element,
dispose: () => { }
};
}
createElement(options: MarkdownRenderOptions): HTMLElement {
const tagName = options.inline ? 'span' : 'div';
const element = document.createElement(tagName);
if (options.className) {
element.className = options.className;
}
return element;
}
parse(text: string): any {
let data = JSON.parse(text);
data = revive(data, 0);
return data;
}
/**
* Create html nodes for the given content element.
* Adapted from htmlContentRenderer. Ensures that the markdown renderer
* gets passed in the correct baseUrl for the notebook's saved location,
* respects the trusted state of a notebook, and allows command links to
* be clickable.
*/
renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement {
const element = this.createElement(options);
// signal to code-block render that the element has been created
let signalInnerHTML: () => void;
const withInnerHTML = new Promise(c => signalInnerHTML = c);
let notebookFolder = path.dirname(this._notebookURI.fsPath) + '/';
if (!this._baseUrls.some(x => x === notebookFolder)) {
this._baseUrls.push(notebookFolder);
}
const renderer = new marked.Renderer({ baseUrl: notebookFolder });
renderer.image = (href: string, title: string, text: string) => {
href = this.cleanUrl(!markdown.isTrusted, notebookFolder, href);
let dimensions: string[] = [];
if (href) {
const splitted = href.split('|').map(s => s.trim());
href = splitted[0];
const parameters = splitted[1];
if (parameters) {
const heightFromParams = /height=(\d+)/.exec(parameters);
const widthFromParams = /width=(\d+)/.exec(parameters);
const height = heightFromParams ? heightFromParams[1] : '';
const width = widthFromParams ? widthFromParams[1] : '';
const widthIsFinite = isFinite(parseInt(width));
const heightIsFinite = isFinite(parseInt(height));
if (widthIsFinite) {
dimensions.push(`width="${width}"`);
}
if (heightIsFinite) {
dimensions.push(`height="${height}"`);
}
}
}
let attributes: string[] = [];
if (href) {
attributes.push(`src="${href}"`);
}
if (text) {
attributes.push(`alt="${text}"`);
}
if (title) {
attributes.push(`title="${title}"`);
}
if (dimensions.length) {
attributes = attributes.concat(dimensions);
}
return '<img ' + attributes.join(' ') + '>';
};
renderer.link = (href: string, title: string, text: string): string => {
href = this.cleanUrl(!markdown.isTrusted, notebookFolder, href);
if (href === null) {
return text;
}
// Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829
if (href === text) { // raw link case
text = removeMarkdownEscapes(text);
}
title = removeMarkdownEscapes(title);
href = removeMarkdownEscapes(href);
if (
!href
|| !markdown.isTrusted
|| href.match(/^data:|javascript:/i)
|| href.match(/^command:(\/\/\/)?_workbench\.downloadResource/i)
) {
// drop the link
return text;
} else {
// HTML Encode href
href = href.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
return `<a href=${href} data-href="${href}" title="${title || href}">${text}</a>`;
}
};
renderer.paragraph = (text): string => {
return `<p>${text}</p>`;
};
if (options.codeBlockRenderer) {
renderer.code = (code, lang) => {
const value = options.codeBlockRenderer!(lang, code);
// when code-block rendering is async we return sync
// but update the node with the real result later.
const id = defaultGenerator.nextId();
const promise = value.then(strValue => {
withInnerHTML.then(e => {
const span = element.querySelector(`div[data-code="${id}"]`);
if (span) {
span.innerHTML = strValue;
}
}).catch(err => {
// ignore
});
});
if (options.codeBlockRenderCallback) {
promise.then(options.codeBlockRenderCallback);
}
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
};
}
const markedOptions: marked.MarkedOptions = {
sanitize: !markdown.isTrusted,
renderer,
baseUrl: notebookFolder
};
element.innerHTML = marked.parse(markdown.value, markedOptions);
signalInnerHTML!();
return element;
}
// This following methods have been adapted from marked.js
// Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
cleanUrl(sanitize: boolean, base: string, href: string) {
if (sanitize) {
let prot: string;
try {
prot = decodeURIComponent(unescape(href))
.replace(/[^\w:]/g, '')
.toLowerCase();
} catch (e) {
return null;
}
if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
return null;
}
}
try {
// The call to resolveUrl() (where relative hrefs are converted to absolute ones) comes after this point
// Therefore, we only want to return immediately if the path is absolute here
if (URI.parse(href) && path.isAbsolute(href)) {
return href;
}
} catch {
// ignore
}
let originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
if (base && !originIndependentUrl.test(href) && !path.isAbsolute(href)) {
href = this.resolveUrl(base, href);
}
try {
href = encodeURI(href).replace(/%5C/g, '\\').replace(/%25/g, '%');
} catch (e) {
return null;
}
return href;
}
resolveUrl(base: string, href: string) {
if (!this._baseUrls[' ' + base]) {
// we can ignore everything in base after the last slash of its path component,
// but we might need to add _that_
// https://tools.ietf.org/html/rfc3986#section-3
if (/^[^:]+:\/*[^/]*$/.test(base)) {
this._baseUrls[' ' + base] = base + '/';
} else {
// Remove trailing 'c's. /c*$/ is vulnerable to REDOS.
this._baseUrls[' ' + base] = base.replace(/c*$/, '');
}
}
base = this._baseUrls[' ' + base];
if (href.slice(0, 2) === '//') {
return base.replace(/:[\s\S]*/, ':') + href;
} else if (href.charAt(0) === '/') {
return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href;
} else if (href.slice(0, 2) === '..') {
return path.join(base, href);
} else {
return base + href;
}
}
// end marked.js adaptation
setNotebookURI(val: URI) {
this._notebookURI = val;
}
}

View File

@@ -0,0 +1,147 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OnInit, Component, Input, ElementRef, ViewChild } from '@angular/core';
import { localize } from 'vs/nls';
import * as types from 'vs/base/common/types';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { IMimeComponent } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry';
import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces';
import { MimeModel } from 'sql/workbench/contrib/notebook/browser/models/mimemodel';
import { getErrorMessage } from 'vs/base/common/errors';
type ObjectType = object;
interface FigureLayout extends ObjectType {
width?: number;
height?: number;
autosize?: boolean;
}
interface Figure extends ObjectType {
data: object[];
layout: Partial<FigureLayout>;
}
declare class PlotlyHTMLElement extends HTMLDivElement {
data: object;
layout: object;
newPlot: () => void;
redraw: () => void;
}
@Component({
selector: PlotlyOutputComponent.SELECTOR,
template: `<div #output class="plotly-wrapper"></div>
<pre *ngIf="hasError" class="p-Widget jp-RenderedText">{{errorText}}</pre>
`
})
export class PlotlyOutputComponent extends AngularDisposable implements IMimeComponent, OnInit {
public static readonly SELECTOR: string = 'plotly-output';
private static Plotly?: Promise<typeof import('plotly.js-dist')>;
@ViewChild('output', { read: ElementRef }) private output: ElementRef;
private _initialized: boolean = false;
private _rendered: boolean = false;
private _cellModel: ICellModel;
private _bundleOptions: MimeModel.IOptions;
private _plotDiv: PlotlyHTMLElement;
public errorText: string;
constructor() {
super();
}
@Input() set bundleOptions(value: MimeModel.IOptions) {
this._bundleOptions = value;
if (this._initialized) {
this.renderPlotly();
}
}
@Input() mimeType: string;
get cellModel(): ICellModel {
return this._cellModel;
}
@Input() set cellModel(value: ICellModel) {
this._cellModel = value;
if (this._initialized) {
this.renderPlotly();
}
}
ngOnInit() {
if (!PlotlyOutputComponent.Plotly) {
PlotlyOutputComponent.Plotly = import('plotly.js-dist');
}
this._plotDiv = this.output.nativeElement;
this.renderPlotly();
this._initialized = true;
}
renderPlotly(): void {
if (this._rendered) {
// just re-layout
this.layout();
return;
}
if (!this._bundleOptions || !this._cellModel || !this.mimeType) {
return;
}
if (this.mimeType === 'text/vnd.plotly.v1+html') {
// Do nothing - this is our way to ignore the offline init Plotly attempts to do via a <script> tag.
// We have "handled" it by pulling in the plotly library into this component instead
return;
}
this.errorText = undefined;
const figure = this.getFigure(true);
if (figure) {
figure.layout = figure.layout || {};
if (!figure.layout.width && !figure.layout.autosize) {
// Workaround: to avoid filling up the entire cell, use plotly's default
figure.layout.width = Math.min(700, this._plotDiv.clientWidth);
}
PlotlyOutputComponent.Plotly.then(plotly => {
return plotly.newPlot(this._plotDiv, figure.data, figure.layout);
}).catch(e => this.displayError(e));
}
this._rendered = true;
}
getFigure(showError: boolean): Figure {
const figure = <Figure><any>this._bundleOptions.data[this.mimeType];
if (typeof figure === 'string') {
try {
JSON.parse(figure);
} catch (error) {
if (showError) {
this.displayError(error);
}
}
}
const { data = [], layout = {} } = figure;
return { data, layout };
}
private displayError(error: Error | string): void {
this.errorText = localize('plotlyError', "Error displaying Plotly graph: {0}", getErrorMessage(error));
}
layout(): void {
// No need to re-layout for now as Plotly is doing its own resize handling.
}
public hasError(): boolean {
return !types.isUndefinedOrNull(this.errorText);
}
}

View File

@@ -0,0 +1,352 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { IRenderMime } from '../models/renderMimeInterfaces';
import { MimeModel } from '../models/mimemodel';
import { ReadonlyJSONObject } from '../../common/models/jsonext';
import { defaultSanitizer } from './sanitizer';
/**
* An object which manages mime renderer factories.
*
* This object is used to render mime models using registered mime
* renderers, selecting the preferred mime renderer to render the
* model into a widget.
*
* #### Notes
* This class is not intended to be subclassed.
*/
export class RenderMimeRegistry {
/**
* Construct a new rendermime.
*
* @param options - The options for initializing the instance.
*/
constructor(options: RenderMimeRegistry.IOptions = {}) {
// Parse the options.
this.resolver = options.resolver || null;
this.linkHandler = options.linkHandler || null;
this.latexTypesetter = options.latexTypesetter || null;
this.sanitizer = options.sanitizer || defaultSanitizer;
// Add the initial factories.
if (options.initialFactories) {
for (let factory of options.initialFactories) {
this.addFactory(factory);
}
}
}
/**
* The sanitizer used by the rendermime instance.
*/
readonly sanitizer: IRenderMime.ISanitizer;
/**
* The object used to resolve relative urls for the rendermime instance.
*/
readonly resolver: IRenderMime.IResolver | null;
/**
* The object used to handle path opening links.
*/
readonly linkHandler: IRenderMime.ILinkHandler | null;
/**
* The LaTeX typesetter for the rendermime.
*/
readonly latexTypesetter: IRenderMime.ILatexTypesetter | null;
/**
* The ordered list of mimeTypes.
*/
get mimeTypes(): ReadonlyArray<string> {
return this._types || (this._types = Private.sortedTypes(this._ranks));
}
/**
* Find the preferred mime type for a mime bundle.
*
* @param bundle - The bundle of mime data.
*
* @param safe - How to consider safe/unsafe factories. If 'ensure',
* it will only consider safe factories. If 'any', any factory will be
* considered. If 'prefer', unsafe factories will be considered, but
* only after the safe options have been exhausted.
*
* @returns The preferred mime type from the available factories,
* or `undefined` if the mime type cannot be rendered.
*/
preferredMimeType(
bundle: ReadonlyJSONObject,
safe: 'ensure' | 'prefer' | 'any' = 'ensure'
): string | undefined {
// Try to find a safe factory first, if preferred.
if (safe === 'ensure' || safe === 'prefer') {
for (let mt of this.mimeTypes) {
if (mt in bundle && this._factories[mt].safe) {
return mt;
}
}
}
if (safe !== 'ensure') {
// Otherwise, search for the best factory among all factories.
for (let mt of this.mimeTypes) {
if (mt in bundle) {
return mt;
}
}
}
// Otherwise, no matching mime type exists.
return undefined;
}
/**
* Create a renderer for a mime type.
*
* @param mimeType - The mime type of interest.
*
* @returns A new renderer for the given mime type.
*
* @throws An error if no factory exists for the mime type.
*/
createRenderer(mimeType: string): IRenderMime.IRenderer {
// Throw an error if no factory exists for the mime type.
if (!(mimeType in this._factories)) {
throw new Error(`No factory for mime type: '${mimeType}'`);
}
// Invoke the best factory for the given mime type.
return this._factories[mimeType].createRenderer({
mimeType,
resolver: this.resolver,
sanitizer: this.sanitizer,
linkHandler: this.linkHandler,
latexTypesetter: this.latexTypesetter
});
}
/**
* Create a new mime model. This is a convenience method.
*
* @options - The options used to create the model.
*
* @returns A new mime model.
*/
createModel(options: MimeModel.IOptions = {}): MimeModel {
return new MimeModel(options);
}
/**
* Create a clone of this rendermime instance.
*
* @param options - The options for configuring the clone.
*
* @returns A new independent clone of the rendermime.
*/
clone(options: RenderMimeRegistry.ICloneOptions = {}): RenderMimeRegistry {
// Create the clone.
let clone = new RenderMimeRegistry({
resolver: options.resolver || this.resolver || undefined,
sanitizer: options.sanitizer || this.sanitizer || undefined,
linkHandler: options.linkHandler || this.linkHandler || undefined,
latexTypesetter: options.latexTypesetter || this.latexTypesetter
});
// Clone the internal state.
clone._factories = { ...this._factories };
clone._ranks = { ...this._ranks };
clone._id = this._id;
// Return the cloned object.
return clone;
}
/**
* Get the renderer factory registered for a mime type.
*
* @param mimeType - The mime type of interest.
*
* @returns The factory for the mime type, or `undefined`.
*/
getFactory(mimeType: string): IRenderMime.IRendererFactory | undefined {
return this._factories[mimeType];
}
/**
* Add a renderer factory to the rendermime.
*
* @param factory - The renderer factory of interest.
*
* @param rank - The rank of the renderer. A lower rank indicates
* a higher priority for rendering. If not given, the rank will
* defer to the `defaultRank` of the factory. If no `defaultRank`
* is given, it will default to 100.
*
* #### Notes
* The renderer will replace an existing renderer for the given
* mimeType.
*/
addFactory(factory: IRenderMime.IRendererFactory, rank?: number): void {
if (rank === undefined) {
rank = factory.defaultRank;
if (rank === undefined) {
rank = 100;
}
}
for (let mt of factory.mimeTypes) {
this._factories[mt] = factory;
this._ranks[mt] = { rank, id: this._id++ };
}
this._types = null;
}
/**
* Remove a mime type.
*
* @param mimeType - The mime type of interest.
*/
removeMimeType(mimeType: string): void {
delete this._factories[mimeType];
delete this._ranks[mimeType];
this._types = null;
}
/**
* Get the rank for a given mime type.
*
* @param mimeType - The mime type of interest.
*
* @returns The rank of the mime type or undefined.
*/
getRank(mimeType: string): number | undefined {
let rank = this._ranks[mimeType];
return rank && rank.rank;
}
/**
* Set the rank of a given mime type.
*
* @param mimeType - The mime type of interest.
*
* @param rank - The new rank to assign.
*
* #### Notes
* This is a no-op if the mime type is not registered.
*/
setRank(mimeType: string, rank: number): void {
if (!this._ranks[mimeType]) {
return;
}
let id = this._id++;
this._ranks[mimeType] = { rank, id };
this._types = null;
}
private _id = 0;
private _ranks: Private.RankMap = {};
private _types: string[] | null = null;
private _factories: Private.FactoryMap = {};
}
/**
* The namespace for `RenderMimeRegistry` class statics.
*/
export namespace RenderMimeRegistry {
/**
* The options used to initialize a rendermime instance.
*/
export interface IOptions {
/**
* Initial factories to add to the rendermime instance.
*/
initialFactories?: ReadonlyArray<IRenderMime.IRendererFactory>;
/**
* The sanitizer used to sanitize untrusted html inputs.
*
* If not given, a default sanitizer will be used.
*/
sanitizer?: IRenderMime.ISanitizer;
/**
* The initial resolver object.
*
* The default is `null`.
*/
resolver?: IRenderMime.IResolver;
/**
* An optional path handler.
*/
linkHandler?: IRenderMime.ILinkHandler;
/**
* An optional LaTeX typesetter.
*/
latexTypesetter?: IRenderMime.ILatexTypesetter;
}
/**
* The options used to clone a rendermime instance.
*/
export interface ICloneOptions {
/**
* The new sanitizer used to sanitize untrusted html inputs.
*/
sanitizer?: IRenderMime.ISanitizer;
/**
* The new resolver object.
*/
resolver?: IRenderMime.IResolver;
/**
* The new path handler.
*/
linkHandler?: IRenderMime.ILinkHandler;
/**
* The new LaTeX typesetter.
*/
latexTypesetter?: IRenderMime.ILatexTypesetter;
}
}
/**
* The namespace for the module implementation details.
*/
namespace Private {
/**
* A type alias for a mime rank and tie-breaking id.
*/
export type RankPair = { readonly id: number; readonly rank: number };
/**
* A type alias for a mapping of mime type -> rank pair.
*/
export type RankMap = { [key: string]: RankPair };
/**
* A type alias for a mapping of mime type -> ordered factories.
*/
export type FactoryMap = { [key: string]: IRenderMime.IRendererFactory };
/**
* Get the mime types in the map, ordered by rank.
*/
export function sortedTypes(map: RankMap): string[] {
return Object.keys(map).sort((a, b) => {
let p1 = map[a];
let p2 = map[b];
if (p1.rank !== p2.rank) {
return p1.rank - p2.rank;
}
return p1.id - p2.id;
});
}
}

View File

@@ -0,0 +1,655 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { default as AnsiUp } from 'ansi_up';
import { IRenderMime } from '../models/renderMimeInterfaces';
import { URLExt } from '../../common/models/url';
import { URI } from 'vs/base/common/uri';
/**
* Render HTML into a host node.
*
* @params options - The options for rendering.
*
* @returns A promise which resolves when rendering is complete.
*/
export function renderHTML(options: renderHTML.IOptions): Promise<void> {
// Unpack the options.
let {
host,
source,
trusted,
sanitizer,
resolver,
linkHandler,
shouldTypeset,
latexTypesetter
} = options;
let originalSource = source;
// Bail early if the source is empty.
if (!source) {
host.textContent = '';
return Promise.resolve(undefined);
}
// Sanitize the source if it is not trusted. This removes all
// `<script>` tags as well as other potentially harmful HTML.
if (!trusted) {
originalSource = `${source}`;
source = sanitizer.sanitize(source);
}
// Set the inner HTML of the host.
host.innerHTML = source;
if (host.getElementsByTagName('script').length > 0) {
// If output it trusted, eval any script tags contained in the HTML.
// This is not done automatically by the browser when script tags are
// created by setting `innerHTML`.
if (trusted) {
Private.evalInnerHTMLScriptTags(host);
} else {
const container = document.createElement('div');
const warning = document.createElement('pre');
warning.textContent =
'This HTML output contains inline scripts. Are you sure that you want to run arbitrary Javascript within your Notebook session?';
const runButton = document.createElement('button');
runButton.textContent = 'Run';
runButton.onclick = event => {
host.innerHTML = originalSource;
Private.evalInnerHTMLScriptTags(host);
host.removeChild(host.firstChild);
};
container.appendChild(warning);
container.appendChild(runButton);
host.insertBefore(container, host.firstChild);
}
}
// Handle default behavior of nodes.
Private.handleDefaults(host, resolver);
// Patch the urls if a resolver is available.
let promise: Promise<void>;
if (resolver) {
promise = Private.handleUrls(host, resolver, linkHandler);
} else {
promise = Promise.resolve(undefined);
}
// Return the final rendered promise.
return promise.then(() => {
if (shouldTypeset && latexTypesetter) {
latexTypesetter.typeset(host);
}
});
}
/**
* The namespace for the `renderHTML` function statics.
*/
export namespace renderHTML {
/**
* The options for the `renderHTML` function.
*/
export interface IOptions {
/**
* The host node for the rendered HTML.
*/
host: HTMLElement;
/**
* The HTML source to render.
*/
source: string;
/**
* Whether the source is trusted.
*/
trusted: boolean;
/**
* The html sanitizer for untrusted source.
*/
sanitizer: IRenderMime.ISanitizer;
/**
* An optional url resolver.
*/
resolver: IRenderMime.IResolver | null;
/**
* An optional link handler.
*/
linkHandler: IRenderMime.ILinkHandler | null;
/**
* Whether the node should be typeset.
*/
shouldTypeset: boolean;
/**
* The LaTeX typesetter for the application.
*/
latexTypesetter: IRenderMime.ILatexTypesetter | null;
}
}
/**
* Render an image into a host node.
*
* @params options - The options for rendering.
*
* @returns A promise which resolves when rendering is complete.
*/
export function renderImage(
options: renderImage.IRenderOptions
): Promise<void> {
// Unpack the options.
let {
host,
mimeType,
source,
width,
height,
needsBackground,
unconfined
} = options;
// Clear the content in the host.
host.textContent = '';
// Create the image element.
let img = document.createElement('img');
// Set the source of the image.
img.src = `data:${mimeType};base64,${source}`;
// Set the size of the image if provided.
if (typeof height === 'number') {
img.height = height;
}
if (typeof width === 'number') {
img.width = width;
}
if (needsBackground === 'light') {
img.classList.add('jp-needs-light-background');
} else if (needsBackground === 'dark') {
img.classList.add('jp-needs-dark-background');
}
if (unconfined === true) {
img.classList.add('jp-mod-unconfined');
}
// Add the image to the host.
host.appendChild(img);
// Return the rendered promise.
return Promise.resolve(undefined);
}
/**
* The namespace for the `renderImage` function statics.
*/
export namespace renderImage {
/**
* The options for the `renderImage` function.
*/
export interface IRenderOptions {
/**
* The image node to update with the content.
*/
host: HTMLElement;
/**
* The mime type for the image.
*/
mimeType: string;
/**
* The base64 encoded source for the image.
*/
source: string;
/**
* The optional width for the image.
*/
width?: number;
/**
* The optional height for the image.
*/
height?: number;
/**
* Whether an image requires a background for legibility.
*/
needsBackground?: string;
/**
* Whether the image should be unconfined.
*/
unconfined?: boolean;
}
}
/**
* Render LaTeX into a host node.
*
* @params options - The options for rendering.
*
* @returns A promise which resolves when rendering is complete.
*/
export function renderLatex(
options: renderLatex.IRenderOptions
): Promise<void> {
// Unpack the options.
let { host, source, shouldTypeset, latexTypesetter } = options;
// Set the source on the node.
host.textContent = source;
// Typeset the node if needed.
if (shouldTypeset && latexTypesetter) {
latexTypesetter.typeset(host);
}
// Return the rendered promise.
return Promise.resolve(undefined);
}
/**
* The namespace for the `renderLatex` function statics.
*/
export namespace renderLatex {
/**
* The options for the `renderLatex` function.
*/
export interface IRenderOptions {
/**
* The host node for the rendered LaTeX.
*/
host: HTMLElement;
/**
* The LaTeX source to render.
*/
source: string;
/**
* Whether the node should be typeset.
*/
shouldTypeset: boolean;
/**
* The LaTeX typesetter for the application.
*/
latexTypesetter: IRenderMime.ILatexTypesetter | null;
}
}
/**
* The namespace for the `renderDataResource` function statics.
*/
export namespace renderDataResource {
/**
* The options for the `renderDataResource` function.
*/
export interface IRenderOptions {
/**
* The host node for the rendered LaTeX.
*/
host: HTMLElement;
/**
* The DataResource source to render.
*/
source: string;
}
}
/**
* Render SVG into a host node.
*
* @params options - The options for rendering.
*
* @returns A promise which resolves when rendering is complete.
*/
export function renderSVG(options: renderSVG.IRenderOptions): Promise<void> {
// Unpack the options.
let { host, source, trusted, unconfined } = options;
// Clear the content if there is no source.
if (!source) {
host.textContent = '';
return Promise.resolve(undefined);
}
// Display a message if the source is not trusted.
if (!trusted) {
host.textContent =
'Cannot display an untrusted SVG. Maybe you need to run the cell?';
return Promise.resolve(undefined);
}
// Render in img so that user can save it easily
const img = new Image();
img.src = `data:image/svg+xml,${encodeURIComponent(source)}`;
host.appendChild(img);
if (unconfined === true) {
host.classList.add('jp-mod-unconfined');
}
return Promise.resolve();
}
/**
* The namespace for the `renderSVG` function statics.
*/
export namespace renderSVG {
/**
* The options for the `renderSVG` function.
*/
export interface IRenderOptions {
/**
* The host node for the rendered SVG.
*/
host: HTMLElement;
/**
* The SVG source.
*/
source: string;
/**
* Whether the source is trusted.
*/
trusted: boolean;
/**
* Whether the svg should be unconfined.
*/
unconfined?: boolean;
}
}
/**
* Render text into a host node.
*
* @params options - The options for rendering.
*
* @returns A promise which resolves when rendering is complete.
*/
export function renderText(options: renderText.IRenderOptions): Promise<void> {
// Unpack the options.
let { host, source } = options;
const ansiUp = new AnsiUp();
ansiUp.escape_for_html = true;
ansiUp.use_classes = true;
// Create the HTML content.
let content = ansiUp.ansi_to_html(source);
// Set the inner HTML for the host node.
host.innerHTML = `<pre>${content}</pre>`;
// Return the rendered promise.
return Promise.resolve(undefined);
}
/**
* The namespace for the `renderText` function statics.
*/
export namespace renderText {
/**
* The options for the `renderText` function.
*/
export interface IRenderOptions {
/**
* The host node for the text content.
*/
host: HTMLElement;
/**
* The source text to render.
*/
source: string;
}
}
/**
* The namespace for module implementation details.
*/
namespace Private {
/**
* Eval the script tags contained in a host populated by `innerHTML`.
*
* When script tags are created via `innerHTML`, the browser does not
* evaluate them when they are added to the page. This function works
* around that by creating new equivalent script nodes manually, and
* replacing the originals.
*/
export function evalInnerHTMLScriptTags(host: HTMLElement): void {
// Create a snapshot of the current script nodes.
let scripts = host.getElementsByTagName('script');
// Loop over each script node.
for (let i = 0; i < scripts.length; i++) {
let script = scripts.item(i);
// Skip any scripts which no longer have a parent.
if (!script.parentNode) {
continue;
}
// Create a new script node which will be clone.
let clone = document.createElement('script');
// Copy the attributes into the clone.
let attrs = script.attributes;
for (let i = 0, n = attrs.length; i < n; ++i) {
let { name, value } = attrs[i];
clone.setAttribute(name, value);
}
// Copy the text content into the clone.
clone.textContent = script.textContent;
// Replace the old script in the parent.
script.parentNode.replaceChild(clone, script);
}
}
/**
* Handle the default behavior of nodes.
*/
export function handleDefaults(
node: HTMLElement,
resolver?: IRenderMime.IResolver
): void {
// Handle anchor elements.
let anchors = node.getElementsByTagName('a');
for (let i = 0; i < anchors.length; i++) {
let path = anchors[i].href || '';
let isLocal = isPathLocal(path, resolver);
if (isLocal) {
anchors[i].target = '_self';
} else {
anchors[i].target = '_blank';
}
}
// Handle image elements.
let imgs = node.getElementsByTagName('img');
for (let i = 0; i < imgs.length; i++) {
if (!imgs[i].alt) {
imgs[i].alt = 'Image';
}
}
}
/**
* Resolve the relative urls in element `src` and `href` attributes.
*
* @param node - The head html element.
*
* @param resolver - A url resolver.
*
* @param linkHandler - An optional link handler for nodes.
*
* @returns a promise fulfilled when the relative urls have been resolved.
*/
export function handleUrls(
node: HTMLElement,
resolver: IRenderMime.IResolver,
linkHandler: IRenderMime.ILinkHandler | null
): Promise<void> {
// Set up an array to collect promises.
let promises: Promise<void>[] = [];
// Handle HTML Elements with src attributes.
let nodes = node.querySelectorAll('*[src]');
for (let i = 0; i < nodes.length; i++) {
promises.push(handleAttr(nodes[i] as HTMLElement, 'src', resolver));
}
// Handle anchor elements.
let anchors = node.getElementsByTagName('a');
for (let i = 0; i < anchors.length; i++) {
promises.push(handleAnchor(anchors[i], resolver, linkHandler));
}
// Handle link elements.
let links = node.getElementsByTagName('link');
for (let i = 0; i < links.length; i++) {
promises.push(handleAttr(links[i], 'href', resolver));
}
// Wait on all promises.
return Promise.all(promises).then(() => undefined);
}
/**
* Apply ids to headers.
*/
export function headerAnchors(node: HTMLElement): void {
let headerNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
for (let headerType of headerNames) {
let headers = node.getElementsByTagName(headerType);
for (let i = 0; i < headers.length; i++) {
let header = headers[i];
header.id = encodeURIComponent(header.innerHTML.replace(/ /g, '-'));
let anchor = document.createElement('a');
anchor.target = '_self';
anchor.textContent = '¶';
anchor.href = '#' + header.id;
anchor.classList.add('jp-InternalAnchorLink');
header.appendChild(anchor);
}
}
}
/**
* Handle a node with a `src` or `href` attribute.
*/
function handleAttr(
node: HTMLElement,
name: 'src' | 'href',
resolver: IRenderMime.IResolver
): Promise<void> {
let source = node.getAttribute(name) || '';
let isLocal = isPathLocal(source, resolver);
if (!source || !isLocal) {
return Promise.resolve(undefined);
}
node.setAttribute(name, '');
return resolver
.resolveUrl(source)
.then(path => {
return resolver.getDownloadUrl(path);
})
.then(url => {
// Check protocol again in case it changed:
if (URI.parse(url).scheme !== 'data:') {
// Bust caching for local src attrs.
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
url += (/\?/.test(url) ? '&' : '?') + new Date().getTime();
}
node.setAttribute(name, url);
})
.catch(err => {
// If there was an error getting the url,
// just make it an empty link.
node.setAttribute(name, '');
});
}
/**
* Handle an anchor node.
*/
function handleAnchor(
anchor: HTMLAnchorElement,
resolver: IRenderMime.IResolver,
linkHandler: IRenderMime.ILinkHandler | null
): Promise<void> {
// Get the link path without the location prepended.
// (e.g. "./foo.md#Header 1" vs "http://localhost:8888/foo.md#Header 1")
let href = anchor.getAttribute('href') || '';
let isLocal = isPathLocal(href, resolver);
// Bail if it is not a file-like url.
if (!href || !isLocal) {
return Promise.resolve(undefined);
}
// Remove the hash until we can handle it.
let hash = anchor.hash;
if (hash) {
// Handle internal link in the file.
if (hash === href) {
anchor.target = '_self';
return Promise.resolve(undefined);
}
// For external links, remove the hash until we have hash handling.
href = href.replace(hash, '');
}
// Get the appropriate file path.
return resolver
.resolveUrl(href)
.then(path => {
// Handle the click override.
if (linkHandler) {
linkHandler.handleLink(anchor, path, hash);
}
// Get the appropriate file download path.
return resolver.getDownloadUrl(path);
})
.then(url => {
// Set the visible anchor.
anchor.href = url + hash;
})
.catch(err => {
// If there was an error getting the url,
// just make it an empty link.
anchor.href = '';
});
}
function isPathLocal(path: string, resolver: IRenderMime.IResolver): boolean {
let isLocal: boolean;
if (path && path.length > 0) {
isLocal = resolver && resolver.isLocal
? resolver.isLocal(path)
: URLExt.isLocal(path);
} else {
// Empty string, so default to local
isLocal = true;
}
return isLocal;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,139 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { Table } from 'sql/base/browser/ui/table/table';
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { escape } from 'sql/base/common/strings';
import { IDataResource } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/contrib/query/common/resultsGridContribution';
import { values } from 'vs/base/common/collections';
/**
* Render DataResource as a grid into a host node.
*
* @params options - The options for rendering.
*
* @returns A promise which resolves when rendering is complete.
*/
export function renderDataResource(
options: renderDataResource.IRenderOptions
): Promise<void> {
// Unpack the options.
let { host, source } = options;
let sourceObject: IDataResource = JSON.parse(source);
// Before doing anything, avoid re-rendering the table multiple
// times (as can be the case when going untrusted -> trusted)
while (host.firstChild) {
host.removeChild(host.firstChild);
}
// Now create the table container
let tableContainer = document.createElement('div');
tableContainer.className = 'notebook-cellTable';
const BOTTOM_PADDING_AND_SCROLLBAR = 14;
let tableResultsData = new TableDataView();
let columns: string[] = sourceObject.schema.fields.map(val => val.name);
// Table object requires passed in columns to be of datatype Slick.Column
let columnsTransformed = transformColumns(columns);
// In order to show row numbers, we need to put the row number column
// ahead of all of the other columns, and register the plugin below
let rowNumberColumn = new RowNumberColumn({ numberOfRows: source.length });
columnsTransformed.unshift(rowNumberColumn.getColumnDefinition());
let transformedData = transformData(sourceObject.data, columnsTransformed);
tableResultsData.push(transformedData);
let detailTable = new Table(tableContainer, {
dataProvider: tableResultsData, columns: columnsTransformed
}, {
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
forceFitColumns: false,
defaultColumnWidth: 120
});
detailTable.registerPlugin(rowNumberColumn);
detailTable.registerPlugin(new MouseWheelSupport());
detailTable.registerPlugin(new AutoColumnSize({ autoSizeOnRender: true }));
detailTable.registerPlugin(new AdditionalKeyBindings());
let numRows = detailTable.grid.getDataLength();
// Need to include column headers and scrollbar, so that's why 1 needs to be added
let rowsHeight = (numRows + 1) * RESULTS_GRID_DEFAULTS.rowHeight + BOTTOM_PADDING_AND_SCROLLBAR + numRows;
// if no rows are in the grid, set height to 100% of the container's height
if (numRows === 0) {
tableContainer.style.height = '100%';
} else {
// Set the height dynamically if the grid's height is < 500px high; otherwise, set height to 500px
tableContainer.style.height = rowsHeight >= 500 ? '500px' : rowsHeight.toString() + 'px';
}
attachTableStyler(detailTable, options.themeService);
host.appendChild(tableContainer);
detailTable.resizeCanvas();
// Return the rendered promise.
return Promise.resolve(undefined);
}
// SlickGrid requires columns and data to be in a very specific format; this code was adapted from tableInsight.component.ts
export function transformData(rows: any[], columns: Slick.Column<any>[]): { [key: string]: string }[] {
return rows.map(row => {
let dataWithSchema = {};
Object.keys(row).forEach((val, index) => {
let displayValue = String(values(row)[index]);
// Since the columns[0] represents the row number, start at 1
dataWithSchema[columns[index + 1].field] = {
displayValue: displayValue,
ariaLabel: escape(displayValue),
isNull: false
};
});
return dataWithSchema;
});
}
export function transformColumns(columns: string[]): Slick.Column<any>[] {
return columns.map((col, index) => {
return <Slick.Column<any>>{
name: col,
id: col,
field: index.toString(),
formatter: textFormatter
};
});
}
/**
* The namespace for the `renderDataResource` function statics.
*/
export namespace renderDataResource {
/**
* The options for the `renderDataResource` function.
*/
export interface IRenderOptions {
/**
* The host node for the rendered LaTeX.
*/
host: HTMLElement;
/**
* The DataResource source to render.
*/
source: string;
/**
* Theme service used to react to theme change events
*/
themeService?: IThemeService;
}
}

View File

@@ -0,0 +1,407 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as renderers from './renderers';
import { IRenderMime } from '../models/renderMimeInterfaces';
import { ReadonlyJSONObject } from '../../common/models/jsonext';
import * as tableRenderers from 'sql/workbench/contrib/notebook/browser/outputs/tableRenderers';
import { Deferred } from 'sql/base/common/promise';
/**
* A common base class for mime renderers.
*/
export abstract class RenderedCommon implements IRenderMime.IRenderer {
private _node: HTMLElement;
private cachedClasses: string[] = [];
/**
* Construct a new rendered common widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
this.mimeType = options.mimeType;
this.sanitizer = options.sanitizer;
this.resolver = options.resolver;
this.linkHandler = options.linkHandler;
this.latexTypesetter = options.latexTypesetter;
}
public get node(): HTMLElement {
return this._node;
}
public set node(value: HTMLElement) {
this._node = value;
value.dataset['mimeType'] = this.mimeType;
this._node.classList.add(...this.cachedClasses);
this.cachedClasses = [];
}
toggleClass(className: string, enabled: boolean): void {
if (enabled) {
this.addClass(className);
} else {
this.removeClass(className);
}
}
addClass(className: string): void {
if (!this._node) {
this.cachedClasses.push(className);
} else if (!this._node.classList.contains(className)) {
this._node.classList.add(className);
}
}
removeClass(className: string): void {
if (!this._node) {
this.cachedClasses = this.cachedClasses.filter(c => c !== className);
} else if (this._node.classList.contains(className)) {
this._node.classList.remove(className);
}
}
/**
* The mimetype being rendered.
*/
readonly mimeType: string;
/**
* The sanitizer used to sanitize untrusted html inputs.
*/
readonly sanitizer: IRenderMime.ISanitizer;
/**
* The resolver object.
*/
readonly resolver: IRenderMime.IResolver | null;
/**
* The link handler.
*/
readonly linkHandler: IRenderMime.ILinkHandler | null;
/**
* The latexTypesetter.
*/
readonly latexTypesetter: IRenderMime.ILatexTypesetter;
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
renderModel(model: IRenderMime.IMimeModel): Promise<void> {
// TODO compare model against old model for early bail?
// Toggle the trusted class on the widget.
this.toggleClass('jp-mod-trusted', model.trusted);
// Render the actual content.
return this.render(model);
}
/**
* Render the mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
abstract render(model: IRenderMime.IMimeModel): Promise<void>;
}
/**
* A common base class for HTML mime renderers.
*/
export abstract class RenderedHTMLCommon extends RenderedCommon {
/**
* Construct a new rendered HTML common widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedHTMLCommon');
}
}
/**
* A mime renderer for displaying HTML and math.
*/
export class RenderedHTML extends RenderedHTMLCommon {
/**
* Construct a new rendered HTML widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedHTML');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
return renderers.renderHTML({
host: this.node,
source: String(model.data[this.mimeType]),
trusted: model.trusted,
resolver: this.resolver,
sanitizer: this.sanitizer,
linkHandler: this.linkHandler,
shouldTypeset: true, //this.isAttached,
latexTypesetter: this.latexTypesetter
});
}
// /**
// * A message handler invoked on an `'after-attach'` message.
// */
// onAfterAttach(msg: Message): void {
// if (this.latexTypesetter) {
// this.latexTypesetter.typeset(this.node);
// }
// }
}
// /**
// * A mime renderer for displaying LaTeX output.
// */
// export class RenderedLatex extends RenderedCommon {
// /**
// * Construct a new rendered LaTeX widget.
// *
// * @param options - The options for initializing the widget.
// */
// constructor(options: IRenderMime.IRendererOptions) {
// super(options);
// this.addClass('jp-RenderedLatex');
// }
// /**
// * Render a mime model.
// *
// * @param model - The mime model to render.
// *
// * @returns A promise which resolves when rendering is complete.
// */
// render(model: IRenderMime.IMimeModel): Promise<void> {
// return renderers.renderLatex({
// host: this.node,
// source: String(model.data[this.mimeType]),
// shouldTypeset: this.isAttached,
// latexTypesetter: this.latexTypesetter
// });
// }
// /**
// * A message handler invoked on an `'after-attach'` message.
// */
// onAfterAttach(msg: Message): void {
// if (this.latexTypesetter) {
// this.latexTypesetter.typeset(this.node);
// }
// }
// }
/**
* A mime renderer for displaying images.
*/
export class RenderedImage extends RenderedCommon {
/**
* Construct a new rendered image widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedImage');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
let metadata = model.metadata[this.mimeType] as ReadonlyJSONObject;
return renderers.renderImage({
host: this.node,
mimeType: this.mimeType,
source: String(model.data[this.mimeType]),
width: metadata && (metadata.width as number | undefined),
height: metadata && (metadata.height as number | undefined),
needsBackground: model.metadata['needs_background'] as string | undefined,
unconfined: metadata && (metadata.unconfined as boolean | undefined)
});
}
}
/**
* A widget for displaying SVG content.
*/
export class RenderedSVG extends RenderedCommon {
/**
* Construct a new rendered SVG widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedSVG');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
let metadata = model.metadata[this.mimeType] as ReadonlyJSONObject;
return renderers.renderSVG({
host: this.node,
source: String(model.data[this.mimeType]),
trusted: model.trusted,
unconfined: metadata && (metadata.unconfined as boolean | undefined)
});
}
// /**
// * A message handler invoked on an `'after-attach'` message.
// */
// onAfterAttach(msg: Message): void {
// if (this.latexTypesetter) {
// this.latexTypesetter.typeset(this.node);
// }
// }
}
/**
* A widget for displaying plain text and console text.
*/
export class RenderedText extends RenderedCommon {
/**
* Construct a new rendered text widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedText');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
return renderers.renderText({
host: this.node,
source: String(model.data[this.mimeType])
});
}
}
/**
* A widget for displaying deprecated JavaScript output.
*/
export class RenderedJavaScript extends RenderedCommon {
/**
* Construct a new rendered text widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedJavaScript');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
return renderers.renderText({
host: this.node,
source: 'JavaScript output is disabled in Notebooks'
});
}
}
/**
* A widget for displaying Data Resource schemas and data.
*/
export class RenderedDataResource extends RenderedCommon {
/**
* Construct a new rendered data resource widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedDataResource');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
return tableRenderers.renderDataResource({
host: this.node,
source: JSON.stringify(model.data[this.mimeType]),
themeService: model.themeService
});
}
}
/**
* A dummy widget for (not) displaying ipywidgets.
*/
export class RenderedIPyWidget extends RenderedCommon {
/**
* Construct a new rendered widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options: IRenderMime.IRendererOptions) {
super(options);
this.addClass('jp-RenderedIPyWidget');
}
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*/
render(model: IRenderMime.IMimeModel): Promise<void> {
let deferred = new Deferred<void>();
deferred.resolve();
return deferred.promise;
}
}