@gramlot/gramlot-examples 0.2.11 → 0.2.14
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 +10 -3
- package/package.json +3 -3
- package/src/gramlot_examples/catalog.json +36 -6
- package/src/gramlot_examples/gallery/dist/frame.js +1742 -5
- package/src/gramlot_examples/gallery/dist/gallery.js +4073 -17144
- package/src/gramlot_examples/gallery/dist/inspector.js +1801 -0
- package/src/gramlot_examples/gallery/dist/notices.json +2 -22
- package/src/gramlot_examples/gallery/dist/theme-styles.js +2 -0
- package/src/gramlot_examples/gallery/gallery-page.js +35 -67
- package/src/gramlot_examples/gallery/gallery.css +23 -94
- package/src/gramlot_examples/index.js +2 -1
- package/src/gramlot_examples/pages/README.md +84 -1
- package/src/gramlot_examples/pages/binding/01_pointers.js +9 -0
- package/src/gramlot_examples/pages/binding/01_pointers.md +56 -9
- package/src/gramlot_examples/pages/binding/02_variable_datapath.md +50 -4
- package/src/gramlot_examples/pages/binding/03_setters_and_defaults.md +45 -6
- package/src/gramlot_examples/pages/binding/04_native_editing.md +48 -6
- package/src/gramlot_examples/pages/binding/05_checkbox_radio.md +44 -4
- package/src/gramlot_examples/pages/binding/06_style_and_visibility.md +40 -6
- package/src/gramlot_examples/pages/binding/07_bound_svg.md +48 -3
- package/src/gramlot_examples/pages/binding/08_freeze.js +1 -1
- package/src/gramlot_examples/pages/binding/08_freeze.md +44 -6
- package/src/gramlot_examples/pages/binding/09_forms.js +2 -1
- package/src/gramlot_examples/pages/binding/09_forms.md +53 -7
- package/src/gramlot_examples/pages/binding/10_svg_shapes.md +50 -5
- package/src/gramlot_examples/pages/binding/11_complete_page.md +53 -7
- package/src/gramlot_examples/pages/binding/README.md +34 -0
- package/src/gramlot_examples/pages/controllers/01_formula.md +48 -7
- package/src/gramlot_examples/pages/controllers/02_controller.js +24 -2
- package/src/gramlot_examples/pages/controllers/02_controller.md +61 -6
- package/src/gramlot_examples/pages/controllers/03_named_logic.md +63 -7
- package/src/gramlot_examples/pages/controllers/04_inline_expressions.md +50 -7
- package/src/gramlot_examples/pages/controllers/05_node_methods.md +41 -7
- package/src/gramlot_examples/pages/controllers/06_button_controller.md +49 -6
- package/src/gramlot_examples/pages/controllers/07_events.md +45 -6
- package/src/gramlot_examples/pages/controllers/08_end_to_end.js +26 -1
- package/src/gramlot_examples/pages/controllers/08_end_to_end.md +138 -9
- package/src/gramlot_examples/pages/controllers/09_endpoint.md +47 -7
- package/src/gramlot_examples/pages/controllers/10_call_on_change.md +48 -8
- package/src/gramlot_examples/pages/controllers/11_call_on_click.js +6 -0
- package/src/gramlot_examples/pages/controllers/11_call_on_click.md +58 -6
- package/src/gramlot_examples/pages/controllers/12_remote.md +55 -8
- package/src/gramlot_examples/pages/controllers/13_cached_remote.js +2 -2
- package/src/gramlot_examples/pages/controllers/13_cached_remote.md +62 -7
- package/src/gramlot_examples/pages/controllers/14_data_remote.js +10 -2
- package/src/gramlot_examples/pages/controllers/14_data_remote.md +65 -6
- package/src/gramlot_examples/pages/controllers/15_lifecycle.js +51 -0
- package/src/gramlot_examples/pages/controllers/15_lifecycle.md +59 -0
- package/src/gramlot_examples/pages/controllers/16_attribute_projection.js +25 -0
- package/src/gramlot_examples/pages/controllers/16_attribute_projection.md +58 -0
- package/src/gramlot_examples/pages/controllers/17_data_exchange.js +51 -0
- package/src/gramlot_examples/pages/controllers/17_data_exchange.md +61 -0
- package/src/gramlot_examples/pages/controllers/18_publish_subscribe.js +36 -0
- package/src/gramlot_examples/pages/controllers/18_publish_subscribe.md +20 -0
- package/src/gramlot_examples/pages/controllers/19_frame_messages.js +39 -0
- package/src/gramlot_examples/pages/controllers/19_frame_messages.md +20 -0
- package/src/gramlot_examples/pages/controllers/README.md +57 -1
- package/src/gramlot_examples/pages/html_svg/01_hello_world.md +48 -4
- package/src/gramlot_examples/pages/html_svg/02_text_and_links.md +42 -4
- package/src/gramlot_examples/pages/html_svg/03_lists.md +45 -4
- package/src/gramlot_examples/pages/html_svg/04_semantic_page.md +44 -4
- package/src/gramlot_examples/pages/html_svg/05_tables.md +46 -4
- package/src/gramlot_examples/pages/html_svg/06_forms.md +48 -4
- package/src/gramlot_examples/pages/html_svg/07_native_disclosure.md +41 -4
- package/src/gramlot_examples/pages/html_svg/08_svg_shapes.md +45 -4
- package/src/gramlot_examples/pages/html_svg/09_svg_composition.md +44 -4
- package/src/gramlot_examples/pages/html_svg/10_cards_with_icons.md +42 -15
- package/src/gramlot_examples/pages/html_svg/11_static_report.md +37 -4
- package/src/gramlot_examples/pages/html_svg/12_complete_page.md +41 -4
- package/src/gramlot_examples/pages/html_svg/13_live_source.md +47 -16
- package/src/gramlot_examples/pages/html_svg/README.md +36 -0
|
@@ -0,0 +1,1801 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
// inspector-runtime:runtime
|
|
3
|
+
var Gramlot = window.gramlot.constructor;
|
|
4
|
+
var Bag = window.gramlot.data.constructor;
|
|
5
|
+
var GramlotBuilder = window.gramlot.src.builder.constructor;
|
|
6
|
+
|
|
7
|
+
// node_modules/@gramlot/gramlot/tools/gramlot-tools-inspector.js
|
|
8
|
+
var editorTypes = ["string", "number", "integer", "boolean", "date", "time", "datetime", "null"];
|
|
9
|
+
function scalarType(value) {
|
|
10
|
+
if (value === null) return "null";
|
|
11
|
+
if (["string", "boolean"].includes(typeof value)) return typeof value;
|
|
12
|
+
if (typeof value === "number" && Number.isFinite(value)) return Number.isSafeInteger(value) ? "integer" : "number";
|
|
13
|
+
if (value instanceof Date && Number.isFinite(value.getTime())) {
|
|
14
|
+
const iso = value.toISOString();
|
|
15
|
+
return iso.slice(0, 10) === "1970-01-01" ? "time" : iso.endsWith("T00:00:00.000Z") ? "date" : "datetime";
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function scalarText(value, type) {
|
|
20
|
+
if (type === "null") return "";
|
|
21
|
+
if (value instanceof Date) {
|
|
22
|
+
const iso = value.toISOString();
|
|
23
|
+
return type === "date" ? iso.slice(0, 10) : type === "time" ? iso.slice(11, 23) : iso;
|
|
24
|
+
}
|
|
25
|
+
return String(value);
|
|
26
|
+
}
|
|
27
|
+
function parseScalar(type, text) {
|
|
28
|
+
if (type === "string") return text;
|
|
29
|
+
if (type === "null") return null;
|
|
30
|
+
if (type === "boolean" && ["true", "false"].includes(text)) return text === "true";
|
|
31
|
+
if (type === "number" || type === "integer") {
|
|
32
|
+
if (/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(text.trim())) {
|
|
33
|
+
const value = Number(text);
|
|
34
|
+
if (Number.isFinite(value) && (type !== "integer" || Number.isSafeInteger(value))) return value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (["date", "time", "datetime"].includes(type)) {
|
|
38
|
+
const pattern = type === "date" ? /^\d{4}-\d{2}-\d{2}$/ : type === "time" ? /^\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?$/ : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?Z$/;
|
|
39
|
+
if (pattern.test(text)) {
|
|
40
|
+
const input = type === "date" ? `${text}T00:00:00Z` : type === "time" ? `1970-01-01T${text}Z` : text;
|
|
41
|
+
const date = new Date(input);
|
|
42
|
+
if (Number.isFinite(date.getTime())) {
|
|
43
|
+
const iso = date.toISOString();
|
|
44
|
+
const supplied = input.replace(/Z$/, "").split(/[T:.]/);
|
|
45
|
+
const actual = iso.replace(/Z$/, "").split(/[T:.]/);
|
|
46
|
+
if (supplied.every((part, index) => index === 4 ? Number(part.padEnd(3, "0")) === Number(actual[index]) : part === actual[index])) return date;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`Invalid ${type} value: ${text || "(empty)"}`);
|
|
51
|
+
}
|
|
52
|
+
var InspectorStyles = class {
|
|
53
|
+
#document;
|
|
54
|
+
#element = null;
|
|
55
|
+
#rule = null;
|
|
56
|
+
constructor(document2) {
|
|
57
|
+
this.#document = document2;
|
|
58
|
+
}
|
|
59
|
+
snapshot() {
|
|
60
|
+
const computed = this.#document.defaultView.getComputedStyle(this.#document.documentElement);
|
|
61
|
+
const names = /* @__PURE__ */ new Set();
|
|
62
|
+
for (let index = 0; index < computed.length; index++) {
|
|
63
|
+
const name = computed.item(index);
|
|
64
|
+
if (name.startsWith("--gramlot-")) names.add(name);
|
|
65
|
+
}
|
|
66
|
+
for (let index = 0; index < (this.#rule?.style.length ?? 0); index++) names.add(this.#rule.style.item(index));
|
|
67
|
+
return [...names].sort((left, right) => left.localeCompare(right, "en", { numeric: true })).map((name) => ({
|
|
68
|
+
name,
|
|
69
|
+
value: computed.getPropertyValue(name).trim(),
|
|
70
|
+
override: this.#rule?.style.getPropertyValue(name) ?? ""
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
set(name, value) {
|
|
74
|
+
if (typeof name !== "string" || !this.snapshot().some((row) => row.name === name)) {
|
|
75
|
+
throw new Error("Choose an existing --gramlot-* variable on the document root");
|
|
76
|
+
}
|
|
77
|
+
if (typeof value !== "string" || !value.trim() || !this.#document.defaultView.CSS.supports(name, value)) {
|
|
78
|
+
throw new Error("Enter a valid CSS variable value");
|
|
79
|
+
}
|
|
80
|
+
if (!this.#element) {
|
|
81
|
+
const element = this.#document.createElement("style");
|
|
82
|
+
this.#document.head.append(element);
|
|
83
|
+
if (!element.sheet) {
|
|
84
|
+
element.remove();
|
|
85
|
+
throw new Error("The page does not allow the inspector preview stylesheet");
|
|
86
|
+
}
|
|
87
|
+
element.sheet.insertRule(":root {}", 0);
|
|
88
|
+
this.#element = element;
|
|
89
|
+
this.#rule = element.sheet.cssRules[0];
|
|
90
|
+
}
|
|
91
|
+
this.#rule.style.setProperty(name, value, "important");
|
|
92
|
+
}
|
|
93
|
+
reset(name) {
|
|
94
|
+
if (typeof name !== "string" || !name.startsWith("--gramlot-")) throw new Error("Invalid CSS variable name");
|
|
95
|
+
this.#rule?.style.removeProperty(name);
|
|
96
|
+
if (this.#rule?.style.length === 0) this.dispose();
|
|
97
|
+
}
|
|
98
|
+
dispose() {
|
|
99
|
+
this.#element?.remove();
|
|
100
|
+
this.#element = null;
|
|
101
|
+
this.#rule = null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var roots = ["data", "source"];
|
|
105
|
+
var forbidden = /* @__PURE__ */ new Set(["_meta", "__proto__", "prototype", "constructor"]);
|
|
106
|
+
function attachInspectorAgent(gramlot, port) {
|
|
107
|
+
if (!gramlot || gramlot.state === "disposed") throw new TypeError("Expected a live Gramlot instance");
|
|
108
|
+
const Bag4 = gramlot.data.constructor;
|
|
109
|
+
const encodeScalar2 = (value) => {
|
|
110
|
+
const wrapper = new Bag4();
|
|
111
|
+
wrapper.setItem("value", value);
|
|
112
|
+
return wrapper.toTytx("json");
|
|
113
|
+
};
|
|
114
|
+
const decodeScalar2 = (text) => {
|
|
115
|
+
const value = Bag4.fromTytx(text, "json").getItem("value");
|
|
116
|
+
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
117
|
+
return value;
|
|
118
|
+
};
|
|
119
|
+
function cell(value) {
|
|
120
|
+
if (scalarType(value)) return { encoded: encodeScalar2(value) };
|
|
121
|
+
const display = value instanceof Bag4 ? `Bag (${value.getNodes().length} nodes)` : value === void 0 ? "undefined" : typeof value === "function" ? "[Function]" : typeof value === "bigint" || typeof value === "symbol" ? String(value) : JSON.stringify(value);
|
|
122
|
+
return { display, readonly: true };
|
|
123
|
+
}
|
|
124
|
+
const document2 = gramlot.src.renderer.destination.ownerDocument;
|
|
125
|
+
const window2 = document2.defaultView;
|
|
126
|
+
const styles = new InspectorStyles(document2);
|
|
127
|
+
const subscriber = `inspector-bridge-${window2.crypto.getRandomValues(new Uint32Array(4)).join("-")}`;
|
|
128
|
+
const identities = /* @__PURE__ */ new WeakMap();
|
|
129
|
+
const versions = /* @__PURE__ */ new WeakMap();
|
|
130
|
+
let nextNode = 0, stopped = false, scheduled = false;
|
|
131
|
+
const breakpoints = /* @__PURE__ */ new Map();
|
|
132
|
+
const bag = (root) => {
|
|
133
|
+
if (!roots.includes(root)) throw new Error(`Unknown root: ${root}`);
|
|
134
|
+
return root === "data" ? gramlot.data : gramlot.src.source;
|
|
135
|
+
};
|
|
136
|
+
const identity = (node) => {
|
|
137
|
+
if (!identities.has(node)) identities.set(node, ++nextNode);
|
|
138
|
+
return identities.get(node);
|
|
139
|
+
};
|
|
140
|
+
const describe = (store, ancestors = /* @__PURE__ */ new Set()) => {
|
|
141
|
+
if (ancestors.has(store)) throw new Error("Inspector snapshots require an acyclic Bag");
|
|
142
|
+
const next = /* @__PURE__ */ new Set([...ancestors, store]);
|
|
143
|
+
return store.getNodes().map((node) => {
|
|
144
|
+
const value = node.getValue(true);
|
|
145
|
+
const branch = !node.resolver && value instanceof Bag4;
|
|
146
|
+
return {
|
|
147
|
+
id: identity(node),
|
|
148
|
+
label: node.label,
|
|
149
|
+
revision: versions.get(node) ?? 0,
|
|
150
|
+
value: branch ? null : cell(value),
|
|
151
|
+
children: branch ? describe(value, next) : null,
|
|
152
|
+
readonly: !!node.resolver,
|
|
153
|
+
attrs: Object.entries(node.getAttr()).filter(([name]) => name !== "_meta").map(([name, value2]) => [name, cell(value2)])
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
const snapshot = () => ({
|
|
158
|
+
data: describe(bag("data")),
|
|
159
|
+
source: describe(bag("source")),
|
|
160
|
+
breakpoints: [...breakpoints.values()].map(({ test, ...record }) => record)
|
|
161
|
+
});
|
|
162
|
+
const send = (data) => {
|
|
163
|
+
if (!stopped) port.postMessage(data);
|
|
164
|
+
};
|
|
165
|
+
const flush = () => {
|
|
166
|
+
if (scheduled || stopped) return;
|
|
167
|
+
scheduled = true;
|
|
168
|
+
queueMicrotask(() => {
|
|
169
|
+
scheduled = false;
|
|
170
|
+
if (stopped) return;
|
|
171
|
+
try {
|
|
172
|
+
send({ type: "snapshot", snapshot: snapshot() });
|
|
173
|
+
} catch (error) {
|
|
174
|
+
send({ type: "error", error: error.message });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
const hit = (config, root, fields) => {
|
|
179
|
+
config.hits++;
|
|
180
|
+
if (config.mode === "debugger") {
|
|
181
|
+
debugger;
|
|
182
|
+
} else window2.console.log("** Gramlot breakpoint **", root, fields);
|
|
183
|
+
};
|
|
184
|
+
const changed = (root, event) => {
|
|
185
|
+
const structural = event.evt === "ins" || event.evt === "del";
|
|
186
|
+
const labels = event.pathlist === null ? [] : event.pathlist;
|
|
187
|
+
for (const node of Array.isArray(event.node) ? event.node : [event.node]) {
|
|
188
|
+
versions.set(node, (versions.get(node) ?? 0) + 1);
|
|
189
|
+
const path = (structural ? [...labels, node.label] : labels).join(".");
|
|
190
|
+
const config = breakpoints.get(`${root}:${path}`);
|
|
191
|
+
if (!config) continue;
|
|
192
|
+
const fields = {
|
|
193
|
+
evt: event.evt,
|
|
194
|
+
node,
|
|
195
|
+
value: node.getValue(true),
|
|
196
|
+
oldvalue: event.oldvalue,
|
|
197
|
+
attrs_diff: event.attrs_diff,
|
|
198
|
+
reason: event.reason,
|
|
199
|
+
path
|
|
200
|
+
};
|
|
201
|
+
if (config.test) {
|
|
202
|
+
try {
|
|
203
|
+
if (!config.test(fields.evt, node, fields.value, fields.oldvalue, fields.attrs_diff, fields.reason, path)) continue;
|
|
204
|
+
config.error = "";
|
|
205
|
+
} catch (error) {
|
|
206
|
+
config.error = error.message;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
hit(config, root, fields);
|
|
211
|
+
}
|
|
212
|
+
flush();
|
|
213
|
+
};
|
|
214
|
+
const edit = (command) => {
|
|
215
|
+
const node = bag(command.root).getNode(command.path);
|
|
216
|
+
if (!node || identity(node) !== command.nodeId || (versions.get(node) ?? 0) !== command.revision) {
|
|
217
|
+
throw new Error("Node changed or was removed. Press Escape to reload before saving.");
|
|
218
|
+
}
|
|
219
|
+
const attrs = { ...node.getAttr() };
|
|
220
|
+
for (const name of command.remove) {
|
|
221
|
+
if (forbidden.has(name) || !Object.hasOwn(attrs, name) || !scalarType(attrs[name])) throw new Error(`Cannot remove attribute: ${name}`);
|
|
222
|
+
delete attrs[name];
|
|
223
|
+
}
|
|
224
|
+
for (const [name, encoded] of command.set) {
|
|
225
|
+
if (!name.trim() || forbidden.has(name) || Object.hasOwn(attrs, name) && !scalarType(attrs[name])) throw new Error(`Cannot edit attribute: ${name}`);
|
|
226
|
+
attrs[name] = decodeScalar2(encoded);
|
|
227
|
+
}
|
|
228
|
+
let value;
|
|
229
|
+
if (Object.hasOwn(command, "value")) {
|
|
230
|
+
if (node.resolver || !scalarType(node.getValue(true))) throw new Error("This value is read-only");
|
|
231
|
+
value = decodeScalar2(command.value);
|
|
232
|
+
}
|
|
233
|
+
if (command.set.length || command.remove.length) node.setAttr(attrs, true, false, false);
|
|
234
|
+
if (Object.hasOwn(command, "value")) node.setValue(value);
|
|
235
|
+
};
|
|
236
|
+
const setBreakpoint = (command) => {
|
|
237
|
+
bag(command.root);
|
|
238
|
+
if (typeof command.path !== "string" || !command.path || !["", "console", "debugger"].includes(command.mode)) throw new Error("Invalid breakpoint");
|
|
239
|
+
const condition = command.condition.trim();
|
|
240
|
+
const test = command.mode && condition ? new window2.Function("evt", "node", "value", "oldvalue", "attrs_diff", "reason", "path", `"use strict"; return (${condition});`) : null;
|
|
241
|
+
const key = `${command.root}:${command.path}`;
|
|
242
|
+
if (command.mode) breakpoints.set(key, { root: command.root, path: command.path, mode: command.mode, condition, hits: 0, error: "", test });
|
|
243
|
+
else breakpoints.delete(key);
|
|
244
|
+
};
|
|
245
|
+
const receive = (event) => {
|
|
246
|
+
const message = event.data;
|
|
247
|
+
if (message.type === "disconnect") {
|
|
248
|
+
dispose();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
if (["styles", "setStyle", "resetStyle", "resetStyles"].includes(message.type)) {
|
|
253
|
+
if (gramlot.state === "disposed") throw new Error("Inspected page disconnected");
|
|
254
|
+
if (message.type === "setStyle") styles.set(message.command.name, message.command.value);
|
|
255
|
+
if (message.type === "resetStyle") styles.reset(message.command.name);
|
|
256
|
+
if (message.type === "resetStyles") styles.dispose();
|
|
257
|
+
send({ type: "result", id: message.id, ok: true, styles: styles.snapshot() });
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (message.type === "edit") edit(message.command);
|
|
261
|
+
else if (message.type === "breakpoint") setBreakpoint(message.command);
|
|
262
|
+
else if (message.type !== "refresh") throw new Error(`Unknown inspector command: ${message.type}`);
|
|
263
|
+
send({ type: "result", id: message.id, ok: true, snapshot: snapshot() });
|
|
264
|
+
} catch (error) {
|
|
265
|
+
send({ type: "result", id: message.id, ok: false, error: error.message });
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
function dispose() {
|
|
269
|
+
if (stopped) return;
|
|
270
|
+
send({ type: "closed" });
|
|
271
|
+
stopped = true;
|
|
272
|
+
for (const root of roots) bag(root).unsubscribe(subscriber, { any: true });
|
|
273
|
+
breakpoints.clear();
|
|
274
|
+
styles.dispose();
|
|
275
|
+
port.removeEventListener("message", receive);
|
|
276
|
+
window2.removeEventListener("pagehide", dispose);
|
|
277
|
+
port.close();
|
|
278
|
+
}
|
|
279
|
+
for (const root of roots) bag(root).subscribe(subscriber, { any: (event) => changed(root, event) });
|
|
280
|
+
port.addEventListener("message", receive);
|
|
281
|
+
window2.addEventListener("pagehide", dispose);
|
|
282
|
+
port.start();
|
|
283
|
+
flush();
|
|
284
|
+
return { dispose };
|
|
285
|
+
}
|
|
286
|
+
var roots2 = ["data", "source"];
|
|
287
|
+
function encodeScalar(value) {
|
|
288
|
+
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
289
|
+
const wrapper = new Bag();
|
|
290
|
+
wrapper.setItem("value", value);
|
|
291
|
+
return wrapper.toTytx("json");
|
|
292
|
+
}
|
|
293
|
+
function decodeScalar(text) {
|
|
294
|
+
const value = Bag.fromTytx(text, "json").getItem("value");
|
|
295
|
+
if (!scalarType(value)) throw new TypeError("The inspector edits scalar values only");
|
|
296
|
+
return value;
|
|
297
|
+
}
|
|
298
|
+
var InspectorConnection = class {
|
|
299
|
+
data = new Bag();
|
|
300
|
+
source = new Bag();
|
|
301
|
+
state = "connecting";
|
|
302
|
+
error = "";
|
|
303
|
+
breakpoints = [];
|
|
304
|
+
styles = null;
|
|
305
|
+
#info = /* @__PURE__ */ new WeakMap();
|
|
306
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
307
|
+
#pending = /* @__PURE__ */ new Map();
|
|
308
|
+
#next = 0;
|
|
309
|
+
#port;
|
|
310
|
+
constructor(port) {
|
|
311
|
+
this.#port = port;
|
|
312
|
+
port.addEventListener("message", this.#receive);
|
|
313
|
+
port.start();
|
|
314
|
+
}
|
|
315
|
+
nodeInfo(node) {
|
|
316
|
+
return this.#info.get(node);
|
|
317
|
+
}
|
|
318
|
+
subscribe(callback) {
|
|
319
|
+
this.#listeners.add(callback);
|
|
320
|
+
return () => this.#listeners.delete(callback);
|
|
321
|
+
}
|
|
322
|
+
#notify() {
|
|
323
|
+
for (const callback of this.#listeners) callback();
|
|
324
|
+
}
|
|
325
|
+
#receive = (event) => {
|
|
326
|
+
const message = event.data;
|
|
327
|
+
if (message.type === "closed") {
|
|
328
|
+
this.close();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
if (message.styles) this.styles = message.styles;
|
|
333
|
+
if (message.snapshot) {
|
|
334
|
+
for (const root of roots2) this.#reconcile(this[root], message.snapshot[root]);
|
|
335
|
+
this.breakpoints = message.snapshot.breakpoints;
|
|
336
|
+
this.state = "started";
|
|
337
|
+
this.error = "";
|
|
338
|
+
} else if (message.type === "error") this.error = message.error;
|
|
339
|
+
} catch (error) {
|
|
340
|
+
this.error = error.message;
|
|
341
|
+
}
|
|
342
|
+
if (message.type === "result") {
|
|
343
|
+
const pending = this.#pending.get(message.id);
|
|
344
|
+
if (pending) {
|
|
345
|
+
this.#pending.delete(message.id);
|
|
346
|
+
if (message.ok && !this.error) pending.resolve();
|
|
347
|
+
else pending.reject(new Error(message.error || this.error));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
this.#notify();
|
|
351
|
+
};
|
|
352
|
+
#reconcile(store, rows) {
|
|
353
|
+
const labels = new Set(rows.map((row) => row.label));
|
|
354
|
+
for (const node of store.getNodes()) if (!labels.has(node.label)) store.popNode(node.label);
|
|
355
|
+
for (const [index, row] of rows.entries()) {
|
|
356
|
+
let node = store.getNode(row.label);
|
|
357
|
+
let previous = node ? this.#info.get(node) : null;
|
|
358
|
+
if (node && previous?.id !== row.id) {
|
|
359
|
+
store.popNode(row.label);
|
|
360
|
+
node = null;
|
|
361
|
+
previous = null;
|
|
362
|
+
}
|
|
363
|
+
const branch = row.children !== null;
|
|
364
|
+
const read = (descriptor) => descriptor.readonly ? descriptor.display : decodeScalar(descriptor.encoded);
|
|
365
|
+
const value = branch ? previous?.branch ? node.getValue(true) : new Bag() : previous?.valueWire === JSON.stringify(row.value) ? node.getValue(true) : read(row.value);
|
|
366
|
+
const attrs = {};
|
|
367
|
+
for (const [name, descriptor] of row.attrs) {
|
|
368
|
+
attrs[name] = previous?.attrsWire.get(name) === JSON.stringify(descriptor) ? node.getAttr(name) : read(descriptor);
|
|
369
|
+
}
|
|
370
|
+
if (!node) node = store.setItem(row.label, value, null, ">", false, false);
|
|
371
|
+
else if (!Object.is(node.getValue(true), value)) node.setValue(value);
|
|
372
|
+
if (!previous || previous.attrsWire.size !== row.attrs.length || row.attrs.some(([name, descriptor]) => previous.attrsWire.get(name) !== JSON.stringify(descriptor))) {
|
|
373
|
+
node.setAttr(attrs, true, false, false);
|
|
374
|
+
}
|
|
375
|
+
this.#info.set(node, {
|
|
376
|
+
id: row.id,
|
|
377
|
+
revision: row.revision,
|
|
378
|
+
branch,
|
|
379
|
+
readonly: row.readonly || !!row.value?.readonly,
|
|
380
|
+
readonlyAttrs: new Set(row.attrs.filter(([, descriptor]) => descriptor.readonly).map(([name]) => name)),
|
|
381
|
+
valueWire: JSON.stringify(row.value),
|
|
382
|
+
attrsWire: new Map(row.attrs.map(([name, descriptor]) => [name, JSON.stringify(descriptor)]))
|
|
383
|
+
});
|
|
384
|
+
if (branch) this.#reconcile(value, row.children);
|
|
385
|
+
const position = store.getNodes().indexOf(node);
|
|
386
|
+
if (position !== index) store.move(position, index);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
request(type, command = {}) {
|
|
390
|
+
if (this.state === "disposed") return Promise.reject(new Error("Inspected page disconnected"));
|
|
391
|
+
const id = ++this.#next;
|
|
392
|
+
return new Promise((resolve, reject) => {
|
|
393
|
+
this.#pending.set(id, { resolve, reject });
|
|
394
|
+
this.#port.postMessage({ type, command, id });
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
close() {
|
|
398
|
+
if (this.state === "disposed") return;
|
|
399
|
+
this.#port.postMessage({ type: "disconnect" });
|
|
400
|
+
this.#port.removeEventListener("message", this.#receive);
|
|
401
|
+
this.#port.close();
|
|
402
|
+
this.state = "disposed";
|
|
403
|
+
for (const pending of this.#pending.values()) pending.reject(new Error("Inspected page disconnected"));
|
|
404
|
+
this.#pending.clear();
|
|
405
|
+
this.#notify();
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
var gramlot_tools_inspector_default = ":host { all: initial; color-scheme: light; font: 12px/1.4 system-ui, sans-serif; color: #38414b; }\n* { box-sizing: border-box; }\nbutton, input, select { font: inherit; color: inherit; }\nbutton { cursor: pointer; }\nbutton:focus-visible, input:focus-visible, select:focus-visible { outline: 1px solid #3574cb; outline-offset: -1px; }\n.tree { padding: 7px 3px; }\n.properties { background: white; }\n.properties-heading { font-size: 11px; display: flex; align-items: center; justify-content: space-between; height: 29px; padding: 0 8px; background: #f1f3f5; color: #6d7989; border-bottom: 1px solid #d1d7df; }\n.properties-heading button { background: white; border: 1px solid #c5cdda; border-radius: 2px; width: 23px; height: 23px; padding: 0; font-size: 18px; line-height: 20px; }\nfieldset { border: 0; margin: 0; padding: 0; min-width: 0; }\n.property-row { display: grid; grid-template-columns: minmax(95px, 30%) minmax(0, 1fr); min-height: 20px; border-bottom: 1px solid #d1d7df; font-size: 11px; }\n.property-name { display: flex; align-items: center; justify-content: flex-end; background: #888; color: white; font-weight: 700; padding: 1px 6px; border-right: 1px solid #d1d7df; overflow-wrap: anywhere; text-align: right; }\n.property-value { display: flex; align-items: center; min-width: 0; }\n.property-row input { border: 0; border-radius: 0; min-width: 0; width: 100%; height: 19px; padding: 1px 5px; background: transparent; }\n.property-name input { color: white; text-align: right; padding: 0; }\n.property-value input { flex: 1; }\n.property-value input[type=checkbox] { flex: none; width: 14px; height: 14px; margin: 2px 6px; }\n.property-value select { max-width: 82px; height: 20px; padding: 0; border: 0; border-left: 1px solid #d1d7df; background: #f1f3f5; font-size: 10px; }\n.remove { margin-left: auto; flex: none; width: 22px; height: 20px; padding: 3px; border: 0; background: transparent; color: #7c8797; opacity: 0; }\n.remove svg { width: 13px; height: 13px; }\n.property-row:hover .remove, .property-row:focus-within .remove { opacity: 1; }\n.remove:hover { color: #b82323; background: #fbe9e9; }\n:disabled { cursor: default; opacity: .65; }\n.path { flex: none; padding: 5px 9px; border-top: 1px solid #c9cfd6; background: #f5f7f9; color: #697687; font-size: 11px; min-height: 27px; overflow-wrap: anywhere; }\n.error, .edit-status, .breakpoint-status, .breakpoint-line { font-size: 11px; margin: 0; padding: 3px 8px; overflow-wrap: anywhere; }\n.error, .edit-status, .breakpoint-status { color: #a12121; }\n.breakpoint-line { background: #fff2ed; color: #854127; border-bottom: 1px solid #d1d7df; }\n.error:empty, .edit-status:empty, .breakpoint-status:empty, .breakpoint-line:empty { display: none; }\n.breakpoint-dialog { width: min(340px, 90vw); color: #38414b; border: 1px solid #bbc3cf; border-radius: 4px; padding: 12px; box-shadow: 0 8px 28px #0003; font: 12px/1.4 system-ui, sans-serif; }\n.breakpoint-dialog::backdrop { background: #0002; }\n.breakpoint-dialog h2 { font-size: 13px; margin: 0 0 10px; overflow-wrap: anywhere; }\n.breakpoint-dialog select, .breakpoint-dialog input { width: 100%; border: 1px solid #bbc3cf; padding: 4px; margin: 0 0 7px; background: white; }\n.dialog-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 7px; }\n.dialog-actions button { padding: 3px 10px; border: 1px solid #bbc3cf; border-radius: 3px; background: #f1f3f5; }\n.heading-label { flex: 1; }\n.properties-heading .breakpoint-count { width: auto; padding: 0 5px; margin-right: 5px; font-size: 11px; color: #b04a35; }\n.breakpoint-entry { display: flex; align-items: center; gap: 6px; padding: 4px 0; border-bottom: 1px solid #d1d7df; }\n.breakpoint-entry span { flex: 1; overflow-wrap: anywhere; }\n.breakpoint-entry button { background: transparent; border: 0; font-size: 17px; }\n[hidden] { display: none !important; }\n.targetbar { flex: none; height: 28px; padding: 2px 6px; background: #edf2fa; border-bottom: 1px solid #dadce0; }\n.targetbar select { width: 100%; height: 23px; border: 0; background: transparent; font-size: 11px; }\n.styles-panel { flex: 1; min-height: 0; overflow: auto; background: #fff; font-size: 11px; }\n.styles-toolbar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 6px 8px; background: #f1f3f5; border-bottom: 1px solid #d1d7df; }\n.styles-toolbar span { flex: 1; font-weight: 600; }\n.styles-toolbar button, .styles-toolbar a { padding: 3px 7px; border: 1px solid #c5cdda; border-radius: 3px; background: white; }\n.styles-toolbar a { color: inherit; text-decoration: none; }\n.styles-toolbar a[aria-disabled=true] { cursor: default; opacity: .65; }\n.styles-toolbar a:focus-visible { outline: 1px solid #3574cb; outline-offset: -1px; }\n.styles-help, .styles-empty, .styles-status { margin: 0; padding: 7px 9px; color: #697687; }\n.styles-status { color: #a12121; }\n.styles-status:empty { display: none; }\n.styles-group h3 { margin: 0; padding: 6px 9px; font-size: 11px; background: #edf2fa; border-block: 1px solid #d1d7df; }\n.style-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 25px 23px; align-items: center; gap: 8px; min-width: 0; padding: 3px 9px; border-bottom: 1px solid #e8ebef; }\n.style-row label { min-width: 0; font: 11px/1.4 ui-monospace, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.style-row input { min-width: 0; width: 100%; height: 23px; padding: 2px 5px; border: 1px solid #c5cdda; border-radius: 3px; background: white; }\n.style-row input[type=color] { width: 25px; padding: 1px; cursor: pointer; }\n.style-row button { grid-column: 4; width: 23px; height: 23px; padding: 0; border: 1px solid #c5cdda; border-radius: 3px; background: #f5f7f9; font-size: 15px; }\n\n:host([embedded]) { display: block; height: 100%; min-height: 0; }\n:host([embedded]) > div { height: 100%; }\n.embedded-panel { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }\n";
|
|
409
|
+
var gramlot_poc_tree_default = "/* Copyright 2025-2026 Softwell S.r.l. - SPDX-License-Identifier: Apache-2.0\n * Inspector subset of the DevTools storeTree stylesheet and inspector.css variables. */\n:host { display:block; font:11px/18px -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; color:#38414b; }\n:host([hidden]) { display:none; }\nul { list-style:none; margin:0; padding-left:17px; }\n.tree-root { padding-left:16px; }\n.tree-root > ul { padding-left:2px; }\nsummary, .leaf { position:relative; }\nli { line-height:18px; }\nsummary { display:flex; align-items:center; gap:7px; padding:0 5px; cursor:pointer; user-select:none; list-style:none; }\nsummary::-webkit-details-marker { display:none; }\nsummary::before { content:''; width:5px; height:5px; border-right:1.5px solid #78818a; border-bottom:1.5px solid #78818a; transform:rotate(-45deg); flex:none; margin:0 3px; }\ndetails[open] > summary::before { transform:rotate(45deg); }\n.leaf { display:flex; align-items:center; gap:7px; padding:0 5px 0 23px; cursor:pointer; }\nsummary:hover,.leaf:hover { background:#f0f3f6; }\n.selected,.selected:hover { background:transparent; color:#315dbd; font-weight:700; border-radius:2px; }\n.caption { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n/* Reserve a left gutter; showing an action must not shift the node label. */\n.actions { position:absolute; left:-14px; top:0; display:inline-flex; opacity:0; pointer-events:none; }\nsummary:hover > .actions,.leaf:hover > .actions,summary:focus-visible > .actions,.leaf:focus-visible > .actions,\n.actions:has(button:focus-visible),.actions:has(button.active) { opacity:1; pointer-events:auto; }\n.actions button { border:0; background:transparent; color:#80868b; font:inherit; font-size:11px; line-height:18px; width:14px; height:18px; padding:0; cursor:pointer; border-radius:3px; }\n.actions button:hover { background:#e1e5ed; }\n.actions button[data-action=breakpoint] { font-size:0; }\n.actions button[data-action=breakpoint]::before { content:''; display:inline-block; box-sizing:border-box; width:8px; height:8px; border:1.5px solid currentColor; border-radius:50%; vertical-align:middle; }\n.actions button.active { color:#b3261e; }\n.actions button.active.conditional { color:#f9ab00; }\n.actions button.active[data-action=breakpoint]::before { background:currentColor; }\n.actions button:focus-visible,summary:focus-visible,.leaf:focus-visible { outline:2px solid #356f9f; outline-offset:-2px; }\n";
|
|
410
|
+
var sequence = 0;
|
|
411
|
+
var GramlotPocTree = class extends HTMLElement {
|
|
412
|
+
constructor() {
|
|
413
|
+
super();
|
|
414
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
415
|
+
const style = this.ownerDocument.createElement("style");
|
|
416
|
+
style.textContent = gramlot_poc_tree_default;
|
|
417
|
+
this._root = this.ownerDocument.createElement("div");
|
|
418
|
+
this._root.className = "tree-root";
|
|
419
|
+
shadow.append(style, this._root);
|
|
420
|
+
this._store = null;
|
|
421
|
+
this._selectedPath = null;
|
|
422
|
+
this._expanded = /* @__PURE__ */ new Set();
|
|
423
|
+
this._nodes = /* @__PURE__ */ new Map();
|
|
424
|
+
this._rowActions = [];
|
|
425
|
+
this._pendingRender = null;
|
|
426
|
+
this._subId = `gramlot-poc-tree-${++sequence}`;
|
|
427
|
+
}
|
|
428
|
+
get storeBag() {
|
|
429
|
+
return this._store;
|
|
430
|
+
}
|
|
431
|
+
set storeBag(bag) {
|
|
432
|
+
if (bag !== null && !(bag instanceof Bag)) throw new TypeError("storeBag requires a Bag or null");
|
|
433
|
+
if (bag === this._store) return;
|
|
434
|
+
if (this.isConnected && this._store) this._store.unsubscribe(this._subId, { any: true });
|
|
435
|
+
this._store = bag;
|
|
436
|
+
this._selectedPath = null;
|
|
437
|
+
this._expanded.clear();
|
|
438
|
+
this._root.replaceChildren();
|
|
439
|
+
if (this.isConnected) {
|
|
440
|
+
this._subscribe();
|
|
441
|
+
this._render();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
get selectedPath() {
|
|
445
|
+
return this._selectedPath;
|
|
446
|
+
}
|
|
447
|
+
set selectedPath(path) {
|
|
448
|
+
if (path !== null && typeof path !== "string") throw new TypeError("selectedPath requires a string or null");
|
|
449
|
+
this._selectedPath = path;
|
|
450
|
+
if (this.isConnected) this._markSelection();
|
|
451
|
+
}
|
|
452
|
+
get rowActions() {
|
|
453
|
+
return this._rowActions;
|
|
454
|
+
}
|
|
455
|
+
set rowActions(actions) {
|
|
456
|
+
if (!Array.isArray(actions) || actions.some((action) => !action || typeof action.id !== "string" || !action.id || typeof action.label !== "string" || !action.label)) {
|
|
457
|
+
throw new TypeError("rowActions requires actions with id and label");
|
|
458
|
+
}
|
|
459
|
+
this._rowActions = actions.map((action) => ({ ...action }));
|
|
460
|
+
if (this.isConnected) this._render();
|
|
461
|
+
}
|
|
462
|
+
connectedCallback() {
|
|
463
|
+
this._subscribe();
|
|
464
|
+
this._render();
|
|
465
|
+
}
|
|
466
|
+
disconnectedCallback() {
|
|
467
|
+
this._pendingRender = null;
|
|
468
|
+
if (this._store) this._store.unsubscribe(this._subId, { any: true });
|
|
469
|
+
}
|
|
470
|
+
_subscribe() {
|
|
471
|
+
if (this._store) this._store.subscribe(this._subId, { any: () => {
|
|
472
|
+
if (this._pendingRender) return;
|
|
473
|
+
const pending = this._pendingRender = {};
|
|
474
|
+
queueMicrotask(() => {
|
|
475
|
+
if (this._pendingRender === pending && this.isConnected) this._render();
|
|
476
|
+
});
|
|
477
|
+
} });
|
|
478
|
+
}
|
|
479
|
+
_render() {
|
|
480
|
+
this._pendingRender = null;
|
|
481
|
+
for (const details of this._root.querySelectorAll("details")) {
|
|
482
|
+
if (details.open) this._expanded.add(details.dataset.path);
|
|
483
|
+
else this._expanded.delete(details.dataset.path);
|
|
484
|
+
}
|
|
485
|
+
this._nodes.clear();
|
|
486
|
+
this._root.replaceChildren(...this._store ? [this._buildLevel(this._store, "", /* @__PURE__ */ new Set())] : []);
|
|
487
|
+
if (this._selectedPath !== null && !this._nodes.has(this._selectedPath)) {
|
|
488
|
+
this._selectedPath = null;
|
|
489
|
+
this._notifySelection();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
_buildLevel(bag, prefix, ancestors) {
|
|
493
|
+
if (ancestors.has(bag)) throw new TypeError("gramlot-poc-tree requires an acyclic Bag");
|
|
494
|
+
const nextAncestors = /* @__PURE__ */ new Set([...ancestors, bag]);
|
|
495
|
+
const document2 = this.ownerDocument;
|
|
496
|
+
const list = document2.createElement("ul");
|
|
497
|
+
for (const node of bag.getNodes()) {
|
|
498
|
+
const path = prefix ? `${prefix}.${node.label}` : node.label;
|
|
499
|
+
this._nodes.set(path, node);
|
|
500
|
+
const value = node.getValue(true);
|
|
501
|
+
const item = document2.createElement("li");
|
|
502
|
+
let row;
|
|
503
|
+
if (!node.resolver && value instanceof Bag) {
|
|
504
|
+
const details = document2.createElement("details");
|
|
505
|
+
details.dataset.path = path;
|
|
506
|
+
details.open = this._expanded.has(path);
|
|
507
|
+
row = document2.createElement("summary");
|
|
508
|
+
details.append(row, this._buildLevel(value, path, nextAncestors));
|
|
509
|
+
item.append(details);
|
|
510
|
+
} else {
|
|
511
|
+
row = item;
|
|
512
|
+
row.className = "leaf";
|
|
513
|
+
row.tabIndex = 0;
|
|
514
|
+
row.addEventListener("keydown", (event) => {
|
|
515
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
516
|
+
event.preventDefault();
|
|
517
|
+
this._select(path);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
row.dataset.nodePath = path;
|
|
522
|
+
row.classList.toggle("selected", path === this._selectedPath);
|
|
523
|
+
const caption = document2.createElement("span");
|
|
524
|
+
caption.className = "caption";
|
|
525
|
+
caption.textContent = node.label;
|
|
526
|
+
row.append(caption);
|
|
527
|
+
row.addEventListener("click", () => this._select(path));
|
|
528
|
+
this._actions(row, path, node);
|
|
529
|
+
list.append(item);
|
|
530
|
+
}
|
|
531
|
+
return list;
|
|
532
|
+
}
|
|
533
|
+
_actions(row, path, node) {
|
|
534
|
+
if (!this._rowActions.length) return;
|
|
535
|
+
const actions = this.ownerDocument.createElement("span");
|
|
536
|
+
actions.className = "actions";
|
|
537
|
+
for (const action of this._rowActions) {
|
|
538
|
+
const button = this.ownerDocument.createElement("button");
|
|
539
|
+
button.type = "button";
|
|
540
|
+
button.textContent = action.icon ?? action.label;
|
|
541
|
+
button.title = action.label;
|
|
542
|
+
button.setAttribute("aria-label", action.label);
|
|
543
|
+
button.dataset.action = action.id;
|
|
544
|
+
button.classList.toggle("active", action.activePaths?.includes(path) ?? false);
|
|
545
|
+
button.classList.toggle("conditional", action.conditionalPaths?.includes(path) ?? false);
|
|
546
|
+
button.addEventListener("click", (event) => {
|
|
547
|
+
event.preventDefault();
|
|
548
|
+
event.stopPropagation();
|
|
549
|
+
this.dispatchEvent(new this.ownerDocument.defaultView.CustomEvent("tree-action", {
|
|
550
|
+
bubbles: true,
|
|
551
|
+
composed: true,
|
|
552
|
+
detail: { action: action.id, path, node }
|
|
553
|
+
}));
|
|
554
|
+
});
|
|
555
|
+
actions.append(button);
|
|
556
|
+
}
|
|
557
|
+
row.append(actions);
|
|
558
|
+
}
|
|
559
|
+
_markSelection() {
|
|
560
|
+
for (const row of this._root.querySelectorAll("[data-node-path]")) {
|
|
561
|
+
row.classList.toggle("selected", row.dataset.nodePath === this._selectedPath);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
_select(path) {
|
|
565
|
+
this._selectedPath = path;
|
|
566
|
+
this._markSelection();
|
|
567
|
+
this._notifySelection();
|
|
568
|
+
}
|
|
569
|
+
_notifySelection() {
|
|
570
|
+
this.dispatchEvent(new this.ownerDocument.defaultView.CustomEvent("change", {
|
|
571
|
+
bubbles: true,
|
|
572
|
+
composed: true,
|
|
573
|
+
detail: { path: this._selectedPath, node: this._nodes.get(this._selectedPath) ?? null }
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
customElements.define("gramlot-poc-tree", GramlotPocTree);
|
|
578
|
+
var GramlotPocPalette = class extends HTMLElement {
|
|
579
|
+
static observedAttributes = ["title", "collapsed"];
|
|
580
|
+
constructor() {
|
|
581
|
+
super();
|
|
582
|
+
this.attachShadow({ mode: "open" }).innerHTML = `<style>
|
|
583
|
+
:host{box-sizing:border-box;position:fixed;z-index:2147483647;left:16px;top:16px;width:480px;height:620px;min-width:min(280px,100vw);min-height:200px;max-width:calc(100vw - 16px);max-height:calc(100vh - 16px);display:block;font:11px/1.45 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:white;color:#38414b;border:1px solid #b9bec5;border-radius:4px;box-shadow:0 6px 22px #20283026;overflow:hidden}
|
|
584
|
+
*{box-sizing:border-box}.frame{height:100%;display:flex;flex-direction:column}.bar{display:flex;align-items:center;height:25px;min-height:25px;padding:0 4px;border-bottom:1px solid #d8d8dc;background:#eceef1;cursor:move;touch-action:none;user-select:none}.title{flex:1;min-width:0;text-align:left;padding-left:5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;font-weight:600}.body{flex:1;min-height:0;display:flex;flex-direction:column}.resize{position:absolute;right:2px;bottom:2px;width:14px;height:14px;cursor:nwse-resize;touch-action:none;background:repeating-linear-gradient(135deg,transparent 0 3px,#aab0b8 3px 4px,transparent 4px 6px);clip-path:polygon(100% 0,100% 100%,0 100%)}button{font:inherit;color:#69727c;background:transparent;border:0;cursor:pointer}.close,.collapse{width:22px;flex:none;height:22px;font-size:18px;line-height:18px;padding:0}.close:hover,.collapse:hover{background:#dce1e7}.bar:focus-visible,button:focus-visible{outline:2px solid #356f9f;outline-offset:-2px}.resize:focus-visible{clip-path:none}::slotted(*){min-height:0}
|
|
585
|
+
:host([collapsed]){height:27px!important;min-height:27px}:host([collapsed]) .body,:host([collapsed]) .resize{display:none}
|
|
586
|
+
</style><div class="frame" role="dialog" aria-modal="false" aria-labelledby="title"><div class="bar" tabindex="0" aria-label="Move inspector: arrow keys, Home to reset"><span class="title" id="title"></span><button class="collapse" type="button" aria-label="Collapse inspector" aria-expanded="true" aria-controls="palette-body">\u2212</button><button class="close" type="button" aria-label="Close inspector">\xD7</button></div><div class="body" id="palette-body"><slot></slot></div><button class="resize" type="button" aria-label="Resize inspector: arrow keys"></button></div>`;
|
|
587
|
+
this._bar = this.shadowRoot.querySelector(".bar");
|
|
588
|
+
this._grip = this.shadowRoot.querySelector(".resize");
|
|
589
|
+
this._collapse = this.shadowRoot.querySelector(".collapse");
|
|
590
|
+
this._collapse.addEventListener("click", () => {
|
|
591
|
+
this.collapsed = !this.collapsed;
|
|
592
|
+
});
|
|
593
|
+
this.shadowRoot.querySelector(".close").addEventListener("click", () => this.dispatchEvent(
|
|
594
|
+
new this.ownerDocument.defaultView.CustomEvent("close", { bubbles: true, composed: true })
|
|
595
|
+
));
|
|
596
|
+
for (const handle of [this._bar, this._grip]) {
|
|
597
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
598
|
+
if (event.button !== 0 || handle === this._bar && event.target.closest("button")) return;
|
|
599
|
+
this._gesture = { x: event.clientX, y: event.clientY, rect: this.getBoundingClientRect(), resize: handle === this._grip };
|
|
600
|
+
handle.setPointerCapture(event.pointerId);
|
|
601
|
+
event.preventDefault();
|
|
602
|
+
});
|
|
603
|
+
handle.addEventListener("pointermove", (event) => {
|
|
604
|
+
if (this._gesture) this._adjust(this._gesture.rect, event.clientX - this._gesture.x, event.clientY - this._gesture.y, this._gesture.resize);
|
|
605
|
+
});
|
|
606
|
+
for (const name of ["pointerup", "pointercancel", "lostpointercapture"]) handle.addEventListener(name, () => {
|
|
607
|
+
this._gesture = null;
|
|
608
|
+
});
|
|
609
|
+
handle.addEventListener("keydown", (event) => {
|
|
610
|
+
if (event.target !== handle) return;
|
|
611
|
+
if (event.key === "Home" && handle === this._bar) {
|
|
612
|
+
this.style.left = "16px";
|
|
613
|
+
this.style.top = "16px";
|
|
614
|
+
event.preventDefault();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const step = event.shiftKey ? 1 : 10;
|
|
618
|
+
const delta = { ArrowLeft: [-step, 0], ArrowRight: [step, 0], ArrowUp: [0, -step], ArrowDown: [0, step] }[event.key];
|
|
619
|
+
if (delta) {
|
|
620
|
+
event.preventDefault();
|
|
621
|
+
this._adjust(this.getBoundingClientRect(), ...delta, handle === this._grip);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
this._fit = () => this._adjust(this.getBoundingClientRect(), 0, 0, false);
|
|
626
|
+
}
|
|
627
|
+
get collapsed() {
|
|
628
|
+
return this.hasAttribute("collapsed");
|
|
629
|
+
}
|
|
630
|
+
set collapsed(value) {
|
|
631
|
+
this.toggleAttribute("collapsed", value);
|
|
632
|
+
}
|
|
633
|
+
attributeChangedCallback(name) {
|
|
634
|
+
if (name === "title") this.shadowRoot.querySelector(".title").textContent = this.getAttribute("title") ?? "";
|
|
635
|
+
else {
|
|
636
|
+
this._collapse.textContent = this.collapsed ? "\uFF0B" : "\u2212";
|
|
637
|
+
this._collapse.setAttribute("aria-expanded", String(!this.collapsed));
|
|
638
|
+
this._collapse.setAttribute("aria-label", this.collapsed ? "Expand inspector" : "Collapse inspector");
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
connectedCallback() {
|
|
642
|
+
this.ownerDocument.defaultView.addEventListener("resize", this._fit);
|
|
643
|
+
}
|
|
644
|
+
disconnectedCallback() {
|
|
645
|
+
this.ownerDocument.defaultView.removeEventListener("resize", this._fit);
|
|
646
|
+
this._gesture = null;
|
|
647
|
+
}
|
|
648
|
+
_adjust(rect, dx, dy, resize) {
|
|
649
|
+
const win = this.ownerDocument.defaultView;
|
|
650
|
+
if (resize && this.collapsed) return;
|
|
651
|
+
if (resize) {
|
|
652
|
+
this.style.width = Math.max(Math.min(280, win.innerWidth - 16), Math.min(win.innerWidth - 16, rect.width + dx)) + "px";
|
|
653
|
+
this.style.height = Math.max(Math.min(200, win.innerHeight - 16), Math.min(win.innerHeight - 16, rect.height + dy)) + "px";
|
|
654
|
+
} else {
|
|
655
|
+
this.style.left = Math.max(0, Math.min(win.innerWidth - Math.min(rect.width, win.innerWidth), rect.left + dx)) + "px";
|
|
656
|
+
this.style.top = Math.max(0, Math.min(win.innerHeight - 25, rect.top + dy)) + "px";
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
customElements.define("gramlot-poc-palette", GramlotPocPalette);
|
|
661
|
+
var GramlotPocTabs = class extends HTMLElement {
|
|
662
|
+
constructor() {
|
|
663
|
+
super();
|
|
664
|
+
this.attachShadow({ mode: "open" }).innerHTML = `<style>
|
|
665
|
+
:host{display:flex;flex:none;background:#edf2fa;border-bottom:1px solid #d3e3fd}
|
|
666
|
+
::slotted(button){height:25px;padding:0 10px;border:0;border-radius:0;border-bottom:2px solid transparent;background:transparent;color:#474747;font:500 11px/23px system-ui,sans-serif;cursor:pointer}
|
|
667
|
+
::slotted(button:hover){background:#e1e5ed}::slotted(button[aria-selected=true]){color:#0b57d0;border-bottom-color:#0b57d0}::slotted(button:focus-visible){outline:2px solid #0b57d0;outline-offset:-2px}
|
|
668
|
+
</style><slot></slot>`;
|
|
669
|
+
this.addEventListener("keydown", (event) => {
|
|
670
|
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
671
|
+
const tabs = [...this.children].filter((element) => element.getAttribute("role") === "tab");
|
|
672
|
+
const index = tabs.indexOf(event.target);
|
|
673
|
+
if (index < 0) return;
|
|
674
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length;
|
|
675
|
+
event.preventDefault();
|
|
676
|
+
tabs[next].focus();
|
|
677
|
+
tabs[next].click();
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
customElements.define("gramlot-poc-tabs", GramlotPocTabs);
|
|
682
|
+
var GramlotPocSplitter = class extends HTMLElement {
|
|
683
|
+
constructor() {
|
|
684
|
+
super();
|
|
685
|
+
this.attachShadow({ mode: "open" }).innerHTML = `<style>
|
|
686
|
+
:host{display:grid;grid-template-rows:minmax(60px,var(--top-size,45%)) 6px minmax(60px,1fr);min-height:126px;flex:1;overflow:hidden}
|
|
687
|
+
.region{overflow:auto;min-height:0;min-width:0}.handle{position:relative;background:#e4e7eb;cursor:row-resize;touch-action:none;user-select:none}.handle:after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);height:1px;width:28px;background:#c2c9d3}.handle:hover{background:#c4d9ef}.handle:focus-visible{outline:2px solid #356f9f;outline-offset:-2px}::slotted(*){min-height:0}
|
|
688
|
+
</style><div class="region"><slot name="top"></slot></div><div class="handle" role="separator" aria-label="Resize tree and properties" aria-orientation="horizontal" tabindex="0" aria-valuemin="10" aria-valuemax="90" aria-valuenow="45"></div><div class="region"><slot name="bottom"></slot></div>`;
|
|
689
|
+
const handle = this.shadowRoot.querySelector(".handle");
|
|
690
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
691
|
+
if (event.button !== 0) return;
|
|
692
|
+
this._dragging = true;
|
|
693
|
+
handle.setPointerCapture(event.pointerId);
|
|
694
|
+
event.preventDefault();
|
|
695
|
+
});
|
|
696
|
+
handle.addEventListener("pointermove", (event) => {
|
|
697
|
+
if (this._dragging) {
|
|
698
|
+
const rect = this.getBoundingClientRect();
|
|
699
|
+
this._resize(100 * (event.clientY - rect.top) / rect.height);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
for (const name of ["pointerup", "pointercancel", "lostpointercapture"]) handle.addEventListener(name, () => {
|
|
703
|
+
this._dragging = false;
|
|
704
|
+
});
|
|
705
|
+
handle.addEventListener("keydown", (event) => {
|
|
706
|
+
if (!["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
707
|
+
event.preventDefault();
|
|
708
|
+
const value = Number(handle.getAttribute("aria-valuenow"));
|
|
709
|
+
this._resize(event.key === "Home" ? 10 : event.key === "End" ? 90 : value + (event.key === "ArrowUp" ? -5 : 5));
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
_resize(value) {
|
|
713
|
+
value = Math.max(10, Math.min(90, value));
|
|
714
|
+
this.style.setProperty("--top-size", `${value}%`);
|
|
715
|
+
this.shadowRoot.querySelector(".handle").setAttribute("aria-valuenow", String(Math.round(value)));
|
|
716
|
+
}
|
|
717
|
+
disconnectedCallback() {
|
|
718
|
+
this._dragging = false;
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
customElements.define("gramlot-poc-splitter", GramlotPocSplitter);
|
|
722
|
+
var nextId = 0;
|
|
723
|
+
var GramlotToolsInspector = class extends HTMLElement {
|
|
724
|
+
#target = null;
|
|
725
|
+
#connection = null;
|
|
726
|
+
#connectionRelease = null;
|
|
727
|
+
#targets = [];
|
|
728
|
+
#selectedTarget = null;
|
|
729
|
+
#ui = null;
|
|
730
|
+
#mount;
|
|
731
|
+
#subscriber = `gramlot-tools-inspector-${++nextId}`;
|
|
732
|
+
#subscriptions = [];
|
|
733
|
+
#nodes = [];
|
|
734
|
+
#nodeIds = /* @__PURE__ */ new WeakMap();
|
|
735
|
+
#nextNodeId = 0;
|
|
736
|
+
#pending = false;
|
|
737
|
+
#generation = 0;
|
|
738
|
+
#snapshot = null;
|
|
739
|
+
#nextRow = 0;
|
|
740
|
+
#nextBreakpoint = 0;
|
|
741
|
+
#breakpointTests = /* @__PURE__ */ new Map();
|
|
742
|
+
#trees = {};
|
|
743
|
+
#breakpointTarget = null;
|
|
744
|
+
#styles = null;
|
|
745
|
+
#styleNames = "";
|
|
746
|
+
#styleRequest = 0;
|
|
747
|
+
#styleDownloadUrl = null;
|
|
748
|
+
constructor() {
|
|
749
|
+
super();
|
|
750
|
+
this.#mount = this.ownerDocument.createElement("div");
|
|
751
|
+
this.attachShadow({ mode: "open" }).append(this.#mount);
|
|
752
|
+
}
|
|
753
|
+
get gramlot() {
|
|
754
|
+
return this.#target;
|
|
755
|
+
}
|
|
756
|
+
set gramlot(value) {
|
|
757
|
+
if (value !== null && (!(value instanceof Gramlot) || value.state === "disposed")) {
|
|
758
|
+
throw new TypeError("gramlot must be a live Gramlot instance or null");
|
|
759
|
+
}
|
|
760
|
+
this.#unsubscribe();
|
|
761
|
+
this.#connection = null;
|
|
762
|
+
this.#targets = [];
|
|
763
|
+
this.#selectedTarget = null;
|
|
764
|
+
this.#target = value;
|
|
765
|
+
this.#snapshot = null;
|
|
766
|
+
this.#breakpointTarget = null;
|
|
767
|
+
this.#breakpointTests.clear();
|
|
768
|
+
if (this.#ui) {
|
|
769
|
+
this.#renderTargets();
|
|
770
|
+
this.#dialog().close();
|
|
771
|
+
this.#dialog("breakpoints").close();
|
|
772
|
+
this.#ui.data.setItem("breakpointError", "");
|
|
773
|
+
this.#ui.data.setItem("selection", null);
|
|
774
|
+
this.#ui.data.setItem("busy", false);
|
|
775
|
+
this.#ui.data.setItem("breakpoints", new Bag());
|
|
776
|
+
this.#ui.data.setItem("editor.dirty", false);
|
|
777
|
+
this.#ui.data.setItem("breakpointDirty", false);
|
|
778
|
+
this.#ui.data.setItem("breakpointStatus", "");
|
|
779
|
+
this.#subscribe();
|
|
780
|
+
this.#refresh();
|
|
781
|
+
if (this.#ui.data.getItem("tab") === "styles") this.#loadStyles();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
get targets() {
|
|
785
|
+
return this.#targets.map((entry) => ({ ...entry }));
|
|
786
|
+
}
|
|
787
|
+
set targets(entries) {
|
|
788
|
+
if (!Array.isArray(entries) || entries.some((entry) => typeof entry.id !== "string" || typeof entry.title !== "string" || !(entry.connection instanceof InspectorConnection)) || new Set(entries.map((entry) => entry.id)).size !== entries.length) {
|
|
789
|
+
throw new TypeError("targets requires unique id/title/InspectorConnection records");
|
|
790
|
+
}
|
|
791
|
+
if (!entries.length) {
|
|
792
|
+
this.gramlot = null;
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
this.#targets = entries.map((entry) => ({ ...entry }));
|
|
796
|
+
this.#renderTargets();
|
|
797
|
+
const chosen = entries.find((entry) => entry.id === this.#selectedTarget) ?? entries[0];
|
|
798
|
+
if (chosen) this.selectedTarget = chosen.id;
|
|
799
|
+
}
|
|
800
|
+
get selectedTarget() {
|
|
801
|
+
return this.#selectedTarget;
|
|
802
|
+
}
|
|
803
|
+
set selectedTarget(id) {
|
|
804
|
+
const entry = this.#targets.find((entry2) => entry2.id === id);
|
|
805
|
+
if (!entry) throw new Error(`Unknown inspector target: ${id}`);
|
|
806
|
+
this.#selectedTarget = id;
|
|
807
|
+
if (this.#ui) this.#ui.data.setItem("targetId", id);
|
|
808
|
+
if (entry.connection === this.#connection) return;
|
|
809
|
+
this.#unsubscribe();
|
|
810
|
+
this.#target = null;
|
|
811
|
+
this.#connection = entry.connection;
|
|
812
|
+
this.#snapshot = null;
|
|
813
|
+
this.#breakpointTarget = null;
|
|
814
|
+
this.#breakpointTests.clear();
|
|
815
|
+
if (this.#ui) {
|
|
816
|
+
this.#dialog().close();
|
|
817
|
+
this.#dialog("breakpoints").close();
|
|
818
|
+
this.#ui.data.setItem("selection", null);
|
|
819
|
+
this.#ui.data.setItem("busy", false);
|
|
820
|
+
this.#ui.data.setItem("editor.dirty", false);
|
|
821
|
+
this.#ui.data.setItem("breakpointDirty", false);
|
|
822
|
+
this.#subscribe();
|
|
823
|
+
this.#refresh();
|
|
824
|
+
if (this.#ui.data.getItem("tab") === "styles") this.#loadStyles();
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
#renderTargets() {
|
|
828
|
+
if (!this.#ui) return;
|
|
829
|
+
const builder = new GramlotBuilder();
|
|
830
|
+
for (const entry of this.#targets) builder.root.option(entry.title, { value: entry.id });
|
|
831
|
+
this.#ui.src.source.getNode("main.palette.targetbar.targets").setValue(this.#ui.src.prepareSource(builder.source));
|
|
832
|
+
this.#ui.data.setItem("singleTarget", this.#targets.length < 2);
|
|
833
|
+
this.#ui.data.setItem("targetId", this.#selectedTarget ?? this.#targets[0]?.id ?? "");
|
|
834
|
+
}
|
|
835
|
+
#state() {
|
|
836
|
+
return (this.#connection ?? this.#target)?.state;
|
|
837
|
+
}
|
|
838
|
+
#targetBag(root) {
|
|
839
|
+
return this.#connection ? this.#connection[root] : root === "data" ? this.#target.data : this.#target.src.source;
|
|
840
|
+
}
|
|
841
|
+
connectedCallback() {
|
|
842
|
+
if (this.#ui) return;
|
|
843
|
+
const inspector = this;
|
|
844
|
+
const ui = new Gramlot({ document: this.ownerDocument, element: this.#mount, transport: false });
|
|
845
|
+
this.#ui = ui;
|
|
846
|
+
class Logic {
|
|
847
|
+
close(node, event) {
|
|
848
|
+
event.stopPropagation();
|
|
849
|
+
inspector.remove();
|
|
850
|
+
inspector.dispatchEvent(new inspector.ownerDocument.defaultView.CustomEvent("close"));
|
|
851
|
+
}
|
|
852
|
+
editChanged(node) {
|
|
853
|
+
const row = ui.data.getItem(`editor.rows.${node.getAttr("data-row")}`);
|
|
854
|
+
row.setItem("isNull", false);
|
|
855
|
+
ui.data.setItem("editor.dirty", true);
|
|
856
|
+
ui.data.setItem("editor.status", "Unsaved changes");
|
|
857
|
+
}
|
|
858
|
+
typeChanged(node) {
|
|
859
|
+
const row = ui.data.getItem(`editor.rows.${node.getAttr("data-row")}`);
|
|
860
|
+
row.setItem("inputType", inspector.#inputType(row.getItem("type")));
|
|
861
|
+
row.setItem("text", row.getItem("type") === "boolean" ? false : "");
|
|
862
|
+
row.setItem("isNull", row.getItem("type") === "null");
|
|
863
|
+
ui.data.setItem("editor.dirty", true);
|
|
864
|
+
}
|
|
865
|
+
leaveRow(node, event) {
|
|
866
|
+
if (event.relatedTarget && event.currentTarget.contains(event.relatedTarget)) return;
|
|
867
|
+
if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(() => {
|
|
868
|
+
}, event.relatedTarget);
|
|
869
|
+
}
|
|
870
|
+
editKey(node, event) {
|
|
871
|
+
if (event.key === "Escape") {
|
|
872
|
+
event.preventDefault();
|
|
873
|
+
inspector.#loadEditor(inspector.#selected());
|
|
874
|
+
} else if (event.key === "Enter" && !event.isComposing) {
|
|
875
|
+
event.preventDefault();
|
|
876
|
+
inspector.#applyEdit();
|
|
877
|
+
} else if (event.key === "Backspace" && !event.repeat && !event.isComposing) {
|
|
878
|
+
const row = ui.data.getItem(`editor.rows.${node.getAttr("data-row")}`);
|
|
879
|
+
if (!row.getItem("locked") && (row.getItem("type") === "boolean" || row.getItem("text") === "" || row.getItem("text") === null)) {
|
|
880
|
+
event.preventDefault();
|
|
881
|
+
row.setItem("isNull", true);
|
|
882
|
+
row.setItem("text", null);
|
|
883
|
+
ui.data.setItem("editor.dirty", true);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
keepFocus(node, event) {
|
|
888
|
+
event.preventDefault();
|
|
889
|
+
}
|
|
890
|
+
addAttribute() {
|
|
891
|
+
if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(() => inspector.#addAttribute());
|
|
892
|
+
else inspector.#addAttribute();
|
|
893
|
+
}
|
|
894
|
+
selectTarget() {
|
|
895
|
+
const id = ui.data.getItem("targetId");
|
|
896
|
+
const choose = () => {
|
|
897
|
+
inspector.selectedTarget = id;
|
|
898
|
+
};
|
|
899
|
+
if (ui.data.getItem("editor.dirty")) {
|
|
900
|
+
ui.data.setItem("targetId", inspector.selectedTarget);
|
|
901
|
+
inspector.#applyEdit(choose);
|
|
902
|
+
} else choose();
|
|
903
|
+
}
|
|
904
|
+
removeAttribute(node) {
|
|
905
|
+
ui.data.setItem(`editor.rows.${node.getAttr("data-row")}.removed`, true);
|
|
906
|
+
ui.data.setItem("editor.dirty", true);
|
|
907
|
+
inspector.#applyEdit();
|
|
908
|
+
}
|
|
909
|
+
selectRoot(node) {
|
|
910
|
+
const choose = () => {
|
|
911
|
+
const tab = node.getAttr("data-root");
|
|
912
|
+
ui.data.setItem("tab", tab);
|
|
913
|
+
if (tab !== "styles") {
|
|
914
|
+
ui.data.setItem("root", tab);
|
|
915
|
+
ui.data.setItem("selection", null);
|
|
916
|
+
inspector.#snapshot = null;
|
|
917
|
+
}
|
|
918
|
+
inspector.#refresh();
|
|
919
|
+
if (tab === "styles") inspector.#loadStyles();
|
|
920
|
+
};
|
|
921
|
+
if (ui.data.getItem("editor.dirty")) inspector.#applyEdit(choose);
|
|
922
|
+
else choose();
|
|
923
|
+
}
|
|
924
|
+
refreshStyles() {
|
|
925
|
+
inspector.#loadStyles();
|
|
926
|
+
}
|
|
927
|
+
styleChanged(node) {
|
|
928
|
+
ui.data.setItem(`styles.rows.${node.getAttr("data-row")}.dirty`, true);
|
|
929
|
+
}
|
|
930
|
+
setStyle(node) {
|
|
931
|
+
const row = ui.data.getItem(`styles.rows.${node.getAttr("data-row")}`);
|
|
932
|
+
if (node.getAttr("data-field") === "color") row.setItem("dirty", false);
|
|
933
|
+
else if (!row.getItem("dirty")) return;
|
|
934
|
+
inspector.#loadStyles("setStyle", { name: row.getItem("name"), value: row.getItem(node.getAttr("data-field")) });
|
|
935
|
+
}
|
|
936
|
+
styleKey(node, event) {
|
|
937
|
+
if (event.key === "Enter" && !event.isComposing) {
|
|
938
|
+
event.preventDefault();
|
|
939
|
+
this.setStyle(node);
|
|
940
|
+
}
|
|
941
|
+
if (event.key === "Escape") {
|
|
942
|
+
event.preventDefault();
|
|
943
|
+
inspector.#loadStyles();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
resetStyle(node) {
|
|
947
|
+
inspector.#loadStyles("resetStyle", { name: ui.data.getItem(`styles.rows.${node.getAttr("data-row")}.name`) });
|
|
948
|
+
}
|
|
949
|
+
resetStyles() {
|
|
950
|
+
inspector.#loadStyles("resetStyles");
|
|
951
|
+
}
|
|
952
|
+
selectNode(node, event) {
|
|
953
|
+
const choose = () => {
|
|
954
|
+
const entry = inspector.#nodes.find((item) => item.node === event.detail.node);
|
|
955
|
+
ui.data.setItem("selection", entry?.id ?? null);
|
|
956
|
+
inspector.#loadEditor(entry);
|
|
957
|
+
inspector.#details();
|
|
958
|
+
};
|
|
959
|
+
if (ui.data.getItem("editor.dirty")) {
|
|
960
|
+
inspector.#trees[ui.data.getItem("root")].selectedPath = inspector.#selected()?.path.join(".") ?? null;
|
|
961
|
+
inspector.#applyEdit(choose);
|
|
962
|
+
} else choose();
|
|
963
|
+
}
|
|
964
|
+
openBreakpoint(node, event) {
|
|
965
|
+
if (event.detail.action !== "breakpoint") return;
|
|
966
|
+
ui.data.setItem("breakpointDirty", false);
|
|
967
|
+
ui.data.setItem("breakpointStatus", "");
|
|
968
|
+
inspector.#breakpointTarget = { root: node.getAttr("data-root"), path: event.detail.path };
|
|
969
|
+
inspector.#loadBreakpoint();
|
|
970
|
+
if (!inspector.#findBreakpoint(inspector.#breakpointTarget.root, event.detail.path)) ui.data.setItem("editor.mode", "debugger");
|
|
971
|
+
ui.data.setItem("breakpointWhere", `${inspector.#breakpointTarget.root}: ${event.detail.path}`);
|
|
972
|
+
inspector.#dialog().showModal();
|
|
973
|
+
}
|
|
974
|
+
closeBreakpoint() {
|
|
975
|
+
inspector.#dialog().close();
|
|
976
|
+
inspector.#breakpointTarget = null;
|
|
977
|
+
ui.data.setItem("breakpointDirty", false);
|
|
978
|
+
}
|
|
979
|
+
breakpointChanged() {
|
|
980
|
+
ui.data.setItem("breakpointDirty", true);
|
|
981
|
+
}
|
|
982
|
+
setBreakpoint() {
|
|
983
|
+
inspector.#setBreakpoint(() => {
|
|
984
|
+
inspector.#dialog().close();
|
|
985
|
+
inspector.#breakpointTarget = null;
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
showBreakpoints() {
|
|
989
|
+
inspector.#renderBreakpoints();
|
|
990
|
+
inspector.#dialog("breakpoints").showModal();
|
|
991
|
+
}
|
|
992
|
+
closeBreakpoints() {
|
|
993
|
+
inspector.#dialog("breakpoints").close();
|
|
994
|
+
}
|
|
995
|
+
removeBreakpoint(node) {
|
|
996
|
+
const id = node.getAttr("data-breakpoint");
|
|
997
|
+
if (inspector.#connection) {
|
|
998
|
+
const config = ui.data.getItem(`breakpoints.${id}`);
|
|
999
|
+
inspector.#connection.request("breakpoint", { root: config.getItem("root"), path: config.getItem("path"), mode: "", condition: "" }).then(() => {
|
|
1000
|
+
inspector.#refresh();
|
|
1001
|
+
inspector.#renderBreakpoints();
|
|
1002
|
+
}).catch((error) => ui.data.setItem("breakpointError", error.message));
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
ui.data.getItem("breakpoints").popNode(id);
|
|
1006
|
+
inspector.#breakpointTests.delete(id);
|
|
1007
|
+
inspector.#renderBreakpoints();
|
|
1008
|
+
inspector.#refresh();
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
ui.src.logicRegistry.register(Logic, { group: "inspector", resource: "gramlot-tools-inspector" });
|
|
1012
|
+
for (const [key, value] of Object.entries({
|
|
1013
|
+
root: "data",
|
|
1014
|
+
tab: "data",
|
|
1015
|
+
stylesActive: false,
|
|
1016
|
+
treeActive: true,
|
|
1017
|
+
dataActive: true,
|
|
1018
|
+
sourceActive: false,
|
|
1019
|
+
targetId: "",
|
|
1020
|
+
singleTarget: true,
|
|
1021
|
+
busy: false,
|
|
1022
|
+
selection: null,
|
|
1023
|
+
path: "",
|
|
1024
|
+
error: "",
|
|
1025
|
+
status: "",
|
|
1026
|
+
breakpointWhere: "",
|
|
1027
|
+
breakpointLine: "",
|
|
1028
|
+
breakpointStatus: "",
|
|
1029
|
+
breakpointError: "",
|
|
1030
|
+
breakpointCount: "\u25CF 0",
|
|
1031
|
+
noBreakpoints: true
|
|
1032
|
+
})) ui.data.setItem(key, value);
|
|
1033
|
+
ui.data.setItem("editor", new Bag({ dirty: false, disabled: true, status: "", mode: "", condition: "" }));
|
|
1034
|
+
ui.data.setItem("breakpoints", new Bag());
|
|
1035
|
+
ui.data.setItem("breakpointDirty", false);
|
|
1036
|
+
ui.data.setItem("styles", new Bag({ status: "", busy: false, noChanges: true, noDownload: true, downloadUrl: null, rows: new Bag() }));
|
|
1037
|
+
const builder = new GramlotBuilder();
|
|
1038
|
+
const root = builder.root;
|
|
1039
|
+
root.style(gramlot_tools_inspector_default);
|
|
1040
|
+
const palette = this.hasAttribute("embedded") ? root.section({ node_label: "palette", class: "embedded-panel", "aria-label": "Gramlot inspector" }) : root.webcomponent({ code: "gramlot-poc-palette", node_label: "palette", title: "Gramlot inspector", connect_onclose: "inspector.close" });
|
|
1041
|
+
const targetbar = palette.div({ node_label: "targetbar", class: "targetbar", hidden: "^singleTarget" });
|
|
1042
|
+
targetbar.select({ node_label: "targets", value: "^targetId", disabled: "^busy", "aria-label": "Inspected page", connect_onchange: "inspector.selectTarget" });
|
|
1043
|
+
const toolbar = palette.webcomponent({ code: "gramlot-poc-tabs", node_label: "tabs", role: "tablist", "aria-label": "Inspected root" });
|
|
1044
|
+
for (const name of ["Data", "Source", "Styles"]) toolbar.button(name, {
|
|
1045
|
+
node_label: name.toLowerCase(),
|
|
1046
|
+
type: "button",
|
|
1047
|
+
role: "tab",
|
|
1048
|
+
"data-root": name.toLowerCase(),
|
|
1049
|
+
"aria-selected": "false",
|
|
1050
|
+
connect_onclick: "inspector.selectRoot"
|
|
1051
|
+
});
|
|
1052
|
+
palette.p("^error", { role: "alert", class: "error" });
|
|
1053
|
+
const content = palette.webcomponent({ code: "gramlot-poc-splitter", node_label: "content", hidden: "^stylesActive" });
|
|
1054
|
+
const trees = content.div({ node_label: "tree", slot: "top", class: "tree" });
|
|
1055
|
+
for (const name of ["data", "source"]) trees.webcomponent({
|
|
1056
|
+
code: "gramlot-poc-tree",
|
|
1057
|
+
node_label: name,
|
|
1058
|
+
"data-root": name,
|
|
1059
|
+
hidden: name === "data" ? "^sourceActive" : "^dataActive",
|
|
1060
|
+
connect_onchange: "inspector.selectNode",
|
|
1061
|
+
"connect_ontree-action": "inspector.openBreakpoint"
|
|
1062
|
+
});
|
|
1063
|
+
const properties = content.section({ node_label: "properties", slot: "bottom", class: "properties", "aria-label": "Selected node properties" });
|
|
1064
|
+
const heading = properties.div({ class: "properties-heading" });
|
|
1065
|
+
heading.span("Properties", { class: "heading-label" });
|
|
1066
|
+
heading.button("^breakpointCount", {
|
|
1067
|
+
type: "button",
|
|
1068
|
+
class: "breakpoint-count",
|
|
1069
|
+
hidden: "^noBreakpoints",
|
|
1070
|
+
"aria-label": "Active breakpoints",
|
|
1071
|
+
title: "Active breakpoints",
|
|
1072
|
+
connect_onclick: "inspector.showBreakpoints"
|
|
1073
|
+
});
|
|
1074
|
+
heading.button("+", {
|
|
1075
|
+
type: "button",
|
|
1076
|
+
"aria-label": "Add attribute",
|
|
1077
|
+
title: "Add attribute",
|
|
1078
|
+
disabled: "^editor.disabled",
|
|
1079
|
+
connect_onpointerdown: "inspector.keepFocus",
|
|
1080
|
+
connect_onclick: "inspector.addAttribute"
|
|
1081
|
+
});
|
|
1082
|
+
properties.p("^breakpointLine", { class: "breakpoint-line" });
|
|
1083
|
+
properties.p("^breakpointError", { class: "breakpoint-status", role: "alert" });
|
|
1084
|
+
const editor = properties.fieldset({ node_label: "editor", disabled: "^editor.disabled" });
|
|
1085
|
+
editor.div({ node_label: "rows", class: "rows" });
|
|
1086
|
+
properties.p("^editor.status", { role: "status", class: "edit-status" });
|
|
1087
|
+
const styles = palette.section({ node_label: "styles", class: "styles-panel", hidden: "^treeActive", "aria-label": "CSS variables" });
|
|
1088
|
+
const stylebar = styles.div({ class: "styles-toolbar" });
|
|
1089
|
+
stylebar.span("Document variables");
|
|
1090
|
+
stylebar.button("Refresh", { type: "button", disabled: "^styles.busy", connect_onclick: "inspector.refreshStyles" });
|
|
1091
|
+
stylebar.button("Reset all", { type: "button", disabled: "^styles.noChanges", connect_onclick: "inspector.resetStyles" });
|
|
1092
|
+
stylebar.a("Download CSS", { href: "^styles.downloadUrl", download: "theme-overrides.css", "aria-disabled": "^styles.noDownload" });
|
|
1093
|
+
styles.p("Temporary preview \xB7 Enter or leave a field to apply. Closing the connection clears previews.", { class: "styles-help" });
|
|
1094
|
+
styles.p("^styles.status", { role: "status", class: "styles-status" });
|
|
1095
|
+
styles.p("No --gramlot-* variables on this document root. Load a Gramlot theme, then Refresh.", { class: "styles-empty", node_label: "empty" });
|
|
1096
|
+
styles.div({ node_label: "rows", class: "styles-rows" });
|
|
1097
|
+
const footer = palette.footer({ class: "path", hidden: "^stylesActive" });
|
|
1098
|
+
footer.span("Path: ");
|
|
1099
|
+
footer.em("^path");
|
|
1100
|
+
const breakpoint = palette.dialog({ node_label: "breakpoint", "aria-label": "Breakpoint", class: "breakpoint-dialog", connect_oncancel: "inspector.closeBreakpoint" });
|
|
1101
|
+
breakpoint.h2("^breakpointWhere");
|
|
1102
|
+
const mode = breakpoint.select({ value: "^editor.mode", connect_onchange: "inspector.breakpointChanged", "aria-label": "Breakpoint mode" });
|
|
1103
|
+
for (const [value, label] of [["", "Off"], ["debugger", "Debugger"], ["console", "Console"]]) mode.option(label, { value });
|
|
1104
|
+
breakpoint.input({ value: "^editor.condition", live: true, connect_oninput: "inspector.breakpointChanged", "aria-label": "Breakpoint condition", placeholder: "Condition (optional)" });
|
|
1105
|
+
breakpoint.p("^breakpointStatus", { role: "status", class: "breakpoint-status" });
|
|
1106
|
+
const actions = breakpoint.div({ class: "dialog-actions" });
|
|
1107
|
+
actions.button("Cancel", { type: "button", connect_onclick: "inspector.closeBreakpoint" });
|
|
1108
|
+
actions.button("Save", { type: "button", connect_onclick: "inspector.setBreakpoint" });
|
|
1109
|
+
const list = palette.dialog({ node_label: "breakpoints", "aria-label": "Active breakpoints", class: "breakpoint-dialog" });
|
|
1110
|
+
list.h2("Active breakpoints");
|
|
1111
|
+
list.div({ node_label: "entries" });
|
|
1112
|
+
list.div({ class: "dialog-actions" }).button("Close", { type: "button", connect_onclick: "inspector.closeBreakpoints" });
|
|
1113
|
+
ui.src.startSource(builder.source);
|
|
1114
|
+
for (const name of ["data", "source"]) this.#trees[name] = ui.dom.getDomNode(ui.src.source.getNode(`main.palette.content.tree.${name}`));
|
|
1115
|
+
this.#renderTargets();
|
|
1116
|
+
this.#subscribe();
|
|
1117
|
+
this.#refresh();
|
|
1118
|
+
}
|
|
1119
|
+
#dialog(name = "breakpoint") {
|
|
1120
|
+
return this.#ui.dom.getDomNode(this.#ui.src.source.getNode(`main.palette.${name}`));
|
|
1121
|
+
}
|
|
1122
|
+
disconnectedCallback() {
|
|
1123
|
+
this.#unsubscribe();
|
|
1124
|
+
this.#ui?.dispose();
|
|
1125
|
+
this.#ui = null;
|
|
1126
|
+
this.#nodes = [];
|
|
1127
|
+
this.#trees = {};
|
|
1128
|
+
this.#breakpointTarget = null;
|
|
1129
|
+
this.#snapshot = null;
|
|
1130
|
+
this.#breakpointTests.clear();
|
|
1131
|
+
}
|
|
1132
|
+
#subscribe() {
|
|
1133
|
+
if (this.#connection) {
|
|
1134
|
+
this.#connectionRelease = this.#connection.subscribe(() => this.#schedule());
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (!this.#target) return;
|
|
1138
|
+
this.#subscriptions = [this.#target.data, this.#target.src.source];
|
|
1139
|
+
for (const [index, bag] of this.#subscriptions.entries()) bag.subscribe(this.#subscriber, { any: (event) => {
|
|
1140
|
+
this.#checkBreakpoints(index === 0 ? "data" : "source", event);
|
|
1141
|
+
this.#schedule();
|
|
1142
|
+
} });
|
|
1143
|
+
}
|
|
1144
|
+
#unsubscribe() {
|
|
1145
|
+
++this.#styleRequest;
|
|
1146
|
+
this.#styles?.dispose();
|
|
1147
|
+
this.#styles = null;
|
|
1148
|
+
if (this.#ui) {
|
|
1149
|
+
this.#renderStyles([]);
|
|
1150
|
+
this.#ui.data.setItem("styles.busy", false);
|
|
1151
|
+
this.#ui.data.setItem("styles.status", "");
|
|
1152
|
+
}
|
|
1153
|
+
this.#connectionRelease?.();
|
|
1154
|
+
this.#connectionRelease = null;
|
|
1155
|
+
++this.#generation;
|
|
1156
|
+
this.#pending = false;
|
|
1157
|
+
for (const bag of this.#subscriptions) bag.unsubscribe(this.#subscriber, { any: true });
|
|
1158
|
+
this.#subscriptions = [];
|
|
1159
|
+
}
|
|
1160
|
+
#schedule() {
|
|
1161
|
+
if (this.#pending) return;
|
|
1162
|
+
this.#pending = true;
|
|
1163
|
+
const generation = this.#generation;
|
|
1164
|
+
queueMicrotask(() => {
|
|
1165
|
+
if (generation !== this.#generation || !this.#ui) return;
|
|
1166
|
+
this.#pending = false;
|
|
1167
|
+
this.#refresh();
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
#bag() {
|
|
1171
|
+
return this.#targetBag(this.#ui.data.getItem("root"));
|
|
1172
|
+
}
|
|
1173
|
+
#refresh() {
|
|
1174
|
+
const ui = this.#ui;
|
|
1175
|
+
if (!ui) return;
|
|
1176
|
+
try {
|
|
1177
|
+
this.#nodes = [];
|
|
1178
|
+
const root = ui.data.getItem("root");
|
|
1179
|
+
const tab = ui.data.getItem("tab");
|
|
1180
|
+
ui.data.setItem("stylesActive", tab === "styles");
|
|
1181
|
+
ui.data.setItem("treeActive", tab !== "styles");
|
|
1182
|
+
for (const name of ["data", "source", "styles"]) {
|
|
1183
|
+
ui.src.source.getNode(`main.palette.tabs.${name}`).setAttr({ "aria-selected": String(tab === name) });
|
|
1184
|
+
}
|
|
1185
|
+
if (this.#connection) {
|
|
1186
|
+
const configurations = new Bag();
|
|
1187
|
+
for (const [index, record] of this.#connection.breakpoints.entries()) configurations.setItem(`b${index}`, new Bag(record));
|
|
1188
|
+
ui.data.setItem("breakpoints", configurations);
|
|
1189
|
+
ui.data.setItem("breakpointError", this.#connection.breakpoints.filter((record) => record.error).map((record) => `${record.root}: ${record.path}: ${record.error}`).join("; "));
|
|
1190
|
+
}
|
|
1191
|
+
const count = ui.data.getItem("breakpoints").getNodes().length;
|
|
1192
|
+
ui.data.setItem("breakpointCount", `\u25CF ${count}`);
|
|
1193
|
+
ui.data.setItem("noBreakpoints", count === 0);
|
|
1194
|
+
ui.data.setItem("dataActive", root === "data");
|
|
1195
|
+
ui.data.setItem("sourceActive", root === "source");
|
|
1196
|
+
for (const name of ["data", "source"]) {
|
|
1197
|
+
const tree = this.#trees[name];
|
|
1198
|
+
tree.storeBag = this.#connection || this.#target ? this.#targetBag(name) : null;
|
|
1199
|
+
const breakpoints = ui.data.getItem("breakpoints").getNodes().filter((node) => node.value.getItem("root") === name);
|
|
1200
|
+
tree.rowActions = [{
|
|
1201
|
+
id: "breakpoint",
|
|
1202
|
+
icon: "\u25CB",
|
|
1203
|
+
label: "Breakpoint",
|
|
1204
|
+
activePaths: breakpoints.map((node) => node.value.getItem("path")),
|
|
1205
|
+
conditionalPaths: breakpoints.filter((node) => node.value.getItem("condition").trim()).map((node) => node.value.getItem("path"))
|
|
1206
|
+
}];
|
|
1207
|
+
}
|
|
1208
|
+
if (this.#target || this.#connection) this.#index(this.#bag(), []);
|
|
1209
|
+
this.#trees[root].selectedPath = this.#selected()?.path.join(".") ?? null;
|
|
1210
|
+
ui.data.setItem("error", this.#connection ? this.#connection.error || (this.#state() === "disposed" ? "Inspected page disconnected." : this.#state() === "connecting" ? "Connecting\u2026" : "") : this.#target ? "" : "No Gramlot instance selected.");
|
|
1211
|
+
this.#details();
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
ui.data.setItem("error", error.message);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
async #loadStyles(type = "styles", command = {}) {
|
|
1217
|
+
const ui = this.#ui;
|
|
1218
|
+
const request = ++this.#styleRequest;
|
|
1219
|
+
ui.data.setItem("styles.busy", true);
|
|
1220
|
+
ui.data.setItem("styles.downloadUrl", null);
|
|
1221
|
+
ui.data.setItem("styles.noDownload", true);
|
|
1222
|
+
ui.data.setItem("styles.status", "");
|
|
1223
|
+
try {
|
|
1224
|
+
if (!this.#state() || this.#state() === "disposed" || this.#connection && this.#state() !== "started") {
|
|
1225
|
+
throw new Error("Select a connected page before editing CSS variables");
|
|
1226
|
+
}
|
|
1227
|
+
let rows;
|
|
1228
|
+
if (this.#connection) {
|
|
1229
|
+
const connection = this.#connection;
|
|
1230
|
+
await connection.request(type, command);
|
|
1231
|
+
rows = connection.styles;
|
|
1232
|
+
} else {
|
|
1233
|
+
this.#styles ??= new InspectorStyles(this.#target.src.renderer.destination.ownerDocument);
|
|
1234
|
+
if (type === "setStyle") this.#styles.set(command.name, command.value);
|
|
1235
|
+
if (type === "resetStyle") this.#styles.reset(command.name);
|
|
1236
|
+
if (type === "resetStyles") this.#styles.dispose();
|
|
1237
|
+
rows = this.#styles.snapshot();
|
|
1238
|
+
}
|
|
1239
|
+
if (this.#ui !== ui || request !== this.#styleRequest) return;
|
|
1240
|
+
this.#renderStyles(rows, type, command);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (this.#ui === ui && request === this.#styleRequest) ui.data.setItem("styles.status", error.message);
|
|
1243
|
+
} finally {
|
|
1244
|
+
if (this.#ui === ui && request === this.#styleRequest) {
|
|
1245
|
+
ui.data.setItem("styles.busy", false);
|
|
1246
|
+
ui.data.setItem("styles.downloadUrl", this.#styleDownloadUrl);
|
|
1247
|
+
ui.data.setItem("styles.noDownload", !this.#styleDownloadUrl);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
#renderStyles(rows, type = "styles", command = {}) {
|
|
1252
|
+
const ui = this.#ui;
|
|
1253
|
+
const browser = this.ownerDocument.defaultView;
|
|
1254
|
+
if (this.#styleDownloadUrl) browser.URL.revokeObjectURL(this.#styleDownloadUrl);
|
|
1255
|
+
this.#styleDownloadUrl = null;
|
|
1256
|
+
const overrides = rows.filter((row) => row.override);
|
|
1257
|
+
if (overrides.length) {
|
|
1258
|
+
const declarations = overrides.map((row) => ` ${browser.CSS.escape(row.name)}: ${row.override} !important;`).join("\n");
|
|
1259
|
+
const css = `/* Gramlot overrides. Load after the theme stylesheet. */
|
|
1260
|
+
:root {
|
|
1261
|
+
${declarations}
|
|
1262
|
+
}
|
|
1263
|
+
`;
|
|
1264
|
+
this.#styleDownloadUrl = browser.URL.createObjectURL(new browser.Blob([css], { type: "text/css;charset=utf-8" }));
|
|
1265
|
+
}
|
|
1266
|
+
ui.data.setItem("styles.downloadUrl", ui.data.getItem("styles.busy") ? null : this.#styleDownloadUrl);
|
|
1267
|
+
ui.data.setItem("styles.noDownload", ui.data.getItem("styles.busy") || !this.#styleDownloadUrl);
|
|
1268
|
+
const names = JSON.stringify(rows.map((row) => row.name));
|
|
1269
|
+
const rebuild = names !== this.#styleNames;
|
|
1270
|
+
this.#styleNames = names;
|
|
1271
|
+
const builder = rebuild ? new GramlotBuilder() : null;
|
|
1272
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1273
|
+
if (rebuild) {
|
|
1274
|
+
ui.src.source.getNode("main.palette.styles.rows").setValue(ui.src.prepareSource(new GramlotBuilder().source));
|
|
1275
|
+
ui.data.setItem("styles.rows", new Bag());
|
|
1276
|
+
}
|
|
1277
|
+
for (const [index, record] of rows.entries()) {
|
|
1278
|
+
const id = `v${index}`, path = `styles.rows.${id}`;
|
|
1279
|
+
const color = /^#[\da-f]{6}$/i.test(record.value) ? record.value : /^#[\da-f]{3}$/i.test(record.value) ? "#" + [...record.value.slice(1)].map((char) => char + char).join("") : null;
|
|
1280
|
+
const fields = {
|
|
1281
|
+
name: record.name,
|
|
1282
|
+
text: record.override || record.value,
|
|
1283
|
+
computed: record.value,
|
|
1284
|
+
dirty: false,
|
|
1285
|
+
color: color ?? "#000000",
|
|
1286
|
+
noColor: color === null,
|
|
1287
|
+
unchanged: !record.override
|
|
1288
|
+
};
|
|
1289
|
+
if (!rebuild && ui.data.getItem(`${path}.dirty`) && !["styles", "resetStyles"].includes(type)) {
|
|
1290
|
+
const acknowledged = record.name === command.name && (type === "resetStyle" || ui.data.getItem(`${path}.text`) === command.value);
|
|
1291
|
+
if (!acknowledged) {
|
|
1292
|
+
delete fields.text;
|
|
1293
|
+
delete fields.dirty;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (rebuild) ui.data.setItem(path, new Bag(fields));
|
|
1297
|
+
else for (const [key, value] of Object.entries(fields)) ui.data.setItem(`${path}.${key}`, value);
|
|
1298
|
+
if (!rebuild) continue;
|
|
1299
|
+
const group = record.name.includes("-font-") ? "Typography" : record.name.includes("-space-") ? "Spacing" : /-(radius|page-width)/.test(record.name) ? "Dimensions" : record.name.endsWith("-shadow") ? "Effects" : color ? "Colors" : "Other";
|
|
1300
|
+
if (!groups.has(group)) {
|
|
1301
|
+
const section = builder.root.section({ class: "styles-group" });
|
|
1302
|
+
section.h3(group);
|
|
1303
|
+
groups.set(group, section);
|
|
1304
|
+
}
|
|
1305
|
+
const row = groups.get(group).div({ class: "style-row" });
|
|
1306
|
+
row.html_label(record.name, { for: `${this.#subscriber}-${id}`, title: record.name });
|
|
1307
|
+
row.input({
|
|
1308
|
+
id: `${this.#subscriber}-${id}`,
|
|
1309
|
+
value: `^${path}.text`,
|
|
1310
|
+
live: true,
|
|
1311
|
+
"aria-label": `${record.name} value`,
|
|
1312
|
+
title: `^${path}.computed`,
|
|
1313
|
+
"data-row": id,
|
|
1314
|
+
"data-field": "text",
|
|
1315
|
+
connect_oninput: "inspector.styleChanged",
|
|
1316
|
+
connect_onchange: "inspector.setStyle",
|
|
1317
|
+
connect_onkeydown: "inspector.styleKey",
|
|
1318
|
+
spellcheck: "false"
|
|
1319
|
+
});
|
|
1320
|
+
row.input({
|
|
1321
|
+
type: "color",
|
|
1322
|
+
value: `^${path}.color`,
|
|
1323
|
+
hidden: `^${path}.noColor`,
|
|
1324
|
+
"aria-label": `${record.name} color`,
|
|
1325
|
+
"data-row": id,
|
|
1326
|
+
"data-field": "color",
|
|
1327
|
+
connect_onchange: "inspector.setStyle"
|
|
1328
|
+
});
|
|
1329
|
+
row.button("\u21B6", {
|
|
1330
|
+
type: "button",
|
|
1331
|
+
"aria-label": `Reset ${record.name}`,
|
|
1332
|
+
title: "Reset variable",
|
|
1333
|
+
disabled: `^${path}.unchanged`,
|
|
1334
|
+
"data-row": id,
|
|
1335
|
+
connect_onclick: "inspector.resetStyle"
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
if (rebuild) ui.src.source.getNode("main.palette.styles.rows").setValue(ui.src.prepareSource(builder.source));
|
|
1339
|
+
ui.data.setItem("styles.noChanges", !rows.some((row) => row.override));
|
|
1340
|
+
ui.src.source.getNode("main.palette.styles.empty").setAttr({ hidden: rows.length !== 0 });
|
|
1341
|
+
}
|
|
1342
|
+
#index(bag, labels) {
|
|
1343
|
+
for (const node of bag.getNodes()) {
|
|
1344
|
+
const path = [...labels, node.label];
|
|
1345
|
+
if (!this.#nodeIds.has(node)) this.#nodeIds.set(node, String(++this.#nextNodeId));
|
|
1346
|
+
this.#nodes.push({ id: this.#nodeIds.get(node), node, path });
|
|
1347
|
+
const value = node.getValue(true);
|
|
1348
|
+
if (!node.resolver && value instanceof Bag) this.#index(value, path);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
#details() {
|
|
1352
|
+
const ui = this.#ui;
|
|
1353
|
+
const entry = this.#selected();
|
|
1354
|
+
const node = entry?.node;
|
|
1355
|
+
ui.data.setItem("path", node ? entry.path.join(".") : "");
|
|
1356
|
+
const bp = node ? this.#findBreakpoint(ui.data.getItem("root"), entry.path.join("."))?.value : null;
|
|
1357
|
+
ui.data.setItem("breakpointLine", bp ? `Breakpoint: ${bp.getItem("mode")} \xB7 ${bp.getItem("hits")} hits${bp.getItem("condition") ? ` \xB7 if ${bp.getItem("condition")}` : ""}` : "");
|
|
1358
|
+
if (!node || this.#snapshot?.node !== node || !ui.data.getItem("editor.dirty") && !this.#unchanged()) this.#loadEditor(entry);
|
|
1359
|
+
else if (!this.#unchanged()) ui.data.setItem("editor.status", "Node changed outside this draft. Press Escape to reload the cell before saving.");
|
|
1360
|
+
}
|
|
1361
|
+
#selected() {
|
|
1362
|
+
return this.#nodes.find((item) => item.id === this.#ui.data.getItem("selection"));
|
|
1363
|
+
}
|
|
1364
|
+
#unchanged() {
|
|
1365
|
+
const entry = this.#selected();
|
|
1366
|
+
const snapshot = this.#snapshot;
|
|
1367
|
+
if (!entry || !snapshot || entry.node !== snapshot.node || !entry.node.parentBag || !entry.node.parentBag.getNodes().includes(entry.node) || this.#state() === "disposed") return false;
|
|
1368
|
+
if (this.#connection && this.#connection.nodeInfo(entry.node)?.revision !== snapshot.revision) return false;
|
|
1369
|
+
const attrs = entry.node.getAttr();
|
|
1370
|
+
return Object.is(entry.node.getValue(true), snapshot.value) && entry.node.resolver === snapshot.resolver && Object.keys(attrs).length === Object.keys(snapshot.attrs).length && Object.entries(snapshot.attrs).every(([name, value]) => Object.is(attrs[name], value));
|
|
1371
|
+
}
|
|
1372
|
+
#inputType(type) {
|
|
1373
|
+
return { boolean: "checkbox", number: "number", integer: "number", date: "date", time: "time", datetime: "datetime-local" }[type] ?? "text";
|
|
1374
|
+
}
|
|
1375
|
+
#row(name, value, primary, locked = false) {
|
|
1376
|
+
const scalar = scalarType(value);
|
|
1377
|
+
const type = scalar === "null" ? "string" : scalar;
|
|
1378
|
+
let text = value === null ? "" : type === "boolean" ? value : type ? scalarText(value, type) : this.#format(value);
|
|
1379
|
+
if (type === "datetime") text = text.replace(/Z$/, "");
|
|
1380
|
+
return new Bag({
|
|
1381
|
+
name,
|
|
1382
|
+
initialName: name,
|
|
1383
|
+
initialText: text,
|
|
1384
|
+
initialType: type,
|
|
1385
|
+
text,
|
|
1386
|
+
type: type ?? "readonly",
|
|
1387
|
+
inputType: this.#inputType(type),
|
|
1388
|
+
isNull: value === null,
|
|
1389
|
+
primary,
|
|
1390
|
+
locked: locked || !type,
|
|
1391
|
+
removed: false
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
#loadEditor(entry) {
|
|
1395
|
+
const ui = this.#ui;
|
|
1396
|
+
this.#snapshot = entry ? {
|
|
1397
|
+
node: entry.node,
|
|
1398
|
+
value: entry.node.getValue(true),
|
|
1399
|
+
attrs: { ...entry.node.getAttr() },
|
|
1400
|
+
resolver: entry.node.resolver,
|
|
1401
|
+
revision: this.#connection?.nodeInfo(entry.node)?.revision
|
|
1402
|
+
} : null;
|
|
1403
|
+
const rows = new Bag();
|
|
1404
|
+
if (entry) {
|
|
1405
|
+
if (!(this.#snapshot.value instanceof Bag)) rows.setItem(`r${++this.#nextRow}`, this.#row("value", this.#snapshot.value, true, !!entry.node.resolver || !!this.#connection?.nodeInfo(entry.node)?.readonly));
|
|
1406
|
+
for (const [name, value] of Object.entries(this.#snapshot.attrs)) {
|
|
1407
|
+
if (name !== "_meta") rows.setItem(`r${++this.#nextRow}`, this.#row(name, value, false, !!this.#connection?.nodeInfo(entry.node)?.readonlyAttrs.has(name)));
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
ui.data.setItem("editor.rows", rows);
|
|
1411
|
+
ui.data.setItem("editor.disabled", !entry || this.#state() === "disposed");
|
|
1412
|
+
ui.data.setItem("editor.dirty", false);
|
|
1413
|
+
ui.data.setItem("editor.status", "");
|
|
1414
|
+
this.#renderEditor();
|
|
1415
|
+
this.#loadBreakpoint();
|
|
1416
|
+
}
|
|
1417
|
+
#renderEditor() {
|
|
1418
|
+
const builder = new GramlotBuilder();
|
|
1419
|
+
for (const row of this.#ui.data.getItem("editor.rows").getNodes()) {
|
|
1420
|
+
const data = row.value;
|
|
1421
|
+
if (data.getItem("removed")) continue;
|
|
1422
|
+
const primary = data.getItem("primary");
|
|
1423
|
+
const name = data.getItem("name");
|
|
1424
|
+
const fresh = data.getItem("initialName") === "";
|
|
1425
|
+
const locked = data.getItem("locked");
|
|
1426
|
+
const line = builder.root.div({
|
|
1427
|
+
class: "property-row",
|
|
1428
|
+
datapath: `editor.rows.${row.label}`,
|
|
1429
|
+
"data-row": row.label,
|
|
1430
|
+
connect_onfocusout: "inspector.leaveRow"
|
|
1431
|
+
});
|
|
1432
|
+
const label = line.div({ class: "property-name" });
|
|
1433
|
+
if (fresh) label.input({ value: "^.name", live: true, "aria-label": "New attribute name", "data-row": row.label, connect_oninput: "inspector.editChanged" });
|
|
1434
|
+
else label.span(name);
|
|
1435
|
+
const cell = line.div({ class: "property-value" });
|
|
1436
|
+
cell.input({
|
|
1437
|
+
value: "^.text",
|
|
1438
|
+
type: "^.inputType",
|
|
1439
|
+
live: true,
|
|
1440
|
+
step: data.getItem("type") === "integer" ? "1" : "any",
|
|
1441
|
+
disabled: locked,
|
|
1442
|
+
"aria-label": primary ? "Value" : `${name || "New attribute"} value`,
|
|
1443
|
+
"data-row": row.label,
|
|
1444
|
+
title: data.getItem("isNull") ? "Null value. Backspace on an empty cell sets null." : "Enter to save; Escape to revert; Backspace on an empty cell sets null.",
|
|
1445
|
+
connect_oninput: "inspector.editChanged",
|
|
1446
|
+
connect_onkeydown: "inspector.editKey"
|
|
1447
|
+
});
|
|
1448
|
+
if (fresh) {
|
|
1449
|
+
const type = cell.select({ value: "^.type", "aria-label": "New attribute type", "data-row": row.label, connect_onchange: "inspector.typeChanged" });
|
|
1450
|
+
for (const value of editorTypes) type.option(value, { value });
|
|
1451
|
+
}
|
|
1452
|
+
if (!primary) {
|
|
1453
|
+
const button = cell.button({
|
|
1454
|
+
type: "button",
|
|
1455
|
+
disabled: locked,
|
|
1456
|
+
"aria-label": `Remove ${name || "new attribute"}`,
|
|
1457
|
+
class: "remove",
|
|
1458
|
+
"data-row": row.label,
|
|
1459
|
+
connect_onpointerdown: "inspector.keepFocus",
|
|
1460
|
+
connect_onclick: "inspector.removeAttribute"
|
|
1461
|
+
});
|
|
1462
|
+
button.svg({
|
|
1463
|
+
viewBox: "0 0 24 24",
|
|
1464
|
+
"aria-hidden": "true",
|
|
1465
|
+
fill: "none",
|
|
1466
|
+
stroke: "currentColor",
|
|
1467
|
+
stroke_width: 1.5,
|
|
1468
|
+
stroke_linecap: "round",
|
|
1469
|
+
stroke_linejoin: "round"
|
|
1470
|
+
}).path({ d: "M3 6h18M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M5 6l1 14a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1l1-14M10 10v7M14 10v7" });
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
this.#ui.src.source.getNode("main.palette.content.properties.editor.rows").setValue(this.#ui.src.prepareSource(builder.source));
|
|
1474
|
+
}
|
|
1475
|
+
#addAttribute() {
|
|
1476
|
+
if (!this.#snapshot) return;
|
|
1477
|
+
this.#ui.data.getItem("editor.rows").setItem(`r${++this.#nextRow}`, this.#row("", "", false));
|
|
1478
|
+
this.#ui.data.setItem("editor.status", "");
|
|
1479
|
+
this.#renderEditor();
|
|
1480
|
+
this.shadowRoot.querySelector('[aria-label="New attribute name"]').focus();
|
|
1481
|
+
}
|
|
1482
|
+
#acceptEdit(target) {
|
|
1483
|
+
this.#ui.data.setItem("editor.dirty", false);
|
|
1484
|
+
const rows = this.#ui.data.getItem("editor.rows").getNodes();
|
|
1485
|
+
if (rows.some((row) => row.value.getItem("removed") || row.value.getItem("initialName") === "")) {
|
|
1486
|
+
this.#loadEditor(this.#selected());
|
|
1487
|
+
} else {
|
|
1488
|
+
this.#snapshot = {
|
|
1489
|
+
node: target,
|
|
1490
|
+
value: target.getValue(true),
|
|
1491
|
+
attrs: { ...target.getAttr() },
|
|
1492
|
+
resolver: target.resolver,
|
|
1493
|
+
revision: this.#connection?.nodeInfo(target).revision
|
|
1494
|
+
};
|
|
1495
|
+
for (const row of rows) {
|
|
1496
|
+
row.value.setItem("initialText", row.value.getItem("text"));
|
|
1497
|
+
row.value.setItem("initialType", row.value.getItem("type"));
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
this.#ui.data.setItem("editor.status", "");
|
|
1501
|
+
}
|
|
1502
|
+
#applyEdit(after = () => {
|
|
1503
|
+
}, focusTarget = this.shadowRoot.activeElement) {
|
|
1504
|
+
if (this.#ui.data.getItem("busy")) return false;
|
|
1505
|
+
try {
|
|
1506
|
+
if (!this.#unchanged()) throw new Error("Node changed or was removed. Press Escape to reload the cell before saving.");
|
|
1507
|
+
const attrs = Object.hasOwn(this.#snapshot.attrs, "_meta") ? { _meta: this.#snapshot.attrs._meta } : {};
|
|
1508
|
+
let value = this.#snapshot.value;
|
|
1509
|
+
for (const node of this.#ui.data.getItem("editor.rows").getNodes()) {
|
|
1510
|
+
const row = node.getValue(true);
|
|
1511
|
+
const primary = row.getItem("primary");
|
|
1512
|
+
const name = row.getItem("name");
|
|
1513
|
+
if (row.getItem("removed")) continue;
|
|
1514
|
+
if (!primary) {
|
|
1515
|
+
if (!name.trim()) throw new Error("Enter an attribute name.");
|
|
1516
|
+
if (["__proto__", "prototype", "constructor"].includes(name) || name === "_meta" && !row.getItem("locked")) throw new Error("Unsupported attribute name.");
|
|
1517
|
+
if (Object.hasOwn(attrs, name)) throw new Error(`Duplicate attribute: ${name}`);
|
|
1518
|
+
}
|
|
1519
|
+
const initialName = row.getItem("initialName");
|
|
1520
|
+
const unchanged = row.getItem("text") === row.getItem("initialText") && row.getItem("type") === row.getItem("initialType") && (primary || Object.hasOwn(this.#snapshot.attrs, initialName));
|
|
1521
|
+
const text = row.getItem("text");
|
|
1522
|
+
const parsed = row.getItem("locked") || unchanged && !row.getItem("isNull") ? primary ? value : this.#snapshot.attrs[initialName] : row.getItem("isNull") || text === null ? null : parseScalar(row.getItem("type"), String(text) + (row.getItem("type") === "datetime" ? "Z" : ""));
|
|
1523
|
+
if (primary) value = parsed;
|
|
1524
|
+
else attrs[name] = parsed;
|
|
1525
|
+
}
|
|
1526
|
+
const target = this.#snapshot.node;
|
|
1527
|
+
const changed = Object.keys(attrs).length !== Object.keys(this.#snapshot.attrs).length || Object.entries(attrs).some(([name, item]) => !Object.is(item, this.#snapshot.attrs[name]));
|
|
1528
|
+
if (this.#connection) {
|
|
1529
|
+
const connection = this.#connection;
|
|
1530
|
+
const entry = this.#selected();
|
|
1531
|
+
const command = {
|
|
1532
|
+
root: this.#ui.data.getItem("root"),
|
|
1533
|
+
path: entry.path.join("."),
|
|
1534
|
+
nodeId: connection.nodeInfo(target).id,
|
|
1535
|
+
revision: this.#snapshot.revision,
|
|
1536
|
+
set: Object.entries(attrs).filter(([name, item]) => name !== "_meta" && !Object.is(item, this.#snapshot.attrs[name])).map(([name, item]) => [name, encodeScalar(item)]),
|
|
1537
|
+
remove: Object.keys(this.#snapshot.attrs).filter((name) => name !== "_meta" && !Object.hasOwn(attrs, name))
|
|
1538
|
+
};
|
|
1539
|
+
if (!Object.is(value, this.#snapshot.value)) command.value = encodeScalar(value);
|
|
1540
|
+
this.#ui.data.setItem("busy", true);
|
|
1541
|
+
this.#ui.data.setItem("editor.disabled", true);
|
|
1542
|
+
const generation = this.#generation;
|
|
1543
|
+
connection.request("edit", command).then(() => {
|
|
1544
|
+
if (!this.#ui || generation !== this.#generation) return;
|
|
1545
|
+
this.#acceptEdit(target);
|
|
1546
|
+
this.#ui.data.setItem("busy", false);
|
|
1547
|
+
this.#ui.data.setItem("editor.disabled", false);
|
|
1548
|
+
if (focusTarget?.isConnected && !this.shadowRoot.activeElement) focusTarget.focus();
|
|
1549
|
+
this.#refresh();
|
|
1550
|
+
after();
|
|
1551
|
+
}).catch((error) => {
|
|
1552
|
+
if (this.#ui && generation === this.#generation) this.#ui.data.setItem("editor.status", error.message);
|
|
1553
|
+
}).finally(() => {
|
|
1554
|
+
if (this.#ui && generation === this.#generation) {
|
|
1555
|
+
this.#ui.data.setItem("busy", false);
|
|
1556
|
+
this.#ui.data.setItem("editor.disabled", !this.#selected() || this.#state() === "disposed");
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
return false;
|
|
1560
|
+
}
|
|
1561
|
+
if (changed) target.setAttr(attrs, true, false, false);
|
|
1562
|
+
if (!Object.is(value, this.#snapshot.value)) target.setValue(value);
|
|
1563
|
+
this.#acceptEdit(target);
|
|
1564
|
+
this.#refresh();
|
|
1565
|
+
this.#ui.data.setItem("editor.status", "");
|
|
1566
|
+
after();
|
|
1567
|
+
return true;
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
this.#ui.data.setItem("editor.status", error.message);
|
|
1570
|
+
return false;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
#findBreakpoint(root, path) {
|
|
1574
|
+
return this.#ui.data.getItem("breakpoints").getNodes().find((node) => {
|
|
1575
|
+
const value = node.getValue(true);
|
|
1576
|
+
return value.getItem("root") === root && value.getItem("path") === path;
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
#loadBreakpoint() {
|
|
1580
|
+
if (this.#ui.data.getItem("breakpointDirty")) return;
|
|
1581
|
+
const entry = this.#selected();
|
|
1582
|
+
const where = this.#breakpointTarget ?? (entry ? { root: this.#ui.data.getItem("root"), path: entry.path.join(".") } : null);
|
|
1583
|
+
const config = where ? this.#findBreakpoint(where.root, where.path)?.getValue(true) : null;
|
|
1584
|
+
this.#ui.data.setItem("editor.mode", config?.getItem("mode") ?? "");
|
|
1585
|
+
this.#ui.data.setItem("editor.condition", config?.getItem("condition") ?? "");
|
|
1586
|
+
}
|
|
1587
|
+
#setBreakpoint(after = () => {
|
|
1588
|
+
}) {
|
|
1589
|
+
const entry = this.#selected();
|
|
1590
|
+
const where = this.#breakpointTarget ?? (entry ? { root: this.#ui.data.getItem("root"), path: entry.path.join(".") } : null);
|
|
1591
|
+
if (!where) return false;
|
|
1592
|
+
const ui = this.#ui;
|
|
1593
|
+
const { root, path } = where;
|
|
1594
|
+
const mode = ui.data.getItem("editor.mode");
|
|
1595
|
+
const condition = ui.data.getItem("editor.condition").trim();
|
|
1596
|
+
const existing = this.#findBreakpoint(root, path);
|
|
1597
|
+
if (this.#connection) {
|
|
1598
|
+
const generation = this.#generation;
|
|
1599
|
+
this.#connection.request("breakpoint", { root, path, mode, condition }).then(() => {
|
|
1600
|
+
if (!this.#ui || generation !== this.#generation) return;
|
|
1601
|
+
ui.data.setItem("breakpointDirty", false);
|
|
1602
|
+
ui.data.setItem("breakpointStatus", "");
|
|
1603
|
+
this.#refresh();
|
|
1604
|
+
after();
|
|
1605
|
+
}).catch((error) => {
|
|
1606
|
+
if (this.#ui && generation === this.#generation) ui.data.setItem("breakpointStatus", `Breakpoint not saved: ${error.message}`);
|
|
1607
|
+
});
|
|
1608
|
+
return false;
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
if (!["", "debugger", "console"].includes(mode)) throw new Error("Unknown breakpoint mode");
|
|
1612
|
+
const test = mode && condition ? new this.ownerDocument.defaultView.Function(
|
|
1613
|
+
"evt",
|
|
1614
|
+
"node",
|
|
1615
|
+
"value",
|
|
1616
|
+
"oldvalue",
|
|
1617
|
+
"attrs_diff",
|
|
1618
|
+
"reason",
|
|
1619
|
+
"path",
|
|
1620
|
+
`"use strict"; return (${condition});`
|
|
1621
|
+
) : null;
|
|
1622
|
+
if (existing) {
|
|
1623
|
+
ui.data.getItem("breakpoints").popNode(existing.label);
|
|
1624
|
+
this.#breakpointTests.delete(existing.label);
|
|
1625
|
+
}
|
|
1626
|
+
if (mode) {
|
|
1627
|
+
const id = `b${++this.#nextBreakpoint}`;
|
|
1628
|
+
ui.data.getItem("breakpoints").setItem(id, new Bag({ root, path, mode, condition, hits: 0 }));
|
|
1629
|
+
if (test) this.#breakpointTests.set(id, test);
|
|
1630
|
+
}
|
|
1631
|
+
ui.data.setItem("breakpointDirty", false);
|
|
1632
|
+
ui.data.setItem("breakpointError", "");
|
|
1633
|
+
ui.data.setItem("breakpointStatus", mode ? `Breakpoint set on ${root}: ${path}` : "Breakpoint removed");
|
|
1634
|
+
this.#refresh();
|
|
1635
|
+
after();
|
|
1636
|
+
return true;
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
ui.data.setItem("breakpointStatus", `Breakpoint not saved: ${error.message}`);
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
#renderBreakpoints() {
|
|
1643
|
+
const builder = new GramlotBuilder();
|
|
1644
|
+
for (const node of this.#ui.data.getItem("breakpoints").getNodes()) {
|
|
1645
|
+
const config = node.value;
|
|
1646
|
+
const row = builder.root.div({ class: "breakpoint-entry" });
|
|
1647
|
+
row.span(`${config.getItem("root")}: ${config.getItem("path")} \xB7 ${config.getItem("mode")} \xB7 ${config.getItem("hits")} hits`);
|
|
1648
|
+
row.button("\xD7", {
|
|
1649
|
+
type: "button",
|
|
1650
|
+
"aria-label": `Remove breakpoint ${config.getItem("root")}: ${config.getItem("path")}`,
|
|
1651
|
+
"data-breakpoint": node.label,
|
|
1652
|
+
connect_onclick: "inspector.removeBreakpoint"
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
this.#ui.src.source.getNode("main.palette.breakpoints.entries").setValue(this.#ui.src.prepareSource(builder.source));
|
|
1656
|
+
}
|
|
1657
|
+
#checkBreakpoints(root, event) {
|
|
1658
|
+
if (!this.#ui) return;
|
|
1659
|
+
const structural = event.evt === "ins" || event.evt === "del";
|
|
1660
|
+
const pathlist = event.pathlist === null ? [] : event.pathlist;
|
|
1661
|
+
for (const node of Array.isArray(event.node) ? event.node : [event.node]) {
|
|
1662
|
+
const path = (structural ? [...pathlist, node.label] : pathlist).join(".");
|
|
1663
|
+
const config = this.#findBreakpoint(root, path);
|
|
1664
|
+
if (!config) continue;
|
|
1665
|
+
const fields = {
|
|
1666
|
+
evt: event.evt,
|
|
1667
|
+
node,
|
|
1668
|
+
value: node.getValue(true),
|
|
1669
|
+
oldvalue: event.oldvalue,
|
|
1670
|
+
attrs_diff: event.attrs_diff,
|
|
1671
|
+
reason: event.reason,
|
|
1672
|
+
path
|
|
1673
|
+
};
|
|
1674
|
+
const test = this.#breakpointTests.get(config.label);
|
|
1675
|
+
if (test) {
|
|
1676
|
+
try {
|
|
1677
|
+
if (!test(fields.evt, node, fields.value, fields.oldvalue, fields.attrs_diff, fields.reason, path)) continue;
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
this.#ui.data.setItem("breakpointStatus", `Condition failed on ${root}: ${path}: ${error.message}`);
|
|
1680
|
+
this.#ui.data.setItem("breakpointError", `Condition failed on ${root}: ${path}: ${error.message}`);
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
const value = config.getValue(true);
|
|
1685
|
+
value.setItem("hits", value.getItem("hits") + 1);
|
|
1686
|
+
this.#ui.data.setItem("breakpointStatus", `Hit ${root}: ${path} (${event.evt})`);
|
|
1687
|
+
this.#hitBreakpoint(value.getItem("mode"), root, fields);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
#hitBreakpoint(mode, root, fields) {
|
|
1691
|
+
if (mode === "debugger") {
|
|
1692
|
+
debugger;
|
|
1693
|
+
} else this.ownerDocument.defaultView.console.log("** Gramlot breakpoint **", root, fields);
|
|
1694
|
+
}
|
|
1695
|
+
#format(value) {
|
|
1696
|
+
if (value instanceof Bag) return `${value.constructor.name} (${value.getNodes().length} nodes)`;
|
|
1697
|
+
if (typeof value === "string") return value;
|
|
1698
|
+
if (value === void 0) return "undefined";
|
|
1699
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return String(value);
|
|
1700
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
1701
|
+
if (value instanceof Date) return value.toISOString();
|
|
1702
|
+
return JSON.stringify(value, null, 2);
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
customElements.define("gramlot-tools-inspector", GramlotToolsInspector);
|
|
1706
|
+
|
|
1707
|
+
// src/gramlot_examples/gallery/browser/inspector-messages.js
|
|
1708
|
+
var INSPECTOR_READY = "gallery:inspector-ready";
|
|
1709
|
+
var INSPECTOR_CONNECT = "gallery:inspector-connect";
|
|
1710
|
+
|
|
1711
|
+
// src/gramlot_examples/gallery/browser/gallery-inspector.js
|
|
1712
|
+
function mountGalleryInspector(renderer, root) {
|
|
1713
|
+
const document2 = renderer.destination.ownerDocument;
|
|
1714
|
+
const window2 = document2.defaultView;
|
|
1715
|
+
const button = document2.getElementById("gallery-inspector");
|
|
1716
|
+
const buttonNode = renderer.elements.get(button).node;
|
|
1717
|
+
let entries = /* @__PURE__ */ new Map(), active = null, palette = null, rootAgent = null;
|
|
1718
|
+
const ready = /* @__PURE__ */ new Set();
|
|
1719
|
+
const connections = /* @__PURE__ */ new Map();
|
|
1720
|
+
const buttonState = () => buttonNode.setAttr({ disabled: false, aria_pressed: String(!!palette) });
|
|
1721
|
+
const close = () => {
|
|
1722
|
+
palette?.remove();
|
|
1723
|
+
palette = null;
|
|
1724
|
+
for (const connection of connections.values()) connection.close();
|
|
1725
|
+
connections.clear();
|
|
1726
|
+
rootAgent?.dispose();
|
|
1727
|
+
rootAgent = null;
|
|
1728
|
+
buttonState();
|
|
1729
|
+
};
|
|
1730
|
+
const connectRoot = () => {
|
|
1731
|
+
const channel = new window2.MessageChannel();
|
|
1732
|
+
rootAgent = attachInspectorAgent(window2.gramlot, channel.port1);
|
|
1733
|
+
connections.set("top", new InspectorConnection(channel.port2));
|
|
1734
|
+
};
|
|
1735
|
+
const connectFrame = (entry) => {
|
|
1736
|
+
const channel = new window2.MessageChannel();
|
|
1737
|
+
connections.set(entry.key, new InspectorConnection(channel.port1));
|
|
1738
|
+
entry.frame.contentWindow.postMessage({ type: INSPECTOR_CONNECT }, "*", [channel.port2]);
|
|
1739
|
+
};
|
|
1740
|
+
const sync = (follow = false) => {
|
|
1741
|
+
if (!palette) return;
|
|
1742
|
+
const entry = entries.get(active);
|
|
1743
|
+
const available = entry?.frame && ready.has(entry.frame.contentWindow);
|
|
1744
|
+
if (available && !connections.has(entry.key)) connectFrame(entry);
|
|
1745
|
+
const targets = [{ id: "top", title: `${document2.title || "Gallery"} (top)`, connection: connections.get("top") }];
|
|
1746
|
+
if (available) targets.push({ id: entry.key, title: entry.tab.textContent, connection: connections.get(entry.key) });
|
|
1747
|
+
palette.targets = targets;
|
|
1748
|
+
if (follow) palette.selectedTarget = available ? entry.key : "top";
|
|
1749
|
+
for (const [id, connection] of connections) if (id !== "top" && !entries.has(id)) {
|
|
1750
|
+
connection.close();
|
|
1751
|
+
connections.delete(id);
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
const toggle = () => {
|
|
1755
|
+
if (palette) {
|
|
1756
|
+
close();
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
connectRoot();
|
|
1760
|
+
palette = document2.createElement("gramlot-tools-inspector");
|
|
1761
|
+
palette.addEventListener("close", close);
|
|
1762
|
+
sync(true);
|
|
1763
|
+
document2.body.append(palette);
|
|
1764
|
+
buttonState();
|
|
1765
|
+
};
|
|
1766
|
+
const receive = (event) => {
|
|
1767
|
+
if (event.data?.type !== INSPECTOR_READY) return;
|
|
1768
|
+
const entry = [...entries.values()].find((item) => item.frame?.contentWindow === event.source);
|
|
1769
|
+
if (!entry) return;
|
|
1770
|
+
ready.add(event.source);
|
|
1771
|
+
connections.get(entry.key)?.close();
|
|
1772
|
+
connections.delete(entry.key);
|
|
1773
|
+
sync(entry.key === active);
|
|
1774
|
+
};
|
|
1775
|
+
const update = (nextEntries, nextActive) => {
|
|
1776
|
+
const changed = nextActive !== active;
|
|
1777
|
+
entries = nextEntries;
|
|
1778
|
+
active = nextActive;
|
|
1779
|
+
sync(changed);
|
|
1780
|
+
};
|
|
1781
|
+
button.addEventListener("click", toggle);
|
|
1782
|
+
window2.addEventListener("message", receive);
|
|
1783
|
+
buttonState();
|
|
1784
|
+
renderer.onDispose(root, () => {
|
|
1785
|
+
button.removeEventListener("click", toggle);
|
|
1786
|
+
window2.removeEventListener("message", receive);
|
|
1787
|
+
close();
|
|
1788
|
+
ready.clear();
|
|
1789
|
+
});
|
|
1790
|
+
return { update };
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// src/gramlot_examples/gallery/browser/inspector-entry.js
|
|
1794
|
+
var script = document.currentScript;
|
|
1795
|
+
queueMicrotask(() => {
|
|
1796
|
+
const renderer = window.gramlot.src.renderer;
|
|
1797
|
+
const record = renderer.elements.get(script);
|
|
1798
|
+
if (!record || renderer.disposed || renderer.records.get(record.node) !== record) return;
|
|
1799
|
+
mountGalleryInspector(renderer, record.node);
|
|
1800
|
+
});
|
|
1801
|
+
})();
|