@aigne/afs-ui 1.11.0-beta.12

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 (196) hide show
  1. package/LICENSE.md +26 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/aup-protocol.cjs +235 -0
  5. package/dist/aup-protocol.d.cts +78 -0
  6. package/dist/aup-protocol.d.cts.map +1 -0
  7. package/dist/aup-protocol.d.mts +78 -0
  8. package/dist/aup-protocol.d.mts.map +1 -0
  9. package/dist/aup-protocol.mjs +235 -0
  10. package/dist/aup-protocol.mjs.map +1 -0
  11. package/dist/aup-registry.cjs +2489 -0
  12. package/dist/aup-registry.mjs +2487 -0
  13. package/dist/aup-registry.mjs.map +1 -0
  14. package/dist/aup-spec.cjs +1467 -0
  15. package/dist/aup-spec.mjs +1466 -0
  16. package/dist/aup-spec.mjs.map +1 -0
  17. package/dist/aup-types.cjs +165 -0
  18. package/dist/aup-types.d.cts +157 -0
  19. package/dist/aup-types.d.cts.map +1 -0
  20. package/dist/aup-types.d.mts +157 -0
  21. package/dist/aup-types.d.mts.map +1 -0
  22. package/dist/aup-types.mjs +157 -0
  23. package/dist/aup-types.mjs.map +1 -0
  24. package/dist/backend.cjs +14 -0
  25. package/dist/backend.d.cts +104 -0
  26. package/dist/backend.d.cts.map +1 -0
  27. package/dist/backend.d.mts +104 -0
  28. package/dist/backend.d.mts.map +1 -0
  29. package/dist/backend.mjs +13 -0
  30. package/dist/backend.mjs.map +1 -0
  31. package/dist/degradation.cjs +85 -0
  32. package/dist/degradation.d.cts +17 -0
  33. package/dist/degradation.d.cts.map +1 -0
  34. package/dist/degradation.d.mts +17 -0
  35. package/dist/degradation.d.mts.map +1 -0
  36. package/dist/degradation.mjs +84 -0
  37. package/dist/degradation.mjs.map +1 -0
  38. package/dist/index.cjs +36 -0
  39. package/dist/index.d.cts +12 -0
  40. package/dist/index.d.mts +12 -0
  41. package/dist/index.mjs +13 -0
  42. package/dist/runtime.cjs +117 -0
  43. package/dist/runtime.d.cts +59 -0
  44. package/dist/runtime.d.cts.map +1 -0
  45. package/dist/runtime.d.mts +59 -0
  46. package/dist/runtime.d.mts.map +1 -0
  47. package/dist/runtime.mjs +118 -0
  48. package/dist/runtime.mjs.map +1 -0
  49. package/dist/session.cjs +159 -0
  50. package/dist/session.d.cts +80 -0
  51. package/dist/session.d.cts.map +1 -0
  52. package/dist/session.d.mts +80 -0
  53. package/dist/session.d.mts.map +1 -0
  54. package/dist/session.mjs +159 -0
  55. package/dist/session.mjs.map +1 -0
  56. package/dist/snapshot.cjs +162 -0
  57. package/dist/snapshot.mjs +163 -0
  58. package/dist/snapshot.mjs.map +1 -0
  59. package/dist/term-page.cjs +264 -0
  60. package/dist/term-page.mjs +264 -0
  61. package/dist/term-page.mjs.map +1 -0
  62. package/dist/term.cjs +295 -0
  63. package/dist/term.d.cts +84 -0
  64. package/dist/term.d.cts.map +1 -0
  65. package/dist/term.d.mts +84 -0
  66. package/dist/term.d.mts.map +1 -0
  67. package/dist/term.mjs +296 -0
  68. package/dist/term.mjs.map +1 -0
  69. package/dist/tty.cjs +136 -0
  70. package/dist/tty.d.cts +53 -0
  71. package/dist/tty.d.cts.map +1 -0
  72. package/dist/tty.d.mts +53 -0
  73. package/dist/tty.d.mts.map +1 -0
  74. package/dist/tty.mjs +135 -0
  75. package/dist/tty.mjs.map +1 -0
  76. package/dist/ui-provider.cjs +4615 -0
  77. package/dist/ui-provider.d.cts +307 -0
  78. package/dist/ui-provider.d.cts.map +1 -0
  79. package/dist/ui-provider.d.mts +307 -0
  80. package/dist/ui-provider.d.mts.map +1 -0
  81. package/dist/ui-provider.mjs +4616 -0
  82. package/dist/ui-provider.mjs.map +1 -0
  83. package/dist/web-page/core.cjs +1388 -0
  84. package/dist/web-page/core.mjs +1387 -0
  85. package/dist/web-page/core.mjs.map +1 -0
  86. package/dist/web-page/css.cjs +1699 -0
  87. package/dist/web-page/css.mjs +1698 -0
  88. package/dist/web-page/css.mjs.map +1 -0
  89. package/dist/web-page/icons.cjs +248 -0
  90. package/dist/web-page/icons.mjs +248 -0
  91. package/dist/web-page/icons.mjs.map +1 -0
  92. package/dist/web-page/overlay-themes.cjs +514 -0
  93. package/dist/web-page/overlay-themes.mjs +513 -0
  94. package/dist/web-page/overlay-themes.mjs.map +1 -0
  95. package/dist/web-page/renderers/action.cjs +72 -0
  96. package/dist/web-page/renderers/action.mjs +72 -0
  97. package/dist/web-page/renderers/action.mjs.map +1 -0
  98. package/dist/web-page/renderers/broadcast.cjs +160 -0
  99. package/dist/web-page/renderers/broadcast.mjs +160 -0
  100. package/dist/web-page/renderers/broadcast.mjs.map +1 -0
  101. package/dist/web-page/renderers/calendar.cjs +137 -0
  102. package/dist/web-page/renderers/calendar.mjs +137 -0
  103. package/dist/web-page/renderers/calendar.mjs.map +1 -0
  104. package/dist/web-page/renderers/canvas.cjs +173 -0
  105. package/dist/web-page/renderers/canvas.mjs +173 -0
  106. package/dist/web-page/renderers/canvas.mjs.map +1 -0
  107. package/dist/web-page/renderers/cdn-loader.cjs +25 -0
  108. package/dist/web-page/renderers/cdn-loader.mjs +25 -0
  109. package/dist/web-page/renderers/cdn-loader.mjs.map +1 -0
  110. package/dist/web-page/renderers/chart.cjs +101 -0
  111. package/dist/web-page/renderers/chart.mjs +101 -0
  112. package/dist/web-page/renderers/chart.mjs.map +1 -0
  113. package/dist/web-page/renderers/deck.cjs +390 -0
  114. package/dist/web-page/renderers/deck.mjs +390 -0
  115. package/dist/web-page/renderers/deck.mjs.map +1 -0
  116. package/dist/web-page/renderers/device.cjs +1015 -0
  117. package/dist/web-page/renderers/device.mjs +1015 -0
  118. package/dist/web-page/renderers/device.mjs.map +1 -0
  119. package/dist/web-page/renderers/editor.cjs +127 -0
  120. package/dist/web-page/renderers/editor.mjs +127 -0
  121. package/dist/web-page/renderers/editor.mjs.map +1 -0
  122. package/dist/web-page/renderers/finance-chart.cjs +178 -0
  123. package/dist/web-page/renderers/finance-chart.mjs +178 -0
  124. package/dist/web-page/renderers/finance-chart.mjs.map +1 -0
  125. package/dist/web-page/renderers/frame.cjs +274 -0
  126. package/dist/web-page/renderers/frame.mjs +274 -0
  127. package/dist/web-page/renderers/frame.mjs.map +1 -0
  128. package/dist/web-page/renderers/globe.cjs +119 -0
  129. package/dist/web-page/renderers/globe.mjs +119 -0
  130. package/dist/web-page/renderers/globe.mjs.map +1 -0
  131. package/dist/web-page/renderers/input.cjs +137 -0
  132. package/dist/web-page/renderers/input.mjs +137 -0
  133. package/dist/web-page/renderers/input.mjs.map +1 -0
  134. package/dist/web-page/renderers/list.cjs +1243 -0
  135. package/dist/web-page/renderers/list.mjs +1243 -0
  136. package/dist/web-page/renderers/list.mjs.map +1 -0
  137. package/dist/web-page/renderers/map.cjs +126 -0
  138. package/dist/web-page/renderers/map.mjs +126 -0
  139. package/dist/web-page/renderers/map.mjs.map +1 -0
  140. package/dist/web-page/renderers/media.cjs +106 -0
  141. package/dist/web-page/renderers/media.mjs +106 -0
  142. package/dist/web-page/renderers/media.mjs.map +1 -0
  143. package/dist/web-page/renderers/moonphase.cjs +105 -0
  144. package/dist/web-page/renderers/moonphase.mjs +105 -0
  145. package/dist/web-page/renderers/moonphase.mjs.map +1 -0
  146. package/dist/web-page/renderers/natal-chart.cjs +222 -0
  147. package/dist/web-page/renderers/natal-chart.mjs +222 -0
  148. package/dist/web-page/renderers/natal-chart.mjs.map +1 -0
  149. package/dist/web-page/renderers/overlay.cjs +531 -0
  150. package/dist/web-page/renderers/overlay.mjs +531 -0
  151. package/dist/web-page/renderers/overlay.mjs.map +1 -0
  152. package/dist/web-page/renderers/table.cjs +74 -0
  153. package/dist/web-page/renderers/table.mjs +74 -0
  154. package/dist/web-page/renderers/table.mjs.map +1 -0
  155. package/dist/web-page/renderers/terminal.cjs +30 -0
  156. package/dist/web-page/renderers/terminal.mjs +30 -0
  157. package/dist/web-page/renderers/terminal.mjs.map +1 -0
  158. package/dist/web-page/renderers/text.cjs +109 -0
  159. package/dist/web-page/renderers/text.mjs +109 -0
  160. package/dist/web-page/renderers/text.mjs.map +1 -0
  161. package/dist/web-page/renderers/ticker.cjs +133 -0
  162. package/dist/web-page/renderers/ticker.mjs +133 -0
  163. package/dist/web-page/renderers/ticker.mjs.map +1 -0
  164. package/dist/web-page/renderers/time.cjs +69 -0
  165. package/dist/web-page/renderers/time.mjs +69 -0
  166. package/dist/web-page/renderers/time.mjs.map +1 -0
  167. package/dist/web-page/renderers/unknown.cjs +20 -0
  168. package/dist/web-page/renderers/unknown.mjs +20 -0
  169. package/dist/web-page/renderers/unknown.mjs.map +1 -0
  170. package/dist/web-page/renderers/view.cjs +161 -0
  171. package/dist/web-page/renderers/view.mjs +161 -0
  172. package/dist/web-page/renderers/view.mjs.map +1 -0
  173. package/dist/web-page/renderers/wm.cjs +669 -0
  174. package/dist/web-page/renderers/wm.mjs +669 -0
  175. package/dist/web-page/renderers/wm.mjs.map +1 -0
  176. package/dist/web-page/skeleton.cjs +103 -0
  177. package/dist/web-page/skeleton.mjs +103 -0
  178. package/dist/web-page/skeleton.mjs.map +1 -0
  179. package/dist/web-page.cjs +114 -0
  180. package/dist/web-page.d.cts +19 -0
  181. package/dist/web-page.d.cts.map +1 -0
  182. package/dist/web-page.d.mts +19 -0
  183. package/dist/web-page.d.mts.map +1 -0
  184. package/dist/web-page.mjs +115 -0
  185. package/dist/web-page.mjs.map +1 -0
  186. package/dist/web.cjs +827 -0
  187. package/dist/web.d.cts +144 -0
  188. package/dist/web.d.cts.map +1 -0
  189. package/dist/web.d.mts +144 -0
  190. package/dist/web.d.mts.map +1 -0
  191. package/dist/web.mjs +828 -0
  192. package/dist/web.mjs.map +1 -0
  193. package/dist/wm-state.cjs +172 -0
  194. package/dist/wm-state.mjs +171 -0
  195. package/dist/wm-state.mjs.map +1 -0
  196. package/package.json +59 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.mjs","names":[],"sources":["../../../src/web-page/renderers/device.ts"],"sourcesContent":["export const DEVICE_JS = `\n // ── Device Renderer (universal surface — AFS path or remote WebSocket) ──\n\n function _resolveDeviceUrl(url) {\n if (!url || typeof url !== \"string\") return null;\n url = url.trim();\n if (url.indexOf(\"ws://\") === 0 || url.indexOf(\"wss://\") === 0) return url;\n if (url.indexOf(\"https://\") === 0) return \"wss://\" + url.slice(8);\n if (url.indexOf(\"http://\") === 0) return \"ws://\" + url.slice(7);\n // Shorthand: \":3300\" or \"localhost:3300\"\n if (/^:\\\\\\\\d+/.test(url)) return \"ws://localhost\" + url;\n if (/^localhost:\\\\\\\\d+/.test(url)) return \"ws://\" + url;\n return null;\n }\n\n function renderDeviceFallback(node) {\n var el = document.createElement(\"div\");\n el.className = \"aup-device-fallback\";\n el.innerHTML = '<span style=\"font-size:1.2em\">\\\\\\\\u26a0</span> '\n + '<strong>' + _escapeHtml(String(node.type)) + '</strong>'\n + ' (not supported on this device)';\n return el;\n }\n\n function renderAupDevice(node) {\n var el = document.createElement(\"div\");\n el.className = \"aup-device\";\n var p = node.props || {};\n var src = node.src || p.url || p.src || \"\";\n var showStatus = p.showStatus !== false;\n\n // Sizing modes: \"fixed\" (default if height set) — scroll internally;\n // \"fit\" — auto-size to content within min/maxHeight range\n var sizing = p.sizing || (p.height ? \"fixed\" : \"fit\");\n if (sizing === \"fixed\") {\n if (p.height) el.style.height = p.height;\n el.style.overflow = \"hidden\";\n el.setAttribute(\"data-aup-device-sizing\", \"fixed\");\n } else {\n // \"fit\" mode — content determines height, constrained by min/maxHeight\n el.style.height = \"auto\";\n el.style.overflow = \"visible\";\n if (p.minHeight) el.style.minHeight = p.minHeight;\n if (p.maxHeight) { el.style.maxHeight = p.maxHeight; el.style.overflow = \"auto\"; }\n el.setAttribute(\"data-aup-device-sizing\", \"fit\");\n }\n if (p.capabilities && p.capabilities.maxWidth) el.style.maxWidth = p.capabilities.maxWidth;\n\n // Route 1: WebSocket URL → remote AUP connection\n var wsUrl = _resolveDeviceUrl(src);\n if (wsUrl) {\n return _renderDeviceWs(el, node, p, wsUrl, showStatus);\n }\n\n // Route 2: AFS path → introspect and auto-render\n if (src && src.indexOf(\"/\") === 0 && window.afs) {\n return _renderDeviceAfs(el, node, p, src, showStatus);\n }\n\n // No valid source\n var placeholder = document.createElement(\"div\");\n placeholder.className = \"aup-device-fallback\";\n placeholder.innerHTML = '<span style=\"font-size:1.2em\">\\\\\\\\u26a0</span> '\n + '<span>Device: set <code>src</code> (AFS path) or <code>url</code> (ws://)</span>';\n el.appendChild(placeholder);\n return el;\n }\n\n // ── Route 1: Remote AUP via WebSocket (existing behavior) ──\n\n function _renderDeviceWs(el, node, p, wsUrl, showStatus) {\n // Status indicator\n var statusEl = document.createElement(\"div\");\n statusEl.className = \"aup-device-status\";\n if (showStatus) el.appendChild(statusEl);\n\n // Content container\n var contentEl = document.createElement(\"div\");\n contentEl.className = \"aup-device-content\";\n el.appendChild(contentEl);\n\n // Disconnected message\n var disconnectEl = document.createElement(\"div\");\n disconnectEl.className = \"aup-device-fallback\";\n disconnectEl.style.display = \"none\";\n disconnectEl.textContent = \"Device disconnected\";\n el.appendChild(disconnectEl);\n\n // Capability filter — recursively replaces unsupported nodes before rendering\n var allowedPrimitives = p.capabilities && p.capabilities.primitives;\n function _filterTree(n) {\n if (!n) return n;\n if (allowedPrimitives && n.type && allowedPrimitives.indexOf(n.type) < 0) {\n return {\n id: n.id, type: \"view\", props: {},\n children: [{ id: (n.id || \"x\") + \"-fb\", type: \"text\",\n props: { content: \"\\\\\\\\u26a0 \" + n.type + \" (not supported on this device)\", intent: \"info\", scale: \"sm\" } }]\n };\n }\n if (n.children) {\n var fc = [];\n for (var ci = 0; ci < n.children.length; ci++) fc.push(_filterTree(n.children[ci]));\n var copy = {}; for (var k in n) copy[k] = n[k];\n copy.children = fc;\n return copy;\n }\n return n;\n }\n function renderFiltered(n) {\n if (!n || !n.type) return null;\n if (allowedPrimitives) n = _filterTree(n);\n return renderAupNode(n);\n }\n\n // Store device WS on the element for event routing\n el.setAttribute(\"data-aup-device-id\", node.id || (\"device-\" + Math.random().toString(36).slice(2)));\n var deviceWs = null;\n var reconnectTimer = null;\n var reconnectDelay = 1000;\n var destroyed = false;\n var deviceNodeTree = null;\n\n function connectDevice() {\n if (destroyed) return;\n try {\n deviceWs = new WebSocket(wsUrl);\n } catch (ex) {\n statusEl.className = \"aup-device-status error\";\n return;\n }\n el._aupDeviceWs = deviceWs;\n\n deviceWs.onopen = function() {\n statusEl.className = \"aup-device-status connected\";\n disconnectEl.style.display = \"none\";\n reconnectDelay = 1000;\n // Handshake\n var handshake = { type: \"join_session\" };\n if (allowedPrimitives) {\n handshake.caps = { primitives: allowedPrimitives };\n }\n deviceWs.send(JSON.stringify(handshake));\n // Fire connect event\n if (node.events && node.events.connect) {\n _fireAupEvent(node.id, \"connect\", {});\n }\n };\n\n deviceWs.onmessage = function(e) {\n var msg;\n try { msg = JSON.parse(e.data); } catch (_ex) { return; }\n\n if (msg.type === \"aup\" && msg.action === \"render\") {\n deviceNodeTree = msg.root;\n contentEl.innerHTML = \"\";\n var rendered = renderFiltered(msg.root);\n if (rendered) {\n contentEl.appendChild(rendered);\n // Trigger animations for device-rendered content immediately\n var anims = contentEl.querySelectorAll(\"[data-animate]\");\n for (var ai = 0; ai < anims.length; ai++) anims[ai].classList.add(\"aup-animated\");\n }\n } else if (msg.type === \"aup\" && msg.action === \"patch\") {\n // Apply patches to device node tree and re-render affected nodes\n if (deviceNodeTree && msg.ops) {\n _applyDevicePatches(contentEl, deviceNodeTree, msg.ops, renderFiltered);\n }\n }\n // Ignore other message types (session, etc.)\n };\n\n deviceWs.onclose = function() {\n statusEl.className = \"aup-device-status\";\n el._aupDeviceWs = null;\n deviceWs = null;\n if (!destroyed) {\n disconnectEl.style.display = \"\";\n // Fire disconnect event\n if (node.events && node.events.disconnect) {\n _fireAupEvent(node.id, \"disconnect\", {});\n }\n // Auto-reconnect with exponential backoff\n reconnectTimer = setTimeout(function() {\n reconnectDelay = Math.min(reconnectDelay * 2, 30000);\n connectDevice();\n }, reconnectDelay);\n }\n };\n\n deviceWs.onerror = function() {\n statusEl.className = \"aup-device-status error\";\n if (node.events && node.events.error) {\n _fireAupEvent(node.id, \"error\", { url: wsUrl });\n }\n };\n }\n\n connectDevice();\n\n // Cleanup on DOM removal\n var observer = new MutationObserver(function(mutations) {\n for (var i = 0; i < mutations.length; i++) {\n for (var j = 0; j < mutations[i].removedNodes.length; j++) {\n if (mutations[i].removedNodes[j] === el || mutations[i].removedNodes[j].contains(el)) {\n destroyed = true;\n if (reconnectTimer) clearTimeout(reconnectTimer);\n if (deviceWs) { try { deviceWs.close(); } catch (_ex) {} }\n el._aupDeviceWs = null;\n observer.disconnect();\n return;\n }\n }\n }\n });\n if (el.parentNode) {\n observer.observe(el.parentNode, { childList: true, subtree: true });\n } else {\n // Defer until appended\n setTimeout(function() {\n if (el.parentNode) observer.observe(el.parentNode, { childList: true, subtree: true });\n }, 0);\n }\n\n return el;\n }\n\n // ── Route 2: AFS path → introspect and auto-render ──\n\n function _renderDeviceAfs(el, node, p, src, showStatus) {\n el.setAttribute(\"data-aup-id\", node.id || \"\");\n el.setAttribute(\"data-aup-device-src\", src);\n\n // Status dot\n var statusEl = document.createElement(\"div\");\n statusEl.className = \"aup-device-status\";\n if (showStatus) el.appendChild(statusEl);\n\n // Navigation breadcrumb (device-level, above content)\n var breadcrumbEl = document.createElement(\"div\");\n breadcrumbEl.className = \"aup-device-breadcrumb\";\n breadcrumbEl.style.display = \"none\";\n el.appendChild(breadcrumbEl);\n\n // View selector container (tabs or dropdown for multi-view .aup/)\n var viewSelectorEl = document.createElement(\"div\");\n viewSelectorEl.className = \"aup-device-view-selector\";\n viewSelectorEl.style.display = \"none\";\n el.appendChild(viewSelectorEl);\n\n // Content container\n var contentEl = document.createElement(\"div\");\n contentEl.className = \"aup-device-content\";\n el.appendChild(contentEl);\n\n var prefix = node.id || \"dev\";\n var rootSrc = src;\n var currentSrc = src;\n // Navigation stack: [{path, isLeaf}]\n var navStack = [{ path: src, isLeaf: false }];\n // Track current active view name and all discovered views\n var activeViewName = (node.state && node.state.activeView) || \"default\";\n var discoveredViews = null;\n // Track in-flight view switch to prevent races\n var viewSwitchSeq = 0;\n\n function updateBreadcrumb() {\n breadcrumbEl.innerHTML = \"\";\n if (navStack.length <= 1) {\n breadcrumbEl.style.display = \"none\";\n return;\n }\n breadcrumbEl.style.display = \"\";\n for (var i = 0; i < navStack.length; i++) {\n if (i > 0) {\n var sep = document.createElement(\"span\");\n sep.className = \"aup-device-breadcrumb-sep\";\n sep.textContent = \"/\";\n breadcrumbEl.appendChild(sep);\n }\n var seg = document.createElement(\"span\");\n var segPath = navStack[i].path;\n var segName = segPath === rootSrc ? (rootSrc.split(\"/\").filter(Boolean).pop() || \"/\") : segPath.split(\"/\").filter(Boolean).pop() || \"/\";\n if (i < navStack.length - 1) {\n seg.className = \"aup-device-breadcrumb-seg\";\n seg.textContent = segName;\n (function(targetIdx) {\n seg.onclick = function() { navigateToStack(targetIdx); };\n })(i);\n } else {\n seg.className = \"aup-device-breadcrumb-cur\";\n seg.textContent = segName;\n }\n breadcrumbEl.appendChild(seg);\n }\n }\n\n function navigateToStack(idx) {\n navStack = navStack.slice(0, idx + 1);\n currentSrc = navStack[navStack.length - 1].path;\n resolveAndRender(currentSrc);\n updateBreadcrumb();\n }\n\n function navigateToPath(path, isLeaf) {\n // Don't navigate above root\n if (path.indexOf(rootSrc) !== 0 && rootSrc.indexOf(path) !== 0) return;\n currentSrc = path;\n navStack.push({ path: path, isLeaf: !!isLeaf });\n resolveAndRender(path);\n updateBreadcrumb();\n }\n\n function resolveAndRender(targetSrc) {\n contentEl.innerHTML = \"\";\n var loader = document.createElement(\"div\");\n loader.className = \"aup-src-loading\";\n loader.innerHTML = '<div class=\"aup-src-loading-bar\"></div>';\n contentEl.appendChild(loader);\n\n window.afs.stat(targetSrc).then(function(statResult) {\n loader.remove();\n statusEl.className = \"aup-device-status connected\";\n var entry = statResult || {};\n var meta = entry.meta || {};\n var kind = meta.kind || \"\";\n var childrenCount = meta.childrenCount;\n var isDir = childrenCount != null && childrenCount >= 0;\n\n // Priority 1: Check for .aup/ recipe (with multi-view discovery)\n _tryAupRecipe(targetSrc, p).then(function(recipe) {\n if (recipe) {\n // Check for multi-view metadata attached by _discoverAupViews\n var views = recipe._aupViews;\n if (views && views.length > 1) {\n discoveredViews = views;\n _renderViewSelector(viewSelectorEl, views, activeViewName, function(viewName) {\n _switchToView(targetSrc, viewName);\n });\n } else {\n discoveredViews = null;\n viewSelectorEl.style.display = \"none\";\n viewSelectorEl.innerHTML = \"\";\n }\n // Clean up internal metadata before rendering\n delete recipe._aupViews;\n var rendered = renderAupNode(recipe);\n if (rendered) contentEl.appendChild(rendered);\n } else if (isDir) {\n discoveredViews = null;\n viewSelectorEl.style.display = \"none\";\n viewSelectorEl.innerHTML = \"\";\n _renderDeviceAsDir(contentEl, el, prefix, targetSrc, p, kind, navigateToPath);\n } else {\n discoveredViews = null;\n viewSelectorEl.style.display = \"none\";\n viewSelectorEl.innerHTML = \"\";\n _renderDeviceAsLeaf(contentEl, prefix, targetSrc, meta);\n }\n });\n\n // Fire connect event on initial load\n if (targetSrc === rootSrc && node.events && node.events.connect) {\n _fireAupEvent(node.id, \"connect\", { src: targetSrc, kind: kind });\n }\n }).catch(function(err) {\n loader.remove();\n statusEl.className = \"aup-device-status error\";\n var errEl = document.createElement(\"div\");\n errEl.className = \"aup-device-fallback\";\n errEl.textContent = \"Failed to resolve: \" + targetSrc + \" (\" + (err.message || \"error\") + \")\";\n contentEl.appendChild(errEl);\n\n if (node.events && node.events.error) {\n _fireAupEvent(node.id, \"error\", { src: targetSrc, message: err.message });\n }\n });\n }\n\n /**\n * Switch to a different .aup/ view by name.\n * Loads the recipe from the selected view and re-renders content.\n */\n function _switchToView(targetSrc, viewName) {\n if (!discoveredViews) return;\n var seq = ++viewSwitchSeq;\n activeViewName = viewName;\n // Persist selection in node.state\n if (node.state) node.state.activeView = viewName;\n else node.state = { activeView: viewName };\n\n // Find the view entry\n var view = null;\n for (var i = 0; i < discoveredViews.length; i++) {\n if (discoveredViews[i].name === viewName) {\n view = discoveredViews[i];\n break;\n }\n }\n if (!view) return;\n\n // Determine preferred variant\n var preferCompact = false;\n if (p && p.capabilities) {\n var caps = p.capabilities;\n if (caps.maxWidth || (caps.primitives && caps.primitives.length <= 4)) {\n preferCompact = true;\n }\n }\n var variants = preferCompact ? [\"compact.json\", \"default.json\"] : [\"default.json\"];\n\n contentEl.innerHTML = \"\";\n var loader = document.createElement(\"div\");\n loader.className = \"aup-src-loading\";\n loader.innerHTML = '<div class=\"aup-src-loading-bar\"></div>';\n contentEl.appendChild(loader);\n\n _tryViewVariants(view, variants, 0).then(function(recipe) {\n // Check if a newer switch has been initiated\n if (seq !== viewSwitchSeq) return;\n loader.remove();\n if (recipe) {\n var rendered = renderAupNode(recipe);\n if (rendered) contentEl.appendChild(rendered);\n } else {\n var errEl = document.createElement(\"div\");\n errEl.className = \"aup-device-fallback\";\n errEl.textContent = \"No recipe found for view: \" + viewName;\n contentEl.appendChild(errEl);\n }\n }).catch(function(err) {\n if (seq !== viewSwitchSeq) return;\n loader.remove();\n var errEl = document.createElement(\"div\");\n errEl.className = \"aup-device-fallback\";\n errEl.textContent = \"Failed to load view: \" + viewName + \" (\" + (err.message || \"error\") + \")\";\n contentEl.appendChild(errEl);\n });\n\n // Update selector UI (mark active)\n _updateViewSelectorActive(viewSelectorEl, viewName);\n }\n\n // Initial resolve\n resolveAndRender(src);\n\n // Live subscription — re-resolve when data changes\n if (window.afs.subscribe) {\n window.afs.subscribe({ type: \"afs:write\", path: src }, function() {\n resolveAndRender(currentSrc);\n });\n }\n\n return el;\n }\n\n // ── AFS rendering strategies ──\n\n function _renderDeviceAsDir(contentEl, deviceEl, prefix, src, props, kind, onNavigate) {\n // Generate an afs-list node and render it inline\n var listNode = {\n id: prefix + \"-list\",\n type: \"afs-list\",\n src: src,\n props: {\n layout: \"list\",\n itemStyle: \"row\",\n showBreadcrumb: false,\n clickMode: \"both\"\n }\n };\n // Kind-aware layout hints\n if (kind === \"gallery\" || kind === \"media\") {\n listNode.props.layout = \"masonry\";\n listNode.props.itemStyle = \"media\";\n } else if (kind === \"themes-directory\" || kind === \"overlay-themes-directory\") {\n listNode.props.layout = \"grid\";\n listNode.props.itemStyle = \"card\";\n }\n var rendered = renderAupNode(listNode);\n if (rendered) {\n contentEl.appendChild(rendered);\n // Listen for select events on files — navigate device to show leaf detail\n if (onNavigate) {\n rendered.addEventListener(\"aup-list:select\", function(e) {\n var d = e.detail;\n if (d && d.path) onNavigate(d.path, true);\n });\n }\n }\n }\n\n function _renderDeviceAsLeaf(contentEl, prefix, src, meta) {\n window.afs.read(src).then(function(result) {\n var content = result;\n var tree;\n\n if (typeof content === \"string\") {\n tree = {\n id: prefix + \"-text\",\n type: \"text\",\n props: { content: content, format: \"markdown\" }\n };\n } else if (content && typeof content === \"object\") {\n tree = _buildDeviceDetailView(prefix, content, meta);\n } else {\n tree = {\n id: prefix + \"-empty\",\n type: \"text\",\n props: { content: \"(empty)\", intent: \"info\" }\n };\n }\n\n var rendered = renderAupNode(tree);\n if (rendered) contentEl.appendChild(rendered);\n }).catch(function(err) {\n var errEl = document.createElement(\"div\");\n errEl.className = \"aup-device-fallback\";\n errEl.textContent = \"Read failed: \" + (err.message || \"error\");\n contentEl.appendChild(errEl);\n });\n }\n\n function _buildDeviceDetailView(prefix, obj, meta) {\n var children = [];\n // Title from meta if available\n if (meta && meta.description) {\n children.push({\n id: prefix + \"-desc\",\n type: \"text\",\n props: { content: String(meta.description), intent: \"info\", scale: \"sm\" }\n });\n }\n var keys = Object.keys(obj);\n for (var i = 0; i < keys.length; i++) {\n var k = keys[i];\n var v = obj[k];\n var valStr = v === null ? \"null\" : typeof v === \"object\" ? JSON.stringify(v) : String(v);\n children.push({\n id: prefix + \"-kv-\" + i,\n type: \"view\",\n props: { layout: { direction: \"row\", gap: \"sm\" }, mode: \"inline\" },\n children: [\n { id: prefix + \"-k-\" + i, type: \"text\", props: { content: k + \":\", intent: \"info\", scale: \"sm\" } },\n { id: prefix + \"-v-\" + i, type: \"text\", props: { content: valStr, scale: \"sm\" } }\n ]\n });\n }\n return {\n id: prefix + \"-detail\",\n type: \"view\",\n props: { layout: { gap: \"xs\" }, mode: \"card\" },\n children: children\n };\n }\n\n // ── Device patch helpers ──\n function _applyDevicePatches(contentEl, tree, ops, renderFn) {\n for (var i = 0; i < ops.length; i++) {\n var op = ops[i];\n if (op.op === \"update\") {\n var target = _findDeviceNode(tree, op.id);\n if (target) {\n if (op.props) target.props = Object.assign(target.props || {}, op.props);\n if (op.state) target.state = Object.assign(target.state || {}, op.state);\n if (op.events !== undefined) target.events = op.events;\n }\n // Re-render the specific node in the DOM\n var domEl = contentEl.querySelector('[data-aup-id=\"' + op.id + '\"]');\n if (domEl && target) {\n var newEl = renderFn(target);\n if (newEl) domEl.replaceWith(newEl);\n }\n } else if (op.op === \"create\") {\n var parent = _findDeviceNode(tree, op.parentId);\n if (parent) {\n if (!parent.children) parent.children = [];\n var newNode = Object.assign({}, op.node, { id: op.id });\n if (typeof op.index === \"number\") parent.children.splice(op.index, 0, newNode);\n else parent.children.push(newNode);\n }\n var parentDom = contentEl.querySelector('[data-aup-id=\"' + op.parentId + '\"]');\n var createdNode = _findDeviceNode(tree, op.id);\n if (parentDom && createdNode) {\n var createdEl = renderFn(createdNode);\n if (createdEl) {\n if (typeof op.index === \"number\" && parentDom.children[op.index]) {\n parentDom.insertBefore(createdEl, parentDom.children[op.index]);\n } else {\n parentDom.appendChild(createdEl);\n }\n }\n }\n } else if (op.op === \"remove\") {\n _removeDeviceNode(tree, op.id);\n var removeDom = contentEl.querySelector('[data-aup-id=\"' + op.id + '\"]');\n if (removeDom) removeDom.remove();\n }\n }\n }\n\n function _findDeviceNode(node, id) {\n if (!node) return null;\n if (node.id === id) return node;\n if (node.children) {\n for (var i = 0; i < node.children.length; i++) {\n var found = _findDeviceNode(node.children[i], id);\n if (found) return found;\n }\n }\n return null;\n }\n\n function _removeDeviceNode(root, id) {\n if (!root.children) return;\n for (var i = 0; i < root.children.length; i++) {\n if (root.children[i].id === id) { root.children.splice(i, 1); return; }\n _removeDeviceNode(root.children[i], id);\n }\n }\n\n // ── .aup/ view selector ──\n\n /**\n * Render the view selector (tabs for <=5 views, dropdown for >5).\n * @param container - The view selector container element\n * @param views - Array of { name, path } view entries\n * @param activeView - Currently active view name\n * @param onSwitch - Callback when user selects a different view\n */\n function _renderViewSelector(container, views, activeView, onSwitch) {\n container.innerHTML = \"\";\n if (!views || views.length <= 1) {\n container.style.display = \"none\";\n return;\n }\n container.style.display = \"\";\n\n // Resolve display labels: try meta.json for each view, fall back to name\n // For now, use directory name directly (meta.json loading is async, done lazily)\n var labels = {};\n for (var i = 0; i < views.length; i++) {\n labels[views[i].name] = _viewDisplayLabel(views[i].name);\n }\n\n // Determine if the active view exists; if not, pick \"default\" or first\n var found = false;\n for (var j = 0; j < views.length; j++) {\n if (views[j].name === activeView) { found = true; break; }\n }\n if (!found) {\n // Try \"default\", then first alphabetically\n activeView = \"default\";\n found = false;\n for (var k = 0; k < views.length; k++) {\n if (views[k].name === activeView) { found = true; break; }\n }\n if (!found) activeView = views[0].name;\n }\n\n if (views.length <= 5) {\n // Tab bar mode\n for (var ti = 0; ti < views.length; ti++) {\n var tab = document.createElement(\"span\");\n tab.className = \"aup-device-view-tab\" + (views[ti].name === activeView ? \" active\" : \"\");\n tab.textContent = labels[views[ti].name] || views[ti].name;\n tab.setAttribute(\"data-view-name\", views[ti].name);\n (function(viewName) {\n tab.onclick = function() {\n if (!tab.classList.contains(\"active\")) onSwitch(viewName);\n };\n })(views[ti].name);\n container.appendChild(tab);\n }\n } else {\n // Dropdown mode\n var select = document.createElement(\"select\");\n select.className = \"aup-device-view-dropdown\";\n for (var di = 0; di < views.length; di++) {\n var option = document.createElement(\"option\");\n option.value = views[di].name;\n option.textContent = labels[views[di].name] || views[di].name;\n if (views[di].name === activeView) option.selected = true;\n select.appendChild(option);\n }\n select.onchange = function() {\n onSwitch(select.value);\n };\n container.appendChild(select);\n }\n\n // Async: try loading meta.json for each view to get better labels\n _loadViewMetaLabels(views, function(updatedLabels) {\n if (!updatedLabels) return;\n var changed = false;\n for (var m in updatedLabels) {\n if (updatedLabels[m] && updatedLabels[m] !== labels[m]) {\n labels[m] = updatedLabels[m];\n changed = true;\n }\n }\n if (changed) {\n // Update displayed labels\n if (views.length <= 5) {\n var tabs = container.querySelectorAll(\".aup-device-view-tab\");\n for (var t = 0; t < tabs.length; t++) {\n var vn = tabs[t].getAttribute(\"data-view-name\");\n if (vn && labels[vn]) tabs[t].textContent = labels[vn];\n }\n } else {\n var options = container.querySelectorAll(\"option\");\n for (var o = 0; o < options.length; o++) {\n var ov = options[o].value;\n if (ov && labels[ov]) options[o].textContent = labels[ov];\n }\n }\n }\n });\n }\n\n /**\n * Generate a human-readable display label from a view directory name.\n */\n function _viewDisplayLabel(name) {\n if (!name) return \"\";\n // Capitalize first letter, replace hyphens/underscores with spaces\n var label = name.replace(/[-_]/g, \" \");\n return label.charAt(0).toUpperCase() + label.slice(1);\n }\n\n /**\n * Load meta.json from each view to get display labels.\n * Calls callback with { viewName: label } map, or null if nothing found.\n */\n function _loadViewMetaLabels(views, callback) {\n if (!window.afs || !window.afs.read) { callback(null); return; }\n var results = {};\n var pending = views.length;\n var anyFound = false;\n\n for (var i = 0; i < views.length; i++) {\n (function(view) {\n var metaPath = view.path + \"/meta.json\";\n window.afs.read(metaPath).then(function(result) {\n var meta = result;\n if (meta && meta.content !== undefined) meta = meta.content;\n if (typeof meta === \"string\") {\n try { meta = JSON.parse(meta); } catch(_e) { meta = null; }\n }\n if (meta && typeof meta === \"object\" && meta.label) {\n results[view.name] = meta.label;\n anyFound = true;\n }\n if (--pending === 0) callback(anyFound ? results : null);\n }).catch(function() {\n if (--pending === 0) callback(anyFound ? results : null);\n });\n })(views[i]);\n }\n }\n\n /**\n * Update the active state on the view selector (tab bar or dropdown).\n */\n function _updateViewSelectorActive(container, activeViewName) {\n // Tab mode\n var tabs = container.querySelectorAll(\".aup-device-view-tab\");\n for (var i = 0; i < tabs.length; i++) {\n var vn = tabs[i].getAttribute(\"data-view-name\");\n if (vn === activeViewName) {\n tabs[i].className = \"aup-device-view-tab active\";\n } else {\n tabs[i].className = \"aup-device-view-tab\";\n }\n }\n // Dropdown mode\n var select = container.querySelector(\".aup-device-view-dropdown\");\n if (select) {\n select.value = activeViewName;\n }\n }\n\n // ── .aup/ recipe discovery ──\n\n /**\n * Discover all .aup/ views via list(), then load the best recipe.\n * Returns { recipe, views } where views is the full list of discovered entries.\n * Falls back to probing hardcoded variant filenames if list is unavailable.\n */\n function _tryAupRecipe(src, props) {\n if (!window.afs || !window.afs.read) {\n return Promise.resolve(null);\n }\n\n // Determine preferred variants based on capabilities\n var preferCompact = false;\n if (props && props.capabilities) {\n var caps = props.capabilities;\n if (caps.maxWidth || (caps.primitives && caps.primitives.length <= 4)) {\n preferCompact = true;\n }\n }\n\n // Strategy 1: list-based discovery (supports supplementary providers)\n if (window.afs.list) {\n return _discoverAupViews(src, preferCompact);\n }\n\n // Strategy 2: fallback to probing hardcoded variant filenames\n var variants = preferCompact ? [\"compact\", \"default\"] : [\"default\"];\n return _tryAupVariants(src, variants, 0);\n }\n\n /**\n * List .aup/ directory to discover all available view entries.\n * Returns the recipe for the best matching view, or null if none found.\n * Stores discovered views on the result for Phase 1 view switching.\n */\n function _discoverAupViews(src, preferCompact) {\n var aupPath = src + \"/.aup\";\n return window.afs.list(aupPath).then(function(result) {\n // list() returns {data: [...]} — unwrap\n var entries = result && result.data ? result.data : (Array.isArray(result) ? result : null);\n if (!entries || !Array.isArray(entries) || entries.length === 0) {\n // No .aup/ entries — fall back to probe-based\n var variants = preferCompact ? [\"compact\", \"default\"] : [\"default\"];\n return _tryAupVariants(src, variants, 0);\n }\n\n // Separate flat recipe files (.json leaves) from view directories.\n // AFSJSON stores recipes as stringified JSON leaves — they appear as\n // \".aup/default.json\" (a file), not \".aup/default/\" (a directory).\n var views = [];\n var flatRecipes = []; // { name, path, content? }\n for (var i = 0; i < entries.length; i++) {\n var entry = entries[i];\n var entryPath = entry.path || \"\";\n var name = entryPath.split(\"/\").filter(Boolean).pop() || \"\";\n if (!name) continue;\n var isLeaf = name.indexOf(\".json\") === name.length - 5;\n var meta = entry.meta || {};\n // Also detect leaves by childrenCount (directories have childrenCount >= 0)\n if (isLeaf || (meta.childrenCount == null && entry.content != null)) {\n flatRecipes.push({ name: name.replace(/\\\\.json$/, \"\"), path: entryPath, content: entry.content });\n } else {\n views.push({ name: name, path: entryPath });\n }\n }\n\n // If we found flat recipe files, try reading them directly\n if (flatRecipes.length > 0 && views.length === 0) {\n // Pure flat structure — pick best recipe by preference order\n var preferred = preferCompact ? [\"compact\", \"default\"] : [\"default\"];\n return _tryFlatRecipes(flatRecipes, preferred, 0);\n }\n\n if (views.length === 0 && flatRecipes.length === 0) {\n var fallbackVariants = preferCompact ? [\"compact\", \"default\"] : [\"default\"];\n return _tryAupVariants(src, fallbackVariants, 0);\n }\n\n // Sort views: \"default\" first, then \"compact\", then alphabetical\n views.sort(function(a, b) {\n if (a.name === \"default\") return -1;\n if (b.name === \"default\") return 1;\n if (a.name === \"compact\") return preferCompact ? -1 : 1;\n if (b.name === \"compact\") return preferCompact ? 1 : -1;\n return a.name < b.name ? -1 : 1;\n });\n\n // Try loading recipe from the best view\n // For each view, try reading {viewPath}/default.json\n return _tryViewRecipes(views, preferCompact ? \"compact\" : \"default\", 0).then(function(result) {\n if (result && result.recipe) {\n // Attach views metadata to the recipe for Phase 1 view switching\n result.recipe._aupViews = views;\n return result.recipe;\n }\n return null;\n });\n }).catch(function() {\n // List failed — fall back to probe-based discovery\n var variants = preferCompact ? [\"compact\", \"default\"] : [\"default\"];\n return _tryAupVariants(src, variants, 0);\n });\n }\n\n /**\n * Try loading a recipe from each discovered view in order.\n * For each view, tries {viewPath}/default.json (and compact.json if preferred).\n */\n function _tryViewRecipes(views, preferredVariant, idx) {\n if (idx >= views.length) return Promise.resolve(null);\n var view = views[idx];\n\n // For the selected view, try variant files\n var variants = preferredVariant === \"compact\"\n ? [\"compact.json\", \"default.json\"]\n : [\"default.json\"];\n\n return _tryViewVariants(view, variants, 0).then(function(recipe) {\n if (recipe) {\n return { recipe: recipe, viewName: view.name };\n }\n return _tryViewRecipes(views, preferredVariant, idx + 1);\n });\n }\n\n /**\n * Try reading recipe variant files from a specific view directory.\n */\n function _tryViewVariants(view, variants, idx) {\n if (idx >= variants.length) return Promise.resolve(null);\n var recipePath = view.path + \"/\" + variants[idx];\n return window.afs.read(recipePath).then(function(result) {\n var recipe = result;\n if (recipe && recipe.content !== undefined) {\n recipe = recipe.content;\n }\n if (typeof recipe === \"string\") {\n try { recipe = JSON.parse(recipe); } catch(_e) { recipe = null; }\n }\n if (recipe && typeof recipe === \"object\" && recipe.type) {\n return recipe;\n }\n return _tryViewVariants(view, variants, idx + 1);\n }).catch(function() {\n return _tryViewVariants(view, variants, idx + 1);\n });\n }\n\n /**\n * Legacy fallback: probe specific variant filenames without list().\n */\n function _tryAupVariants(src, variants, idx) {\n if (idx >= variants.length) return Promise.resolve(null);\n var recipePath = src + \"/.aup/\" + variants[idx] + \".json\";\n return window.afs.read(recipePath).then(function(result) {\n var recipe = result;\n // If read returns an entry wrapper, unwrap content\n if (recipe && recipe.content !== undefined) {\n recipe = recipe.content;\n }\n // If content is a JSON string (e.g. from AFSJSON leaf), parse it\n if (typeof recipe === \"string\") {\n try { recipe = JSON.parse(recipe); } catch(_e) { recipe = null; }\n }\n // Validate: must be an AUP node (has type field)\n if (recipe && typeof recipe === \"object\" && recipe.type) {\n return recipe;\n }\n // Invalid recipe — try next variant\n return _tryAupVariants(src, variants, idx + 1);\n }).catch(function() {\n // Recipe doesn't exist — try next variant\n return _tryAupVariants(src, variants, idx + 1);\n });\n }\n\n /**\n * Try flat recipe files from .aup/ list results.\n * These are leaf entries (e.g. default.json) rather than view directories.\n * Picks the best match from the preferred order, then falls back to first available.\n */\n function _tryFlatRecipes(flatRecipes, preferred, idx) {\n // First try preferred names in order\n if (idx < preferred.length) {\n var target = preferred[idx];\n for (var i = 0; i < flatRecipes.length; i++) {\n if (flatRecipes[i].name === target) {\n return _parseFlatRecipe(flatRecipes[i]).then(function(recipe) {\n if (recipe) return recipe;\n return _tryFlatRecipes(flatRecipes, preferred, idx + 1);\n });\n }\n }\n return _tryFlatRecipes(flatRecipes, preferred, idx + 1);\n }\n // No preferred match — try first available\n if (flatRecipes.length > 0) {\n return _parseFlatRecipe(flatRecipes[0]);\n }\n return Promise.resolve(null);\n }\n\n /**\n * Parse a flat recipe entry — may have inline content or need a read().\n */\n function _parseFlatRecipe(entry) {\n // If content was included in the list result, use it directly\n if (entry.content != null) {\n var recipe = entry.content;\n if (typeof recipe === \"string\") {\n try { recipe = JSON.parse(recipe); } catch(_e) { return Promise.resolve(null); }\n }\n if (recipe && typeof recipe === \"object\" && recipe.type) {\n return Promise.resolve(recipe);\n }\n }\n // Otherwise read the file\n return window.afs.read(entry.path).then(function(result) {\n var r = result;\n if (r && r.content !== undefined) r = r.content;\n if (typeof r === \"string\") {\n try { r = JSON.parse(r); } catch(_e) { r = null; }\n }\n if (r && typeof r === \"object\" && r.type) return r;\n return null;\n }).catch(function() { return null; });\n }\n`;\n"],"mappings":";AAAA,MAAa,YAAY"}
@@ -0,0 +1,127 @@
1
+
2
+ //#region src/web-page/renderers/editor.ts
3
+ const EDITOR_JS = `
4
+ // ── Editor Subsystem (CodeMirror 6 from CDN) ──
5
+ function renderAupEditor(node) {
6
+ var el = document.createElement("div");
7
+ el.className = "aup-editor";
8
+ var p = node.props || {};
9
+ var language = p.language || "text";
10
+ var readOnly = p.readOnly === true;
11
+ var showLineNumbers = p.lineNumbers !== false;
12
+ var content = p.content || "";
13
+
14
+ // Toolbar
15
+ var toolbar = document.createElement("div");
16
+ toolbar.className = "aup-editor-toolbar";
17
+ var langLabel = document.createElement("span");
18
+ langLabel.style.cssText = "font-size: 0.78em; color: var(--dim); margin-right: auto;";
19
+ langLabel.textContent = language.toUpperCase();
20
+ toolbar.appendChild(langLabel);
21
+ if (!readOnly) {
22
+ var wrapBtn = document.createElement("button");
23
+ wrapBtn.textContent = "Wrap";
24
+ wrapBtn.onclick = function() {
25
+ var ta = el.querySelector("textarea");
26
+ if (ta) {
27
+ var isWrapped = ta.style.whiteSpace !== "pre";
28
+ ta.style.whiteSpace = isWrapped ? "pre" : "pre-wrap";
29
+ wrapBtn.classList.toggle("active", !isWrapped);
30
+ }
31
+ };
32
+ toolbar.appendChild(wrapBtn);
33
+ }
34
+ el.appendChild(toolbar);
35
+
36
+ // Editor area
37
+ var area = document.createElement("div");
38
+ area.className = "aup-editor-area";
39
+ if (showLineNumbers) area.classList.add("has-gutter");
40
+
41
+ // Syntax highlight overlay (behind textarea)
42
+ var highlight = document.createElement("pre");
43
+ highlight.className = "aup-editor-highlight";
44
+ var hCode = document.createElement("code");
45
+ highlight.appendChild(hCode);
46
+ area.appendChild(highlight);
47
+
48
+ function updateHighlight(text) {
49
+ if (typeof hljs !== "undefined" && language !== "text") {
50
+ try {
51
+ var lang = language === "typescript" ? "typescript" : language;
52
+ var result = hljs.getLanguage(lang) ? hljs.highlight(text, { language: lang }) : hljs.highlightAuto(text);
53
+ hCode.innerHTML = result.value;
54
+ } catch(e) {
55
+ hCode.textContent = text;
56
+ }
57
+ } else {
58
+ hCode.textContent = text;
59
+ }
60
+ }
61
+ updateHighlight(content);
62
+
63
+ var ta = document.createElement("textarea");
64
+ ta.value = content;
65
+ ta.readOnly = readOnly;
66
+ ta.spellcheck = false;
67
+ ta.autocomplete = "off";
68
+ ta.setAttribute("autocapitalize", "off");
69
+ area.appendChild(ta);
70
+
71
+ // Sync scroll between textarea and highlight overlay
72
+ ta.addEventListener("scroll", function() { highlight.scrollTop = ta.scrollTop; highlight.scrollLeft = ta.scrollLeft; });
73
+
74
+ // Line numbers gutter
75
+ if (showLineNumbers) {
76
+ var gutter = document.createElement("div");
77
+ gutter.className = "aup-editor-gutter";
78
+ function updateGutter() {
79
+ var lines = ta.value.split("\\n").length;
80
+ var nums = [];
81
+ for (var i = 1; i <= lines; i++) nums.push(String(i));
82
+ gutter.textContent = nums.join("\\n");
83
+ }
84
+ updateGutter();
85
+ ta.addEventListener("input", updateGutter);
86
+ ta.addEventListener("scroll", function() { gutter.scrollTop = ta.scrollTop; });
87
+ area.appendChild(gutter);
88
+ }
89
+
90
+ el.appendChild(area);
91
+
92
+ // Status bar
93
+ var statusbar = document.createElement("div");
94
+ statusbar.className = "aup-editor-statusbar";
95
+ var lineInfo = document.createElement("span");
96
+ lineInfo.textContent = "Ln 1, Col 1";
97
+ var charInfo = document.createElement("span");
98
+ charInfo.textContent = content.length + " chars";
99
+ statusbar.appendChild(lineInfo);
100
+ statusbar.appendChild(charInfo);
101
+ ta.addEventListener("input", function() {
102
+ charInfo.textContent = ta.value.length + " chars";
103
+ updateHighlight(ta.value);
104
+ });
105
+ ta.addEventListener("click", function() {
106
+ var val = ta.value.substring(0, ta.selectionStart);
107
+ var ln = val.split("\\n").length;
108
+ var col = ta.selectionStart - val.lastIndexOf("\\n");
109
+ lineInfo.textContent = "Ln " + ln + ", Col " + col;
110
+ });
111
+ ta.addEventListener("keyup", ta.onclick);
112
+ el.appendChild(statusbar);
113
+
114
+ // Event: content change
115
+ if (node.events && node.events.change) {
116
+ ta.addEventListener("input", function() {
117
+ _fireAupEvent(node.id, "change", { content: ta.value });
118
+ });
119
+ }
120
+
121
+ return el;
122
+ }
123
+
124
+ `;
125
+
126
+ //#endregion
127
+ exports.EDITOR_JS = EDITOR_JS;
@@ -0,0 +1,127 @@
1
+ //#region src/web-page/renderers/editor.ts
2
+ const EDITOR_JS = `
3
+ // ── Editor Subsystem (CodeMirror 6 from CDN) ──
4
+ function renderAupEditor(node) {
5
+ var el = document.createElement("div");
6
+ el.className = "aup-editor";
7
+ var p = node.props || {};
8
+ var language = p.language || "text";
9
+ var readOnly = p.readOnly === true;
10
+ var showLineNumbers = p.lineNumbers !== false;
11
+ var content = p.content || "";
12
+
13
+ // Toolbar
14
+ var toolbar = document.createElement("div");
15
+ toolbar.className = "aup-editor-toolbar";
16
+ var langLabel = document.createElement("span");
17
+ langLabel.style.cssText = "font-size: 0.78em; color: var(--dim); margin-right: auto;";
18
+ langLabel.textContent = language.toUpperCase();
19
+ toolbar.appendChild(langLabel);
20
+ if (!readOnly) {
21
+ var wrapBtn = document.createElement("button");
22
+ wrapBtn.textContent = "Wrap";
23
+ wrapBtn.onclick = function() {
24
+ var ta = el.querySelector("textarea");
25
+ if (ta) {
26
+ var isWrapped = ta.style.whiteSpace !== "pre";
27
+ ta.style.whiteSpace = isWrapped ? "pre" : "pre-wrap";
28
+ wrapBtn.classList.toggle("active", !isWrapped);
29
+ }
30
+ };
31
+ toolbar.appendChild(wrapBtn);
32
+ }
33
+ el.appendChild(toolbar);
34
+
35
+ // Editor area
36
+ var area = document.createElement("div");
37
+ area.className = "aup-editor-area";
38
+ if (showLineNumbers) area.classList.add("has-gutter");
39
+
40
+ // Syntax highlight overlay (behind textarea)
41
+ var highlight = document.createElement("pre");
42
+ highlight.className = "aup-editor-highlight";
43
+ var hCode = document.createElement("code");
44
+ highlight.appendChild(hCode);
45
+ area.appendChild(highlight);
46
+
47
+ function updateHighlight(text) {
48
+ if (typeof hljs !== "undefined" && language !== "text") {
49
+ try {
50
+ var lang = language === "typescript" ? "typescript" : language;
51
+ var result = hljs.getLanguage(lang) ? hljs.highlight(text, { language: lang }) : hljs.highlightAuto(text);
52
+ hCode.innerHTML = result.value;
53
+ } catch(e) {
54
+ hCode.textContent = text;
55
+ }
56
+ } else {
57
+ hCode.textContent = text;
58
+ }
59
+ }
60
+ updateHighlight(content);
61
+
62
+ var ta = document.createElement("textarea");
63
+ ta.value = content;
64
+ ta.readOnly = readOnly;
65
+ ta.spellcheck = false;
66
+ ta.autocomplete = "off";
67
+ ta.setAttribute("autocapitalize", "off");
68
+ area.appendChild(ta);
69
+
70
+ // Sync scroll between textarea and highlight overlay
71
+ ta.addEventListener("scroll", function() { highlight.scrollTop = ta.scrollTop; highlight.scrollLeft = ta.scrollLeft; });
72
+
73
+ // Line numbers gutter
74
+ if (showLineNumbers) {
75
+ var gutter = document.createElement("div");
76
+ gutter.className = "aup-editor-gutter";
77
+ function updateGutter() {
78
+ var lines = ta.value.split("\\n").length;
79
+ var nums = [];
80
+ for (var i = 1; i <= lines; i++) nums.push(String(i));
81
+ gutter.textContent = nums.join("\\n");
82
+ }
83
+ updateGutter();
84
+ ta.addEventListener("input", updateGutter);
85
+ ta.addEventListener("scroll", function() { gutter.scrollTop = ta.scrollTop; });
86
+ area.appendChild(gutter);
87
+ }
88
+
89
+ el.appendChild(area);
90
+
91
+ // Status bar
92
+ var statusbar = document.createElement("div");
93
+ statusbar.className = "aup-editor-statusbar";
94
+ var lineInfo = document.createElement("span");
95
+ lineInfo.textContent = "Ln 1, Col 1";
96
+ var charInfo = document.createElement("span");
97
+ charInfo.textContent = content.length + " chars";
98
+ statusbar.appendChild(lineInfo);
99
+ statusbar.appendChild(charInfo);
100
+ ta.addEventListener("input", function() {
101
+ charInfo.textContent = ta.value.length + " chars";
102
+ updateHighlight(ta.value);
103
+ });
104
+ ta.addEventListener("click", function() {
105
+ var val = ta.value.substring(0, ta.selectionStart);
106
+ var ln = val.split("\\n").length;
107
+ var col = ta.selectionStart - val.lastIndexOf("\\n");
108
+ lineInfo.textContent = "Ln " + ln + ", Col " + col;
109
+ });
110
+ ta.addEventListener("keyup", ta.onclick);
111
+ el.appendChild(statusbar);
112
+
113
+ // Event: content change
114
+ if (node.events && node.events.change) {
115
+ ta.addEventListener("input", function() {
116
+ _fireAupEvent(node.id, "change", { content: ta.value });
117
+ });
118
+ }
119
+
120
+ return el;
121
+ }
122
+
123
+ `;
124
+
125
+ //#endregion
126
+ export { EDITOR_JS };
127
+ //# sourceMappingURL=editor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.mjs","names":[],"sources":["../../../src/web-page/renderers/editor.ts"],"sourcesContent":["export const EDITOR_JS = `\n // ── Editor Subsystem (CodeMirror 6 from CDN) ──\n function renderAupEditor(node) {\n var el = document.createElement(\"div\");\n el.className = \"aup-editor\";\n var p = node.props || {};\n var language = p.language || \"text\";\n var readOnly = p.readOnly === true;\n var showLineNumbers = p.lineNumbers !== false;\n var content = p.content || \"\";\n\n // Toolbar\n var toolbar = document.createElement(\"div\");\n toolbar.className = \"aup-editor-toolbar\";\n var langLabel = document.createElement(\"span\");\n langLabel.style.cssText = \"font-size: 0.78em; color: var(--dim); margin-right: auto;\";\n langLabel.textContent = language.toUpperCase();\n toolbar.appendChild(langLabel);\n if (!readOnly) {\n var wrapBtn = document.createElement(\"button\");\n wrapBtn.textContent = \"Wrap\";\n wrapBtn.onclick = function() {\n var ta = el.querySelector(\"textarea\");\n if (ta) {\n var isWrapped = ta.style.whiteSpace !== \"pre\";\n ta.style.whiteSpace = isWrapped ? \"pre\" : \"pre-wrap\";\n wrapBtn.classList.toggle(\"active\", !isWrapped);\n }\n };\n toolbar.appendChild(wrapBtn);\n }\n el.appendChild(toolbar);\n\n // Editor area\n var area = document.createElement(\"div\");\n area.className = \"aup-editor-area\";\n if (showLineNumbers) area.classList.add(\"has-gutter\");\n\n // Syntax highlight overlay (behind textarea)\n var highlight = document.createElement(\"pre\");\n highlight.className = \"aup-editor-highlight\";\n var hCode = document.createElement(\"code\");\n highlight.appendChild(hCode);\n area.appendChild(highlight);\n\n function updateHighlight(text) {\n if (typeof hljs !== \"undefined\" && language !== \"text\") {\n try {\n var lang = language === \"typescript\" ? \"typescript\" : language;\n var result = hljs.getLanguage(lang) ? hljs.highlight(text, { language: lang }) : hljs.highlightAuto(text);\n hCode.innerHTML = result.value;\n } catch(e) {\n hCode.textContent = text;\n }\n } else {\n hCode.textContent = text;\n }\n }\n updateHighlight(content);\n\n var ta = document.createElement(\"textarea\");\n ta.value = content;\n ta.readOnly = readOnly;\n ta.spellcheck = false;\n ta.autocomplete = \"off\";\n ta.setAttribute(\"autocapitalize\", \"off\");\n area.appendChild(ta);\n\n // Sync scroll between textarea and highlight overlay\n ta.addEventListener(\"scroll\", function() { highlight.scrollTop = ta.scrollTop; highlight.scrollLeft = ta.scrollLeft; });\n\n // Line numbers gutter\n if (showLineNumbers) {\n var gutter = document.createElement(\"div\");\n gutter.className = \"aup-editor-gutter\";\n function updateGutter() {\n var lines = ta.value.split(\"\\\\n\").length;\n var nums = [];\n for (var i = 1; i <= lines; i++) nums.push(String(i));\n gutter.textContent = nums.join(\"\\\\n\");\n }\n updateGutter();\n ta.addEventListener(\"input\", updateGutter);\n ta.addEventListener(\"scroll\", function() { gutter.scrollTop = ta.scrollTop; });\n area.appendChild(gutter);\n }\n\n el.appendChild(area);\n\n // Status bar\n var statusbar = document.createElement(\"div\");\n statusbar.className = \"aup-editor-statusbar\";\n var lineInfo = document.createElement(\"span\");\n lineInfo.textContent = \"Ln 1, Col 1\";\n var charInfo = document.createElement(\"span\");\n charInfo.textContent = content.length + \" chars\";\n statusbar.appendChild(lineInfo);\n statusbar.appendChild(charInfo);\n ta.addEventListener(\"input\", function() {\n charInfo.textContent = ta.value.length + \" chars\";\n updateHighlight(ta.value);\n });\n ta.addEventListener(\"click\", function() {\n var val = ta.value.substring(0, ta.selectionStart);\n var ln = val.split(\"\\\\n\").length;\n var col = ta.selectionStart - val.lastIndexOf(\"\\\\n\");\n lineInfo.textContent = \"Ln \" + ln + \", Col \" + col;\n });\n ta.addEventListener(\"keyup\", ta.onclick);\n el.appendChild(statusbar);\n\n // Event: content change\n if (node.events && node.events.change) {\n ta.addEventListener(\"input\", function() {\n _fireAupEvent(node.id, \"change\", { content: ta.value });\n });\n }\n\n return el;\n }\n\n`;\n"],"mappings":";AAAA,MAAa,YAAY"}
@@ -0,0 +1,178 @@
1
+
2
+ //#region src/web-page/renderers/finance-chart.ts
3
+ const FINANCE_CHART_JS = `
4
+ // ── Finance Chart Primitive (TradingView Lightweight Charts v5) ──
5
+
6
+ function renderAupFinanceChart(node) {
7
+ var el = document.createElement("div");
8
+ el.className = "aup-finance-chart";
9
+ var p = node.props || {};
10
+ var variant = p.variant || "candlestick";
11
+ if (p.height) el.style.height = p.height;
12
+ else el.style.height = "400px";
13
+
14
+ var chartDiv = document.createElement("div");
15
+ chartDiv.style.width = "100%";
16
+ chartDiv.style.height = "100%";
17
+ el.appendChild(chartDiv);
18
+
19
+ function isDarkMode() {
20
+ var bg = getComputedStyle(document.documentElement).getPropertyValue("--bg").trim();
21
+ if (!bg) return true;
22
+ // Simple heuristic: dark if bg starts with # and first hex digit < 8
23
+ if (bg.charAt(0) === "#") {
24
+ var r = parseInt(bg.substring(1, 3), 16);
25
+ return r < 128;
26
+ }
27
+ return true;
28
+ }
29
+
30
+ function chartColors() {
31
+ var dark = isDarkMode();
32
+ return {
33
+ bg: "transparent",
34
+ text: dark ? "#d1d5db" : "#374151",
35
+ grid: dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)",
36
+ crosshair: dark ? "#6b7280" : "#9ca3af",
37
+ upColor: p.upColor || "#26a69a",
38
+ downColor: p.downColor || "#ef5350",
39
+ lineColor: p.lineColor || "#2962FF",
40
+ areaTop: p.areaTopColor || "rgba(41,98,255,0.4)",
41
+ areaBottom: p.areaBottomColor || "rgba(41,98,255,0)",
42
+ };
43
+ }
44
+
45
+ function initFinanceChart() {
46
+ if (typeof LightweightCharts === "undefined") {
47
+ var loading = document.createElement("div");
48
+ loading.className = "aup-finance-chart-loading";
49
+ loading.textContent = "Loading chart...";
50
+ el.insertBefore(loading, chartDiv);
51
+ loadScript("https://cdn.jsdelivr.net/npm/lightweight-charts@5/dist/lightweight-charts.standalone.production.js", function() {
52
+ if (loading.parentNode) loading.parentNode.removeChild(loading);
53
+ createFinanceChart();
54
+ });
55
+ } else {
56
+ createFinanceChart();
57
+ }
58
+ }
59
+
60
+ var chartInstance = null;
61
+ var primarySeries = null;
62
+ var volumeSeries = null;
63
+
64
+ function createFinanceChart() {
65
+ try {
66
+ var c = chartColors();
67
+ chartInstance = LightweightCharts.createChart(chartDiv, {
68
+ layout: { background: { type: "solid", color: c.bg }, textColor: c.text, fontFamily: "inherit" },
69
+ grid: { vertLines: { color: c.grid }, horzLines: { color: c.grid } },
70
+ crosshair: { vertLine: { color: c.crosshair, labelBackgroundColor: c.crosshair }, horzLine: { color: c.crosshair, labelBackgroundColor: c.crosshair } },
71
+ rightPriceScale: { borderColor: c.grid },
72
+ timeScale: { borderColor: c.grid, timeVisible: true },
73
+ autoSize: true,
74
+ });
75
+
76
+ // Add primary series based on variant
77
+ if (variant === "candlestick") {
78
+ primarySeries = chartInstance.addSeries(LightweightCharts.CandlestickSeries, {
79
+ upColor: c.upColor, downColor: c.downColor, borderVisible: false,
80
+ wickUpColor: c.upColor, wickDownColor: c.downColor,
81
+ });
82
+ } else if (variant === "ohlc") {
83
+ primarySeries = chartInstance.addSeries(LightweightCharts.BarSeries, {
84
+ upColor: c.upColor, downColor: c.downColor,
85
+ });
86
+ } else if (variant === "trading-line") {
87
+ primarySeries = chartInstance.addSeries(LightweightCharts.LineSeries, {
88
+ color: c.lineColor, lineWidth: 2,
89
+ });
90
+ } else if (variant === "trading-area") {
91
+ primarySeries = chartInstance.addSeries(LightweightCharts.AreaSeries, {
92
+ lineColor: c.lineColor, topColor: c.areaTop, bottomColor: c.areaBottom, lineWidth: 2,
93
+ });
94
+ } else if (variant === "baseline") {
95
+ var bv = p.baseValue || 0;
96
+ primarySeries = chartInstance.addSeries(LightweightCharts.BaselineSeries, {
97
+ baseValue: { type: "price", price: bv },
98
+ topLineColor: c.upColor, topFillColor1: "rgba(38,166,154,0.28)", topFillColor2: "rgba(38,166,154,0)",
99
+ bottomLineColor: c.downColor, bottomFillColor1: "rgba(239,83,80,0)", bottomFillColor2: "rgba(239,83,80,0.28)",
100
+ });
101
+ } else if (variant === "volume") {
102
+ primarySeries = chartInstance.addSeries(LightweightCharts.HistogramSeries, {
103
+ priceFormat: { type: "volume" },
104
+ priceScaleId: "",
105
+ });
106
+ }
107
+
108
+ // Optional volume overlay (for candlestick/ohlc)
109
+ if (p.volumeData || p.showVolume) {
110
+ volumeSeries = chartInstance.addSeries(LightweightCharts.HistogramSeries, {
111
+ priceFormat: { type: "volume" },
112
+ priceScaleId: "volume",
113
+ });
114
+ chartInstance.priceScale("volume").applyOptions({
115
+ scaleMargins: { top: 0.8, bottom: 0 },
116
+ });
117
+ }
118
+
119
+ // Set data
120
+ var data = p.data || [];
121
+ if (data.length) {
122
+ primarySeries.setData(data);
123
+ if (volumeSeries && p.volumeData) volumeSeries.setData(p.volumeData);
124
+ chartInstance.timeScale().fitContent();
125
+ }
126
+ } catch(e) {
127
+ chartDiv.textContent = "Chart error: " + e.message;
128
+ }
129
+ }
130
+
131
+ function updateFinanceData(raw) {
132
+ var d = (typeof raw === "object" && raw !== null) ? raw : {};
133
+ if (d.content && typeof d.content === "object") d = d.content;
134
+ if (primarySeries && d.data) {
135
+ primarySeries.setData(d.data);
136
+ if (volumeSeries && d.volumeData) volumeSeries.setData(d.volumeData);
137
+ if (chartInstance) chartInstance.timeScale().fitContent();
138
+ }
139
+ }
140
+
141
+ // src binding
142
+ if (node.src && window.afs) {
143
+ var loading = document.createElement("div");
144
+ loading.className = "aup-finance-chart-loading";
145
+ loading.textContent = "Loading data...";
146
+ el.insertBefore(loading, chartDiv);
147
+
148
+ function applyFinanceData(raw) {
149
+ if (loading.parentNode) loading.parentNode.removeChild(loading);
150
+ if (!chartInstance) {
151
+ // Chart not created yet — store data and init
152
+ var d = (typeof raw === "object" && raw !== null) ? raw : {};
153
+ if (d.content && typeof d.content === "object") d = d.content;
154
+ if (d.data) p.data = d.data;
155
+ if (d.volumeData) { p.volumeData = d.volumeData; p.showVolume = true; }
156
+ initFinanceChart();
157
+ } else {
158
+ updateFinanceData(raw);
159
+ }
160
+ }
161
+
162
+ window.afs.read(node.src).then(applyFinanceData).catch(function(e) {
163
+ loading.textContent = "Data error: " + e.message;
164
+ });
165
+ window.afs.subscribe({ type: "afs:write", path: node.src }, function(event) {
166
+ if (event && event.data) applyFinanceData(event.data);
167
+ });
168
+ } else {
169
+ setTimeout(initFinanceChart, 0);
170
+ }
171
+
172
+ return el;
173
+ }
174
+
175
+ `;
176
+
177
+ //#endregion
178
+ exports.FINANCE_CHART_JS = FINANCE_CHART_JS;