mayu-live 0.0.0

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.
Files changed (204) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +661 -0
  3. data/README.md +598 -0
  4. data/exe/mayu +33 -0
  5. data/lib/mayu/app_metrics.rb +93 -0
  6. data/lib/mayu/banner.rb +12 -0
  7. data/lib/mayu/client/README.md +17 -0
  8. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js +1 -0
  9. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.br +0 -0
  10. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map +1 -0
  11. data/lib/mayu/client/dist/DecompressionStreamPolyfill-3ceba43e.js.map.br +0 -0
  12. data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js +1 -0
  13. data/lib/mayu/client/dist/custom-elements/mayu-alert-cd7ad2a4.js.map +1 -0
  14. data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js +1 -0
  15. data/lib/mayu/client/dist/custom-elements/mayu-disconnected-9f349f46.js.map +1 -0
  16. data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js +1 -0
  17. data/lib/mayu/client/dist/custom-elements/mayu-exception-63df4e8c.js.map +1 -0
  18. data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js +1 -0
  19. data/lib/mayu/client/dist/custom-elements/mayu-ping-c498c2a6.js.map +1 -0
  20. data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js +1 -0
  21. data/lib/mayu/client/dist/custom-elements/mayu-progress-bar-eb3e1ac8.js.map +1 -0
  22. data/lib/mayu/client/dist/entries.json +3 -0
  23. data/lib/mayu/client/dist/main-4b49dbc4.js +1 -0
  24. data/lib/mayu/client/dist/main-4b49dbc4.js.br +0 -0
  25. data/lib/mayu/client/dist/main-4b49dbc4.js.map +1 -0
  26. data/lib/mayu/client/dist/main-4b49dbc4.js.map.br +0 -0
  27. data/lib/mayu/client/package.json +39 -0
  28. data/lib/mayu/client/rollup.config.js +81 -0
  29. data/lib/mayu/client/src/DecompressionStream.ts +15 -0
  30. data/lib/mayu/client/src/DecompressionStreamPolyfill.ts +43 -0
  31. data/lib/mayu/client/src/MimeTypes.ts +4 -0
  32. data/lib/mayu/client/src/NodeTree.ts +445 -0
  33. data/lib/mayu/client/src/custom-elements/mayu-alert.html +137 -0
  34. data/lib/mayu/client/src/custom-elements/mayu-alert.ts +62 -0
  35. data/lib/mayu/client/src/custom-elements/mayu-disconnected.html +134 -0
  36. data/lib/mayu/client/src/custom-elements/mayu-disconnected.ts +51 -0
  37. data/lib/mayu/client/src/custom-elements/mayu-exception.html +79 -0
  38. data/lib/mayu/client/src/custom-elements/mayu-exception.ts +28 -0
  39. data/lib/mayu/client/src/custom-elements/mayu-log.html +70 -0
  40. data/lib/mayu/client/src/custom-elements/mayu-log.ts +42 -0
  41. data/lib/mayu/client/src/custom-elements/mayu-ping.html +36 -0
  42. data/lib/mayu/client/src/custom-elements/mayu-ping.ts +53 -0
  43. data/lib/mayu/client/src/custom-elements/mayu-progress-bar.html +44 -0
  44. data/lib/mayu/client/src/custom-elements/mayu-progress-bar.ts +40 -0
  45. data/lib/mayu/client/src/custom-elements/types.d.ts +4 -0
  46. data/lib/mayu/client/src/global.d.ts +26 -0
  47. data/lib/mayu/client/src/h.ts +27 -0
  48. data/lib/mayu/client/src/logger.ts +56 -0
  49. data/lib/mayu/client/src/main.ts +271 -0
  50. data/lib/mayu/client/src/serializeEvent.ts +90 -0
  51. data/lib/mayu/client/src/stream.ts +175 -0
  52. data/lib/mayu/client/src/types.ts +1 -0
  53. data/lib/mayu/client/src/utils.ts +71 -0
  54. data/lib/mayu/client/tsconfig.json +18 -0
  55. data/lib/mayu/colors.rb +34 -0
  56. data/lib/mayu/commands/base.rb +22 -0
  57. data/lib/mayu/commands/build.rb +82 -0
  58. data/lib/mayu/commands.rb +53 -0
  59. data/lib/mayu/component/base.rb +177 -0
  60. data/lib/mayu/component/handler_ref.rb +99 -0
  61. data/lib/mayu/component/helpers.rb +93 -0
  62. data/lib/mayu/component/interface.rb +18 -0
  63. data/lib/mayu/component/wrapper.rb +165 -0
  64. data/lib/mayu/component.rb +54 -0
  65. data/lib/mayu/configuration.rb +195 -0
  66. data/lib/mayu/disable_sorbet.rb +23 -0
  67. data/lib/mayu/environment.rb +151 -0
  68. data/lib/mayu/event_stream.rb +158 -0
  69. data/lib/mayu/fetch.rb +88 -0
  70. data/lib/mayu/html.rb +53 -0
  71. data/lib/mayu/html.yaml +767 -0
  72. data/lib/mayu/message_cipher.rb +172 -0
  73. data/lib/mayu/message_cipher.test.rb +16 -0
  74. data/lib/mayu/metrics/collector.rb +161 -0
  75. data/lib/mayu/metrics/exporter.rb +47 -0
  76. data/lib/mayu/metrics/reporter.rb +187 -0
  77. data/lib/mayu/metrics.rb +82 -0
  78. data/lib/mayu/ref_counter.rb +57 -0
  79. data/lib/mayu/resources/README.md +14 -0
  80. data/lib/mayu/resources/asset.rb +71 -0
  81. data/lib/mayu/resources/assets.rb +76 -0
  82. data/lib/mayu/resources/dependency_graph.rb +306 -0
  83. data/lib/mayu/resources/dot_exporter.rb +167 -0
  84. data/lib/mayu/resources/generators/base.rb +18 -0
  85. data/lib/mayu/resources/generators/copy_file.rb +26 -0
  86. data/lib/mayu/resources/generators/image.rb +106 -0
  87. data/lib/mayu/resources/generators/write_file.rb +39 -0
  88. data/lib/mayu/resources/hot_swap/file_watcher.rb +69 -0
  89. data/lib/mayu/resources/hot_swap.rb +46 -0
  90. data/lib/mayu/resources/mermaid_exporter.rb +210 -0
  91. data/lib/mayu/resources/registry.rb +190 -0
  92. data/lib/mayu/resources/resolver/base.rb +32 -0
  93. data/lib/mayu/resources/resolver/filesystem.rb +94 -0
  94. data/lib/mayu/resources/resolver/static.rb +27 -0
  95. data/lib/mayu/resources/resolver.rb +13 -0
  96. data/lib/mayu/resources/resource.rb +150 -0
  97. data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.in.css +3 -0
  98. data/lib/mayu/resources/transformers/__test__/css/adjacent_selectors.out.css +6 -0
  99. data/lib/mayu/resources/transformers/__test__/css/attributes.in.css +3 -0
  100. data/lib/mayu/resources/transformers/__test__/css/attributes.out.css +6 -0
  101. data/lib/mayu/resources/transformers/__test__/css/composes.in.css +6 -0
  102. data/lib/mayu/resources/transformers/__test__/css/composes.out.css +10 -0
  103. data/lib/mayu/resources/transformers/__test__/css/element_selectors.in.css +3 -0
  104. data/lib/mayu/resources/transformers/__test__/css/element_selectors.out.css +6 -0
  105. data/lib/mayu/resources/transformers/__test__/css/has.in.css +7 -0
  106. data/lib/mayu/resources/transformers/__test__/css/has.out.css +10 -0
  107. data/lib/mayu/resources/transformers/__test__/css/media_queries.in.css +8 -0
  108. data/lib/mayu/resources/transformers/__test__/css/media_queries.out.css +12 -0
  109. data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.in.css +5 -0
  110. data/lib/mayu/resources/transformers/__test__/css/pseudo_classes.out.css +6 -0
  111. data/lib/mayu/resources/transformers/__test__/haml/README.md +10 -0
  112. data/lib/mayu/resources/transformers/__test__/haml/case.haml +8 -0
  113. data/lib/mayu/resources/transformers/__test__/haml/case.rb +15 -0
  114. data/lib/mayu/resources/transformers/__test__/haml/class_names.haml +13 -0
  115. data/lib/mayu/resources/transformers/__test__/haml/class_names.rb +26 -0
  116. data/lib/mayu/resources/transformers/__test__/haml/comments.haml +5 -0
  117. data/lib/mayu/resources/transformers/__test__/haml/comments.rb +5 -0
  118. data/lib/mayu/resources/transformers/__test__/haml/css.haml +3 -0
  119. data/lib/mayu/resources/transformers/__test__/haml/css.rb +11 -0
  120. data/lib/mayu/resources/transformers/__test__/haml/dashes.haml +3 -0
  121. data/lib/mayu/resources/transformers/__test__/haml/dashes.rb +11 -0
  122. data/lib/mayu/resources/transformers/__test__/haml/early_return.haml +4 -0
  123. data/lib/mayu/resources/transformers/__test__/haml/early_return.rb +9 -0
  124. data/lib/mayu/resources/transformers/__test__/haml/early_return2.haml +3 -0
  125. data/lib/mayu/resources/transformers/__test__/haml/early_return2.rb +6 -0
  126. data/lib/mayu/resources/transformers/__test__/haml/handlers.haml +6 -0
  127. data/lib/mayu/resources/transformers/__test__/haml/handlers.rb +12 -0
  128. data/lib/mayu/resources/transformers/__test__/haml/if_else.haml +6 -0
  129. data/lib/mayu/resources/transformers/__test__/haml/if_else.rb +12 -0
  130. data/lib/mayu/resources/transformers/__test__/haml/interpolation.haml +8 -0
  131. data/lib/mayu/resources/transformers/__test__/haml/interpolation.rb +11 -0
  132. data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.haml +1 -0
  133. data/lib/mayu/resources/transformers/__test__/haml/object_ref_as_key.rb +5 -0
  134. data/lib/mayu/resources/transformers/__test__/haml/props.haml +4 -0
  135. data/lib/mayu/resources/transformers/__test__/haml/props.rb +11 -0
  136. data/lib/mayu/resources/transformers/__test__/haml/slots.haml +5 -0
  137. data/lib/mayu/resources/transformers/__test__/haml/slots.rb +9 -0
  138. data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.haml +3 -0
  139. data/lib/mayu/resources/transformers/__test__/haml/slots_dynamic.rb +9 -0
  140. data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.haml +3 -0
  141. data/lib/mayu/resources/transformers/__test__/haml/slots_fallback.rb +5 -0
  142. data/lib/mayu/resources/transformers/__test__/haml/spacing.haml +5 -0
  143. data/lib/mayu/resources/transformers/__test__/haml/spacing.rb +14 -0
  144. data/lib/mayu/resources/transformers/__test__/haml/spacing2.haml +10 -0
  145. data/lib/mayu/resources/transformers/__test__/haml/spacing2.rb +11 -0
  146. data/lib/mayu/resources/transformers/__test__/haml/spacing3.haml +3 -0
  147. data/lib/mayu/resources/transformers/__test__/haml/spacing3.rb +10 -0
  148. data/lib/mayu/resources/transformers/css/rouge_lexer.rb +841 -0
  149. data/lib/mayu/resources/transformers/css.rb +100 -0
  150. data/lib/mayu/resources/transformers/css.test.rb +87 -0
  151. data/lib/mayu/resources/transformers/haml.rb +984 -0
  152. data/lib/mayu/resources/transformers/haml.test.rb +114 -0
  153. data/lib/mayu/resources/types/README.md +36 -0
  154. data/lib/mayu/resources/types/base.rb +35 -0
  155. data/lib/mayu/resources/types/component.rb +198 -0
  156. data/lib/mayu/resources/types/image.rb +169 -0
  157. data/lib/mayu/resources/types/javascript.rb +50 -0
  158. data/lib/mayu/resources/types/nil.rb +23 -0
  159. data/lib/mayu/resources/types/stylesheet.rb +119 -0
  160. data/lib/mayu/resources/types/svg.rb +69 -0
  161. data/lib/mayu/resources/types.rb +37 -0
  162. data/lib/mayu/routes.rb +170 -0
  163. data/lib/mayu/routing/builder.rb +108 -0
  164. data/lib/mayu/routing/matcher.rb +58 -0
  165. data/lib/mayu/routing/routes.rb +85 -0
  166. data/lib/mayu/routing.rb +17 -0
  167. data/lib/mayu/server/app.rb +494 -0
  168. data/lib/mayu/server/controller.rb +152 -0
  169. data/lib/mayu/server/errors.rb +110 -0
  170. data/lib/mayu/server/file_server.rb +140 -0
  171. data/lib/mayu/server.rb +63 -0
  172. data/lib/mayu/session.rb +358 -0
  173. data/lib/mayu/state/README.md +6 -0
  174. data/lib/mayu/state/action_creator.rb +191 -0
  175. data/lib/mayu/state/action_wrapper.rb +30 -0
  176. data/lib/mayu/state/loader.rb +220 -0
  177. data/lib/mayu/state/store.rb +82 -0
  178. data/lib/mayu/state.rb +8 -0
  179. data/lib/mayu/state.test.rb +97 -0
  180. data/lib/mayu/utils.rb +114 -0
  181. data/lib/mayu/vdom/children.rb +117 -0
  182. data/lib/mayu/vdom/component_marshaler.rb +53 -0
  183. data/lib/mayu/vdom/css_attributes.rb +131 -0
  184. data/lib/mayu/vdom/descriptor.rb +151 -0
  185. data/lib/mayu/vdom/descriptor.test.rb +26 -0
  186. data/lib/mayu/vdom/dom.rb +239 -0
  187. data/lib/mayu/vdom/h.rb +22 -0
  188. data/lib/mayu/vdom/id_generator.rb +55 -0
  189. data/lib/mayu/vdom/interfaces.rb +186 -0
  190. data/lib/mayu/vdom/marshalling.rb +78 -0
  191. data/lib/mayu/vdom/reconciliation.rb +205 -0
  192. data/lib/mayu/vdom/reconciliation.test.rb +56 -0
  193. data/lib/mayu/vdom/special_elements.rb +108 -0
  194. data/lib/mayu/vdom/update_context.rb +180 -0
  195. data/lib/mayu/vdom/vdom.perf.test.rb +146 -0
  196. data/lib/mayu/vdom/vnode.rb +266 -0
  197. data/lib/mayu/vdom/vtree.rb +672 -0
  198. data/lib/mayu/vdom/vtree.test.rb +68 -0
  199. data/lib/mayu/vdom.rb +8 -0
  200. data/lib/mayu/vdom.test.rb +73 -0
  201. data/lib/mayu/version.rb +6 -0
  202. data/lib/mayu.rb +8 -0
  203. data/mayu-live.gemspec +70 -0
  204. metadata +612 -0
@@ -0,0 +1,445 @@
1
+ import { createLogger, createSilentLogger } from "./logger";
2
+
3
+ const SILENT = true;
4
+ const logger = SILENT ? createSilentLogger() : createLogger("mayu/NodeTree/");
5
+
6
+ export type IdNode = { id: number; ch?: [IdNode]; type: string };
7
+ type CacheEntry = { node: Node; childIds: Set<number> };
8
+
9
+ type InsertPatch = {
10
+ type: "insert";
11
+ parent: number;
12
+ before?: number;
13
+ after?: number;
14
+ html: string;
15
+ ids: any;
16
+ };
17
+
18
+ type RemovePatch = { type: "remove"; id: number };
19
+
20
+ type MovePatch = {
21
+ type: "move";
22
+ parent: number;
23
+ id: number;
24
+ before?: number;
25
+ after?: number;
26
+ };
27
+
28
+ type StylePatch = { type: "css"; id: number; attr: string; value?: string };
29
+ type StylesheetPatch = { type: "stylesheet"; paths: string[] };
30
+
31
+ type AddTextPatch = { type: "text"; id: number; text: string };
32
+ type AppendTextPatch = { type: "text"; id: number; append: string };
33
+ type TextPatch = AddTextPatch | AppendTextPatch;
34
+
35
+ type AttributePatch = {
36
+ type: "attr";
37
+ id: number;
38
+ name: string;
39
+ value?: string;
40
+ };
41
+
42
+ export type Patch =
43
+ | InsertPatch
44
+ | MovePatch
45
+ | RemovePatch
46
+ | TextPatch
47
+ | AttributePatch
48
+ | StylePatch
49
+ | StylesheetPatch;
50
+
51
+ function cloneScriptElement(element: HTMLScriptElement) {
52
+ const script = document.createElement("script");
53
+ script.text = element.innerHTML;
54
+ for (const attr of element.attributes) {
55
+ // console.log("Setting attribute", attr.name, "to", attr.value);
56
+ script.setAttribute(attr.name, attr.value);
57
+ }
58
+ return script;
59
+ }
60
+
61
+ function replaceScriptNodes(parent: Node, node: Node) {
62
+ if ((node as Element).tagName === "SCRIPT") {
63
+ parent.replaceChild(cloneScriptElement(node as HTMLScriptElement), node);
64
+ return;
65
+ }
66
+
67
+ for (const child of node.childNodes) {
68
+ replaceScriptNodes(node, child);
69
+ }
70
+ }
71
+
72
+ function handleAutofocus(node: Node) {
73
+ if (node instanceof HTMLInputElement) {
74
+ if (node.autofocus) {
75
+ node.focus();
76
+ return;
77
+ }
78
+ }
79
+
80
+ for (const child of node.childNodes) {
81
+ handleAutofocus(child);
82
+ }
83
+ }
84
+
85
+ class NodeTree {
86
+ #cache = new Map<number, CacheEntry>();
87
+
88
+ constructor(root: IdNode, element = document.documentElement) {
89
+ this.updateCache(element, root);
90
+ //console.log(JSON.stringify(root, null, 2))
91
+ }
92
+
93
+ apply(patches: Patch[]) {
94
+ for (const patch of patches) {
95
+ this.applyPatch(patch);
96
+ }
97
+ }
98
+
99
+ applyPatch(patch: Patch) {
100
+ switch (patch.type) {
101
+ case "insert": {
102
+ this.insert(patch);
103
+ return;
104
+ }
105
+ case "move": {
106
+ this.move(patch);
107
+ break;
108
+ }
109
+ case "remove": {
110
+ this.remove(patch.id);
111
+ break;
112
+ }
113
+ case "css": {
114
+ const element = this.#getEntry(patch.id).node as HTMLElement;
115
+
116
+ if (patch.value) {
117
+ element.style.setProperty(patch.attr, patch.value);
118
+ } else {
119
+ element.style.removeProperty(patch.attr);
120
+ }
121
+ }
122
+ case "text": {
123
+ if ("text" in patch) {
124
+ this.updateText(patch.id, patch.text);
125
+ }
126
+
127
+ if ("append" in patch) {
128
+ this.appendText(patch.id, patch.append);
129
+ }
130
+ break;
131
+ }
132
+ case "attr": {
133
+ if (patch.value !== undefined) {
134
+ this.setAttribute(patch.id, patch.name, patch.value);
135
+ } else {
136
+ this.removeAttribute(patch.id, patch.name);
137
+ }
138
+ break;
139
+ }
140
+ case "stylesheet": {
141
+ for (const href of patch.paths) {
142
+ // TODO: This should be possible in Chrome, but not yet in Firefox.
143
+ // const stylesheet = await import(path, { assert: { type: 'css' } });
144
+ // document.adoptedStyleSheets.push(stylesheet)
145
+ if (document.querySelector(`link[href="${href}"]`)) {
146
+ continue;
147
+ }
148
+
149
+ const link = document.createElement("link");
150
+ link.setAttribute("rel", "stylesheet");
151
+ link.setAttribute("href", href);
152
+ document.head.insertAdjacentElement("beforeend", link);
153
+ }
154
+
155
+ break;
156
+ }
157
+ default: {
158
+ console.error("Unknown patch", patch);
159
+ }
160
+ }
161
+ }
162
+
163
+ updateText(id: number, text: string) {
164
+ const node = this.#getEntry(id).node;
165
+
166
+ if (node.nodeType !== node.TEXT_NODE) {
167
+ throw new Error("Trying to update text on a non text node");
168
+ }
169
+
170
+ node.textContent = text;
171
+ }
172
+
173
+ appendText(id: number, text: string) {
174
+ const node = this.#getEntry(id).node;
175
+
176
+ if (node.nodeType !== node.TEXT_NODE) {
177
+ throw new Error("Trying to update text on a non text node");
178
+ }
179
+
180
+ node.textContent += text;
181
+ }
182
+
183
+ setAttribute(id: number, name: string, value: string) {
184
+ const node = this.#getEntry(id).node as Element;
185
+
186
+ // logger.log("Trying to set attribute", name, value);
187
+
188
+ if (name === "open") {
189
+ if (node instanceof HTMLDialogElement) {
190
+ node.showModal();
191
+ return;
192
+ }
193
+ }
194
+
195
+ if (node instanceof HTMLInputElement) {
196
+ if (name === "value") {
197
+ node.value = value;
198
+ }
199
+
200
+ if (name === "checked") {
201
+ node.checked = true;
202
+ }
203
+
204
+ if (name === "indeterminate") {
205
+ node.indeterminate = true;
206
+ return;
207
+ }
208
+ }
209
+
210
+ if (name === "initial_value") {
211
+ name = "value";
212
+ } else {
213
+ name = name.replaceAll(/_/g, "");
214
+ }
215
+
216
+ node.setAttribute(name, value);
217
+ }
218
+
219
+ removeAttribute(id: number, name: string) {
220
+ const node = this.#getEntry(id).node as Element;
221
+
222
+ if (name === "open") {
223
+ if (node instanceof HTMLDialogElement) {
224
+ node.open = false;
225
+ node.close();
226
+ }
227
+ }
228
+
229
+ if (node instanceof HTMLInputElement) {
230
+ if (name === "value") {
231
+ node.value = "";
232
+ }
233
+
234
+ if (name === "checked") {
235
+ node.checked = false;
236
+ }
237
+
238
+ if (name === "indeterminate") {
239
+ node.indeterminate = false;
240
+ return;
241
+ }
242
+ }
243
+
244
+ node.removeAttribute(name);
245
+ }
246
+
247
+ insert({ parent, before, after, ids, html }: InsertPatch) {
248
+ logger.group(`Trying to insert html into`, parent);
249
+
250
+ const parentEntry = this.#getEntry(parent);
251
+ const referenceId = before || after;
252
+ const referenceEntry = referenceId && this.#cache.get(referenceId);
253
+
254
+ const template = document
255
+ .createRange()
256
+ .createContextualFragment(`<template>${html}</template>`)
257
+ .firstElementChild!;
258
+ const content = (template as HTMLTemplateElement).content;
259
+
260
+ const children = Array.from(content.childNodes).reverse();
261
+
262
+ const idsArray = [ids].flat();
263
+
264
+ idsArray.forEach((idTreeNode, i) => {
265
+ parentEntry.childIds.add(idTreeNode.id);
266
+ const entry = this.#cache.get(idTreeNode.id);
267
+ const node = entry?.node || children[i];
268
+ const ref = referenceEntry
269
+ ? after
270
+ ? referenceEntry.node.nextSibling
271
+ : referenceEntry.node
272
+ : null;
273
+
274
+ const insertedNode = parentEntry.node.insertBefore(node, ref);
275
+
276
+ if (entry) {
277
+ (entry.node as HTMLElement).outerHTML = (node as HTMLElement).outerHTML;
278
+ }
279
+
280
+ requestIdleCallback(() => {
281
+ handleAutofocus(insertedNode);
282
+ });
283
+
284
+ requestIdleCallback(() => {
285
+ replaceScriptNodes(parentEntry.node, insertedNode);
286
+ });
287
+
288
+ this.updateCache(insertedNode, idTreeNode);
289
+ });
290
+
291
+ logger.groupEnd();
292
+ }
293
+
294
+ #getEntry(id: number) {
295
+ const entry = this.#cache.get(id);
296
+
297
+ if (!entry) {
298
+ logger.error("Could not find", id, "in cache!");
299
+ logger.error(Array.from(this.#cache.keys()));
300
+ throw new Error(`Could not find ${id} in cache!`);
301
+ }
302
+
303
+ return entry;
304
+ }
305
+
306
+ remove(nodeId: number) {
307
+ logger.info("Trying to remove", nodeId);
308
+
309
+ try {
310
+ const entry = this.#getEntry(nodeId);
311
+ const parentNode = entry.node.parentNode;
312
+
313
+ if (parentNode) {
314
+ const parentId = parentNode.__MAYU_ID;
315
+ const parentEntry = this.#cache.get(parentId);
316
+
317
+ logger.log(`Removing child`, entry.node.textContent);
318
+ parentNode.removeChild(entry.node);
319
+
320
+ if (parentEntry) {
321
+ parentEntry.childIds.delete(nodeId);
322
+ }
323
+ } else {
324
+ logger.warn(`Node`, entry.node.__MAYU_ID, "has no parent??");
325
+ }
326
+
327
+ this.#removeRecursiveFromCache(nodeId, false);
328
+ } catch (e) {
329
+ logger.warn(e);
330
+ }
331
+ }
332
+
333
+ move({ id, parent, before, after }: MovePatch) {
334
+ const entry = this.#getEntry(id);
335
+ const parentEntry = this.#getEntry(parent);
336
+ const refId = before || after;
337
+ const refEntry = refId && this.#cache.get(refId);
338
+
339
+ const ref = refEntry ? (after ? refEntry.node : refEntry.node) : null;
340
+
341
+ logger.log(
342
+ "Moving",
343
+ entry.node.textContent,
344
+ before ? "before" : after ? "after" : "last",
345
+ ref?.textContent || parentEntry.node.__MAYU_ID
346
+ );
347
+ logger.log({ before, after });
348
+ logger.log(ref?.textContent);
349
+
350
+ parentEntry.node.insertBefore(entry.node, ref);
351
+ }
352
+
353
+ #removeRecursiveFromCache(id: number, includeParent = false) {
354
+ const entry = this.#cache.get(id);
355
+
356
+ if (!entry) return;
357
+
358
+ logger.group("Removing from cache", id);
359
+
360
+ if (includeParent) {
361
+ const parentEntry = this.#cache.get(entry.node.parentNode!.__MAYU_ID);
362
+ parentEntry?.childIds?.delete(id);
363
+ }
364
+
365
+ this.#cache.delete(id);
366
+
367
+ entry.childIds.forEach((childId) => {
368
+ this.#removeRecursiveFromCache(childId, false);
369
+ });
370
+
371
+ entry.childIds.delete(id);
372
+
373
+ logger.groupEnd();
374
+ }
375
+
376
+ isIgnoredNode(node: Node) {
377
+ if (node.nodeType === node.TEXT_NODE) return false;
378
+ if (node.nodeType === node.COMMENT_NODE) return false;
379
+ if (node.nodeType === node.ELEMENT_NODE) {
380
+ const dataset = (node as HTMLElement).dataset;
381
+ if (typeof dataset.mayuId === "string") return false;
382
+ }
383
+
384
+ return true;
385
+ }
386
+
387
+ updateCache(node: Node, idTreeNode: IdNode) {
388
+ if (!node) {
389
+ logger.error(idTreeNode);
390
+ throw new Error("No node found for idTreeNode");
391
+ }
392
+ const childIds = new Set((idTreeNode.ch || []).map((child) => child.id));
393
+
394
+ this.#removeRecursiveFromCache(idTreeNode.id);
395
+
396
+ this.#cache.set(idTreeNode.id, { node, childIds });
397
+ node.__MAYU_ID = idTreeNode.id;
398
+
399
+ logger.group(
400
+ "Add to cache",
401
+ idTreeNode.id,
402
+ "type",
403
+ node.nodeName,
404
+ idTreeNode.type
405
+ );
406
+
407
+ // logger.log('Updating cache for', node, 'with id', idTreeNode.i)
408
+
409
+ let i = 0;
410
+ const ch = idTreeNode.ch || [];
411
+
412
+ node.childNodes.forEach((childNode) => {
413
+ if (this.isIgnoredNode(childNode)) {
414
+ logger.warn(`Ignored:`, childNode);
415
+ return;
416
+ }
417
+
418
+ const childIdNode = ch[i++];
419
+
420
+ if (!childIdNode) {
421
+ logger.error(
422
+ `No childIdNode at index`,
423
+ i,
424
+ "on node",
425
+ null,
426
+ "with parent id",
427
+ idTreeNode.id,
428
+ "and child node",
429
+ null
430
+ );
431
+ return;
432
+ }
433
+
434
+ this.updateCache(childNode, childIdNode);
435
+ });
436
+
437
+ if (i < ch.length) {
438
+ // throw new Error("hello");
439
+ }
440
+
441
+ logger.groupEnd();
442
+ }
443
+ }
444
+
445
+ export default NodeTree;
@@ -0,0 +1,137 @@
1
+ <style>
2
+ @keyframes show-dialog {
3
+ 0% {
4
+ opacity: 0;
5
+ scale: 0;
6
+ }
7
+ 90% {
8
+ scale: 1.1;
9
+ }
10
+ 100% {
11
+ opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+
16
+ @keyframes hide-dialog {
17
+ from {
18
+ scale: 1;
19
+ }
20
+ to {
21
+ scale: 0;
22
+ }
23
+ }
24
+
25
+ @keyframes show-backdrop {
26
+ 0% {
27
+ opacity: 0;
28
+ }
29
+ 100% {
30
+ opacity: 1;
31
+ }
32
+ }
33
+
34
+ @keyframes hide-backdrop {
35
+ 0% {
36
+ opacity: 1;
37
+ }
38
+ 100% {
39
+ opacity: 0;
40
+ }
41
+ }
42
+
43
+ dialog {
44
+ all: initial;
45
+ display: flex;
46
+ flex-direction: column;
47
+ gap: 1em;
48
+ border: 3px solid #ccc;
49
+ border-bottom-left-radius: 3px;
50
+ border-bottom-right-radius: 3px;
51
+ border-top: 0;
52
+ position: fixed;
53
+ top: 0;
54
+ left: 50%;
55
+ translate: -50% 0;
56
+ margin: 0;
57
+ padding: 1em;
58
+ min-width: 10em;
59
+ background: #fff;
60
+ font-size: 1rem;
61
+ box-shadow: #0000003f 0px 54px 55px, #0000001e 0px -12px 30px,
62
+ #0000001e 0px 4px 6px, #0000002b 0px 12px 13px, #00000016 0px -3px 5px;
63
+ }
64
+ dialog[open] {
65
+ animation-delay: 0s;
66
+ animation-duration: 300ms;
67
+ animation-iteration-count: 1;
68
+ animation-name: show-dialog;
69
+ animation-play-state: running;
70
+ animation-timing-function: ease;
71
+ }
72
+ dialog:not([open]) {
73
+ animation-delay: 0s;
74
+ animation-duration: 300ms;
75
+ animation-iteration-count: 1;
76
+ animation-name: hide-dialog;
77
+ animation-play-state: running;
78
+ animation-timing-function: ease;
79
+ }
80
+
81
+ dialog::backdrop {
82
+ backdrop-filter: brightness(50%) blur(5px);
83
+ }
84
+ dialog[open]::backdrop {
85
+ animation-delay: 0s;
86
+ animation-duration: 300ms;
87
+ animation-iteration-count: 1;
88
+ animation-name: show-backdrop;
89
+ animation-play-state: running;
90
+ animation-timing-function: ease;
91
+ }
92
+ dialog:not([open])::backdrop {
93
+ animation-delay: 0s;
94
+ animation-duration: 300ms;
95
+ animation-iteration-count: 1;
96
+ animation-name: hide-backdrop;
97
+ animation-play-state: running;
98
+ animation-timing-function: ease;
99
+ }
100
+
101
+ h1 {
102
+ all: initial;
103
+ display: block;
104
+ font-family: system-ui;
105
+ font-size: 1.2em;
106
+ margin: 0;
107
+ }
108
+
109
+ p {
110
+ all: initial;
111
+ display: block;
112
+ font-family: system-ui;
113
+ font-size: 1em;
114
+ margin: 0;
115
+ white-space: pre-wrap;
116
+ font-size: 0.9em;
117
+ user-select: contain;
118
+ }
119
+
120
+ button {
121
+ user-select: none;
122
+ flex: 0;
123
+ padding: 0.5em 1em;
124
+ }
125
+
126
+ .button-wrap {
127
+ display: flex;
128
+ justify-content: flex-end;
129
+ }
130
+ </style>
131
+ <dialog>
132
+ <h1>Alert</h1>
133
+ <p id="message"></p>
134
+ <div class="button-wrap">
135
+ <button onclick="this.getRootNode().host.remove()" autofocus>Ok</button>
136
+ </div>
137
+ </dialog>
@@ -0,0 +1,62 @@
1
+ import html from "./mayu-alert.html";
2
+
3
+ const template = document.createElement("template");
4
+ template.innerHTML = html;
5
+
6
+ class MayuAlert extends HTMLElement {
7
+ #dialog: HTMLDialogElement;
8
+ #message: HTMLParagraphElement;
9
+ #button: HTMLButtonElement;
10
+
11
+ static observedAttributes = ["message"];
12
+
13
+ constructor() {
14
+ super();
15
+
16
+ if (!this.shadowRoot) {
17
+ this.attachShadow({ mode: "open" });
18
+ }
19
+
20
+ this.shadowRoot!.appendChild(
21
+ template.content.cloneNode(true)
22
+ ) as DocumentFragment;
23
+
24
+ this.#dialog = this.shadowRoot!.querySelector(
25
+ "dialog"
26
+ ) as HTMLDialogElement;
27
+
28
+ this.#button = this.shadowRoot!.querySelector(
29
+ "button"
30
+ ) as HTMLButtonElement;
31
+
32
+ this.#message = this.shadowRoot!.getElementById(
33
+ "message"
34
+ ) as HTMLParagraphElement;
35
+
36
+ this.#dialog.addEventListener("close", () => this.remove());
37
+ }
38
+
39
+ connectedCallback() {
40
+ this.#dialog.showModal();
41
+ this.#message.textContent = this.getAttribute("message");
42
+ this.#button.focus();
43
+ }
44
+
45
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
46
+ switch (name) {
47
+ case "message":
48
+ this.#message.textContent = String(newValue);
49
+ break;
50
+ default:
51
+ break;
52
+ }
53
+ }
54
+
55
+ disconnectedCallback() {
56
+ this.#dialog.close();
57
+ }
58
+ }
59
+
60
+ window.customElements.define("mayu-alert", MayuAlert);
61
+
62
+ export default MayuAlert;