@plutojl/rainbow
Version:
TypeScript/JavaScript API for programmatically interacting with Pluto notebooks
1,632 lines (1,458 loc) • 227 kB
JavaScript
import { createContext, useState, useEffect, useMemo, createElement, useContext } from 'react';
var EOL = {},
EOF = {},
QUOTE = 34,
NEWLINE = 10,
RETURN = 13;
function objectConverter(columns) {
return new Function("d", "return {" + columns.map(function(name, i) {
return JSON.stringify(name) + ": d[" + i + "] || \"\"";
}).join(",") + "}");
}
function customConverter(columns, f) {
var object = objectConverter(columns);
return function(row, i) {
return f(object(row), i, columns);
};
}
// Compute unique columns in order of discovery.
function inferColumns(rows) {
var columnSet = Object.create(null),
columns = [];
rows.forEach(function(row) {
for (var column in row) {
if (!(column in columnSet)) {
columns.push(columnSet[column] = column);
}
}
});
return columns;
}
function pad(value, width) {
var s = value + "", length = s.length;
return length < width ? new Array(width - length + 1).join(0) + s : s;
}
function formatYear(year) {
return year < 0 ? "-" + pad(-year, 6)
: year > 9999 ? "+" + pad(year, 6)
: pad(year, 4);
}
function formatDate(date) {
var hours = date.getUTCHours(),
minutes = date.getUTCMinutes(),
seconds = date.getUTCSeconds(),
milliseconds = date.getUTCMilliseconds();
return isNaN(date) ? "Invalid Date"
: formatYear(date.getUTCFullYear()) + "-" + pad(date.getUTCMonth() + 1, 2) + "-" + pad(date.getUTCDate(), 2)
+ (milliseconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(milliseconds, 3) + "Z"
: seconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "Z"
: minutes || hours ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + "Z"
: "");
}
function dsv$1(delimiter) {
var reFormat = new RegExp("[\"" + delimiter + "\n\r]"),
DELIMITER = delimiter.charCodeAt(0);
function parse(text, f) {
var convert, columns, rows = parseRows(text, function(row, i) {
if (convert) return convert(row, i - 1);
columns = row, convert = f ? customConverter(row, f) : objectConverter(row);
});
rows.columns = columns || [];
return rows;
}
function parseRows(text, f) {
var rows = [], // output rows
N = text.length,
I = 0, // current character index
n = 0, // current line number
t, // current token
eof = N <= 0, // current token followed by EOF?
eol = false; // current token followed by EOL?
// Strip the trailing newline.
if (text.charCodeAt(N - 1) === NEWLINE) --N;
if (text.charCodeAt(N - 1) === RETURN) --N;
function token() {
if (eof) return EOF;
if (eol) return eol = false, EOL;
// Unescape quotes.
var i, j = I, c;
if (text.charCodeAt(j) === QUOTE) {
while (I++ < N && text.charCodeAt(I) !== QUOTE || text.charCodeAt(++I) === QUOTE);
if ((i = I) >= N) eof = true;
else if ((c = text.charCodeAt(I++)) === NEWLINE) eol = true;
else if (c === RETURN) { eol = true; if (text.charCodeAt(I) === NEWLINE) ++I; }
return text.slice(j + 1, i - 1).replace(/""/g, "\"");
}
// Find next delimiter or newline.
while (I < N) {
if ((c = text.charCodeAt(i = I++)) === NEWLINE) eol = true;
else if (c === RETURN) { eol = true; if (text.charCodeAt(I) === NEWLINE) ++I; }
else if (c !== DELIMITER) continue;
return text.slice(j, i);
}
// Return last token before EOF.
return eof = true, text.slice(j, N);
}
while ((t = token()) !== EOF) {
var row = [];
while (t !== EOL && t !== EOF) row.push(t), t = token();
if (f && (row = f(row, n++)) == null) continue;
rows.push(row);
}
return rows;
}
function preformatBody(rows, columns) {
return rows.map(function(row) {
return columns.map(function(column) {
return formatValue(row[column]);
}).join(delimiter);
});
}
function format(rows, columns) {
if (columns == null) columns = inferColumns(rows);
return [columns.map(formatValue).join(delimiter)].concat(preformatBody(rows, columns)).join("\n");
}
function formatBody(rows, columns) {
if (columns == null) columns = inferColumns(rows);
return preformatBody(rows, columns).join("\n");
}
function formatRows(rows) {
return rows.map(formatRow).join("\n");
}
function formatRow(row) {
return row.map(formatValue).join(delimiter);
}
function formatValue(value) {
return value == null ? ""
: value instanceof Date ? formatDate(value)
: reFormat.test(value += "") ? "\"" + value.replace(/"/g, "\"\"") + "\""
: value;
}
return {
parse: parse,
parseRows: parseRows,
format: format,
formatBody: formatBody,
formatRows: formatRows,
formatRow: formatRow,
formatValue: formatValue
};
}
var csv = dsv$1(",");
var csvParse = csv.parse;
var csvParseRows = csv.parseRows;
var tsv = dsv$1("\t");
var tsvParse = tsv.parse;
var tsvParseRows = tsv.parseRows;
function autoType(object) {
for (var key in object) {
var value = object[key].trim(), number, m;
if (!value) value = null;
else if (value === "true") value = true;
else if (value === "false") value = false;
else if (value === "NaN") value = NaN;
else if (!isNaN(number = +value)) value = number;
else if (m = value.match(/^([-+]\d{2})?\d{4}(-\d{2}(-\d{2})?)?(T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/)) {
if (fixtz && !!m[4] && !m[7]) value = value.replace(/-/g, "/").replace(/T/, " ");
value = new Date(value);
}
else continue;
object[key] = value;
}
return object;
}
// https://github.com/d3/d3-dsv/issues/45
const fixtz = new Date("2019-01-01T00:00").getHours() || new Date("2019-07-01T00:00").getHours();
function dependency(name, version, main) {
return {
resolve(path = main) {
return `${name}@${version}/${path}`;
}
};
}
const d3 = dependency("d3", "7.6.1", "dist/d3.min.js");
const inputs = dependency("@observablehq/inputs", "0.10.4", "dist/inputs.min.js");
const plot = dependency("@observablehq/plot", "0.6.0", "dist/plot.umd.min.js");
const graphviz = dependency("@observablehq/graphviz", "0.2.1", "dist/graphviz.min.js");
const highlight = dependency("@observablehq/highlight.js", "2.0.0", "highlight.min.js");
const katex = dependency("@observablehq/katex", "0.11.1", "dist/katex.min.js");
const lodash = dependency("lodash", "4.17.21", "lodash.min.js");
const htl = dependency("htl", "0.3.1", "dist/htl.min.js");
const jszip = dependency("jszip", "3.10.0", "dist/jszip.min.js");
const marked = dependency("marked", "0.3.12", "marked.min.js");
const sql = dependency("sql.js", "1.7.0", "dist/sql-wasm.js");
const vega = dependency("vega", "5.22.1", "build/vega.min.js");
const vegalite = dependency("vega-lite", "5.5.0", "build/vega-lite.min.js");
const vegaliteApi = dependency("vega-lite-api", "5.0.0", "build/vega-lite-api.min.js");
const arrow = dependency("apache-arrow", "4.0.1", "Arrow.es2015.min.js");
const arquero = dependency("arquero", "4.8.8", "dist/arquero.min.js");
const topojson = dependency("topojson-client", "3.1.0", "dist/topojson-client.min.js");
const exceljs = dependency("exceljs", "4.3.0", "dist/exceljs.min.js");
const mermaid$1 = dependency("mermaid", "9.1.6", "dist/mermaid.min.js");
const leaflet$1 = dependency("leaflet", "1.8.0", "dist/leaflet.js");
const metas = new Map;
const queue$1 = [];
const map$1 = queue$1.map;
const some = queue$1.some;
const hasOwnProperty = queue$1.hasOwnProperty;
const identifierRe = /^((?:@[^/@]+\/)?[^/@]+)(?:@([^/]+))?(?:\/(.*))?$/;
const versionRe = /^\d+\.\d+\.\d+(-[\w-.+]+)?$/;
const extensionRe = /(?:\.[^/]*|\/)$/;
class RequireError extends Error {
constructor(message) {
super(message);
}
}
RequireError.prototype.name = RequireError.name;
function parseIdentifier(identifier) {
const match = identifierRe.exec(identifier);
return match && {
name: match[1],
version: match[2],
path: match[3]
};
}
function resolveFrom(origin = "https://cdn.jsdelivr.net/npm/", mains = ["unpkg", "jsdelivr", "browser", "main"]) {
if (!/\/$/.test(origin)) throw new Error("origin lacks trailing slash");
function main(meta) {
for (const key of mains) {
let value = meta[key];
if (typeof value === "string") {
if (value.startsWith("./")) value = value.slice(2);
return extensionRe.test(value) ? value : `${value}.js`;
}
}
}
function resolveMeta(target) {
const url = `${origin}${target.name}${target.version ? `@${target.version}` : ""}/package.json`;
let meta = metas.get(url);
if (!meta) metas.set(url, meta = fetch(url).then(response => {
if (!response.ok) throw new RequireError("unable to load package.json");
if (response.redirected && !metas.has(response.url)) metas.set(response.url, meta);
return response.json();
}));
return meta;
}
return async function resolve(name, base) {
if (name.startsWith(origin)) name = name.substring(origin.length);
if (/^(\w+:)|\/\//i.test(name)) return name;
if (/^[.]{0,2}\//i.test(name)) return new URL(name, base == null ? location : base).href;
if (!name.length || /^[\s._]/.test(name) || /\s$/.test(name)) throw new RequireError("illegal name");
const target = parseIdentifier(name);
if (!target) return `${origin}${name}`;
if (!target.version && base != null && base.startsWith(origin)) {
const meta = await resolveMeta(parseIdentifier(base.substring(origin.length)));
target.version = meta.dependencies && meta.dependencies[target.name] || meta.peerDependencies && meta.peerDependencies[target.name];
}
if (target.path && !extensionRe.test(target.path)) target.path += ".js";
if (target.path && target.version && versionRe.test(target.version)) return `${origin}${target.name}@${target.version}/${target.path}`;
const meta = await resolveMeta(target);
return `${origin}${meta.name}@${meta.version}/${target.path || main(meta) || "index.js"}`;
};
}
var require$1 = requireFrom(resolveFrom());
function requireFrom(resolver) {
const cache = new Map;
const requireBase = requireRelative(null);
function requireAbsolute(url) {
if (typeof url !== "string") return url;
let module = cache.get(url);
if (!module) cache.set(url, module = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.onload = () => {
try { resolve(queue$1.pop()(requireRelative(url))); }
catch (error) { reject(new RequireError("invalid module")); }
script.remove();
};
script.onerror = () => {
reject(new RequireError("unable to load module"));
script.remove();
};
script.async = true;
script.src = url;
window.define = define;
document.head.appendChild(script);
}));
return module;
}
function requireRelative(base) {
return name => Promise.resolve(resolver(name, base)).then(requireAbsolute);
}
function requireAlias(aliases) {
return requireFrom((name, base) => {
if (name in aliases) {
name = aliases[name], base = null;
if (typeof name !== "string") return name;
}
return resolver(name, base);
});
}
function require(name) {
return arguments.length > 1
? Promise.all(map$1.call(arguments, requireBase)).then(merge)
: requireBase(name);
}
require.alias = requireAlias;
require.resolve = resolver;
return require;
}
function merge(modules) {
const o = {};
for (const m of modules) {
for (const k in m) {
if (hasOwnProperty.call(m, k)) {
if (m[k] == null) Object.defineProperty(o, k, {get: getter(m, k)});
else o[k] = m[k];
}
}
}
return o;
}
function getter(object, name) {
return () => object[name];
}
function isbuiltin(name) {
name = name + "";
return name === "exports" || name === "module";
}
function define(name, dependencies, factory) {
const n = arguments.length;
if (n < 2) factory = name, dependencies = [];
else if (n < 3) factory = dependencies, dependencies = typeof name === "string" ? [] : name;
queue$1.push(some.call(dependencies, isbuiltin) ? require => {
const exports = {};
const module = {exports};
return Promise.all(map$1.call(dependencies, name => {
name = name + "";
return name === "exports" ? exports : name === "module" ? module : require(name);
})).then(dependencies => {
factory.apply(null, dependencies);
return module.exports;
});
} : require => {
return Promise.all(map$1.call(dependencies, require)).then(dependencies => {
return typeof factory === "function" ? factory.apply(null, dependencies) : factory;
});
});
}
define.amd = {};
let requireDefault = require$1;
function setDefaultRequire(require) {
requireDefault = require;
}
function requirer(resolve) {
return resolve == null ? requireDefault : requireFrom(resolve);
}
async function sqlite(require) {
const [init, dist] = await Promise.all([require(sql.resolve()), require.resolve(sql.resolve("dist/"))]);
return init({locateFile: file => `${dist}${file}`});
}
class SQLiteDatabaseClient {
constructor(db) {
Object.defineProperties(this, {
_db: {value: db}
});
}
static async open(source) {
const [SQL, buffer] = await Promise.all([sqlite(requireDefault), Promise.resolve(source).then(load)]);
return new SQLiteDatabaseClient(new SQL.Database(buffer));
}
async query(query, params) {
return await exec(this._db, query, params);
}
async queryRow(query, params) {
return (await this.query(query, params))[0] || null;
}
async explain(query, params) {
const rows = await this.query(`EXPLAIN QUERY PLAN ${query}`, params);
return element$1("pre", {className: "observablehq--inspect"}, [
text$2(rows.map(row => row.detail).join("\n"))
]);
}
async describeTables({schema} = {}) {
return this.query(`SELECT NULLIF(schema, 'main') AS schema, name FROM pragma_table_list() WHERE type = 'table'${schema == null ? "" : ` AND schema = ?`} AND name NOT LIKE 'sqlite_%'`, schema == null ? [] : [schema]);
}
async describeColumns({schema, table} = {}) {
if (table == null) throw new Error(`missing table`);
const rows = await this.query(`SELECT name, type, "notnull" FROM pragma_table_info(?${schema == null ? "" : `, ?`}) ORDER BY cid`, schema == null ? [table] : [table, schema]);
if (!rows.length) throw new Error(`table not found: ${table}`);
return rows.map(({name, type, notnull}) => ({name, type: sqliteType(type), databaseType: type, nullable: !notnull}));
}
async describe(object) {
const rows = await (object === undefined
? this.query(`SELECT name FROM sqlite_master WHERE type = 'table'`)
: this.query(`SELECT * FROM pragma_table_info(?)`, [object]));
if (!rows.length) throw new Error("Not found");
const {columns} = rows;
return element$1("table", {value: rows}, [
element$1("thead", [element$1("tr", columns.map(c => element$1("th", [text$2(c)])))]),
element$1("tbody", rows.map(r => element$1("tr", columns.map(c => element$1("td", [text$2(r[c])])))))
]);
}
async sql() {
return this.query(...this.queryTag.apply(this, arguments));
}
queryTag(strings, ...params) {
return [strings.join("?"), params];
}
}
Object.defineProperty(SQLiteDatabaseClient.prototype, "dialect", {
value: "sqlite"
});
// https://www.sqlite.org/datatype3.html
function sqliteType(type) {
switch (type) {
case "NULL":
return "null";
case "INT":
case "INTEGER":
case "TINYINT":
case "SMALLINT":
case "MEDIUMINT":
case "BIGINT":
case "UNSIGNED BIG INT":
case "INT2":
case "INT8":
return "integer";
case "TEXT":
case "CLOB":
return "string";
case "REAL":
case "DOUBLE":
case "DOUBLE PRECISION":
case "FLOAT":
case "NUMERIC":
return "number";
case "BLOB":
return "buffer";
case "DATE":
case "DATETIME":
return "string"; // TODO convert strings to Date instances in sql.js
default:
return /^(?:(?:(?:VARYING|NATIVE) )?CHARACTER|(?:N|VAR|NVAR)CHAR)\(/.test(type) ? "string"
: /^(?:DECIMAL|NUMERIC)\(/.test(type) ? "number"
: "other";
}
}
function load(source) {
return typeof source === "string" ? fetch(source).then(load)
: source instanceof Response || source instanceof Blob ? source.arrayBuffer().then(load)
: source instanceof ArrayBuffer ? new Uint8Array(source)
: source;
}
async function exec(db, query, params) {
const [result] = await db.exec(query, params);
if (!result) return [];
const {columns, values} = result;
const rows = values.map(row => Object.fromEntries(row.map((value, i) => [columns[i], value])));
rows.columns = columns;
return rows;
}
function element$1(name, props, children) {
if (arguments.length === 2) children = props, props = undefined;
const element = document.createElement(name);
if (props !== undefined) for (const p in props) element[p] = props[p];
if (children !== undefined) for (const c of children) element.appendChild(c);
return element;
}
function text$2(value) {
return document.createTextNode(value);
}
class Workbook {
constructor(workbook) {
Object.defineProperties(this, {
_: {value: workbook},
sheetNames: {
value: workbook.worksheets.map((s) => s.name),
enumerable: true
}
});
}
sheet(name, options) {
const sname =
typeof name === "number"
? this.sheetNames[name]
: this.sheetNames.includes((name += ""))
? name
: null;
if (sname == null) throw new Error(`Sheet not found: ${name}`);
const sheet = this._.getWorksheet(sname);
return extract(sheet, options);
}
}
function extract(sheet, {range, headers} = {}) {
let [[c0, r0], [c1, r1]] = parseRange(range, sheet);
const headerRow = headers ? sheet._rows[r0++] : null;
let names = new Set(["#"]);
for (let n = c0; n <= c1; n++) {
const value = headerRow ? valueOf(headerRow.findCell(n + 1)) : null;
let name = (value && value + "") || toColumn(n);
while (names.has(name)) name += "_";
names.add(name);
}
names = new Array(c0).concat(Array.from(names));
const output = new Array(r1 - r0 + 1);
for (let r = r0; r <= r1; r++) {
const row = (output[r - r0] = Object.create(null, {"#": {value: r + 1}}));
const _row = sheet.getRow(r + 1);
if (_row.hasValues)
for (let c = c0; c <= c1; c++) {
const value = valueOf(_row.findCell(c + 1));
if (value != null) row[names[c + 1]] = value;
}
}
output.columns = names.filter(() => true); // Filter sparse columns
return output;
}
function valueOf(cell) {
if (!cell) return;
const {value} = cell;
if (value && typeof value === "object" && !(value instanceof Date)) {
if (value.formula || value.sharedFormula) {
return value.result && value.result.error ? NaN : value.result;
}
if (value.richText) {
return richText(value);
}
if (value.text) {
let {text} = value;
if (text.richText) text = richText(text);
return value.hyperlink && value.hyperlink !== text
? `${value.hyperlink} ${text}`
: text;
}
return value;
}
return value;
}
function richText(value) {
return value.richText.map((d) => d.text).join("");
}
function parseRange(specifier = ":", {columnCount, rowCount}) {
specifier += "";
if (!specifier.match(/^[A-Z]*\d*:[A-Z]*\d*$/))
throw new Error("Malformed range specifier");
const [[c0 = 0, r0 = 0], [c1 = columnCount - 1, r1 = rowCount - 1]] =
specifier.split(":").map(fromCellReference);
return [
[c0, r0],
[c1, r1]
];
}
// Returns the default column name for a zero-based column index.
// For example: 0 -> "A", 1 -> "B", 25 -> "Z", 26 -> "AA", 27 -> "AB".
function toColumn(c) {
let sc = "";
c++;
do {
sc = String.fromCharCode(64 + (c % 26 || 26)) + sc;
} while ((c = Math.floor((c - 1) / 26)));
return sc;
}
// Returns the zero-based indexes from a cell reference.
// For example: "A1" -> [0, 0], "B2" -> [1, 1], "AA10" -> [26, 9].
function fromCellReference(s) {
const [, sc, sr] = s.match(/^([A-Z]*)(\d*)$/);
let c = 0;
if (sc)
for (let i = 0; i < sc.length; i++)
c += Math.pow(26, sc.length - i - 1) * (sc.charCodeAt(i) - 64);
return [c ? c - 1 : undefined, sr ? +sr - 1 : undefined];
}
async function remote_fetch(file) {
const response = await fetch(await file.url());
if (!response.ok) throw new Error(`Unable to load file: ${file.name}`);
return response;
}
async function dsv(file, delimiter, {array = false, typed = false} = {}) {
const text = await file.text();
return (delimiter === "\t"
? (array ? tsvParseRows : tsvParse)
: (array ? csvParseRows : csvParse))(text, typed && autoType);
}
class AbstractFile {
constructor(name, mimeType) {
Object.defineProperty(this, "name", {value: name, enumerable: true});
if (mimeType !== undefined) Object.defineProperty(this, "mimeType", {value: mimeType + "", enumerable: true});
}
async blob() {
return (await remote_fetch(this)).blob();
}
async arrayBuffer() {
return (await remote_fetch(this)).arrayBuffer();
}
async text() {
return (await remote_fetch(this)).text();
}
async json() {
return (await remote_fetch(this)).json();
}
async stream() {
return (await remote_fetch(this)).body;
}
async csv(options) {
return dsv(this, ",", options);
}
async tsv(options) {
return dsv(this, "\t", options);
}
async image(props) {
const url = await this.url();
return new Promise((resolve, reject) => {
const i = new Image();
if (new URL(url, document.baseURI).origin !== new URL(location).origin) {
i.crossOrigin = "anonymous";
}
Object.assign(i, props);
i.onload = () => resolve(i);
i.onerror = () => reject(new Error(`Unable to load file: ${this.name}`));
i.src = url;
});
}
async arrow() {
const [Arrow, response] = await Promise.all([requireDefault(arrow.resolve()), remote_fetch(this)]);
return Arrow.Table.from(response);
}
async sqlite() {
return SQLiteDatabaseClient.open(remote_fetch(this));
}
async zip() {
const [JSZip, buffer] = await Promise.all([requireDefault(jszip.resolve()), this.arrayBuffer()]);
return new ZipArchive(await JSZip.loadAsync(buffer));
}
async xml(mimeType = "application/xml") {
return (new DOMParser).parseFromString(await this.text(), mimeType);
}
async html() {
return this.xml("text/html");
}
async xlsx() {
const [ExcelJS, buffer] = await Promise.all([requireDefault(exceljs.resolve()), this.arrayBuffer()]);
return new Workbook(await new ExcelJS.Workbook().xlsx.load(buffer));
}
}
class FileAttachment extends AbstractFile {
constructor(url, name, mimeType) {
super(name, mimeType);
Object.defineProperty(this, "_url", {value: url});
}
async url() {
return (await this._url) + "";
}
}
function NoFileAttachments(name) {
throw new Error(`File not found: ${name}`);
}
class ZipArchive {
constructor(archive) {
Object.defineProperty(this, "_", {value: archive});
this.filenames = Object.keys(archive.files).filter(name => !archive.files[name].dir);
}
file(path) {
const object = this._.file(path += "");
if (!object || object.dir) throw new Error(`file not found: ${path}`);
return new ZipArchiveEntry(object);
}
}
class ZipArchiveEntry extends AbstractFile {
constructor(object) {
super(object.name);
Object.defineProperty(this, "_", {value: object});
Object.defineProperty(this, "_url", {writable: true});
}
async url() {
return this._url || (this._url = this.blob().then(URL.createObjectURL));
}
async blob() {
return this._.async("blob");
}
async arrayBuffer() {
return this._.async("arraybuffer");
}
async text() {
return this._.async("text");
}
async json() {
return JSON.parse(await this.text());
}
}
function canvas(width, height) {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
return canvas;
}
function context2d(width, height, dpi) {
if (dpi == null) dpi = devicePixelRatio;
var canvas = document.createElement("canvas");
canvas.width = width * dpi;
canvas.height = height * dpi;
canvas.style.width = width + "px";
var context = canvas.getContext("2d");
context.scale(dpi, dpi);
return context;
}
function download(value, name = "untitled", label = "Save") {
const a = document.createElement("a");
const b = a.appendChild(document.createElement("button"));
b.textContent = label;
a.download = name;
async function reset() {
await new Promise(requestAnimationFrame);
URL.revokeObjectURL(a.href);
a.removeAttribute("href");
b.textContent = label;
b.disabled = false;
}
a.onclick = async event => {
b.disabled = true;
if (a.href) return reset(); // Already saved.
b.textContent = "Saving…";
try {
const object = await (typeof value === "function" ? value() : value);
b.textContent = "Download";
a.href = URL.createObjectURL(object); // eslint-disable-line require-atomic-updates
} catch (ignore) {
b.textContent = label;
}
if (event.eventPhase) return reset(); // Already downloaded.
b.disabled = false;
};
return a;
}
var namespaces = {
math: "http://www.w3.org/1998/Math/MathML",
svg: "http://www.w3.org/2000/svg",
xhtml: "http://www.w3.org/1999/xhtml",
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace",
xmlns: "http://www.w3.org/2000/xmlns/"
};
function element(name, attributes) {
var prefix = name += "", i = prefix.indexOf(":"), value;
if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
var element = namespaces.hasOwnProperty(prefix) // eslint-disable-line no-prototype-builtins
? document.createElementNS(namespaces[prefix], name)
: document.createElement(name);
if (attributes) for (var key in attributes) {
prefix = key, i = prefix.indexOf(":"), value = attributes[key];
if (i >= 0 && (prefix = key.slice(0, i)) !== "xmlns") key = key.slice(i + 1);
if (namespaces.hasOwnProperty(prefix)) element.setAttributeNS(namespaces[prefix], key, value); // eslint-disable-line no-prototype-builtins
else element.setAttribute(key, value);
}
return element;
}
function input$1(type) {
var input = document.createElement("input");
if (type != null) input.type = type;
return input;
}
function range$1(min, max, step) {
if (arguments.length === 1) max = min, min = null;
var input = document.createElement("input");
input.min = min = min == null ? 0 : +min;
input.max = max = max == null ? 1 : +max;
input.step = step == null ? "any" : step = +step;
input.type = "range";
return input;
}
function select(values) {
var select = document.createElement("select");
Array.prototype.forEach.call(values, function(value) {
var option = document.createElement("option");
option.value = option.textContent = value;
select.appendChild(option);
});
return select;
}
function svg$1(width, height) {
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", [0, 0, width, height]);
svg.setAttribute("width", width);
svg.setAttribute("height", height);
return svg;
}
function text$1(value) {
return document.createTextNode(value);
}
var count = 0;
function uid(name) {
return new Id("O-" + (name == null ? "" : name + "-") + ++count);
}
function Id(id) {
this.id = id;
this.href = new URL(`#${id}`, location) + "";
}
Id.prototype.toString = function() {
return "url(" + this.href + ")";
};
var DOM = {
canvas: canvas,
context2d: context2d,
download: download,
element: element,
input: input$1,
range: range$1,
select: select,
svg: svg$1,
text: text$1,
uid: uid
};
function buffer(file) {
return new Promise(function(resolve, reject) {
var reader = new FileReader;
reader.onload = function() { resolve(reader.result); };
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
function text(file) {
return new Promise(function(resolve, reject) {
var reader = new FileReader;
reader.onload = function() { resolve(reader.result); };
reader.onerror = reject;
reader.readAsText(file);
});
}
function url(file) {
return new Promise(function(resolve, reject) {
var reader = new FileReader;
reader.onload = function() { resolve(reader.result); };
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
var Files = {
buffer: buffer,
text: text,
url: url
};
function that() {
return this;
}
function disposable(value, dispose) {
let done = false;
if (typeof dispose !== "function") {
throw new Error("dispose is not a function");
}
return {
[Symbol.iterator]: that,
next: () => done ? {done: true} : (done = true, {done: false, value}),
return: () => (done = true, dispose(value), {done: true}),
throw: () => ({done: done = true})
};
}
function* filter(iterator, test) {
var result, index = -1;
while (!(result = iterator.next()).done) {
if (test(result.value, ++index)) {
yield result.value;
}
}
}
function observe(initialize) {
let stale = false;
let value;
let resolve;
const dispose = initialize(change);
if (dispose != null && typeof dispose !== "function") {
throw new Error(typeof dispose.then === "function"
? "async initializers are not supported"
: "initializer returned something, but not a dispose function");
}
function change(x) {
if (resolve) resolve(x), resolve = null;
else stale = true;
return value = x;
}
function next() {
return {done: false, value: stale
? (stale = false, Promise.resolve(value))
: new Promise(_ => (resolve = _))};
}
return {
[Symbol.iterator]: that,
throw: () => ({done: true}),
return: () => (dispose != null && dispose(), {done: true}),
next
};
}
function input(input) {
return observe(function(change) {
var event = eventof(input), value = valueof(input);
function inputted() { change(valueof(input)); }
input.addEventListener(event, inputted);
if (value !== undefined) change(value);
return function() { input.removeEventListener(event, inputted); };
});
}
function valueof(input) {
switch (input.type) {
case "range":
case "number": return input.valueAsNumber;
case "date": return input.valueAsDate;
case "checkbox": return input.checked;
case "file": return input.multiple ? input.files : input.files[0];
case "select-multiple": return Array.from(input.selectedOptions, o => o.value);
default: return input.value;
}
}
function eventof(input) {
switch (input.type) {
case "button":
case "submit":
case "checkbox": return "click";
case "file": return "change";
default: return "input";
}
}
function* map(iterator, transform) {
var result, index = -1;
while (!(result = iterator.next()).done) {
yield transform(result.value, ++index);
}
}
function queue(initialize) {
let resolve;
const queue = [];
const dispose = initialize(push);
if (dispose != null && typeof dispose !== "function") {
throw new Error(typeof dispose.then === "function"
? "async initializers are not supported"
: "initializer returned something, but not a dispose function");
}
function push(x) {
queue.push(x);
if (resolve) resolve(queue.shift()), resolve = null;
return x;
}
function next() {
return {done: false, value: queue.length
? Promise.resolve(queue.shift())
: new Promise(_ => (resolve = _))};
}
return {
[Symbol.iterator]: that,
throw: () => ({done: true}),
return: () => (dispose != null && dispose(), {done: true}),
next
};
}
function* range(start, stop, step) {
start = +start;
stop = +stop;
step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
var i = -1, n = Math.max(0, Math.ceil((stop - start) / step)) | 0;
while (++i < n) {
yield start + i * step;
}
}
function valueAt(iterator, i) {
if (!isFinite(i = +i) || i < 0 || i !== i | 0) return;
var result, index = -1;
while (!(result = iterator.next()).done) {
if (++index === i) {
return result.value;
}
}
}
function worker(source) {
const url = URL.createObjectURL(new Blob([source], {type: "text/javascript"}));
const worker = new Worker(url);
return disposable(worker, () => {
worker.terminate();
URL.revokeObjectURL(url);
});
}
var Generators = {
disposable: disposable,
filter: filter,
input: input,
map: map,
observe: observe,
queue: queue,
range: range,
valueAt: valueAt,
worker: worker
};
function template(render, wrapper) {
return function(strings) {
var string = strings[0],
parts = [], part,
root = null,
node, nodes,
walker,
i, n, j, m, k = -1;
// Concatenate the text using comments as placeholders.
for (i = 1, n = arguments.length; i < n; ++i) {
part = arguments[i];
if (part instanceof Node) {
parts[++k] = part;
string += "<!--o:" + k + "-->";
} else if (Array.isArray(part)) {
for (j = 0, m = part.length; j < m; ++j) {
node = part[j];
if (node instanceof Node) {
if (root === null) {
parts[++k] = root = document.createDocumentFragment();
string += "<!--o:" + k + "-->";
}
root.appendChild(node);
} else {
root = null;
string += node;
}
}
root = null;
} else {
string += part;
}
string += strings[i];
}
// Render the text.
root = render(string);
// Walk the rendered content to replace comment placeholders.
if (++k > 0) {
nodes = new Array(k);
walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null, false);
while (walker.nextNode()) {
node = walker.currentNode;
if (/^o:/.test(node.nodeValue)) {
nodes[+node.nodeValue.slice(2)] = node;
}
}
for (i = 0; i < k; ++i) {
if (node = nodes[i]) {
node.parentNode.replaceChild(parts[i], node);
}
}
}
// Is the rendered content
// … a parent of a single child? Detach and return the child.
// … a document fragment? Replace the fragment with an element.
// … some other node? Return it.
return root.childNodes.length === 1 ? root.removeChild(root.firstChild)
: root.nodeType === 11 ? ((node = wrapper()).appendChild(root), node)
: root;
};
}
var html = template(function(string) {
var template = document.createElement("template");
template.innerHTML = string.trim();
return document.importNode(template.content, true);
}, function() {
return document.createElement("span");
});
async function leaflet(require) {
const L = await require(leaflet$1.resolve());
if (!L._style) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = await require.resolve(leaflet$1.resolve("dist/leaflet.css"));
L._style = document.head.appendChild(link);
}
return L;
}
function md(require) {
return require(marked.resolve()).then(function(marked) {
return template(
function(string) {
var root = document.createElement("div");
root.innerHTML = marked(string, {langPrefix: ""}).trim();
var code = root.querySelectorAll("pre code[class]");
if (code.length > 0) {
require(highlight.resolve()).then(function(hl) {
code.forEach(function(block) {
function done() {
hl.highlightBlock(block);
block.parentNode.classList.add("observablehq--md-pre");
}
if (hl.getLanguage(block.className)) {
done();
} else {
require(highlight.resolve("async-languages/index.js"))
.then(index => {
if (index.has(block.className)) {
return require(highlight.resolve("async-languages/" + index.get(block.className))).then(language => {
hl.registerLanguage(block.className, language);
});
}
})
.then(done, done);
}
});
});
}
return root;
},
function() {
return document.createElement("div");
}
);
});
}
async function mermaid(require) {
const mer = await require(mermaid$1.resolve());
mer.initialize({securityLevel: "loose", theme: "neutral"});
return function mermaid() {
const root = document.createElement("div");
root.innerHTML = mer.render(uid().id, String.raw.apply(String, arguments));
return root.removeChild(root.firstChild);
};
}
function Mutable(value) {
let change;
Object.defineProperties(this, {
generator: {value: observe(_ => void (change = _))},
value: {get: () => value, set: x => change(value = x)} // eslint-disable-line no-setter-return
});
if (value !== undefined) change(value);
}
function* now$1() {
while (true) {
yield Date.now();
}
}
function delay(duration, value) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(value);
}, duration);
});
}
var timeouts = new Map;
function timeout(now, time) {
var t = new Promise(function(resolve) {
timeouts.delete(time);
var delay = time - now;
if (!(delay > 0)) throw new Error("invalid time");
if (delay > 0x7fffffff) throw new Error("too long to wait");
setTimeout(resolve, delay);
});
timeouts.set(time, t);
return t;
}
function when(time, value) {
var now;
return (now = timeouts.get(time = +time)) ? now.then(() => value)
: (now = Date.now()) >= time ? Promise.resolve(value)
: timeout(now, time).then(() => value);
}
function tick(duration, value) {
return when(Math.ceil((Date.now() + 1) / duration) * duration, value);
}
var Promises$1 = {
delay: delay,
tick: tick,
when: when
};
function resolve(name, base) {
if (/^(\w+:)|\/\//i.test(name)) return name;
if (/^[.]{0,2}\//i.test(name)) return new URL(name, base == null ? location : base).href;
if (!name.length || /^[\s._]/.test(name) || /\s$/.test(name)) throw new Error("illegal name");
return "https://unpkg.com/" + name;
}
var svg = template(function(string) {
var root = document.createElementNS("http://www.w3.org/2000/svg", "g");
root.innerHTML = string.trim();
return root;
}, function() {
return document.createElementNS("http://www.w3.org/2000/svg", "g");
});
var raw = String.raw;
function style(href) {
return new Promise(function(resolve, reject) {
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;
link.onerror = reject;
link.onload = resolve;
document.head.appendChild(link);
});
}
function tex(require) {
return Promise.all([
require(katex.resolve()),
require.resolve(katex.resolve("dist/katex.min.css")).then(style)
]).then(function(values) {
var katex = values[0], tex = renderer();
function renderer(options) {
return function() {
var root = document.createElement("div");
katex.render(raw.apply(String, arguments), root, options);
return root.removeChild(root.firstChild);
};
}
tex.options = renderer;
tex.block = renderer({displayMode: true});
return tex;
});
}
async function vl(require) {
const [v, vl, api] = await Promise.all([vega, vegalite, vegaliteApi].map(d => require(d.resolve())));
return api.register(v, vl);
}
function width() {
return observe(function(change) {
var width = change(document.body.clientWidth);
function resized() {
var w = document.body.clientWidth;
if (w !== width) change(width = w);
}
window.addEventListener("resize", resized);
return function() {
window.removeEventListener("resize", resized);
};
});
}
// These are copied from d3-array; TODO import once this package adopts type: module.
function descending(a, b) {
return a == null || b == null ? NaN : b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
}
function ascending(a, b) {
return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}
function reverse(values) {
if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable");
return Array.from(values).reverse();
}
const nChecks = 20; // number of values to check in each array
// We support two levels of DatabaseClient. The simplest DatabaseClient
// implements only the client.sql tagged template literal. More advanced
// DatabaseClients implement client.query and client.queryStream, which support
// streaming and abort, and the client.queryTag tagged template literal is used
// to translate the contents of a SQL cell or Table cell into the appropriate
// arguments for calling client.query or client.queryStream. For table cells, we
// additionally require client.describeColumns. The client.describeTables method
// is optional.
function isDatabaseClient(value, mode) {
return (
value &&
(typeof value.sql === "function" ||
(typeof value.queryTag === "function" &&
(typeof value.query === "function" ||
typeof value.queryStream === "function"))) &&
(mode !== "table") &&
value !== __query // don’t match our internal helper
);
}
// Returns true if the value is a typed array (for a single-column table), or if
// it’s an array. In the latter case, the elements of the array must be
// consistently typed: either plain objects or primitives or dates.
function isDataArray(value) {
return (
(Array.isArray(value) &&
(isQueryResultSetSchema(value.schema) ||
isQueryResultSetColumns(value.columns) ||
arrayContainsObjects(value) ||
arrayContainsPrimitives(value) ||
arrayContainsDates(value))) ||
isTypedArray(value)
);
}
// Given an array, checks that the given value is an array that does not contain
// any primitive values (at least for the first few values that we check), and
// that the first object contains enumerable keys (see computeSchema for how we
// infer the columns). We assume that the contents of the table are homogenous,
// but we don’t currently enforce this.
// https://observablehq.com/@observablehq/database-client-specification#§1
function arrayContainsObjects(value) {
const n = Math.min(nChecks, value.length);
for (let i = 0; i < n; ++i) {
const v = value[i];
if (v === null || typeof v !== "object") return false;
}
return n > 0 && objectHasEnumerableKeys(value[0]);
}
// Using a for-in loop here means that we can abort after finding at least one
// enumerable key (whereas Object.keys would require materializing the array of
// all keys, which would be considerably slower if the value has many keys!).
// This function assumes that value is an object; see arrayContainsObjects.
function objectHasEnumerableKeys(value) {
for (const _ in value) return true;
return false;
}
function isQueryResultSetSchema(schemas) {
return (Array.isArray(schemas) && schemas.every((s) => s && typeof s.name === "string"));
}
function isQueryResultSetColumns(columns) {
return (Array.isArray(columns) && columns.every((name) => typeof name === "string"));
}
// Returns true if the value represents an array of primitives (i.e., a
// single-column table). This should only be passed values for which
// isDataArray returns true.
function arrayIsPrimitive(value) {
return (
isTypedArray(value) ||
arrayContainsPrimitives(value) ||
arrayContainsDates(value)
);
}
// Given an array, checks that the first n elements are primitives (number,
// string, boolean, bigint) of a consistent type.
function arrayContainsPrimitives(value) {
const n = Math.min(nChecks, value.length);
if (!(n > 0)) return false;
let type;
let hasPrimitive = false; // ensure we encounter 1+ primitives
for (let i = 0; i < n; ++i) {
const v = value[i];
if (v == null) continue; // ignore null and undefined
const t = typeof v;
if (type === undefined) {
switch (t) {
case "number":
case "boolean":
case "string":
case "bigint":
type = t;
break;
default:
return false;
}
} else if (t !== type) {
return false;
}
hasPrimitive = true;
}
return hasPrimitive;
}
// Given an array, checks that the first n elements are dates.
function arrayContainsDates(value) {
const n = Math.min(nChecks, value.length);
if (!(n > 0)) return false;
let hasDate = false; // ensure we encounter 1+ dates
for (let i = 0; i < n; ++i) {
const v = value[i];
if (v == null) continue; // ignore null and undefined
if (!(v instanceof Date)) return false;
hasDate = true;
}
return hasDate;
}
function isTypedArray(value) {
return (
value instanceof Int8Array ||
value instanceof Int16Array ||
value instanceof Int32Array ||
value instanceof Uint8Array ||
value instanceof Uint8ClampedArray ||
value instanceof Uint16Array ||
value instanceof Uint32Array ||
value instanceof Float32Array ||
value instanceof Float64Array
);
}
// __query is used by table cells; __query.sql is used by SQL cells.
const __query = Object.assign(
async (source, operations, invalidation) => {
source = await loadDataSource(await source, "table");
if (isDatabaseClient(source)) return evaluateQuery(source, makeQueryTemplate(operations, source), invalidation);
if (isDataArray(source)) return __table(source, operations);
if (!source) throw new Error("missing data source");
throw new Error("invalid data source");
},
{
sql(source, invalidation) {
return async function () {
return evaluateQuery(await loadDataSource(await source, "sql"), arguments, invalidation);
};
}
}
);
async function loadDataSource(source, mode) {
if (source instanceof FileAttachment) {
if (mode === "table") {
switch (source.mimeType) {
case "text/csv": return source.csv({typed: true});
case "text/tab-separated-values": return source.tsv({typed: true});
case "application/json": return source.json();
}
}
if (mode === "table" || mode === "sql") {
switch (source.mimeType) {
case "application/x-sqlite3": return source.sqlite();
}
}
throw new Error(`unsupported file type: ${source.mimeType}`);
}
return source;
}
async function evaluateQuery(source, args, invalidation) {
if (!source) throw new Error("missing data source");
// If this DatabaseClient supports abort and streaming, use that.
if (typeof source.queryTag === "function") {
const abortController = new AbortController();
const options = {signal: abortController.signal};
invalidation.then(() => abortController.abort("invalidated"));
if (typeof source.queryStream === "function") {
return accumulateQuery(
source.queryStream(...source.queryTag.apply(source, args), options)
);
}
if (typeof source.query === "function") {
return source.query(...source.queryTag.apply(source, args), options);
}
}
// Otherwise, fallback to the basic sql tagged template literal.
if (typeof source.sql === "function") {
return source.sql.apply(source, args);
}
// TODO: test if source is a file attachment, and support CSV etc.
throw new Error("source does not implement query, queryStream, or sql");
}
// Generator function that yields accumulated query results client.queryStream
async function* accumulateQuery(queryRequest) {
const queryResponse = await queryRequest;
const values = [];
values.done = false;
values.error = null;
values.schema = queryResponse.schema;
try {
const iterator = queryResponse.readRows();
do {
const result = await iterator.next();
if (result.done) {
values.done = true;
} else {
for (const value of result.value) {
values.push(value);
}
}
yield values;
} while (!values.done);
} catch (error) {
values.error = error;
yield values;
}
}
/**
* Returns a SQL query in the form [[parts], ...params] where parts is an array
* of sub-strings and params are the parameter values to be inserted between each
* sub-string.
*/
function makeQueryTemplate(operations, source) {
const escaper =
typeof source.escape === "function" ? source.escape : (i) => i;
const {select, from, filter, sort, slice} = operations;
if (!from.table)
throw new Error("missing from table");
if (select.columns && select.columns.length === 0)
throw new Error("at least one column must be selected");
const columns = select.columns ? select.columns.map((c) => `t.${escaper(c)}`) : "*";
const args = [
[`SELECT ${columns} FROM ${formatTable(from.table, escaper)} t`]
];
for (let i = 0; i < filter.length; ++i) {
appendSql(i ? `\nAND ` : `\nWHERE `, args);
appendWhereEntry(filter[i], args, escaper);
}
for (let i = 0; i < sort.length; ++i) {
appendSql(i ? `, ` : `\nORDER BY `, args);
appendOrderBy(sort[i], args, escaper);
}
if (source.dialect === "mssql") {
if (slice.to !== null || slice.from !== null) {
if (!sort.length) {
if (!select.columns)
throw new Err