@gramlot/gramlot-examples 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @gramlot/gramlot-examples
|
|
|
12
12
|
[](https://pypi.org/project/gramlot-examples/)
|
|
13
13
|
[](https://www.npmjs.com/package/@gramlot/gramlot-examples)
|
|
14
14
|
|
|
15
|
-
Current release: 0.3.
|
|
15
|
+
Current release: 0.3.6.
|
|
16
16
|
|
|
17
17
|
[Documentation and learning sequence](https://gramlot-examples.readthedocs.io/en/latest/)
|
|
18
18
|
include every lesson, its Python and JavaScript source, and the gallery guide.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gramlot/gramlot-examples",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Gramlot example pages, the gallery page and its catalogue.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"prepack": "node scripts/build-browser.mjs"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@gramlot/gramlot": ">=0.3.
|
|
17
|
+
"@gramlot/gramlot": ">=0.3.3"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@genrojs/bag": ">=0.11.0",
|
|
@@ -1920,7 +1920,9 @@
|
|
|
1920
1920
|
// node_modules/@gramlot/gramlot/src/index-page-runtime.js
|
|
1921
1921
|
function mountIndexPage(renderer, root, onSelection = () => {
|
|
1922
1922
|
}, prepareEntry = () => {
|
|
1923
|
-
}, { containerLayout = false, state: suppliedState = null
|
|
1923
|
+
}, { containerLayout = false, state: suppliedState = null, createEntry = null, onLoad = () => {
|
|
1924
|
+
} } = {}) {
|
|
1925
|
+
if (createEntry && !containerLayout) throw new TypeError("Deferred index entries require containerLayout");
|
|
1924
1926
|
const shell = renderer.records.get(root).element;
|
|
1925
1927
|
const document2 = shell.ownerDocument;
|
|
1926
1928
|
const window2 = document2.defaultView;
|
|
@@ -2028,17 +2030,53 @@
|
|
|
2028
2030
|
set(shell, { data_code_visible: String(visible) });
|
|
2029
2031
|
}
|
|
2030
2032
|
};
|
|
2033
|
+
const prepare = (entry) => {
|
|
2034
|
+
entry.panel = byId(`panel-${entry.key}`);
|
|
2035
|
+
entry.tab = containerLayout ? entry.panel : byId(`tab-${entry.key}`);
|
|
2036
|
+
entry.code = byId(`codepane-${entry.key}`);
|
|
2037
|
+
if (entry.key === "intro") return;
|
|
2038
|
+
prepareEntry(entry, { byId, node, set, listen });
|
|
2039
|
+
entry.frame = byId(`frame-${entry.key}`);
|
|
2040
|
+
if (entry.frame) listen(entry.frame, "load", () => send(entry.frame));
|
|
2041
|
+
};
|
|
2042
|
+
const openEntry = (entry) => {
|
|
2043
|
+
if (opened.getItem(entry.key)) return;
|
|
2044
|
+
opened.setItem(entry.key, true);
|
|
2045
|
+
set(containerLayout ? entry.panel : entry.tab, { hidden: false });
|
|
2046
|
+
if (entry.frame) set(entry.frame, { src: entry.open.getAttribute("href") });
|
|
2047
|
+
};
|
|
2031
2048
|
let selecting = false;
|
|
2032
2049
|
function select(key) {
|
|
2033
|
-
if (selecting) return;
|
|
2050
|
+
if (selecting || disposed) return;
|
|
2051
|
+
const requested = entries2.get(key);
|
|
2052
|
+
if (!requested) return;
|
|
2053
|
+
state.setItem("requested", key);
|
|
2054
|
+
if (!requested.panel) {
|
|
2055
|
+
onLoad(key, "loading");
|
|
2056
|
+
if (!requested.loading) {
|
|
2057
|
+
requested.loading = Promise.resolve().then(() => {
|
|
2058
|
+
if (!disposed) return createEntry(requested);
|
|
2059
|
+
}).then(() => {
|
|
2060
|
+
if (disposed) return;
|
|
2061
|
+
prepare(requested);
|
|
2062
|
+
openEntry(requested);
|
|
2063
|
+
if (state.getItem("requested") === key) {
|
|
2064
|
+
onLoad(key, "ready");
|
|
2065
|
+
select(key);
|
|
2066
|
+
}
|
|
2067
|
+
}).catch((error) => {
|
|
2068
|
+
if (!disposed && state.getItem("requested") === key) onLoad(key, "error", error);
|
|
2069
|
+
}).finally(() => {
|
|
2070
|
+
requested.loading = null;
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
onLoad(key, "ready");
|
|
2034
2076
|
selecting = true;
|
|
2035
2077
|
try {
|
|
2036
2078
|
const entry = entries2.get(key);
|
|
2037
|
-
|
|
2038
|
-
opened.setItem(key, true);
|
|
2039
|
-
set(containerLayout ? entry.panel : entry.tab, { hidden: false });
|
|
2040
|
-
if (entry.frame) set(entry.frame, { src: entry.open.getAttribute("href") });
|
|
2041
|
-
}
|
|
2079
|
+
openEntry(entry);
|
|
2042
2080
|
if (entry.family) set(entry.family, { open: true });
|
|
2043
2081
|
for (const [current, item] of entries2) {
|
|
2044
2082
|
const active = current === key;
|
|
@@ -2202,9 +2240,9 @@
|
|
|
2202
2240
|
});
|
|
2203
2241
|
listen(window2, "resize", () => setSidebarWidth(state.getItem("preferredSidebarWidth")));
|
|
2204
2242
|
}
|
|
2205
|
-
const choices = containerLayout ? byId("index-pages").children : shell.querySelectorAll(".index-tabs button");
|
|
2243
|
+
const choices = createEntry ? [byId("panel-intro"), ...shell.querySelectorAll('.index-sidebar a[id^="open-"]')] : containerLayout ? byId("index-pages").children : shell.querySelectorAll(".index-tabs button");
|
|
2206
2244
|
for (const tab of choices) {
|
|
2207
|
-
const key = tab.id.slice(containerLayout ? "panel-".length : "tab-".length);
|
|
2245
|
+
const key = tab.id.slice(createEntry && tab.id.startsWith("open-") ? "open-".length : containerLayout ? "panel-".length : "tab-".length);
|
|
2208
2246
|
const entry = {
|
|
2209
2247
|
key,
|
|
2210
2248
|
tab,
|
|
@@ -2213,7 +2251,7 @@
|
|
|
2213
2251
|
open: key === "intro" ? null : byId(`open-${key}`)
|
|
2214
2252
|
};
|
|
2215
2253
|
entries2.set(key, entry);
|
|
2216
|
-
if (containerLayout ? node(
|
|
2254
|
+
if (entry.panel && (containerLayout ? node(entry.panel).getAttr("hidden") !== true : !tab.hidden)) opened.setItem(key, true);
|
|
2217
2255
|
if (!containerLayout && tab.getAttribute("aria-selected") === "true") state.setItem("active", key);
|
|
2218
2256
|
if (!containerLayout) listen(tab, "click", () => select(key));
|
|
2219
2257
|
if (entry.open) listen(entry.open, "click", (event) => {
|
|
@@ -2231,12 +2269,15 @@
|
|
|
2231
2269
|
});
|
|
2232
2270
|
if (key === "intro") continue;
|
|
2233
2271
|
entry.family = entry.open.closest("details");
|
|
2234
|
-
|
|
2235
|
-
if (entry.panel.classList.contains("index-category")) continue;
|
|
2236
|
-
entry.frame = byId(`frame-${key}`);
|
|
2237
|
-
listen(entry.frame, "load", () => send(entry.frame));
|
|
2272
|
+
if (entry.panel) prepare(entry);
|
|
2238
2273
|
}
|
|
2239
2274
|
if (containerLayout) {
|
|
2275
|
+
const pages = byId("index-pages");
|
|
2276
|
+
listen(pages, "click", (event) => {
|
|
2277
|
+
if (event.target === pages && event.composedPath().some((element) => element instanceof window2.Element && element.getAttribute("role") === "tab")) {
|
|
2278
|
+
select(state.getItem("active"));
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2240
2281
|
const subscription = `${shell.id}-selection`;
|
|
2241
2282
|
state.subscribe(subscription, { any: (event) => {
|
|
2242
2283
|
if (event.node === state.getNode("active")) select(state.getItem("active"));
|
|
@@ -6838,33 +6879,160 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
6838
6879
|
element.innerHTML = purifier.sanitize(markdown.parse(element.textContent), { USE_PROFILES: { html: true } });
|
|
6839
6880
|
}
|
|
6840
6881
|
|
|
6882
|
+
// src/gramlot_examples/gallery/browser/gallery-content.js
|
|
6883
|
+
var pending = /* @__PURE__ */ new WeakMap();
|
|
6884
|
+
function mountGalleryContent(renderer, root) {
|
|
6885
|
+
const document2 = renderer.destination.ownerDocument;
|
|
6886
|
+
const byId = (id) => document2.getElementById(id);
|
|
6887
|
+
const node = (element) => renderer.elements.get(element).node;
|
|
6888
|
+
const entries2 = /* @__PURE__ */ new Map();
|
|
6889
|
+
let disposed = false;
|
|
6890
|
+
function format({ key, view }) {
|
|
6891
|
+
if (view === "description") {
|
|
6892
|
+
const readme = byId(`readme-${key}`);
|
|
6893
|
+
readme.textContent = node(readme).value;
|
|
6894
|
+
renderReadme(readme);
|
|
6895
|
+
return;
|
|
6896
|
+
}
|
|
6897
|
+
const code = byId(`code-${key}`);
|
|
6898
|
+
code.textContent = node(code).value;
|
|
6899
|
+
highlightCode(code);
|
|
6900
|
+
const logic = byId(`logic-${key}`);
|
|
6901
|
+
if (logic) {
|
|
6902
|
+
const source = logicSource(node(logic).value);
|
|
6903
|
+
node(byId(`logic-panel-${key}`)).setAttr({ hidden: source === null });
|
|
6904
|
+
logic.textContent = source ?? "";
|
|
6905
|
+
if (source !== null) highlightCode(logic);
|
|
6906
|
+
}
|
|
6907
|
+
}
|
|
6908
|
+
async function load(entry) {
|
|
6909
|
+
if (entry.loading) return;
|
|
6910
|
+
entry.loading = true;
|
|
6911
|
+
const targetId = entry.view === "code" ? `code-tabs-${entry.key}` : entry.element.id;
|
|
6912
|
+
const target = node(byId(targetId));
|
|
6913
|
+
const current = () => {
|
|
6914
|
+
const element = byId(targetId);
|
|
6915
|
+
return element && renderer.elements.get(element)?.node === target ? element : null;
|
|
6916
|
+
};
|
|
6917
|
+
let request = pending.get(target);
|
|
6918
|
+
if (!request) {
|
|
6919
|
+
request = renderer.binding.gramlot.src.remoteSource(target, "lesson", { key: entry.key, view: entry.view });
|
|
6920
|
+
pending.set(target, request);
|
|
6921
|
+
}
|
|
6922
|
+
try {
|
|
6923
|
+
const mounted = await request;
|
|
6924
|
+
if (disposed || !current() || !mounted) return;
|
|
6925
|
+
format(entry);
|
|
6926
|
+
observer.unobserve(entry.element);
|
|
6927
|
+
entries2.delete(entry.element);
|
|
6928
|
+
} catch {
|
|
6929
|
+
if (disposed || !current()) return;
|
|
6930
|
+
const status = current().querySelector('[role="status"]');
|
|
6931
|
+
node(status).setValue("Could not load this content. Close and reopen the panel to retry.");
|
|
6932
|
+
} finally {
|
|
6933
|
+
if (pending.get(target) === request) pending.delete(target);
|
|
6934
|
+
entry.loading = false;
|
|
6935
|
+
}
|
|
6936
|
+
}
|
|
6937
|
+
const observer = new document2.defaultView.IntersectionObserver((records) => {
|
|
6938
|
+
for (const record of records) {
|
|
6939
|
+
const entry = entries2.get(record.target);
|
|
6940
|
+
if (!entry) continue;
|
|
6941
|
+
const visible = record.isIntersecting && record.intersectionRect.width > 0 && record.intersectionRect.height > 0;
|
|
6942
|
+
if (visible && !entry.visible) void load(entry);
|
|
6943
|
+
entry.visible = visible;
|
|
6944
|
+
}
|
|
6945
|
+
});
|
|
6946
|
+
const dispose = () => {
|
|
6947
|
+
disposed = true;
|
|
6948
|
+
observer.disconnect();
|
|
6949
|
+
entries2.clear();
|
|
6950
|
+
};
|
|
6951
|
+
renderer.onDispose(root, dispose);
|
|
6952
|
+
return {
|
|
6953
|
+
observe(key) {
|
|
6954
|
+
for (const [view, id, loaded] of [
|
|
6955
|
+
["code", `codepane-${key}`, `code-${key}`],
|
|
6956
|
+
["description", `description-${key}`, `readme-${key}`]
|
|
6957
|
+
]) {
|
|
6958
|
+
const element = byId(id);
|
|
6959
|
+
const entry = { key, view, element, visible: false, loading: false };
|
|
6960
|
+
if (byId(loaded)) format(entry);
|
|
6961
|
+
else if (!entries2.has(element)) {
|
|
6962
|
+
entries2.set(element, entry);
|
|
6963
|
+
observer.observe(element);
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
},
|
|
6967
|
+
dispose
|
|
6968
|
+
};
|
|
6969
|
+
}
|
|
6970
|
+
|
|
6841
6971
|
// src/gramlot_examples/gallery/browser/gallery.js
|
|
6842
6972
|
function mountGallery(renderer, root, inspector) {
|
|
6843
6973
|
const state = root.builder.data.getItem("galleryIndex");
|
|
6844
6974
|
const settings = mountGallerySettings(renderer, root, state);
|
|
6845
|
-
|
|
6975
|
+
let guide = null;
|
|
6976
|
+
const content = mountGalleryContent(renderer, root);
|
|
6846
6977
|
const mobile = mountGalleryMobile(renderer, root, inspector, state, (visible) => {
|
|
6847
|
-
if (!visible) guide
|
|
6978
|
+
if (!visible) guide?.pause();
|
|
6848
6979
|
});
|
|
6849
6980
|
const index = mountIndexPage(renderer, root, (entries2, active) => {
|
|
6850
6981
|
inspector.update(entries2, active);
|
|
6851
6982
|
mobile.update(entries2, active);
|
|
6852
|
-
guide
|
|
6853
|
-
}, (entry, { byId
|
|
6983
|
+
guide?.update(entries2, active);
|
|
6984
|
+
}, (entry, { byId }) => {
|
|
6854
6985
|
const { key } = entry;
|
|
6855
|
-
|
|
6856
|
-
if (entry.panel.classList.contains("index-category"))
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
if (logic) {
|
|
6860
|
-
const source = logicSource(node(logic).value);
|
|
6861
|
-
set(byId(`logic-panel-${key}`), { hidden: source === null });
|
|
6862
|
-
logic.textContent = source ?? "";
|
|
6863
|
-
if (source !== null) highlightCode(logic);
|
|
6986
|
+
if (key === "l04" && !guide) guide = mountGuidedDemo(renderer, root);
|
|
6987
|
+
if (entry.panel.classList.contains("index-category")) {
|
|
6988
|
+
renderReadme(byId(`readme-${key}`));
|
|
6989
|
+
return;
|
|
6864
6990
|
}
|
|
6865
|
-
|
|
6991
|
+
content.observe(entry.key);
|
|
6992
|
+
}, {
|
|
6993
|
+
containerLayout: true,
|
|
6994
|
+
state,
|
|
6995
|
+
async createEntry(entry) {
|
|
6996
|
+
const document2 = renderer.destination.ownerDocument;
|
|
6997
|
+
const node = (id) => renderer.elements.get(document2.getElementById(id)).node;
|
|
6998
|
+
const source = (id) => root.builder.wrapSource(node(id));
|
|
6999
|
+
const category = entry.open.classList.contains("index-family");
|
|
7000
|
+
const pane = source("index-pages").contentPane({
|
|
7001
|
+
id: `panel-${entry.key}`,
|
|
7002
|
+
title: entry.open.textContent,
|
|
7003
|
+
pageName: entry.key,
|
|
7004
|
+
hidden: true,
|
|
7005
|
+
class: category ? "index-category" : "gallery-panel"
|
|
7006
|
+
});
|
|
7007
|
+
const code = category ? null : source("gallery-code-pages").div({
|
|
7008
|
+
id: `codepane-${entry.key}`,
|
|
7009
|
+
class: "gallery-codepane",
|
|
7010
|
+
hidden: true,
|
|
7011
|
+
height: "100%"
|
|
7012
|
+
});
|
|
7013
|
+
if (code) code.tabContainer({
|
|
7014
|
+
id: `code-tabs-${entry.key}`,
|
|
7015
|
+
class: "gallery-source-tabs",
|
|
7016
|
+
height: "100%",
|
|
7017
|
+
tabMode: "underline"
|
|
7018
|
+
}).contentPane({ title: "Code" }).p("Loading code\u2026", { role: "status" });
|
|
7019
|
+
try {
|
|
7020
|
+
const runtime = renderer.binding.gramlot;
|
|
7021
|
+
const mounted = await runtime.src.remoteSource(node(`panel-${entry.key}`), "lesson", { key: entry.key });
|
|
7022
|
+
if (!mounted) throw new Error("Lesson loading was cancelled");
|
|
7023
|
+
} catch (error) {
|
|
7024
|
+
for (const target of [pane, code]) if (target?.parentBag) target.parentBag.popNode(target.label);
|
|
7025
|
+
throw error;
|
|
7026
|
+
}
|
|
7027
|
+
},
|
|
7028
|
+
onLoad(key, status) {
|
|
7029
|
+
const element = renderer.destination.ownerDocument.getElementById("gallery-load-status");
|
|
7030
|
+
renderer.elements.get(element).node.setValue(status === "loading" ? "Loading lesson\u2026" : status === "error" ? "Could not load this lesson. Select it again to retry." : "");
|
|
7031
|
+
}
|
|
7032
|
+
});
|
|
6866
7033
|
return { dispose() {
|
|
6867
|
-
guide
|
|
7034
|
+
guide?.dispose();
|
|
7035
|
+
content.dispose();
|
|
6868
7036
|
settings.dispose();
|
|
6869
7037
|
index.dispose();
|
|
6870
7038
|
mobile.dispose();
|
|
@@ -7470,11 +7638,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
7470
7638
|
this.error = error.message;
|
|
7471
7639
|
}
|
|
7472
7640
|
if (message.type === "result") {
|
|
7473
|
-
const
|
|
7474
|
-
if (
|
|
7641
|
+
const pending2 = this.#pending.get(message.id);
|
|
7642
|
+
if (pending2) {
|
|
7475
7643
|
this.#pending.delete(message.id);
|
|
7476
|
-
if (message.ok && !this.error)
|
|
7477
|
-
else
|
|
7644
|
+
if (message.ok && !this.error) pending2.resolve(message);
|
|
7645
|
+
else pending2.reject(new Error(message.error || this.error));
|
|
7478
7646
|
}
|
|
7479
7647
|
}
|
|
7480
7648
|
this.#notify();
|
|
@@ -7531,7 +7699,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
7531
7699
|
this.#port.removeEventListener("message", this.#receive);
|
|
7532
7700
|
this.#port.close();
|
|
7533
7701
|
this.state = "disposed";
|
|
7534
|
-
for (const
|
|
7702
|
+
for (const pending2 of this.#pending.values()) pending2.reject(new Error("Inspected page disconnected"));
|
|
7535
7703
|
this.#pending.clear();
|
|
7536
7704
|
this.#notify();
|
|
7537
7705
|
}
|
|
@@ -7610,9 +7778,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
7610
7778
|
_subscribe() {
|
|
7611
7779
|
if (this._store) this._store.subscribe(this._subId, { any: () => {
|
|
7612
7780
|
if (this._pendingRender) return;
|
|
7613
|
-
const
|
|
7781
|
+
const pending2 = this._pendingRender = {};
|
|
7614
7782
|
queueMicrotask(() => {
|
|
7615
|
-
if (this._pendingRender ===
|
|
7783
|
+
if (this._pendingRender === pending2 && this.isConnected) this._render();
|
|
7616
7784
|
});
|
|
7617
7785
|
} });
|
|
7618
7786
|
}
|
|
@@ -10,8 +10,8 @@ export class GalleryPage extends IndexPage {
|
|
|
10
10
|
static galleryScript = '/gallery/dist/gallery.js';
|
|
11
11
|
static css = [...IndexPage.css, '/gallery/gallery.css'];
|
|
12
12
|
static get navigation() {
|
|
13
|
-
return this.families.map(group => ({
|
|
14
|
-
children: group.examples.map(item => ({
|
|
13
|
+
return this.families.map(group => ({key: group.key, title: group.title,
|
|
14
|
+
children: group.examples.map(item => ({key: item.key, title: item.title, url: item.frameUrl}))}));
|
|
15
15
|
}
|
|
16
16
|
main(root) {
|
|
17
17
|
const families = this.constructor.navigation;
|
|
@@ -38,17 +38,10 @@ export class GalleryPage extends IndexPage {
|
|
|
38
38
|
const panels = center.tabContainer({id: 'index-pages', class: 'index-main', height: '100%',
|
|
39
39
|
selectedPage: '^galleryIndex.active', tabMode: 'tabs'});
|
|
40
40
|
this.introduction(panels.contentPane({id: 'panel-intro', title: 'Introduction', pageName: 'intro', class: 'index-intro'}));
|
|
41
|
-
for (const family of families) {
|
|
42
|
-
this.groupPanel(panels.contentPane({id: `panel-${family.key}`, title: family.title,
|
|
43
|
-
pageName: family.key, class: 'index-category', hidden: true}), family);
|
|
44
|
-
for (const example of family.children) this.panel(panels, example);
|
|
45
|
-
}
|
|
46
41
|
const code = regions.contentPane({region: 'right', width: '420px', minSize: 220, maxSize: 900,
|
|
47
42
|
splitter: true, drawer: true, drawer_icon: '⌘', drawer_label: 'Code', drawer_minimize: true,
|
|
48
43
|
node_id: 'galleryCode', id: 'index-code', aria_label: 'Example source code', hidden: true});
|
|
49
|
-
|
|
50
|
-
for (const example of family.children) this.codePanel(code, example);
|
|
51
|
-
}
|
|
44
|
+
code.div({id: 'gallery-code-pages', height: '100%'});
|
|
52
45
|
this.footer(shell.footer({class: 'index-footer'}));
|
|
53
46
|
this.behavior(shell);
|
|
54
47
|
}
|
|
@@ -84,6 +77,7 @@ export class GalleryPage extends IndexPage {
|
|
|
84
77
|
header.html_button('☰', {type: 'button', id: 'gallery-mobile-menu', class: 'gallery-mobile-only',
|
|
85
78
|
aria_label: 'Navigation', aria_controls: 'index-sidebar', aria_expanded: 'false'});
|
|
86
79
|
header.h1(this.constructor.title);
|
|
80
|
+
header.span('', {id: 'gallery-load-status', role: 'status', aria_live: 'polite'});
|
|
87
81
|
header.html_button('Code ⟨/⟩', {type: 'button', id: 'index-code-toggle',
|
|
88
82
|
aria_controls: 'index-code', aria_expanded: 'false', disabled: true});
|
|
89
83
|
const views = header.div({class: 'gallery-mobile-views gallery-mobile-only', role: 'group', aria_label: 'Gallery view'});
|
|
@@ -98,10 +92,25 @@ export class GalleryPage extends IndexPage {
|
|
|
98
92
|
shell.script({src: this.constructor.galleryScript});
|
|
99
93
|
}
|
|
100
94
|
|
|
95
|
+
galleryItem(key) {
|
|
96
|
+
for (const family of this.constructor.families) {
|
|
97
|
+
if (family.key === key) return family;
|
|
98
|
+
for (const example of family.examples) if (example.key === key) return example;
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Unknown gallery entry: ${key}`);
|
|
101
|
+
}
|
|
102
|
+
lesson(root, {key, view = 'example'}) {
|
|
103
|
+
const item = this.galleryItem(key);
|
|
104
|
+
if (view === 'code' && !item.examples) this.codePanel(root, item);
|
|
105
|
+
else if (view === 'description' && !item.examples) root.div(item.readme, {id: `readme-${key}`});
|
|
106
|
+
else if (view === 'example') {
|
|
107
|
+
if (item.examples) this.groupPanel(root, item);
|
|
108
|
+
else this.panel(root, item);
|
|
109
|
+
} else throw new Error(`Unknown gallery view: ${key}/${view}`);
|
|
110
|
+
}
|
|
101
111
|
panel(parent, example) {
|
|
102
112
|
const key = example.key;
|
|
103
|
-
const panel = parent.
|
|
104
|
-
class: 'gallery-panel', hidden: true}).div({class: 'gallery-panel-layout'});
|
|
113
|
+
const panel = parent.div({class: 'gallery-panel-layout'});
|
|
105
114
|
const header = panel.header({class: 'gallery-explanation'});
|
|
106
115
|
header.h2(example.title);
|
|
107
116
|
header.p('In this example: ' + example.description);
|
|
@@ -116,13 +125,12 @@ export class GalleryPage extends IndexPage {
|
|
|
116
125
|
guidedDemo(layout);
|
|
117
126
|
}
|
|
118
127
|
stage.iframe({title: `${example.title} preview`, name: `example-${key}`, id: `frame-${key}`});
|
|
119
|
-
preview.contentPane({title: 'Explanation', pageName: 'explanation', class: 'gallery-lesson', overflow: 'auto'})
|
|
120
|
-
.
|
|
128
|
+
preview.contentPane({title: 'Explanation', pageName: 'explanation', class: 'gallery-lesson', overflow: 'auto', id: `description-${key}`})
|
|
129
|
+
.p('Loading explanation…', {role: 'status'});
|
|
121
130
|
}
|
|
122
131
|
codePanel(parent, example) {
|
|
123
132
|
const key = example.key;
|
|
124
|
-
const codepane = parent
|
|
125
|
-
hidden: true, height: '100%', tabMode: 'underline'});
|
|
133
|
+
const codepane = parent;
|
|
126
134
|
codepane.contentPane({title: 'JavaScript', pageName: 'javascript', id: `code-panel-${key}`})
|
|
127
135
|
.pre({tabindex: 0}).code(example.source, {class: 'language-javascript', id: `code-${key}`});
|
|
128
136
|
if (example.logic != null) {
|
|
@@ -131,3 +139,5 @@ export class GalleryPage extends IndexPage {
|
|
|
131
139
|
}
|
|
132
140
|
}
|
|
133
141
|
}
|
|
142
|
+
|
|
143
|
+
GalleryPage.registerSource('lesson');
|
|
@@ -14,14 +14,14 @@ export class Page extends GalleryPage {
|
|
|
14
14
|
return families.map(({key, title, path, examples}) => {
|
|
15
15
|
const read = name => readFileSync(join(path, name), 'utf8');
|
|
16
16
|
return {
|
|
17
|
-
key, title, readme
|
|
17
|
+
key, title, get readme() { return read('README.md'); },
|
|
18
18
|
examples: examples.map(example => ({
|
|
19
19
|
key: example.key, title: example.title, description: example.description, folder: example.folder, frameUrl: example.key,
|
|
20
|
-
readme
|
|
21
|
-
source
|
|
20
|
+
get readme() { return read(`${example.folder}.md`); },
|
|
21
|
+
get source() { return read(`${example.folder}.js`); },
|
|
22
22
|
// The page module shows its own Logic; only a companion NN_name_aux.js is shown apart.
|
|
23
|
-
logic
|
|
24
|
-
? null : readFileSync(routes[example.key].logic, 'utf8'),
|
|
23
|
+
get logic() { return routes[example.key].logic === routes[example.key].page
|
|
24
|
+
? null : readFileSync(routes[example.key].logic, 'utf8'); },
|
|
25
25
|
})),
|
|
26
26
|
};
|
|
27
27
|
});
|