@webpieces/nx-webpieces-rules 0.4.870 → 0.4.872
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/package.json +7 -7
- package/src/executors/generate/executor.d.ts +3 -1
- package/src/executors/generate/executor.js +9 -1
- package/src/executors/generate/executor.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +5 -1
- package/src/index.js.map +1 -1
- package/src/lib/di-graph/runtime-client-bindings.d.ts +5 -1
- package/src/lib/di-graph/runtime-client-bindings.js +21 -5
- package/src/lib/di-graph/runtime-client-bindings.js.map +1 -1
- package/src/lib/dot-syntax.d.ts +9 -0
- package/src/lib/dot-syntax.js +17 -0
- package/src/lib/dot-syntax.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +136 -0
- package/src/lib/graph-color-modes.js +299 -0
- package/src/lib/graph-color-modes.js.map +1 -0
- package/src/lib/graph-filter.client.d.ts +11 -1
- package/src/lib/graph-filter.client.js +25 -4
- package/src/lib/graph-filter.client.js.map +1 -1
- package/src/lib/graph-impact.d.ts +119 -0
- package/src/lib/graph-impact.js +295 -0
- package/src/lib/graph-impact.js.map +1 -0
- package/src/lib/graph-legend.d.ts +34 -0
- package/src/lib/graph-legend.js +116 -0
- package/src/lib/graph-legend.js.map +1 -0
- package/src/lib/graph-names.d.ts +2 -2
- package/src/lib/graph-names.js +2 -2
- package/src/lib/graph-names.js.map +1 -1
- package/src/lib/graph-navigation.d.ts +10 -0
- package/src/lib/graph-navigation.js +47 -12
- package/src/lib/graph-navigation.js.map +1 -1
- package/src/lib/graph-node-menu.d.ts +4 -3
- package/src/lib/graph-node-menu.js +137 -20
- package/src/lib/graph-node-menu.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +34 -0
- package/src/lib/graph-page-shell.js +121 -0
- package/src/lib/graph-page-shell.js.map +1 -0
- package/src/lib/graph-page-styles.d.ts +20 -0
- package/src/lib/graph-page-styles.js +144 -0
- package/src/lib/graph-page-styles.js.map +1 -0
- package/src/lib/graph-render-model.d.ts +14 -2
- package/src/lib/graph-render-model.js +19 -3
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-responsibilities.js +1 -1
- package/src/lib/graph-responsibilities.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +65 -0
- package/src/lib/graph-visualizer.client.js +311 -7
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.d.ts +19 -31
- package/src/lib/graph-visualizer.js +50 -286
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/runtime-html-page.js +2 -2
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-wiring/source-extractor.d.ts +2 -1
- package/src/lib/runtime-wiring/source-extractor.js +3 -2
- package/src/lib/runtime-wiring/source-extractor.js.map +1 -1
- package/src/lib/runtime-wiring/wiring-format.js +2 -2
- package/src/lib/runtime-wiring/wiring-format.js.map +1 -1
- package/src/rule-help.js +1 -1
- package/src/rule-help.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-node-menu.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-node-menu.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,aAAa;IACtB;;;;OAIG;IACH,MAAM;QACF,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAmDL,CAAC;IACP,CAAC;IAED;;;;;;OAMG;IACH,SAAS,CAAC,KAAa;QACnB,OAAO;UACL,KAAK,WAAW,KAAK;UACrB,KAAK;UACL,KAAK;UACL,KAAK;UACL,KAAK;UACL,KAAK,yCAAyC,CAAC;IACrD,CAAC;IAED,+FAA+F;IAC/F,MAAM;QACF,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,IAAI,CAAC,UAAU,EAAE,KAAK,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;IACjF,CAAC;IAED;;;;OAIG;IACK,UAAU;QACd,OAAO;;;;;cAKD,IAAI,CAAC,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+BlB,IAAI,CAAC,cAAc,EAAE;cACrB,IAAI,CAAC,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;UA2BzB,CAAC;IACP,CAAC;IAEO,cAAc;QAClB,OAAO;;;;;;;;;CASd,CAAC;IACE,CAAC;IAED,yFAAyF;IACjF,WAAW;QACf,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Bd,CAAC;IACE,CAAC;IAEO,cAAc;QAClB,OAAO;;;;;;;;UAQL,CAAC;IACP,CAAC;IAED;;;OAGG;IACK,UAAU;QACd,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAkCL,CAAC;IACP,CAAC;IAED,2EAA2E;IACnE,aAAa;QACjB,OAAO;;;;;;;;;;;;;;kBAcG,CAAC;IACf,CAAC;CACJ;AArRD,sCAqRC","sourcesContent":["/**\n * The floating per-node menu, shared by ALL THREE generated graphs.\n *\n * There is exactly ONE implementation of the menu — its CSS, its positioning, its dismissal and the\n * click wiring that opens it — and `architecture/dependencies.html` (graph-visualizer.ts), every\n * project's `design.html` (di-graph/design-visualizer.ts) and `tmp/webpieces/runtime-architecture.html`\n * (runtime-visualizer.ts) all inline these same bytes. Duplicating it into the three emitters is what\n * this module exists to prevent.\n *\n * Why the browser code is emitted as a STRING here rather than living in a compiled `*.client.ts`\n * beside its emitter: `generateDesignHTML` is called straight from a source checkout (by the\n * di-graph-generate executor's unit tests) and has no injection seam for the client text, so a\n * `readCompiledClient` sibling would make the design page unbuildable from source. A string keeps one\n * copy reachable by every emitter with no build-order coupling. The two pages that DO have a compiled\n * client (`graph-visualizer.client.ts`, `runtime-visualizer.client.ts`) call into these classes as\n * ambient globals — see viz-client-globals.d.ts.\n *\n * What the menu does NOT own is the LOCK BEHAVIOUR, because the pages genuinely differ:\n * - the architecture page already has a `#wp-lock` dropdown backed by GraphHighlighter (chain\n * highlight + responsibilities filter), and its menu item drives that, so the two stay in sync;\n * - a design page and the runtime page have neither dropdown nor responsibilities, so they use\n * `WpNodeLock` below — dim every other box in that graph, light the locked one.\n * All three spell the dim with the SAME class names and the SAME CSS from `dimStyles()`.\n */\nexport class GraphNodeMenu {\n /**\n * Menu chrome + the \"this box is clickable\" affordance. Every node is clickable now (the menu\n * replaced direct navigation), so the cursor/glow is on every box rather than only the ones with\n * a design page.\n */\n styles(): string {\n return `\n .wp-node-menu {\n position: absolute;\n z-index: 1000;\n min-width: 150px;\n padding: 4px 0;\n background: white;\n border: 1px solid #cfcfcf;\n border-radius: 6px;\n box-shadow: 0 4px 14px rgba(0,0,0,0.22);\n font-family: Arial, sans-serif;\n font-size: 14px;\n }\n .wp-node-menu-title {\n padding: 4px 14px 6px;\n color: #777;\n font-size: 12px;\n font-family: monospace;\n border-bottom: 1px solid #eee;\n margin-bottom: 4px;\n }\n .wp-node-menu-item {\n display: block;\n width: 100%;\n padding: 7px 14px;\n border: 0;\n background: none;\n color: #1565C0;\n font: inherit;\n text-align: left;\n cursor: pointer;\n }\n .wp-node-menu-item:hover { background: #E3F2FD; }\n g.wp-node-clickable { cursor: pointer; outline: none; }\n /* DOM keyboard focus is independent of graph highlight/Lock and stays dim with its node. */\n g.wp-node-clickable.wp-keyboard-focus {\n outline: 2px dashed #7b1fa2;\n outline-offset: 2px;\n }\n /* Every shape Graphviz (or the runtime page's own queue-cylinder redraw) can emit for a node\n * body: box/record -> polygon, circle -> ellipse, cylinder and the redrawn queue -> path. A\n * shape left out of this list would be a box that is clickable but never looks it. */\n g.wp-node-clickable polygon,\n g.wp-node-clickable ellipse,\n g.wp-node-clickable path { transition: stroke-width 0.12s ease, filter 0.12s ease; }\n g.wp-node-clickable:hover polygon,\n g.wp-node-clickable:hover ellipse,\n g.wp-node-clickable:hover path {\n stroke: #1976d2;\n stroke-width: 5;\n filter: drop-shadow(0 0 6px rgba(25, 118, 210, 0.85));\n }`;\n }\n\n /**\n * The dim/undim rules a lock (or a hover) toggles, scoped to whatever element holds the rendered\n * SVG on this page (`#graph` on the architecture page, `.graph` on a design page).\n *\n * We ONLY dim: the lit subgraph keeps its exact normal look. The un-dim rules repeat\n * `svg.wp-dim` so they out-specify the dim rule, which carries an extra type selector.\n */\n dimStyles(scope: string): string {\n return `\n ${scope} .node, ${scope} .edge { transition: opacity 0.12s ease; }\n ${scope} svg.wp-dim .node,\n ${scope} svg.wp-dim .edge { opacity: 0.15; }\n ${scope} svg.wp-dim .node.wp-focus,\n ${scope} svg.wp-dim .node.wp-neighbor,\n ${scope} svg.wp-dim .edge.wp-hl { opacity: 1; }`;\n }\n\n /** The whole browser side: the menu, the design-page lock, and the global dismiss handlers. */\n script(): string {\n return `${this.menuScript()}\\n${this.lockScript()}\\n${this.dismissScript()}`;\n }\n\n /**\n * `WpNodeMenuItem` + `WpNodeMenu`. Anchored at the node's own bounding box (bottom-left corner,\n * 4px below it) and clamped so a box at the right edge of a very wide graph still shows its whole\n * menu. Opening a menu closes any other, so at most one is ever on screen.\n */\n private menuScript(): string {\n return `\n class WpNodeMenuItem {\n constructor(label, onSelect) { this.label = label; this.onSelect = onSelect; }\n }\n class WpNodeMenu {\n ${this.focusScript()}\n static open(nodeEl, name, items) {\n WpNodeMenu.close();\n WpNodeMenu.owner = nodeEl;\n WpNodeMenu.ownerKeyboard = WpNodeMenu.keyboard;\n const menu = document.createElement('div');\n menu.id = 'wp-node-menu';\n menu.className = 'wp-node-menu';\n menu.addEventListener('click', function (ev) { ev.stopPropagation(); });\n const heading = document.createElement('div');\n heading.className = 'wp-node-menu-title';\n heading.textContent = name;\n menu.appendChild(heading);\n for (const item of items) menu.appendChild(WpNodeMenu.button(item));\n document.body.appendChild(menu);\n WpNodeMenu.place(menu, nodeEl);\n menu.querySelector('button')?.focus({ preventScroll: true });\n }\n static button(item) {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'wp-node-menu-item';\n button.textContent = item.label;\n button.addEventListener('click', function (ev) {\n ev.preventDefault();\n ev.stopPropagation();\n WpNodeMenu.close(true);\n item.onSelect();\n });\n return button;\n }\n ${this.positionScript()}\n ${this.keyboardScript()}\n static wire(svg, itemsFor) {\n svg.querySelectorAll('g.node').forEach(function (node) {\n const title = node.querySelector('title');\n const name = title === null || title.textContent === null\n ? '' : title.textContent.trim();\n if (name === '') return;\n if (node.classList.contains('wp-layout')) return;\n if (WpNodeMenu.wired.has(node)) return;\n WpNodeMenu.wired.add(node);\n node.addEventListener('focus', function () {\n node.classList.toggle('wp-keyboard-focus', WpNodeMenu.keyboard);\n });\n node.addEventListener('blur', function () {\n node.classList.remove('wp-keyboard-focus');\n });\n node.classList.add('wp-node-clickable');\n node.setAttribute('tabindex', '0');\n node.setAttribute('role', 'button');\n WpNodeMenu.wireKeyboard(node, name, itemsFor);\n node.addEventListener('click', function (ev) {\n ev.preventDefault();\n ev.stopPropagation();\n WpNodeMenu.open(node, name, itemsFor(name, node));\n });\n });\n }\n }`;\n }\n\n private positionScript(): string {\n return ` static place(menu, nodeEl) {\n const box = nodeEl.getBoundingClientRect();\n const own = menu.getBoundingClientRect();\n const maxLeft = window.scrollX + document.documentElement.clientWidth - own.width - 8;\n let left = box.left + window.scrollX;\n if (left > maxLeft) left = Math.max(window.scrollX + 8, maxLeft);\n menu.style.left = left + 'px';\n menu.style.top = (box.bottom + window.scrollY + 4) + 'px';\n }\n`;\n }\n\n /** Explicit input modality avoids Chromium's pointer-focus :focus-visible heuristics. */\n private focusScript(): string {\n return ` static keyboard = false;\n static owner = null;\n static ownerKeyboard = false;\n static wired = new WeakSet();\n static close(returnFocus = false) {\n const open = document.getElementById('wp-node-menu');\n const owner = WpNodeMenu.owner;\n const keyboard = WpNodeMenu.ownerKeyboard;\n WpNodeMenu.owner = null;\n if (open === null) return;\n open.remove();\n if (returnFocus && keyboard && owner?.isConnected) {\n WpNodeMenu.keyboard = true;\n owner.focus({ preventScroll: true });\n }\n }\n static focusedName(svg) {\n const active = document.activeElement;\n const menu = document.getElementById('wp-node-menu');\n const node = menu?.contains(active) && WpNodeMenu.ownerKeyboard\n ? WpNodeMenu.owner : active;\n return svg?.contains(node) && (node === WpNodeMenu.owner || node?.classList.contains('wp-keyboard-focus'))\n ? node.querySelector('title')?.textContent.trim() : null;\n }\n static restoreFocus(svg, name) {\n if (name == null) return;\n const nodes = Array.from(svg.querySelectorAll('g.wp-node-clickable'));\n const node = nodes.find(node => node.querySelector('title')?.textContent.trim() === name);\n WpNodeMenu.keyboard = true;\n (node || nodes[0])?.focus({ preventScroll: true });\n }\n`;\n }\n\n private keyboardScript(): string {\n return `static wireKeyboard(node, name, itemsFor) {\n node.addEventListener('keydown', function (ev) {\n if (ev.key !== 'Enter' && ev.key !== ' ') return;\n ev.preventDefault();\n ev.stopPropagation();\n WpNodeMenu.keyboard = true;\n WpNodeMenu.open(node, name, itemsFor(name, node));\n });\n }`;\n }\n\n /**\n * The lock the DESIGN pages and the RUNTIME page use: neither has a dropdown or a responsibilities\n * list, so locking a box dims every other box in that graph and lights the locked one.\n */\n private lockScript(): string {\n return `\n class WpNodeLock {\n constructor(svg) { this.svg = svg; this.locked = null; }\n rebind(svg) {\n const name = this.locked;\n this.svg = svg;\n this.clear();\n this.locked = name;\n if (name === null) return;\n for (const node of svg.querySelectorAll('g.node')) {\n if (node.querySelector('title')?.textContent === name) {\n this.lock(name, node);\n break;\n }\n }\n }\n isLocked(name) { return this.locked === name; }\n toggle(name, nodeEl) {\n if (this.locked === name) this.clear();\n else this.lock(name, nodeEl);\n }\n lock(name, nodeEl) {\n this.clear();\n this.locked = name;\n this.svg.classList.add('wp-dim');\n nodeEl.classList.add('wp-focus');\n }\n clear() {\n this.locked = null;\n this.svg.classList.remove('wp-dim');\n this.svg.querySelectorAll('.wp-focus').forEach(function (el) {\n el.classList.remove('wp-focus');\n });\n }\n }`;\n }\n\n /** Outside click and Escape both dismiss — wired once, on the document. */\n private dismissScript(): string {\n return `\n document.addEventListener('pointerdown', function () {\n WpNodeMenu.keyboard = false;\n document.querySelectorAll('.wp-keyboard-focus').forEach(function (node) {\n node.classList.remove('wp-keyboard-focus');\n });\n }, true);\n document.addEventListener('click', function () { WpNodeMenu.close(); });\n document.addEventListener('keydown', function (ev) {\n if (ev.altKey || ev.ctrlKey || ev.metaKey) return;\n WpNodeMenu.keyboard = true;\n const active = document.activeElement;\n if (active?.classList.contains('wp-node-clickable')) active.classList.add('wp-keyboard-focus');\n if (ev.key === 'Escape') WpNodeMenu.close(true);\n }, true);`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-node-menu.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-node-menu.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The floating per-node menu, shared by ALL THREE generated graphs.\n *\n * There is exactly ONE implementation of the menu — its CSS, its positioning, its dismissal and the\n * click wiring that opens it — and `architecture/dependencies.html` (graph-visualizer.ts), every\n * project's `design.html` (di-graph/design-visualizer.ts) and `tmp/webpieces/runtime-architecture.html`\n * (runtime-visualizer.ts) all inline these same bytes. Duplicating it into the three emitters is what\n * this module exists to prevent.\n *\n * Why the browser code is emitted as a STRING here rather than living in a compiled `*.client.ts`\n * beside its emitter: `generateDesignHTML` is called straight from a source checkout (by the\n * di-graph-generate executor's unit tests) and has no injection seam for the client text, so a\n * `readCompiledClient` sibling would make the design page unbuildable from source. A string keeps one\n * copy reachable by every emitter with no build-order coupling. The two pages that DO have a compiled\n * client (`graph-visualizer.client.ts`, `runtime-visualizer.client.ts`) call into these classes as\n * ambient globals — see viz-client-globals.d.ts.\n *\n * What the menu does NOT own is the LOCK BEHAVIOUR, because the pages genuinely differ:\n * - the architecture page already has a `#wp-lock` search field backed by GraphHighlighter (chain\n * highlight + responsibilities filter), and its menu item drives that, so the two stay in sync;\n * - a design page and the runtime page have neither lock field nor responsibilities, so they use\n * `WpNodeLock` below — dim every other box in that graph, light the locked one.\n * All three spell the dim with the SAME class names and the SAME CSS from `dimStyles()`.\n */\nexport class GraphNodeMenu {\n /**\n * Menu chrome + the \"this box is clickable\" affordance. Every node is clickable now (the menu\n * replaced direct navigation), so the cursor/glow is on every box rather than only the ones with\n * a design page.\n */\n styles(): string {\n return `\n .wp-node-menu {\n position: absolute;\n z-index: 1000;\n min-width: 150px;\n padding: 4px 0;\n background: white;\n border: 1px solid #cfcfcf;\n border-radius: 6px;\n box-shadow: 0 4px 14px rgba(0,0,0,0.22);\n font-family: Arial, sans-serif;\n font-size: 14px;\n }\n .wp-node-menu-title {\n padding: 4px 14px 6px;\n color: #777;\n font-size: 12px;\n font-family: monospace;\n border-bottom: 1px solid #eee;\n margin-bottom: 4px;\n }\n .wp-node-menu-item {\n display: block;\n width: 100%;\n padding: 7px 14px;\n border: 0;\n background: none;\n color: #1565C0;\n font: inherit;\n text-align: left;\n cursor: pointer;\n }\n .wp-node-menu-item:hover { background: #E3F2FD; }\n .wp-node-menu-item:disabled { color: #9aa0ad; cursor: not-allowed; background: none; }\n .wp-node-menu-item[aria-checked=\"true\"]::after { content: \"✓\"; float: right; margin-left: 12px; }\n /* A submenu (e.g. the architecture page's \"Mode ▸\") opens beside its parent item, on hover,\n * on click and on ArrowRight; ArrowLeft returns to the parent. */\n .wp-node-menu-sub-host { position: relative; }\n .wp-node-menu-arrow { float: right; margin-left: 12px; color: #777; }\n .wp-node-menu .wp-node-menu-sub { display: none; left: 100%; top: -5px; }\n .wp-node-menu .wp-node-menu-sub.wp-flip { left: auto; right: 100%; }\n .wp-node-menu-sub-host.wp-open > .wp-node-menu-sub { display: block; }\n g.wp-node-clickable { cursor: pointer; outline: none; }\n /* DOM keyboard focus is independent of graph highlight/Lock and stays dim with its node. */\n g.wp-node-clickable.wp-keyboard-focus {\n outline: 2px dashed #7b1fa2;\n outline-offset: 2px;\n }\n /* Every shape Graphviz (or the runtime page's own queue-cylinder redraw) can emit for a node\n * body: box/record -> polygon, circle -> ellipse, cylinder and the redrawn queue -> path. A\n * shape left out of this list would be a box that is clickable but never looks it. */\n g.wp-node-clickable polygon,\n g.wp-node-clickable ellipse,\n g.wp-node-clickable path { transition: stroke-width 0.12s ease, filter 0.12s ease; }\n g.wp-node-clickable:hover polygon,\n g.wp-node-clickable:hover ellipse,\n g.wp-node-clickable:hover path {\n stroke: #1976d2;\n stroke-width: 5;\n filter: drop-shadow(0 0 6px rgba(25, 118, 210, 0.85));\n }`;\n }\n\n /**\n * The dim/undim rules a lock (or a hover) toggles, scoped to whatever element holds the rendered\n * SVG on this page (`#graph` on the architecture page, `.graph` on a design page).\n *\n * We ONLY dim: the lit subgraph keeps its exact normal look. The un-dim rules repeat\n * `svg.wp-dim` so they out-specify the dim rule, which carries an extra type selector.\n */\n dimStyles(scope: string): string {\n return `\n ${scope} .node, ${scope} .edge { transition: opacity 0.12s ease; }\n ${scope} svg.wp-dim .node,\n ${scope} svg.wp-dim .edge { opacity: 0.15; }\n ${scope} svg.wp-dim .node.wp-focus,\n ${scope} svg.wp-dim .node.wp-neighbor,\n ${scope} svg.wp-dim .edge.wp-hl { opacity: 1; }`;\n }\n\n /** The whole browser side: the menu, the design-page lock, and the global dismiss handlers. */\n script(): string {\n return `${this.menuScript()}\\n${this.lockScript()}\\n${this.dismissScript()}`;\n }\n\n /**\n * `WpNodeMenuItem` + `WpNodeMenu`. Anchored at the node's own bounding box (bottom-left corner,\n * 4px below it) and clamped so a box at the right edge of a very wide graph still shows its whole\n * menu. Opening a menu closes any other, so at most one is ever on screen.\n */\n private itemScript(): string {\n return `\n class WpNodeMenuItem {\n constructor(label, onSelect, children = []) {\n this.label = label;\n this.onSelect = onSelect;\n this.children = children;\n this.checked = null;\n this.disabledReason = '';\n }\n }`;\n }\n\n private menuScript(): string {\n return `${this.itemScript()}\n ${WpMenuButton.toString()}\n ${WpSubmenu.toString()}\n class WpNodeMenu {\n static submenu(item) { return new WpSubmenu().build(item, WpNodeMenu.button); }\n ${this.focusScript()}\n static open(nodeEl, name, items) {\n WpNodeMenu.close();\n WpNodeMenu.owner = nodeEl;\n WpNodeMenu.ownerKeyboard = WpNodeMenu.keyboard;\n const menu = document.createElement('div');\n menu.id = 'wp-node-menu';\n menu.className = 'wp-node-menu';\n menu.addEventListener('click', function (ev) { ev.stopPropagation(); });\n const heading = document.createElement('div');\n heading.className = 'wp-node-menu-title';\n heading.textContent = name;\n menu.appendChild(heading);\n for (const item of items) {\n const hasChildren = Array.isArray(item.children) && item.children.length > 0;\n menu.appendChild(hasChildren ? WpNodeMenu.submenu(item) : WpNodeMenu.button(item));\n }\n document.body.appendChild(menu);\n WpNodeMenu.place(menu, nodeEl);\n menu.querySelector('button')?.focus({ preventScroll: true });\n }\n static button(item) { return new WpMenuButton().build(item, WpNodeMenu.close.bind(WpNodeMenu)); }\n ${this.positionScript()}\n ${this.keyboardScript()}\n static wire(svg, itemsFor) {\n svg.querySelectorAll('g.node').forEach(function (node) {\n const title = node.querySelector('title');\n const name = title === null || title.textContent === null\n ? '' : title.textContent.trim();\n if (name === '') return;\n if (node.classList.contains('wp-layout')) return;\n if (WpNodeMenu.wired.has(node)) return;\n WpNodeMenu.wired.add(node);\n node.addEventListener('focus', function () {\n node.classList.toggle('wp-keyboard-focus', WpNodeMenu.keyboard);\n });\n node.addEventListener('blur', function () {\n node.classList.remove('wp-keyboard-focus');\n });\n node.classList.add('wp-node-clickable');\n node.setAttribute('tabindex', '0');\n node.setAttribute('role', 'button');\n WpNodeMenu.wireKeyboard(node, name, itemsFor);\n node.addEventListener('click', function (ev) {\n ev.preventDefault();\n ev.stopPropagation();\n WpNodeMenu.open(node, name, itemsFor(name, node));\n });\n });\n }\n }`;\n }\n\n private positionScript(): string {\n return ` static place(menu, nodeEl) {\n const box = nodeEl.getBoundingClientRect();\n const own = menu.getBoundingClientRect();\n const maxLeft = window.scrollX + document.documentElement.clientWidth - own.width - 8;\n let left = box.left + window.scrollX;\n if (left > maxLeft) left = Math.max(window.scrollX + 8, maxLeft);\n menu.style.left = left + 'px';\n menu.style.top = (box.bottom + window.scrollY + 4) + 'px';\n }\n`;\n }\n\n /** Explicit input modality avoids Chromium's pointer-focus :focus-visible heuristics. */\n private focusScript(): string {\n return ` static keyboard = false;\n static owner = null;\n static ownerKeyboard = false;\n static wired = new WeakSet();\n static close(returnFocus = false) {\n const open = document.getElementById('wp-node-menu');\n const owner = WpNodeMenu.owner;\n const keyboard = WpNodeMenu.ownerKeyboard;\n WpNodeMenu.owner = null;\n if (open === null) return;\n open.remove();\n if (returnFocus && keyboard && owner?.isConnected) {\n WpNodeMenu.keyboard = true;\n owner.focus({ preventScroll: true });\n }\n }\n static focusedName(svg) {\n const active = document.activeElement;\n const menu = document.getElementById('wp-node-menu');\n const node = menu?.contains(active) && WpNodeMenu.ownerKeyboard\n ? WpNodeMenu.owner : active;\n return svg?.contains(node) && (node === WpNodeMenu.owner || node?.classList.contains('wp-keyboard-focus'))\n ? node.querySelector('title')?.textContent.trim() : null;\n }\n static restoreFocus(svg, name) {\n if (name == null) return;\n const nodes = Array.from(svg.querySelectorAll('g.wp-node-clickable'));\n const node = nodes.find(node => node.querySelector('title')?.textContent.trim() === name);\n WpNodeMenu.keyboard = true;\n (node || nodes[0])?.focus({ preventScroll: true });\n }\n`;\n }\n\n private keyboardScript(): string {\n return `static wireKeyboard(node, name, itemsFor) {\n node.addEventListener('keydown', function (ev) {\n if (ev.key !== 'Enter' && ev.key !== ' ') return;\n ev.preventDefault();\n ev.stopPropagation();\n WpNodeMenu.keyboard = true;\n WpNodeMenu.open(node, name, itemsFor(name, node));\n });\n }`;\n }\n\n /**\n * The lock the DESIGN pages and the RUNTIME page use: neither has a lock field or a responsibilities\n * list, so locking a box dims every other box in that graph and lights the locked one.\n */\n private lockScript(): string {\n return `\n class WpNodeLock {\n constructor(svg) { this.svg = svg; this.locked = null; }\n rebind(svg) {\n const name = this.locked;\n this.svg = svg;\n this.clear();\n this.locked = name;\n if (name === null) return;\n for (const node of svg.querySelectorAll('g.node')) {\n if (node.querySelector('title')?.textContent === name) {\n this.lock(name, node);\n break;\n }\n }\n }\n isLocked(name) { return this.locked === name; }\n toggle(name, nodeEl) {\n if (this.locked === name) this.clear();\n else this.lock(name, nodeEl);\n }\n lock(name, nodeEl) {\n this.clear();\n this.locked = name;\n this.svg.classList.add('wp-dim');\n nodeEl.classList.add('wp-focus');\n }\n clear() {\n this.locked = null;\n this.svg.classList.remove('wp-dim');\n this.svg.querySelectorAll('.wp-focus').forEach(function (el) {\n el.classList.remove('wp-focus');\n });\n }\n }`;\n }\n\n /** Outside click and Escape both dismiss — wired once, on the document. */\n private dismissScript(): string {\n return `\n document.addEventListener('pointerdown', function () {\n WpNodeMenu.keyboard = false;\n document.querySelectorAll('.wp-keyboard-focus').forEach(function (node) {\n node.classList.remove('wp-keyboard-focus');\n });\n }, true);\n document.addEventListener('click', function () { WpNodeMenu.close(); });\n document.addEventListener('keydown', function (ev) {\n if (ev.altKey || ev.ctrlKey || ev.metaKey) return;\n WpNodeMenu.keyboard = true;\n const active = document.activeElement;\n if (active?.classList.contains('wp-node-clickable')) active.classList.add('wp-keyboard-focus');\n if (ev.key === 'Escape') WpNodeMenu.close(true);\n }, true);`;\n }\n}\n\n/**\n * One plain menu item. Selecting it closes the menu (returning keyboard focus to the box it was\n * opened from) and then runs the item. A radio item (`checked` true/false) is announced as one, and\n * an item with a `disabledReason` is disabled with that reason as its tooltip.\n *\n * Serialized with `toString()`, like WpSubmenu below; `close` is handed in because WpNodeMenu, which\n * owns it, is declared after this class in the emitted script.\n */\nclass WpMenuButton {\n build(item: WpNodeMenuItem, close: (returnFocus: boolean) => void): HTMLButtonElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'wp-node-menu-item';\n button.textContent = item.label;\n if (item.checked === true || item.checked === false) {\n button.setAttribute('role', 'menuitemradio');\n button.setAttribute('aria-checked', String(item.checked));\n }\n if (item.disabledReason) {\n button.disabled = true;\n button.title = item.disabledReason;\n }\n button.addEventListener('click', (ev: MouseEvent): void => {\n ev.preventDefault();\n ev.stopPropagation();\n close(true);\n item.onSelect();\n });\n return button;\n }\n}\n\n/**\n * A menu item that opens a nested menu beside it — the architecture page's \"Mode ▸\".\n *\n * Real TypeScript serialized into every page's menu script with `toString()` (the technique\n * GraphNavigation uses), so it is type-checked here and still needs no compiled sibling file: the\n * design pages render straight from a source checkout. It touches nothing but the DOM and the\n * `button` builder it is handed, because nothing else exists where it runs.\n *\n * An item without children renders exactly as before, so the runtime page and the design pages —\n * which never build one — are unaffected.\n */\nclass WpSubmenu {\n build(\n item: WpNodeMenuItem,\n button: (child: WpNodeMenuItem) => HTMLButtonElement,\n ): HTMLDivElement {\n const host = document.createElement('div');\n host.className = 'wp-node-menu-sub-host';\n const parent = document.createElement('button');\n parent.type = 'button';\n parent.className = 'wp-node-menu-item wp-node-menu-parent';\n parent.setAttribute('aria-haspopup', 'menu');\n parent.setAttribute('aria-expanded', 'false');\n parent.textContent = item.label;\n const arrow = document.createElement('span');\n arrow.className = 'wp-node-menu-arrow';\n arrow.setAttribute('aria-hidden', 'true');\n arrow.textContent = '▸';\n parent.append(arrow);\n const sub = document.createElement('div');\n sub.className = 'wp-node-menu wp-node-menu-sub';\n sub.setAttribute('role', 'menu');\n sub.setAttribute('aria-label', item.label);\n for (const child of item.children) sub.append(button(child));\n host.append(parent, sub);\n this.wire(host, parent, sub);\n return host;\n }\n\n private wire(host: HTMLDivElement, parent: HTMLButtonElement, sub: HTMLDivElement): void {\n const enter = (): void => {\n this.show(host, parent, sub, true);\n sub.querySelector<HTMLButtonElement>('button:not([disabled])')?.focus({ preventScroll: true });\n };\n host.addEventListener('mouseenter', (): void => this.show(host, parent, sub, true));\n host.addEventListener('mouseleave', (): void => this.show(host, parent, sub, false));\n parent.addEventListener('click', (ev: MouseEvent): void => {\n ev.preventDefault();\n ev.stopPropagation();\n enter();\n });\n parent.addEventListener('keydown', (ev: KeyboardEvent): void => {\n if (ev.key !== 'ArrowRight') return;\n ev.preventDefault();\n enter();\n });\n sub.addEventListener('keydown', (ev: KeyboardEvent): void => {\n if (ev.key !== 'ArrowLeft') return;\n ev.preventDefault();\n this.show(host, parent, sub, false);\n parent.focus({ preventScroll: true });\n });\n }\n\n /** Opens to the right, or to the LEFT when the right would run off the viewport. */\n private show(host: HTMLDivElement, parent: HTMLButtonElement, sub: HTMLDivElement, open: boolean): void {\n host.classList.toggle('wp-open', open);\n parent.setAttribute('aria-expanded', String(open));\n if (!open) return;\n sub.classList.remove('wp-flip');\n const right = host.getBoundingClientRect().right + sub.offsetWidth + 8;\n if (right > document.documentElement.clientWidth) sub.classList.add('wp-flip');\n }\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,
|
|
3
|
+
* and the graph filling the rest of the viewport.
|
|
4
|
+
*
|
|
5
|
+
* Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all
|
|
6
|
+
* sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a "?"
|
|
7
|
+
* popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right
|
|
8
|
+
* (graph-navigation.ts) and the responsibilities open in a right-hand panel.
|
|
9
|
+
*
|
|
10
|
+
* Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element
|
|
11
|
+
* carries a `style=` attribute — every look is a class.
|
|
12
|
+
*/
|
|
13
|
+
/** Everything the shell frames, already rendered by its owner. */
|
|
14
|
+
export declare class ShellParts {
|
|
15
|
+
readonly title: string;
|
|
16
|
+
readonly lockControl: string;
|
|
17
|
+
readonly legend: string;
|
|
18
|
+
readonly edgeKey: string;
|
|
19
|
+
readonly filterStatus: string;
|
|
20
|
+
readonly snapshot: string;
|
|
21
|
+
readonly responsibilities: string;
|
|
22
|
+
constructor(title: string, lockControl: string, legend: string, edgeKey: string, filterStatus: string, snapshot: string, responsibilities: string);
|
|
23
|
+
}
|
|
24
|
+
export declare class GraphPageShell {
|
|
25
|
+
body(parts: ShellParts): string;
|
|
26
|
+
private drawer;
|
|
27
|
+
/** A stacked two-line item: the mode's name, then its grey subtitle. Keys 1/2/3 select them. */
|
|
28
|
+
private modeItem;
|
|
29
|
+
private topbar;
|
|
30
|
+
private overlays;
|
|
31
|
+
/** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */
|
|
32
|
+
private help;
|
|
33
|
+
private brandIcon;
|
|
34
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,
|
|
4
|
+
* and the graph filling the rest of the viewport.
|
|
5
|
+
*
|
|
6
|
+
* Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all
|
|
7
|
+
* sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a "?"
|
|
8
|
+
* popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right
|
|
9
|
+
* (graph-navigation.ts) and the responsibilities open in a right-hand panel.
|
|
10
|
+
*
|
|
11
|
+
* Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element
|
|
12
|
+
* carries a `style=` attribute — every look is a class.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.GraphPageShell = exports.ShellParts = void 0;
|
|
16
|
+
const graph_color_modes_1 = require("./graph-color-modes");
|
|
17
|
+
/** Everything the shell frames, already rendered by its owner. */
|
|
18
|
+
class ShellParts {
|
|
19
|
+
title;
|
|
20
|
+
lockControl;
|
|
21
|
+
legend;
|
|
22
|
+
edgeKey;
|
|
23
|
+
filterStatus;
|
|
24
|
+
snapshot;
|
|
25
|
+
responsibilities;
|
|
26
|
+
constructor(title, lockControl, legend, edgeKey, filterStatus, snapshot, responsibilities) {
|
|
27
|
+
this.title = title;
|
|
28
|
+
this.lockControl = lockControl;
|
|
29
|
+
this.legend = legend;
|
|
30
|
+
this.edgeKey = edgeKey;
|
|
31
|
+
this.filterStatus = filterStatus;
|
|
32
|
+
this.snapshot = snapshot;
|
|
33
|
+
this.responsibilities = responsibilities;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.ShellParts = ShellParts;
|
|
37
|
+
class GraphPageShell {
|
|
38
|
+
body(parts) {
|
|
39
|
+
return `<div class="wp-shell" id="wp-shell">
|
|
40
|
+
${this.drawer(parts)}
|
|
41
|
+
<main class="wp-main">
|
|
42
|
+
${this.topbar(parts)}
|
|
43
|
+
<div id="graph"></div>
|
|
44
|
+
${this.overlays(parts)}
|
|
45
|
+
</main>
|
|
46
|
+
</div>`;
|
|
47
|
+
}
|
|
48
|
+
drawer(parts) {
|
|
49
|
+
return `<aside class="wp-side" id="wp-side" aria-label="Graph controls">
|
|
50
|
+
<div class="wp-brand">${this.brandIcon()}<span>${parts.title}</span></div>
|
|
51
|
+
<div class="wp-group" role="group" aria-label="Color mode">
|
|
52
|
+
<div class="wp-label">Color by</div>
|
|
53
|
+
<div class="wp-modes">${graph_color_modes_1.GRAPH_MODES.map((info, index) => this.modeItem(info, index)).join('')}</div>
|
|
54
|
+
<p class="wp-mode-reason" id="wp-impact-reason" hidden></p>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="wp-group">
|
|
57
|
+
<label class="wp-label" for="wp-lock">Focus</label>
|
|
58
|
+
${parts.lockControl}
|
|
59
|
+
<button type="button" class="wp-toggle" id="wp-filter-toggle" aria-pressed="false">Hide unconnected <span class="wp-sw" aria-hidden="true"></span></button>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="wp-group">
|
|
62
|
+
<div class="wp-label wp-label-row">Legend <button type="button" class="wp-icon wp-icon-mini wp-icon-dark" id="wp-legend-pop" title="Pop the legend out over the graph" aria-label="Pop the legend out over the graph">⇱</button></div>
|
|
63
|
+
${parts.legend}
|
|
64
|
+
</div>
|
|
65
|
+
<div class="wp-group">
|
|
66
|
+
<div class="wp-label">Edges</div>
|
|
67
|
+
${parts.edgeKey}
|
|
68
|
+
</div>
|
|
69
|
+
<div class="wp-foot">
|
|
70
|
+
<button type="button" class="wp-link" id="wp-resp-open" aria-expanded="false">Responsibilities</button>
|
|
71
|
+
<button type="button" class="wp-link" id="wp-snapshot-open" aria-expanded="false">Saved snapshot</button>
|
|
72
|
+
</div>
|
|
73
|
+
</aside>`;
|
|
74
|
+
}
|
|
75
|
+
/** A stacked two-line item: the mode's name, then its grey subtitle. Keys 1/2/3 select them. */
|
|
76
|
+
modeItem(info, index) {
|
|
77
|
+
const pressed = info.mode === graph_color_modes_1.GraphMode.RUNTIME ? 'true' : 'false';
|
|
78
|
+
return (`<button type="button" class="wp-mode" data-wp-mode="${info.mode}" aria-pressed="${pressed}" title="Key ${index + 1}">` +
|
|
79
|
+
`<span class="wp-mode-name">${info.name}</span><span class="wp-mode-sub">${info.subtitle}</span></button>`);
|
|
80
|
+
}
|
|
81
|
+
topbar(parts) {
|
|
82
|
+
return `<div class="wp-topbar">
|
|
83
|
+
<button type="button" class="wp-icon" id="wp-collapse" aria-label="Toggle the controls drawer" aria-controls="wp-side" aria-expanded="true">☰</button>
|
|
84
|
+
<div class="wp-crumb" id="wp-crumb">Mode: <b>Runtime</b></div>
|
|
85
|
+
${parts.filterStatus}
|
|
86
|
+
<button type="button" class="wp-icon" id="wp-help-btn" aria-label="How to read this graph" aria-controls="wp-help" aria-expanded="false">?</button>
|
|
87
|
+
</div>`;
|
|
88
|
+
}
|
|
89
|
+
overlays(parts) {
|
|
90
|
+
return `<div class="wp-pop wp-legend-popout" id="wp-legend-popout" hidden>
|
|
91
|
+
<header><span id="wp-legend-pop-title">Legend · Runtime</span><button type="button" class="wp-icon wp-icon-mini" id="wp-legend-close" aria-label="Close the legend">✕</button></header>
|
|
92
|
+
${parts.legend}
|
|
93
|
+
</div>
|
|
94
|
+
${this.help()}
|
|
95
|
+
<div class="wp-pop wp-snapshot" id="wp-snapshot" hidden>${parts.snapshot}</div>
|
|
96
|
+
<aside class="wp-panel" id="wp-resp-panel" aria-label="Responsibilities" hidden>
|
|
97
|
+
<header><span>Responsibilities</span><button type="button" class="wp-icon wp-icon-mini" id="wp-resp-close" aria-label="Close responsibilities">✕</button></header>
|
|
98
|
+
${parts.responsibilities}
|
|
99
|
+
</aside>`;
|
|
100
|
+
}
|
|
101
|
+
/** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */
|
|
102
|
+
help() {
|
|
103
|
+
return `<div class="wp-pop wp-help" id="wp-help" role="dialog" aria-label="How to read this graph" hidden>
|
|
104
|
+
<header><span>Reading the graph</span><button type="button" class="wp-icon wp-icon-mini" id="wp-help-close" aria-label="Close help">✕</button></header>
|
|
105
|
+
<p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Transitive dependencies are allowed but not drawn.</p>
|
|
106
|
+
<p>💡 <strong>Click any box</strong> for its menu: <strong>View Design</strong> (only where the project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it), <strong>Lock/Unlock</strong> (the same lock as the Focus field), <strong>Filter Unconnected</strong> (removes unrelated boxes, keeping each L-number row) and <strong>Mode ▸</strong>.</p>
|
|
107
|
+
<p>🔦 <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain — every ancestor above it and every dependency below it, with the lines between — while the rest of the graph dims.</p>
|
|
108
|
+
<p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what this branch changed (amber) and what depends on it (amber outline).</p>
|
|
109
|
+
<p><kbd>/</kbd> focus the lock search · <kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd> switch mode · <kbd>Esc</kbd> unlock</p>
|
|
110
|
+
</div>`;
|
|
111
|
+
}
|
|
112
|
+
brandIcon() {
|
|
113
|
+
return ('<svg width="22" height="22" viewBox="0 0 22 22" aria-hidden="true">' +
|
|
114
|
+
'<rect x="1" y="1" width="8" height="6" rx="1.5" fill="none" stroke="#e9eaf0" stroke-width="1.6"/>' +
|
|
115
|
+
'<rect x="13" y="1" width="8" height="6" rx="1.5" fill="none" stroke="#e9eaf0" stroke-width="1.6"/>' +
|
|
116
|
+
'<rect x="7" y="15" width="8" height="6" rx="1.5" fill="none" stroke="#e9eaf0" stroke-width="1.6"/>' +
|
|
117
|
+
'<path d="M5 7v4h12V7M11 11v4" fill="none" stroke="#e9eaf0" stroke-width="1.6"/></svg>');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.GraphPageShell = GraphPageShell;
|
|
121
|
+
//# sourceMappingURL=graph-page-shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-page-shell.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-shell.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAEH,2DAAuE;AAEvE,kEAAkE;AAClE,MAAa,UAAU;IAEC;IACA;IACA;IACA;IACA;IACA;IACA;IAPpB,YACoB,KAAa,EACb,WAAmB,EACnB,MAAc,EACd,OAAe,EACf,YAAoB,EACpB,QAAgB,EAChB,gBAAwB;QANxB,UAAK,GAAL,KAAK,CAAQ;QACb,gBAAW,GAAX,WAAW,CAAQ;QACnB,WAAM,GAAN,MAAM,CAAQ;QACd,YAAO,GAAP,OAAO,CAAQ;QACf,iBAAY,GAAZ,YAAY,CAAQ;QACpB,aAAQ,GAAR,QAAQ,CAAQ;QAChB,qBAAgB,GAAhB,gBAAgB,CAAQ;IACzC,CAAC;CACP;AAVD,gCAUC;AAED,MAAa,cAAc;IACvB,IAAI,CAAC,KAAiB;QAClB,OAAO;MACT,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;;UAEd,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;;UAElB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;;OAEvB,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,KAAiB;QAC5B,OAAO;gCACiB,IAAI,CAAC,SAAS,EAAE,SAAS,KAAK,CAAC,KAAK;;;oCAGhC,+BAAW,CAAC,GAAG,CAAC,CAAC,IAAc,EAAE,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;;;cAKrH,KAAK,CAAC,WAAW;;;;;cAKjB,KAAK,CAAC,MAAM;;;;cAIZ,KAAK,CAAC,OAAO;;;;;;aAMd,CAAC;IACV,CAAC;IAED,gGAAgG;IACxF,QAAQ,CAAC,IAAc,EAAE,KAAa;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,KAAK,6BAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QACnE,OAAO,CACH,uDAAuD,IAAI,CAAC,IAAI,mBAAmB,OAAO,gBAAgB,KAAK,GAAG,CAAC,IAAI;YACvH,8BAA8B,IAAI,CAAC,IAAI,oCAAoC,IAAI,CAAC,QAAQ,kBAAkB,CAC7G,CAAC;IACN,CAAC;IAEO,MAAM,CAAC,KAAiB;QAC5B,OAAO;;;cAGD,KAAK,CAAC,YAAY;;eAEjB,CAAC;IACZ,CAAC;IAEO,QAAQ,CAAC,KAAiB;QAC9B,OAAO;;cAED,KAAK,CAAC,MAAM;;UAEhB,IAAI,CAAC,IAAI,EAAE;kEAC6C,KAAK,CAAC,QAAQ;;;cAGlE,KAAK,CAAC,gBAAgB;iBACnB,CAAC;IACd,CAAC;IAED,6FAA6F;IACrF,IAAI;QACR,OAAO;;;;;;;eAOA,CAAC;IACZ,CAAC;IAEO,SAAS;QACb,OAAO,CACH,qEAAqE;YACrE,mGAAmG;YACnG,oGAAoG;YACpG,oGAAoG;YACpG,uFAAuF,CAC1F,CAAC;IACN,CAAC;CACJ;AA5FD,wCA4FC","sourcesContent":["/**\n * The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,\n * and the graph filling the rest of the viewport.\n *\n * Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all\n * sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a \"?\"\n * popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right\n * (graph-navigation.ts) and the responsibilities open in a right-hand panel.\n *\n * Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element\n * carries a `style=` attribute — every look is a class.\n */\n\nimport { GRAPH_MODES, GraphMode, ModeInfo } from './graph-color-modes';\n\n/** Everything the shell frames, already rendered by its owner. */\nexport class ShellParts {\n constructor(\n public readonly title: string,\n public readonly lockControl: string,\n public readonly legend: string,\n public readonly edgeKey: string,\n public readonly filterStatus: string,\n public readonly snapshot: string,\n public readonly responsibilities: string,\n ) {}\n}\n\nexport class GraphPageShell {\n body(parts: ShellParts): string {\n return `<div class=\"wp-shell\" id=\"wp-shell\">\n ${this.drawer(parts)}\n <main class=\"wp-main\">\n ${this.topbar(parts)}\n <div id=\"graph\"></div>\n ${this.overlays(parts)}\n </main>\n</div>`;\n }\n\n private drawer(parts: ShellParts): string {\n return `<aside class=\"wp-side\" id=\"wp-side\" aria-label=\"Graph controls\">\n <div class=\"wp-brand\">${this.brandIcon()}<span>${parts.title}</span></div>\n <div class=\"wp-group\" role=\"group\" aria-label=\"Color mode\">\n <div class=\"wp-label\">Color by</div>\n <div class=\"wp-modes\">${GRAPH_MODES.map((info: ModeInfo, index: number): string => this.modeItem(info, index)).join('')}</div>\n <p class=\"wp-mode-reason\" id=\"wp-impact-reason\" hidden></p>\n </div>\n <div class=\"wp-group\">\n <label class=\"wp-label\" for=\"wp-lock\">Focus</label>\n ${parts.lockControl}\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-filter-toggle\" aria-pressed=\"false\">Hide unconnected <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label wp-label-row\">Legend <button type=\"button\" class=\"wp-icon wp-icon-mini wp-icon-dark\" id=\"wp-legend-pop\" title=\"Pop the legend out over the graph\" aria-label=\"Pop the legend out over the graph\">⇱</button></div>\n ${parts.legend}\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label\">Edges</div>\n ${parts.edgeKey}\n </div>\n <div class=\"wp-foot\">\n <button type=\"button\" class=\"wp-link\" id=\"wp-resp-open\" aria-expanded=\"false\">Responsibilities</button>\n <button type=\"button\" class=\"wp-link\" id=\"wp-snapshot-open\" aria-expanded=\"false\">Saved snapshot</button>\n </div>\n </aside>`;\n }\n\n /** A stacked two-line item: the mode's name, then its grey subtitle. Keys 1/2/3 select them. */\n private modeItem(info: ModeInfo, index: number): string {\n const pressed = info.mode === GraphMode.RUNTIME ? 'true' : 'false';\n return (\n `<button type=\"button\" class=\"wp-mode\" data-wp-mode=\"${info.mode}\" aria-pressed=\"${pressed}\" title=\"Key ${index + 1}\">` +\n `<span class=\"wp-mode-name\">${info.name}</span><span class=\"wp-mode-sub\">${info.subtitle}</span></button>`\n );\n }\n\n private topbar(parts: ShellParts): string {\n return `<div class=\"wp-topbar\">\n <button type=\"button\" class=\"wp-icon\" id=\"wp-collapse\" aria-label=\"Toggle the controls drawer\" aria-controls=\"wp-side\" aria-expanded=\"true\">☰</button>\n <div class=\"wp-crumb\" id=\"wp-crumb\">Mode: <b>Runtime</b></div>\n ${parts.filterStatus}\n <button type=\"button\" class=\"wp-icon\" id=\"wp-help-btn\" aria-label=\"How to read this graph\" aria-controls=\"wp-help\" aria-expanded=\"false\">?</button>\n </div>`;\n }\n\n private overlays(parts: ShellParts): string {\n return `<div class=\"wp-pop wp-legend-popout\" id=\"wp-legend-popout\" hidden>\n <header><span id=\"wp-legend-pop-title\">Legend · Runtime</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-legend-close\" aria-label=\"Close the legend\">✕</button></header>\n ${parts.legend}\n </div>\n ${this.help()}\n <div class=\"wp-pop wp-snapshot\" id=\"wp-snapshot\" hidden>${parts.snapshot}</div>\n <aside class=\"wp-panel\" id=\"wp-resp-panel\" aria-label=\"Responsibilities\" hidden>\n <header><span>Responsibilities</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-resp-close\" aria-label=\"Close responsibilities\">✕</button></header>\n ${parts.responsibilities}\n </aside>`;\n }\n\n /** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */\n private help(): string {\n return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Transitive dependencies are allowed but not drawn.</p>\n <p>💡 <strong>Click any box</strong> for its menu: <strong>View Design</strong> (only where the project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it), <strong>Lock/Unlock</strong> (the same lock as the Focus field), <strong>Filter Unconnected</strong> (removes unrelated boxes, keeping each L-number row) and <strong>Mode ▸</strong>.</p>\n <p>🔦 <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain — every ancestor above it and every dependency below it, with the lines between — while the rest of the graph dims.</p>\n <p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what this branch changed (amber) and what depends on it (amber outline).</p>\n <p><kbd>/</kbd> focus the lock search · <kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd> switch mode · <kbd>Esc</kbd> unlock</p>\n </div>`;\n }\n\n private brandIcon(): string {\n return (\n '<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">' +\n '<rect x=\"1\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"13\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"7\" y=\"15\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<path d=\"M5 7v4h12V7M11 11v4\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/></svg>'\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The architecture page's stylesheet: the dark drawer, the graph filling the viewport, the floating
|
|
3
|
+
* popovers and the responsibilities panel. Emitted AFTER the shared menu/filter/navigation styles so
|
|
4
|
+
* its `#graph` rules (fill the viewport, no 75vh cap) win.
|
|
5
|
+
*/
|
|
6
|
+
export declare class GraphPageStyles {
|
|
7
|
+
css(): string;
|
|
8
|
+
private base;
|
|
9
|
+
private drawer;
|
|
10
|
+
private controls;
|
|
11
|
+
private main;
|
|
12
|
+
private overlays;
|
|
13
|
+
/**
|
|
14
|
+
* The graph fills what the topbar leaves. A Runtime box with a specialization is an HTML-like
|
|
15
|
+
* table, so its cells are extra polygons inside the node: the hover glow and the lock outline are
|
|
16
|
+
* kept on the box's own outline (the FIRST polygon) instead of stroking every cell.
|
|
17
|
+
*/
|
|
18
|
+
private graph;
|
|
19
|
+
private cards;
|
|
20
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GraphPageStyles = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The architecture page's stylesheet: the dark drawer, the graph filling the viewport, the floating
|
|
6
|
+
* popovers and the responsibilities panel. Emitted AFTER the shared menu/filter/navigation styles so
|
|
7
|
+
* its `#graph` rules (fill the viewport, no 75vh cap) win.
|
|
8
|
+
*/
|
|
9
|
+
class GraphPageStyles {
|
|
10
|
+
css() {
|
|
11
|
+
return this.base() + this.drawer() + this.controls() + this.main() + this.overlays() + this.graph() + this.cards();
|
|
12
|
+
}
|
|
13
|
+
base() {
|
|
14
|
+
return `
|
|
15
|
+
:root {
|
|
16
|
+
--wp-side-bg: #0c0d12; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;
|
|
17
|
+
--wp-side-hover: #1f2129; --wp-side-line: #2a2c36; --wp-accent: #3559d6; --wp-accent-dark: #8aa4ff;
|
|
18
|
+
--wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #fbfbfd;
|
|
19
|
+
}
|
|
20
|
+
html, body { height: 100%; }
|
|
21
|
+
/* A class that sets display (the legend lists are grids) must not un-hide a [hidden] element. */
|
|
22
|
+
[hidden] { display: none !important; }
|
|
23
|
+
body { margin: 0; padding: 0; overflow: hidden; background: var(--wp-canvas); color: var(--wp-fg);
|
|
24
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; font-size: 14px; }
|
|
25
|
+
kbd { font-family: ui-monospace, Menlo, monospace; font-size: 12px; border: 1px solid var(--wp-line);
|
|
26
|
+
border-radius: 4px; padding: 0 5px; background: #f3f4f7; }
|
|
27
|
+
.wp-shell { display: grid; grid-template-columns: 272px minmax(0, 1fr); height: 100vh; }
|
|
28
|
+
.wp-shell.wp-collapsed { grid-template-columns: 0 minmax(0, 1fr); }
|
|
29
|
+
.wp-shell.wp-collapsed .wp-side { visibility: hidden; }
|
|
30
|
+
@media (max-width: 760px) {
|
|
31
|
+
.wp-shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, auto) minmax(0, 1fr); }
|
|
32
|
+
.wp-side { max-height: 42vh; }
|
|
33
|
+
.wp-shell.wp-collapsed { grid-template-columns: minmax(0, 1fr); }
|
|
34
|
+
.wp-shell.wp-collapsed .wp-side { display: none; }
|
|
35
|
+
.wp-main { height: auto; min-height: 0; }
|
|
36
|
+
}
|
|
37
|
+
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }`;
|
|
38
|
+
}
|
|
39
|
+
drawer() {
|
|
40
|
+
return `
|
|
41
|
+
.wp-side { background: var(--wp-side-bg); color: var(--wp-side-fg); overflow-y: auto; overflow-x: hidden;
|
|
42
|
+
display: flex; flex-direction: column; padding: 12px 0 10px; min-width: 0; font-size: 13px; }
|
|
43
|
+
.wp-brand { display: flex; align-items: center; gap: 10px; padding: 0 16px 10px; font-weight: 600; font-size: 14px; }
|
|
44
|
+
.wp-brand svg { flex: none; }
|
|
45
|
+
.wp-group { padding: 8px 14px; border-top: 1px solid var(--wp-side-line); display: grid; gap: 6px; }
|
|
46
|
+
.wp-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em; color: var(--wp-side-muted);
|
|
47
|
+
font-weight: 600; padding-left: 4px; }
|
|
48
|
+
.wp-label-row { display: flex; justify-content: space-between; align-items: center; }
|
|
49
|
+
.wp-modes { display: grid; gap: 1px; }
|
|
50
|
+
.wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 5px 10px; border-radius: 8px; }
|
|
51
|
+
.wp-mode:hover { background: var(--wp-side-hover); }
|
|
52
|
+
.wp-mode[aria-pressed="true"] { background: #262833; box-shadow: inset 3px 0 0 var(--wp-accent-dark); }
|
|
53
|
+
.wp-mode:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
54
|
+
.wp-mode-name { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }
|
|
55
|
+
.wp-mode-sub { display: block; font-size: 12px; color: var(--wp-side-muted); line-height: 1.3; }
|
|
56
|
+
.wp-mode-reason { margin: 2px 4px 0; font-size: 11.5px; line-height: 1.35; color: var(--wp-side-muted); }
|
|
57
|
+
.wp-legend-list { display: grid; gap: 5px; padding-left: 4px; }
|
|
58
|
+
.wp-legend-row { display: flex; align-items: center; gap: 10px; line-height: 1.25; }
|
|
59
|
+
.wp-legend-row svg { flex: none; }
|
|
60
|
+
.wp-legend-row small { display: block; color: var(--wp-side-muted); font-size: 11.5px; }
|
|
61
|
+
.wp-impact-note { margin: 2px 0 0; font-size: 11.5px; color: var(--wp-side-muted); }
|
|
62
|
+
.wp-impact-note:empty { display: none; }
|
|
63
|
+
.wp-foot { margin-top: auto; padding: 10px 18px 0; border-top: 1px solid var(--wp-side-line); display: flex; gap: 14px; flex-wrap: wrap; }`;
|
|
64
|
+
}
|
|
65
|
+
controls() {
|
|
66
|
+
return `
|
|
67
|
+
.wp-side input[type="search"] { width: 100%; box-sizing: border-box; background: #17181f; color: var(--wp-side-fg);
|
|
68
|
+
border: 1px solid var(--wp-side-line); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }
|
|
69
|
+
.wp-toggle { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;
|
|
70
|
+
align-items: center; padding: 6px 10px; border-radius: 8px; font-size: 13px; }
|
|
71
|
+
.wp-toggle:hover { background: var(--wp-side-hover); }
|
|
72
|
+
.wp-toggle:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
73
|
+
.wp-sw { width: 30px; height: 18px; border-radius: 9px; background: #3a3d4a; position: relative; transition: background 0.15s; }
|
|
74
|
+
.wp-sw::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;
|
|
75
|
+
background: #fff; transition: transform 0.15s; }
|
|
76
|
+
.wp-toggle[aria-pressed="true"] .wp-sw { background: var(--wp-accent); }
|
|
77
|
+
.wp-toggle[aria-pressed="true"] .wp-sw::after { transform: translateX(12px); }
|
|
78
|
+
.wp-link { all: unset; cursor: pointer; color: var(--wp-side-muted); font-size: 12px; text-decoration: underline; }
|
|
79
|
+
.wp-link:hover { color: var(--wp-side-fg); }
|
|
80
|
+
.wp-icon { all: unset; box-sizing: border-box; cursor: pointer; display: inline-grid; place-items: center; flex: none;
|
|
81
|
+
width: 34px; height: 34px; border-radius: 8px; background: var(--wp-surface); border: 1px solid var(--wp-line);
|
|
82
|
+
color: var(--wp-fg); font-size: 15px; }
|
|
83
|
+
.wp-icon:hover { border-color: var(--wp-accent); }
|
|
84
|
+
.wp-icon-mini { width: 24px; height: 24px; font-size: 12px; }
|
|
85
|
+
.wp-icon-dark { background: transparent; border-color: var(--wp-side-line); color: var(--wp-side-fg); }
|
|
86
|
+
.wp-mode:focus-visible, .wp-toggle:focus-visible, .wp-side input:focus-visible, .wp-icon:focus-visible,
|
|
87
|
+
.wp-link:focus-visible { outline: 2px solid var(--wp-accent-dark); outline-offset: 2px; }`;
|
|
88
|
+
}
|
|
89
|
+
main() {
|
|
90
|
+
return `
|
|
91
|
+
.wp-main { position: relative; min-width: 0; height: 100vh; display: flex; flex-direction: column; }
|
|
92
|
+
.wp-topbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 7px 12px;
|
|
93
|
+
border-bottom: 1px solid var(--wp-line); background: var(--wp-surface); }
|
|
94
|
+
.wp-crumb { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--wp-muted); overflow: hidden;
|
|
95
|
+
text-overflow: ellipsis; white-space: nowrap; }
|
|
96
|
+
.wp-crumb b { color: var(--wp-fg); }
|
|
97
|
+
.wp-topbar #wp-filter-status, .wp-topbar #wp-graph-error { margin: 0; font-size: 13px; }`;
|
|
98
|
+
}
|
|
99
|
+
overlays() {
|
|
100
|
+
return `
|
|
101
|
+
.wp-pop { position: absolute; z-index: 30; background: var(--wp-surface); border: 1px solid var(--wp-line);
|
|
102
|
+
border-radius: 10px; box-shadow: 0 8px 24px #0002; padding: 12px 14px; font-size: 13px; box-sizing: border-box; }
|
|
103
|
+
.wp-pop header { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 8px; }
|
|
104
|
+
.wp-legend-popout { left: 12px; bottom: 12px; width: 270px; max-height: 60vh; overflow: auto; }
|
|
105
|
+
.wp-legend-popout .wp-legend-row small, .wp-legend-popout .wp-impact-note { color: var(--wp-muted); }
|
|
106
|
+
.wp-help { right: 12px; top: 56px; width: min(380px, calc(100% - 24px)); line-height: 1.5; }
|
|
107
|
+
.wp-help p { margin: 0 0 8px; }
|
|
108
|
+
.wp-snapshot { left: 12px; bottom: 12px; width: min(440px, calc(100% - 24px)); }
|
|
109
|
+
.wp-snapshot .hint { margin: 0; }
|
|
110
|
+
.wp-panel { position: absolute; top: 0; right: 0; bottom: 0; z-index: 40; width: min(540px, 100%); box-sizing: border-box;
|
|
111
|
+
overflow-y: auto; padding: 0 16px 16px; background: #f5f6f9; border-left: 1px solid var(--wp-line); box-shadow: -8px 0 24px #0002; }
|
|
112
|
+
.wp-panel > header { position: sticky; top: 0; z-index: 1; display: flex; justify-content: space-between; align-items: center;
|
|
113
|
+
padding: 12px 0; background: #f5f6f9; font-weight: 600; font-size: 15px; }`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The graph fills what the topbar leaves. A Runtime box with a specialization is an HTML-like
|
|
117
|
+
* table, so its cells are extra polygons inside the node: the hover glow and the lock outline are
|
|
118
|
+
* kept on the box's own outline (the FIRST polygon) instead of stroking every cell.
|
|
119
|
+
*/
|
|
120
|
+
graph() {
|
|
121
|
+
return `
|
|
122
|
+
#graph { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; padding: 16px 16px 64px;
|
|
123
|
+
box-sizing: border-box; background: var(--wp-canvas); }
|
|
124
|
+
#graph g.wp-node-clickable:hover polygon:not(:first-of-type) { stroke: transparent; stroke-width: 0; filter: none; }
|
|
125
|
+
#graph g.node.wp-locked > polygon:first-of-type,
|
|
126
|
+
#graph g.node.wp-locked > ellipse,
|
|
127
|
+
#graph g.node.wp-locked > path { stroke: #b26a00; stroke-width: 3; }`;
|
|
128
|
+
}
|
|
129
|
+
cards() {
|
|
130
|
+
return `
|
|
131
|
+
#wp-responsibilities { margin: 0; }
|
|
132
|
+
#wp-responsibilities > h2 { display: none; }
|
|
133
|
+
#wp-responsibilities > .hint { color: var(--wp-muted); margin: 0 0 8px; }
|
|
134
|
+
.wp-resp-card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px 0; padding: 10px 15px; }
|
|
135
|
+
.wp-resp-card > summary { cursor: pointer; color: #333; }
|
|
136
|
+
.wp-resp-level { display: inline-block; min-width: 26px; padding: 1px 6px; margin-right: 6px; border-radius: 4px;
|
|
137
|
+
background: #eef; font-size: 12px; font-weight: bold; text-align: center; }
|
|
138
|
+
.wp-resp-body { margin-top: 8px; color: #444; }
|
|
139
|
+
.wp-resp-body code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }
|
|
140
|
+
.wp-hidden { display: none; }`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.GraphPageStyles = GraphPageStyles;
|
|
144
|
+
//# sourceMappingURL=graph-page-styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-page-styles.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-styles.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,MAAa,eAAe;IACxB,GAAG;QACC,OAAO,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IACvH,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;;;;;;;;;;uFAuBwE,CAAC;IACpF,CAAC;IAEO,MAAM;QACV,OAAO;;;;;;;;;;;;;;;;;;;;;;;mJAuBoI,CAAC;IAChJ,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;;;;;;;;;kGAqBmF,CAAC;IAC/F,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;iGAOkF,CAAC;IAC9F,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;uFAawE,CAAC;IACpF,CAAC;IAED;;;;OAIG;IACK,KAAK;QACT,OAAO;;;;;;6EAM8D,CAAC;IAC1E,CAAC;IAEO,KAAK;QACT,OAAO;;;;;;;;;;sCAUuB,CAAC;IACnC,CAAC;CACJ;AA5ID,0CA4IC","sourcesContent":["/**\n * The architecture page's stylesheet: the dark drawer, the graph filling the viewport, the floating\n * popovers and the responsibilities panel. Emitted AFTER the shared menu/filter/navigation styles so\n * its `#graph` rules (fill the viewport, no 75vh cap) win.\n */\nexport class GraphPageStyles {\n css(): string {\n return this.base() + this.drawer() + this.controls() + this.main() + this.overlays() + this.graph() + this.cards();\n }\n\n private base(): string {\n return `\n :root {\n --wp-side-bg: #0c0d12; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;\n --wp-side-hover: #1f2129; --wp-side-line: #2a2c36; --wp-accent: #3559d6; --wp-accent-dark: #8aa4ff;\n --wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #fbfbfd;\n }\n html, body { height: 100%; }\n /* A class that sets display (the legend lists are grids) must not un-hide a [hidden] element. */\n [hidden] { display: none !important; }\n body { margin: 0; padding: 0; overflow: hidden; background: var(--wp-canvas); color: var(--wp-fg);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Arial, sans-serif; font-size: 14px; }\n kbd { font-family: ui-monospace, Menlo, monospace; font-size: 12px; border: 1px solid var(--wp-line);\n border-radius: 4px; padding: 0 5px; background: #f3f4f7; }\n .wp-shell { display: grid; grid-template-columns: 272px minmax(0, 1fr); height: 100vh; }\n .wp-shell.wp-collapsed { grid-template-columns: 0 minmax(0, 1fr); }\n .wp-shell.wp-collapsed .wp-side { visibility: hidden; }\n @media (max-width: 760px) {\n .wp-shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, auto) minmax(0, 1fr); }\n .wp-side { max-height: 42vh; }\n .wp-shell.wp-collapsed { grid-template-columns: minmax(0, 1fr); }\n .wp-shell.wp-collapsed .wp-side { display: none; }\n .wp-main { height: auto; min-height: 0; }\n }\n @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }`;\n }\n\n private drawer(): string {\n return `\n .wp-side { background: var(--wp-side-bg); color: var(--wp-side-fg); overflow-y: auto; overflow-x: hidden;\n display: flex; flex-direction: column; padding: 12px 0 10px; min-width: 0; font-size: 13px; }\n .wp-brand { display: flex; align-items: center; gap: 10px; padding: 0 16px 10px; font-weight: 600; font-size: 14px; }\n .wp-brand svg { flex: none; }\n .wp-group { padding: 8px 14px; border-top: 1px solid var(--wp-side-line); display: grid; gap: 6px; }\n .wp-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em; color: var(--wp-side-muted);\n font-weight: 600; padding-left: 4px; }\n .wp-label-row { display: flex; justify-content: space-between; align-items: center; }\n .wp-modes { display: grid; gap: 1px; }\n .wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 5px 10px; border-radius: 8px; }\n .wp-mode:hover { background: var(--wp-side-hover); }\n .wp-mode[aria-pressed=\"true\"] { background: #262833; box-shadow: inset 3px 0 0 var(--wp-accent-dark); }\n .wp-mode:disabled { cursor: not-allowed; opacity: 0.45; }\n .wp-mode-name { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }\n .wp-mode-sub { display: block; font-size: 12px; color: var(--wp-side-muted); line-height: 1.3; }\n .wp-mode-reason { margin: 2px 4px 0; font-size: 11.5px; line-height: 1.35; color: var(--wp-side-muted); }\n .wp-legend-list { display: grid; gap: 5px; padding-left: 4px; }\n .wp-legend-row { display: flex; align-items: center; gap: 10px; line-height: 1.25; }\n .wp-legend-row svg { flex: none; }\n .wp-legend-row small { display: block; color: var(--wp-side-muted); font-size: 11.5px; }\n .wp-impact-note { margin: 2px 0 0; font-size: 11.5px; color: var(--wp-side-muted); }\n .wp-impact-note:empty { display: none; }\n .wp-foot { margin-top: auto; padding: 10px 18px 0; border-top: 1px solid var(--wp-side-line); display: flex; gap: 14px; flex-wrap: wrap; }`;\n }\n\n private controls(): string {\n return `\n .wp-side input[type=\"search\"] { width: 100%; box-sizing: border-box; background: #17181f; color: var(--wp-side-fg);\n border: 1px solid var(--wp-side-line); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }\n .wp-toggle { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;\n align-items: center; padding: 6px 10px; border-radius: 8px; font-size: 13px; }\n .wp-toggle:hover { background: var(--wp-side-hover); }\n .wp-toggle:disabled { cursor: not-allowed; opacity: 0.45; }\n .wp-sw { width: 30px; height: 18px; border-radius: 9px; background: #3a3d4a; position: relative; transition: background 0.15s; }\n .wp-sw::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;\n background: #fff; transition: transform 0.15s; }\n .wp-toggle[aria-pressed=\"true\"] .wp-sw { background: var(--wp-accent); }\n .wp-toggle[aria-pressed=\"true\"] .wp-sw::after { transform: translateX(12px); }\n .wp-link { all: unset; cursor: pointer; color: var(--wp-side-muted); font-size: 12px; text-decoration: underline; }\n .wp-link:hover { color: var(--wp-side-fg); }\n .wp-icon { all: unset; box-sizing: border-box; cursor: pointer; display: inline-grid; place-items: center; flex: none;\n width: 34px; height: 34px; border-radius: 8px; background: var(--wp-surface); border: 1px solid var(--wp-line);\n color: var(--wp-fg); font-size: 15px; }\n .wp-icon:hover { border-color: var(--wp-accent); }\n .wp-icon-mini { width: 24px; height: 24px; font-size: 12px; }\n .wp-icon-dark { background: transparent; border-color: var(--wp-side-line); color: var(--wp-side-fg); }\n .wp-mode:focus-visible, .wp-toggle:focus-visible, .wp-side input:focus-visible, .wp-icon:focus-visible,\n .wp-link:focus-visible { outline: 2px solid var(--wp-accent-dark); outline-offset: 2px; }`;\n }\n\n private main(): string {\n return `\n .wp-main { position: relative; min-width: 0; height: 100vh; display: flex; flex-direction: column; }\n .wp-topbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 7px 12px;\n border-bottom: 1px solid var(--wp-line); background: var(--wp-surface); }\n .wp-crumb { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--wp-muted); overflow: hidden;\n text-overflow: ellipsis; white-space: nowrap; }\n .wp-crumb b { color: var(--wp-fg); }\n .wp-topbar #wp-filter-status, .wp-topbar #wp-graph-error { margin: 0; font-size: 13px; }`;\n }\n\n private overlays(): string {\n return `\n .wp-pop { position: absolute; z-index: 30; background: var(--wp-surface); border: 1px solid var(--wp-line);\n border-radius: 10px; box-shadow: 0 8px 24px #0002; padding: 12px 14px; font-size: 13px; box-sizing: border-box; }\n .wp-pop header { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 8px; }\n .wp-legend-popout { left: 12px; bottom: 12px; width: 270px; max-height: 60vh; overflow: auto; }\n .wp-legend-popout .wp-legend-row small, .wp-legend-popout .wp-impact-note { color: var(--wp-muted); }\n .wp-help { right: 12px; top: 56px; width: min(380px, calc(100% - 24px)); line-height: 1.5; }\n .wp-help p { margin: 0 0 8px; }\n .wp-snapshot { left: 12px; bottom: 12px; width: min(440px, calc(100% - 24px)); }\n .wp-snapshot .hint { margin: 0; }\n .wp-panel { position: absolute; top: 0; right: 0; bottom: 0; z-index: 40; width: min(540px, 100%); box-sizing: border-box;\n overflow-y: auto; padding: 0 16px 16px; background: #f5f6f9; border-left: 1px solid var(--wp-line); box-shadow: -8px 0 24px #0002; }\n .wp-panel > header { position: sticky; top: 0; z-index: 1; display: flex; justify-content: space-between; align-items: center;\n padding: 12px 0; background: #f5f6f9; font-weight: 600; font-size: 15px; }`;\n }\n\n /**\n * The graph fills what the topbar leaves. A Runtime box with a specialization is an HTML-like\n * table, so its cells are extra polygons inside the node: the hover glow and the lock outline are\n * kept on the box's own outline (the FIRST polygon) instead of stroking every cell.\n */\n private graph(): string {\n return `\n #graph { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; padding: 16px 16px 64px;\n box-sizing: border-box; background: var(--wp-canvas); }\n #graph g.wp-node-clickable:hover polygon:not(:first-of-type) { stroke: transparent; stroke-width: 0; filter: none; }\n #graph g.node.wp-locked > polygon:first-of-type,\n #graph g.node.wp-locked > ellipse,\n #graph g.node.wp-locked > path { stroke: #b26a00; stroke-width: 3; }`;\n }\n\n private cards(): string {\n return `\n #wp-responsibilities { margin: 0; }\n #wp-responsibilities > h2 { display: none; }\n #wp-responsibilities > .hint { color: var(--wp-muted); margin: 0 0 8px; }\n .wp-resp-card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px 0; padding: 10px 15px; }\n .wp-resp-card > summary { cursor: pointer; color: #333; }\n .wp-resp-level { display: inline-block; min-width: 26px; padding: 1px 6px; margin-right: 6px; border-radius: 4px;\n background: #eef; font-size: 12px; font-weight: bold; text-align: center; }\n .wp-resp-body { margin-top: 8px; color: #444; }\n .wp-resp-body code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }\n .wp-hidden { display: none; }`;\n }\n}\n"]}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { LevelBand } from './graph-level-bands';
|
|
2
|
-
|
|
2
|
+
import { LegendFacts, NodeModeDots } from './graph-color-modes';
|
|
3
|
+
/**
|
|
4
|
+
* Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.
|
|
5
|
+
*
|
|
6
|
+
* `dot` is the statement `fullDot` carries. `modes` is set only by the architecture graph, whose page
|
|
7
|
+
* switches color modes by re-rendering each box from its per-mode statement; the runtime graph has
|
|
8
|
+
* one look and leaves it null.
|
|
9
|
+
*/
|
|
3
10
|
export declare class RenderNode {
|
|
4
11
|
readonly id: string;
|
|
5
12
|
readonly dot: string;
|
|
6
|
-
|
|
13
|
+
readonly modes: NodeModeDots | null;
|
|
14
|
+
constructor(id: string, dot: string, modes: NodeModeDots | null);
|
|
7
15
|
}
|
|
8
16
|
export declare class RenderEdge {
|
|
9
17
|
readonly from: string;
|
|
@@ -20,7 +28,11 @@ export declare class GraphRenderModel {
|
|
|
20
28
|
fullDot: string;
|
|
21
29
|
header: string;
|
|
22
30
|
footer: string;
|
|
31
|
+
/** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */
|
|
32
|
+
legend: LegendFacts;
|
|
23
33
|
node(id: string, dot: string): string;
|
|
34
|
+
/** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */
|
|
35
|
+
styledNode(id: string, modes: NodeModeDots): string;
|
|
24
36
|
completeEndpoints(): void;
|
|
25
37
|
edge(from: string, to: string, dot: string): string;
|
|
26
38
|
}
|
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GraphFilterAssets = exports.GraphRenderModel = exports.RenderEdge = exports.RenderNode = void 0;
|
|
4
4
|
const dot_syntax_1 = require("./dot-syntax");
|
|
5
|
-
|
|
5
|
+
const graph_color_modes_1 = require("./graph-color-modes");
|
|
6
|
+
/**
|
|
7
|
+
* Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.
|
|
8
|
+
*
|
|
9
|
+
* `dot` is the statement `fullDot` carries. `modes` is set only by the architecture graph, whose page
|
|
10
|
+
* switches color modes by re-rendering each box from its per-mode statement; the runtime graph has
|
|
11
|
+
* one look and leaves it null.
|
|
12
|
+
*/
|
|
6
13
|
class RenderNode {
|
|
7
14
|
id;
|
|
8
15
|
dot;
|
|
9
|
-
|
|
16
|
+
modes;
|
|
17
|
+
constructor(id, dot, modes) {
|
|
10
18
|
this.id = id;
|
|
11
19
|
this.dot = dot;
|
|
20
|
+
this.modes = modes;
|
|
12
21
|
}
|
|
13
22
|
}
|
|
14
23
|
exports.RenderNode = RenderNode;
|
|
@@ -33,10 +42,17 @@ class GraphRenderModel {
|
|
|
33
42
|
fullDot = '';
|
|
34
43
|
header = '';
|
|
35
44
|
footer = '';
|
|
45
|
+
/** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */
|
|
46
|
+
legend = new graph_color_modes_1.LegendFacts();
|
|
36
47
|
node(id, dot) {
|
|
37
|
-
this.nodes.push(new RenderNode(id, dot));
|
|
48
|
+
this.nodes.push(new RenderNode(id, dot, null));
|
|
38
49
|
return dot;
|
|
39
50
|
}
|
|
51
|
+
/** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */
|
|
52
|
+
styledNode(id, modes) {
|
|
53
|
+
this.nodes.push(new RenderNode(id, modes.runtime, modes));
|
|
54
|
+
return modes.runtime;
|
|
55
|
+
}
|
|
40
56
|
completeEndpoints() {
|
|
41
57
|
const ids = new Set(this.nodes.map((node) => node.id));
|
|
42
58
|
for (const edge of this.edges) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-render-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-render-model.ts"],"names":[],"mappings":";;;AAAA,6CAAwC;
|
|
1
|
+
{"version":3,"file":"graph-render-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-render-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { dotValue } from './dot-syntax';\nimport { LevelBand } from './graph-level-bands';\nimport { LegendFacts, NodeModeDots } from './graph-color-modes';\n\n/**\n * Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.\n *\n * `dot` is the statement `fullDot` carries. `modes` is set only by the architecture graph, whose page\n * switches color modes by re-rendering each box from its per-mode statement; the runtime graph has\n * one look and leaves it null.\n */\nexport class RenderNode {\n constructor(\n public readonly id: string,\n public readonly dot: string,\n public readonly modes: NodeModeDots | null,\n ) {}\n}\n\nexport class RenderEdge {\n constructor(\n public readonly from: string,\n public readonly to: string,\n public readonly dot: string,\n public readonly id: string,\n ) {}\n}\n\n/** The immutable drawable snapshot carried by a generated page. */\nexport class GraphRenderModel {\n readonly nodes: RenderNode[] = [];\n readonly edges: RenderEdge[] = [];\n bands: LevelBand[] = [];\n fullDot = '';\n header = '';\n footer = '';\n /** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */\n legend = new LegendFacts();\n\n node(id: string, dot: string): string {\n this.nodes.push(new RenderNode(id, dot, null));\n return dot;\n }\n\n /** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */\n styledNode(id: string, modes: NodeModeDots): string {\n this.nodes.push(new RenderNode(id, modes.runtime, modes));\n return modes.runtime;\n }\n\n completeEndpoints(): void {\n const ids = new Set(this.nodes.map((node) => node.id));\n for (const edge of this.edges) {\n for (const id of [edge.from, edge.to]) {\n if (ids.has(id)) continue;\n ids.add(id);\n this.node(id, ` \"${dotValue(id)}\";\\n`);\n }\n }\n }\n\n edge(from: string, to: string, dot: string): string {\n const id = `wp-real-edge-${this.edges.length}`;\n const statement = `${dot.slice(0, -2)} [id=\"${id}\"];\\n`;\n this.edges.push(new RenderEdge(from, to, statement, id));\n return statement;\n }\n}\n\n/** Shared filter chrome and script-safe JSON for pages opened directly from disk. */\nexport class GraphFilterAssets {\n json<T>(value: T): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n }\n\n html(): string {\n return (\n '<p id=\"wp-filter-status\" hidden>Filtering: <strong id=\"wp-filter-anchor\"></strong> ' +\n '<button id=\"wp-filter-off\" type=\"button\">Turn off Filter</button></p>' +\n '<p id=\"wp-graph-error\" role=\"alert\" hidden></p>'\n );\n }\n\n styles(): string {\n return (\n '#wp-filter-status { text-align:center; } #wp-graph-error { color:#b71c1c; } ' +\n '#graph .wp-filter-anchor polygon, #graph .wp-filter-anchor path, ' +\n '#graph .wp-filter-anchor ellipse { stroke:#1565c0; stroke-width:3; }'\n );\n }\n}\n"]}
|
|
@@ -51,7 +51,7 @@ class ResponsibilitiesRenderer {
|
|
|
51
51
|
}
|
|
52
52
|
return (`<section id="wp-responsibilities">` +
|
|
53
53
|
`<h2>Responsibilities (level high → low)</h2>` +
|
|
54
|
-
`<p class="hint">Lock a box
|
|
54
|
+
`<p class="hint">Lock a box (the Focus field, or a box's menu) to narrow this list to just that box's dependency chain.</p>` +
|
|
55
55
|
cards.join('\n') +
|
|
56
56
|
`</section>`);
|
|
57
57
|
}
|