import * as omd from "@hpcc-js/observable-md";
import { Observable, OJSRuntimeError } from "@hpcc-js/observable-md";
import { Class, HTMLWidget, SVGWidget, Widget } from "@hpcc-js/common";
import { expect } from "chai";
import { classDef, renderWide } from "../../test-data/src/index";
const text = `\
\`\`\`
import {checkbox, select} from "@jashkenas/inputs"
viewof sorting = select({title: 'Sorted by', options:["region","time"], value:"time"})
\`\`\`
# Five-Minute Introduction
\`\`\`
viewof colorzzz = html\`\`
html\`The color input (viewof colorzzz) is a \${viewof colorzzz.constructor.name}.\`
viewof colorzzz;
\`\`\`
Welcome! This notebook gives a quick overview of "Observable Markdown" a mashup of the excellent [Observable HQ](https://observablehq.com) + regular Markdown. Here follows a quick introduction to Observable. For a more technical introduction, see [Observable’s not JavaScript](/@observablehq/observables-not-javascript). For hands-on, see our [introductory tutorial series](/collection/@observablehq/introduction). To watch rather than read, see our [short introductory video](https://www.youtube.com/watch?v=uEmDwflQ3xE)!
Its also very easy to embed a value: \${i} inside the Markdown!!!
Observable Markdown consists of a single markdown document with live "code" sections.
\`\`\`
2 * 3 * 7
{
let sum = 0;
for (let i = 0; i <= 100; ++i) {
sum += i;
}
return sum;
}
\`\`\`
Cells can have names. This allows a cell’s value to be referenced by other cells.
\`\`\`
color = "red";
\`My favorite color is \${color}.\`
\`\`\`
A cell referencing another cell is re-evaluated automatically when the referenced value changes. Try editing the definition of color above and shift-return to re-evaluate.
Cells can generate DOM (HTML, SVG, Canvas, WebGL, etc.). You can use the standard DOM API like document.createElement, or use the built-in html tagged template literal:
\`\`\`
html\`
My favorite language is HTML.
\`
\`\`\`
There’s a Markdown tagged template literal, too. (This notebook is written in Markdown.)
\`\`\`
md\`My favorite language is *Markdown*.\`
\`\`\`
DOM can be made reactive simply by referring to other cells. The next cell refers to color. (Try editing the definition of color above.)
\`\`\`
html\`My favorite color is \${color}.\`
\`\`\`
Sometimes you need to load data from a remote server, or compute something expensive in a web worker. For that, cells can be defined asynchronously using [promises](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Using_promises):
\`\`\`
status = new Promise(resolve => {
setTimeout(() => {
resolve({resolved: new Date});
}, 2000);
})
\`\`\`
A cell that refers to a promise cell sees the value when it is resolved; this implicit await means that referencing cells don’t care whether the value is synchronous or not. Edit the status cell above to see the cell below update after two seconds.
\`\`\`
status
\`\`\`
Promises are also useful for loading libraries from npm. Below, require returns a promise that resolves to the d3-fetch library:
\`\`\`
d3 = require("d3-fetch@1")
\`\`\`
If you prefer, you can use async and await explicitly (not this ):
\`\`\`
countries = (await d3.tsv("https://cdn.jsdelivr.net/npm/world-atlas@1/world/110m.tsv"))
.sort((a, b) => b.pop_est - a.pop_est) // Sort by descending estimated population.
.slice(0, 10) // Take the top ten.
\`\`\`
Cells can be defined as [generators](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Iterators_and_Generators#Generators); a value is yielded up to sixty times a second.
\`\`\`
i = {
let i = 0;
while (true) {
yield ++i;
}
}
\`The current value of i is \${i}.\`
\`\`\`
Any cell that refers to a generator cell sees its current value; the referencing cell is re-evaluated whenever the generator yields a new value. As you might guess, a generator can yield promises for [async iteration](https://github.com/tc39/proposal-async-iteration); referencing cells see the current resolved value.
\`\`\`
date = {
while (true) {
yield new Promise(resolve => {
setTimeout(() => resolve(new Date), 1000);
});
}
}
\`\`\`
Combining these primitives—promises, generators and DOM—you can build custom user interfaces. Here’s a slider and a generator that yields the slider’s value:
\`\`\`
slider = html\`\`
sliderValue = Generators.input(slider)
\`\`\`
Generators.input returns a generator that yields promises. The promise resolves whenever the associated input element emits an input event. You don’t need to implement that generator by hand, though. There’s a builtin viewof operator which exposes the current value of a given input element:
\`\`\`
viewof value = html\`\`
value
\`\`\`
## Imports (dot)
\`\`\`
dot\`digraph { x -> y -> z; }\`;
\`\`\`
\`\`\`
import { dot } from "@gordonsmith/graphviz";
\`\`\`
`;
class ObservableEx extends Observable {
_logErrors: OJSRuntimeError[] = [];
constructor() {
super();
}
enter(domNode: HTMLElement, element: any) {
super.enter(domNode, element);
this._logErrors = [];
return this;
}
render(callback?: (w: Widget) => void) {
super.render(w => {
setTimeout(() => {
if (callback) {
this._logErrors = [...this._logErrors, ...this.errors().filter(row => row.severity === "rejected")];
this._logErrors.forEach(error => {
console.error(error);
});
expect(this._logErrors.length, `${this._logErrors[0]?.message}`).to.be.equal(0);
callback(w);
}
}, 1000);
});
return this;
}
}
describe("@hpcc-js/observable-md", () => {
for (const key in omd) {
const item = (omd as any)[key];
if (item && item.prototype && item.prototype.constructor) {
describe(`${item.prototype.constructor.name}`, () => {
if (item.prototype instanceof Class) {
classDef("observable-md", item);
}
if (item.prototype instanceof HTMLWidget || item.prototype instanceof SVGWidget) {
switch (item.prototype.constructor) {
case Observable:
renderWide(new ObservableEx()
.showValues(false)
.mode("omd")
.text(text));
break;
default:
it("Has render test", () => {
expect(false, item.prototype.constructor.name).to.be.true;
});
}
}
});
}
}
});