Skip to content

Commit 605b250

Browse files
authored
Merge pull request #4447 from GordonSmith/OHQ_UPDATE_FIX
fix: improve runtime placeholder handling
2 parents f5c0c82 + 237c452 commit 605b250

File tree

8 files changed

+138
-95
lines changed

8 files changed

+138
-95
lines changed

packages/observablehq-compiler/index-kit.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
<title>Home</title>
66
<meta charset="utf-8">
77
<title>ObservableHQ Kit Preview</title>
8-
<!-- Google Fonts: Roboto -->
9-
<!-- <style>
8+
<style>
109
body,
1110
html {
1211
font-family: "Segoe UI", Verdana, Arial, Helvetica, sans-serif;
1312
}
14-
</style> -->
13+
</style>
1514
</head>
1615

1716
<body>

packages/observablehq-compiler/src/compiler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
import { type Notebook, type Definition, compile as compileKit, fixRelativeUrl, isRelativePath, obfuscatedImport } from "./kit/index.ts";
2+
import { type Notebook, type Definition, compileNotebook, fixRelativeUrl, isRelativePath, obfuscatedImport } from "./kit/index.ts";
33
import { ohq, splitModule } from "./observable-shim.ts";
44
import { parseCell, ParsedImportCell } from "./cst.ts";
55
import { Writer } from "./writer.ts";
@@ -330,7 +330,7 @@ export async function compile(notebookOrOjs: ohq.Notebook, options?: CompileOpti
330330
export async function compile(notebookOrOjs: string, options?: CompileOptions): Promise<NotebookFunc>;
331331
export async function compile(notebookOrOjs: Notebook | ohq.Notebook | string, { baseUrl = ".", importMode = "precompiled" }: CompileOptions = {}) {
332332
if (isNotebookKit(notebookOrOjs)) {
333-
return compileKit(notebookOrOjs);
333+
return compileNotebook(notebookOrOjs);
334334
} else if (typeof notebookOrOjs === "string") {
335335
notebookOrOjs = ojs2notebook(notebookOrOjs);
336336
}

packages/observablehq-compiler/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ export type { ohq } from "./observable-shim.ts";
22

33
export * from "./compiler.ts";
44
export { ojs2notebook, omd2notebook, omd2notebookKit, ojs2notebookKit, download } from "./util.ts";
5-
export { compile as compileKit, html2notebook, notebook2html } from "./kit/index.ts";
5+
export * from "./kit/index.ts";
66
export * from "./writer.ts";
Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,56 @@
11

2-
import { type Notebook, transpile } from "@observablehq/notebook-kit";
2+
import { type Notebook, type Cell, transpile } from "@observablehq/notebook-kit";
33
import { type Definition } from "@observablehq/notebook-kit/runtime";
44
import { constructFunction } from "./util.ts";
55

66
export interface CompileKitOptions {
77
inline?: boolean;
88
}
99

10-
export function compile(notebook: Notebook, options: CompileKitOptions = { inline: true }): Definition[] {
10+
export function compileCell(cell: Cell, options: CompileKitOptions = { inline: true }): Definition[] {
1111
const retVal: Definition[] = [];
12-
let id = 1;
13-
for (const cell of notebook.cells) {
14-
try {
15-
const compiled = transpile(cell);
16-
retVal.push({
17-
id: id++,
18-
...compiled,
19-
body: options.inline ? constructFunction(compiled.body, `cell_${id}`) : compiled.body,
20-
});
21-
if (cell.pinned) {
22-
const compiled = transpile({
23-
...cell,
24-
mode: "md",
25-
value: `\
12+
const sourceIDOffset = 1000000;
13+
try {
14+
const compiled = transpile(cell);
15+
retVal.push({
16+
id: cell.id,
17+
...compiled,
18+
body: options.inline ? constructFunction(compiled.body, `cell_${cell.id}`) : compiled.body,
19+
});
20+
if (cell.pinned) {
21+
const compiled = transpile({
22+
...cell,
23+
mode: "md",
24+
value: `\
2625
\`\`\`${cell.mode}
2726
${cell.value}
2827
\`\`\``
29-
});
30-
retVal.push({
31-
id: id++,
32-
...compiled,
33-
body: options.inline ? constructFunction(compiled.body, `cell_${id}`) : compiled.body,
34-
});
35-
}
36-
} catch (error) {
37-
console.error(`Error compiling cell ${id}:`, error);
28+
});
29+
retVal.push({
30+
id: sourceIDOffset + cell.id,
31+
...compiled,
32+
body: options.inline ? constructFunction(compiled.body, `cell_source_${sourceIDOffset + cell.id}`) : compiled.body,
33+
});
3834
}
35+
} catch (error) {
36+
console.error(`Error compiling cell ${cell.id}:`, error);
37+
}
38+
return retVal;
39+
}
40+
41+
export function compileNotebook(notebook: Notebook, options: CompileKitOptions = { inline: true }): Definition[] {
42+
const retVal: Definition[] = [];
43+
for (const cell of notebook.cells) {
44+
const cellDefs = compileCell(cell, options);
45+
retVal.push(...cellDefs);
3946
}
4047
return retVal;
4148
}
49+
50+
export function resetCellIDs(notebook: Notebook, start: number = 0, increment: number = 1): Notebook {
51+
for (const cell of notebook.cells) {
52+
cell.id = start;
53+
start += increment;
54+
}
55+
return notebook;
56+
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export * from "./util.ts";
21
export * from "./compiler.ts";
32
export * from "./util.ts";

packages/observablehq-compiler/src/kit/runtime.ts

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { type Definition } from "./index.ts";
44
import "@observablehq/notebook-kit/index.css";
55
import "@observablehq/notebook-kit/theme-air.css";
66

7+
export { DefineState };
8+
79
export class NotebookRuntime extends NotebookRuntimeBase {
810

911
stateById = new Map<number, DefineState>();
@@ -16,23 +18,50 @@ export class NotebookRuntime extends NotebookRuntimeBase {
1618
return this.stateById.has(cellId);
1719
}
1820

19-
async add(cellId: number, definition: Definition, placeholderDiv: HTMLDivElement): Promise<void> {
20-
let state: DefineState | undefined = this.stateById.get(cellId);
21-
if (state) {
22-
this.remove(cellId);
21+
async add(definition: Definition): Promise<HTMLDivElement> {
22+
if (this.stateById.has(definition.id)) {
23+
throw new Error(`Cell with id ${definition.id} already exists`);
2324
}
24-
state = { root: placeholderDiv, expanded: [], variables: [] };
25-
this.stateById.set(cellId, state);
25+
const placeholderDiv = document.createElement("div");
26+
placeholderDiv.className = "observablehq observablehq--cell";
27+
const state = { root: placeholderDiv, expanded: [], variables: [] };
28+
this.stateById.set(definition.id, state);
2629
this.define(state, definition);
2730
await this.runtime._computeNow();
31+
return state.root;
2832
}
2933

30-
async remove(cellId: number): Promise<void> {
34+
async update(definition: Definition): Promise<HTMLDivElement> {
35+
const state = this.stateById.get(definition.id);
36+
if (!state) {
37+
throw new Error(`Cell with id ${definition.id} does not exist`);
38+
}
39+
await this.clear(definition.id);
40+
this.define(state, definition);
41+
await this.runtime._computeNow();
42+
return state.root;
43+
}
44+
45+
async clear(cellId: number): Promise<void> {
3146
const state = this.stateById.get(cellId);
32-
if (!state) return;
47+
if (!state) {
48+
throw new Error(`Cell with id ${cellId} does not exist`);
49+
}
3350
[...state.variables].forEach(v => v.delete());
51+
await this.runtime._computeNow();
52+
state.variables = [];
53+
state.expanded = [];
54+
state.root.innerHTML = "";
55+
}
56+
57+
async remove(cellId: number): Promise<void> {
58+
const state = this.stateById.get(cellId);
59+
if (!state) {
60+
throw new Error(`Cell with id ${cellId} does not exist`);
61+
}
62+
void this.clear(cellId);
3463
this.stateById.delete(cellId);
35-
state.root?.remove();
64+
state.root.remove();
3665
await this.runtime._computeNow();
3766
}
3867

@@ -44,14 +73,15 @@ export class NotebookRuntime extends NotebookRuntimeBase {
4473
await this.runtime._computeNow();
4574
}
4675

47-
render(definitions: Definition[], target: HTMLElement) {
48-
definitions.forEach(definition => {
49-
const placeholderDiv = document.createElement("div");
50-
placeholderDiv.id = `cell-${definition.id}`;
51-
placeholderDiv.className = "observablehq observablehq--cell";
52-
this.stateById.set(definition.id, { root: placeholderDiv, expanded: [], variables: [] });
53-
this.define(this.stateById.get(definition.id)!, definition);
54-
target.appendChild(placeholderDiv);
55-
});
76+
async render(definitions: Definition[], target: HTMLElement) {
77+
for (const definition of definitions) {
78+
let observableDiv: HTMLDivElement;
79+
if (this.stateById.has(definition.id)) {
80+
observableDiv = await this.update(definition);
81+
} else {
82+
observableDiv = await this.add(definition);
83+
}
84+
target.appendChild(observableDiv);
85+
}
5686
}
5787
}

packages/observablehq-compiler/tests/index-notebookkit.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { compileKit, html2notebook } from "@hpcc-js/observablehq-compiler";
1+
import { compileNotebook, html2notebook } from "@hpcc-js/observablehq-compiler";
22
import { NotebookRuntime } from "@hpcc-js/observablehq-compiler/runtime";
33

44
// import "@observablehq/notebook-kit/theme-air.css";
@@ -19,13 +19,13 @@ import { NotebookRuntime } from "@hpcc-js/observablehq-compiler/runtime";
1919

2020
export async function testHtml(target) {
2121

22-
const element = document.getElementById(target);
23-
if (!element) {
24-
throw new Error(`Element with id ${target} not found`);
25-
}
22+
const element = document.getElementById(target);
23+
if (!element) {
24+
throw new Error(`Element with id ${target} not found`);
25+
}
2626

27-
// const html = await fetch("../tests/albers-usa-projection.txt");
28-
const html = `\
27+
// const html = await fetch("../tests/albers-usa-projection.txt");
28+
const html = `\
2929
<!doctype html>
3030
<notebook theme="slate">
3131
<title>Observable Notebooks 2.0 System guide</title>
@@ -210,8 +210,8 @@ export async function testHtml(target) {
210210
</script>
211211
</notebook>
212212
`;
213-
const notebook = html2notebook(html);
214-
const definitions = compileKit(notebook);
215-
const runtime = new NotebookRuntime();
216-
runtime.render(definitions, element);
213+
const notebook = html2notebook(html);
214+
const definitions = compileNotebook(notebook);
215+
const runtime = new NotebookRuntime();
216+
runtime.render(definitions, element);
217217
}

packages/observablehq-compiler/tests/index-notebookkit.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { omd2notebookKit, ojs2notebookKit, compileKit, html2notebook } from "../src/index.ts";
1+
import { omd2notebookKit, ojs2notebookKit, compileNotebook, html2notebook } from "../src/index.ts";
22
import { NotebookRuntime } from "../src/kit/runtime.ts";
33

44
// import "@observablehq/notebook-kit/theme-air.css";
55
// import "@observablehq/notebook-kit/theme-coffee.css";
66
// import "@observablehq/notebook-kit/theme-cotton.css";
77
// import "@observablehq/notebook-kit/theme-deep-space.css";
88
// import "@observablehq/notebook-kit/theme-glacier.css";
9-
// import "@observablehq/notebook-kit/theme-ink.css";
9+
import "@observablehq/notebook-kit/theme-ink.css";
1010
// import "@observablehq/notebook-kit/theme-midnight.css";
1111
// import "@observablehq/notebook-kit/theme-near-midnight.css";
1212
// import "@observablehq/notebook-kit/theme-ocean-floor.css";
@@ -19,28 +19,28 @@ import { NotebookRuntime } from "../src/kit/runtime.ts";
1919

2020
export async function testHtml(target: string): Promise<void> {
2121

22-
const element = document.getElementById(target) as HTMLDivElement;
23-
if (!element) {
24-
throw new Error(`Element with id ${target} not found`);
25-
}
26-
27-
// const html = await fetch("../tests/albers-usa-projection.txt");
28-
const html = await fetch("../tests/system-guide.txt")
29-
.then((response) => {
30-
return response.text();
31-
});
32-
const notebook = html2notebook(html);
33-
const definitions = compileKit(notebook);
34-
const runtime = new NotebookRuntime();
35-
runtime.render(definitions, element);
22+
const element = document.getElementById(target) as HTMLDivElement;
23+
if (!element) {
24+
throw new Error(`Element with id ${target} not found`);
25+
}
26+
27+
// const html = await fetch("../tests/albers-usa-projection.txt");
28+
const html = await fetch("../tests/system-guide.txt")
29+
.then((response) => {
30+
return response.text();
31+
});
32+
const notebook = html2notebook(html);
33+
const definitions = compileNotebook(notebook);
34+
const runtime = new NotebookRuntime();
35+
runtime.render(definitions, element);
3636
}
3737

3838
export function testOmd(target: string): void {
39-
const element = document.getElementById(target) as HTMLDivElement;
40-
if (!element) {
41-
throw new Error(`Element with id ${target} not found`);
42-
}
43-
const omd2 = `\
39+
const element = document.getElementById(target) as HTMLDivElement;
40+
if (!element) {
41+
throw new Error(`Element with id ${target} not found`);
42+
}
43+
const omd2 = `\
4444
4545
## Imports (dot)
4646
@@ -331,18 +331,18 @@ value
331331
332332
`;
333333

334-
const notebook = omd2notebookKit(omd2);
335-
const definitions = compileKit(notebook);
336-
const runtime = new NotebookRuntime();
337-
runtime.render(definitions, element);
334+
const notebook = omd2notebookKit(omd2);
335+
const definitions = compileKit(notebook);
336+
const runtime = new NotebookRuntime();
337+
runtime.render(definitions, element);
338338
}
339339

340340
export function testOjs(target: string): void {
341-
const element = document.getElementById(target) as HTMLDivElement;
342-
if (!element) {
343-
throw new Error(`Element with id ${target} not found`);
344-
}
345-
const ojs = `\
341+
const element = document.getElementById(target) as HTMLDivElement;
342+
if (!element) {
343+
throw new Error(`Element with id ${target} not found`);
344+
}
345+
const ojs = `\
346346
2 * 3 * 7
347347
{
348348
let sum = 0;
@@ -357,8 +357,8 @@ md \`# lib.ojs\`;
357357
mo = 38 + 40;
358358
`;
359359

360-
const notebook = ojs2notebookKit(ojs);
361-
const definitions = compileKit(notebook);
362-
const runtime = new NotebookRuntime();
363-
runtime.render(definitions, element);
360+
const notebook = ojs2notebookKit(ojs);
361+
const definitions = compileKit(notebook);
362+
const runtime = new NotebookRuntime();
363+
runtime.render(definitions, element);
364364
}

0 commit comments

Comments
 (0)