mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-29 09:35:38 -05:00
Update compile pipeline and fix eslint (#16129)
* Update pipelines * eslint * Fix layering * update tsec exemption
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|----------------------------------------------------------------------------*/
|
||||
|
||||
import * as widgets from 'sql/workbench/contrib/notebook/browser/outputs/widgets';
|
||||
import * as widgets from 'sql/workbench/services/notebook/browser/outputs/widgets';
|
||||
import { ImageMimeTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||
import { IRenderMime } from './renderMimeInterfaces';
|
||||
|
||||
|
||||
656
src/sql/workbench/services/notebook/browser/outputs/renderers.ts
Normal file
656
src/sql/workbench/services/notebook/browser/outputs/renderers.ts
Normal file
@@ -0,0 +1,656 @@
|
||||
/*-----------------------------------------------------------------------------
|
||||
| Copyright (c) Jupyter Development Team.
|
||||
| Distributed under the terms of the Modified BSD License.
|
||||
|----------------------------------------------------------------------------*/
|
||||
|
||||
import { default as AnsiUp } from 'ansi_up';
|
||||
import { IRenderMime } from 'sql/workbench/services/notebook/browser/outputs/renderMimeInterfaces';
|
||||
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 in the host.
|
||||
host.textContent = '';
|
||||
|
||||
// Clear the content if there is no source.
|
||||
if (!source) {
|
||||
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)
|
||||
: !!URI.parse(path).scheme;
|
||||
} else {
|
||||
// Empty string, so default to local
|
||||
isLocal = true;
|
||||
}
|
||||
return isLocal;
|
||||
}
|
||||
}
|
||||
@@ -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/common/constants';
|
||||
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;
|
||||
}
|
||||
}
|
||||
407
src/sql/workbench/services/notebook/browser/outputs/widgets.ts
Normal file
407
src/sql/workbench/services/notebook/browser/outputs/widgets.ts
Normal 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 { Deferred } from 'sql/base/common/promise';
|
||||
import { ReadonlyJSONObject } from 'sql/workbench/services/notebook/common/jsonext';
|
||||
import * as tableRenderers from 'sql/workbench/services/notebook/browser/outputs/tableRenderers';
|
||||
import type { IRenderMime } from 'sql/workbench/services/notebook/browser/outputs/renderMimeInterfaces';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user