@gramlot/gramlot-examples 0.3.4 → 0.3.5
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.5.
|
|
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.5",
|
|
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"));
|
|
@@ -6842,16 +6883,17 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
6842
6883
|
function mountGallery(renderer, root, inspector) {
|
|
6843
6884
|
const state = root.builder.data.getItem("galleryIndex");
|
|
6844
6885
|
const settings = mountGallerySettings(renderer, root, state);
|
|
6845
|
-
|
|
6886
|
+
let guide = null;
|
|
6846
6887
|
const mobile = mountGalleryMobile(renderer, root, inspector, state, (visible) => {
|
|
6847
|
-
if (!visible) guide
|
|
6888
|
+
if (!visible) guide?.pause();
|
|
6848
6889
|
});
|
|
6849
6890
|
const index = mountIndexPage(renderer, root, (entries2, active) => {
|
|
6850
6891
|
inspector.update(entries2, active);
|
|
6851
6892
|
mobile.update(entries2, active);
|
|
6852
|
-
guide
|
|
6893
|
+
guide?.update(entries2, active);
|
|
6853
6894
|
}, (entry, { byId, node, set, listen }) => {
|
|
6854
6895
|
const { key } = entry;
|
|
6896
|
+
if (key === "l04" && !guide) guide = mountGuidedDemo(renderer, root);
|
|
6855
6897
|
renderReadme(byId(`readme-${key}`));
|
|
6856
6898
|
if (entry.panel.classList.contains("index-category")) return;
|
|
6857
6899
|
highlightCode(byId(`code-${key}`));
|
|
@@ -6862,9 +6904,47 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
6862
6904
|
logic.textContent = source ?? "";
|
|
6863
6905
|
if (source !== null) highlightCode(logic);
|
|
6864
6906
|
}
|
|
6865
|
-
}, {
|
|
6907
|
+
}, {
|
|
6908
|
+
containerLayout: true,
|
|
6909
|
+
state,
|
|
6910
|
+
async createEntry(entry) {
|
|
6911
|
+
const document2 = renderer.destination.ownerDocument;
|
|
6912
|
+
const node = (id) => renderer.elements.get(document2.getElementById(id)).node;
|
|
6913
|
+
const source = (id) => root.builder.wrapSource(node(id));
|
|
6914
|
+
const category = entry.open.classList.contains("index-family");
|
|
6915
|
+
const pane = source("index-pages").contentPane({
|
|
6916
|
+
id: `panel-${entry.key}`,
|
|
6917
|
+
title: entry.open.textContent,
|
|
6918
|
+
pageName: entry.key,
|
|
6919
|
+
hidden: true,
|
|
6920
|
+
class: category ? "index-category" : "gallery-panel"
|
|
6921
|
+
});
|
|
6922
|
+
const code = category ? null : source("gallery-code-pages").tabContainer({
|
|
6923
|
+
id: `codepane-${entry.key}`,
|
|
6924
|
+
class: "gallery-codepane gallery-source-tabs",
|
|
6925
|
+
hidden: true,
|
|
6926
|
+
height: "100%",
|
|
6927
|
+
tabMode: "underline"
|
|
6928
|
+
});
|
|
6929
|
+
try {
|
|
6930
|
+
const runtime = renderer.binding.gramlot;
|
|
6931
|
+
const results = await Promise.all([
|
|
6932
|
+
runtime.src.remoteSource(node(`panel-${entry.key}`), "lesson", { key: entry.key }),
|
|
6933
|
+
...code ? [runtime.src.remoteSource(node(`codepane-${entry.key}`), "lesson", { key: entry.key, view: "code" })] : []
|
|
6934
|
+
]);
|
|
6935
|
+
if (results.some((mounted) => !mounted)) throw new Error("Lesson loading was cancelled");
|
|
6936
|
+
} catch (error) {
|
|
6937
|
+
for (const target of [pane, code]) if (target?.parentBag) target.parentBag.popNode(target.label);
|
|
6938
|
+
throw error;
|
|
6939
|
+
}
|
|
6940
|
+
},
|
|
6941
|
+
onLoad(key, status) {
|
|
6942
|
+
const element = renderer.destination.ownerDocument.getElementById("gallery-load-status");
|
|
6943
|
+
renderer.elements.get(element).node.setValue(status === "loading" ? "Loading lesson\u2026" : status === "error" ? "Could not load this lesson. Select it again to retry." : "");
|
|
6944
|
+
}
|
|
6945
|
+
});
|
|
6866
6946
|
return { dispose() {
|
|
6867
|
-
guide
|
|
6947
|
+
guide?.dispose();
|
|
6868
6948
|
settings.dispose();
|
|
6869
6949
|
index.dispose();
|
|
6870
6950
|
mobile.dispose();
|
|
@@ -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,24 @@ 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 === 'example') {
|
|
106
|
+
if (item.examples) this.groupPanel(root, item);
|
|
107
|
+
else this.panel(root, item);
|
|
108
|
+
} else throw new Error(`Unknown gallery view: ${key}/${view}`);
|
|
109
|
+
}
|
|
101
110
|
panel(parent, example) {
|
|
102
111
|
const key = example.key;
|
|
103
|
-
const panel = parent.
|
|
104
|
-
class: 'gallery-panel', hidden: true}).div({class: 'gallery-panel-layout'});
|
|
112
|
+
const panel = parent.div({class: 'gallery-panel-layout'});
|
|
105
113
|
const header = panel.header({class: 'gallery-explanation'});
|
|
106
114
|
header.h2(example.title);
|
|
107
115
|
header.p('In this example: ' + example.description);
|
|
@@ -121,8 +129,7 @@ export class GalleryPage extends IndexPage {
|
|
|
121
129
|
}
|
|
122
130
|
codePanel(parent, example) {
|
|
123
131
|
const key = example.key;
|
|
124
|
-
const codepane = parent
|
|
125
|
-
hidden: true, height: '100%', tabMode: 'underline'});
|
|
132
|
+
const codepane = parent;
|
|
126
133
|
codepane.contentPane({title: 'JavaScript', pageName: 'javascript', id: `code-panel-${key}`})
|
|
127
134
|
.pre({tabindex: 0}).code(example.source, {class: 'language-javascript', id: `code-${key}`});
|
|
128
135
|
if (example.logic != null) {
|
|
@@ -131,3 +138,5 @@ export class GalleryPage extends IndexPage {
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
}
|
|
141
|
+
|
|
142
|
+
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
|
});
|