@webpieces/nx-webpieces-rules 0.4.947 → 0.4.949
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 +20 -11
- package/package.json +7 -7
- package/src/index.d.ts +1 -1
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +18 -12
- package/src/lib/graph-color-modes.js +18 -20
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-legend.d.ts +11 -3
- package/src/lib/graph-legend.js +25 -9
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +2 -1
- package/src/lib/graph-mode-state.client.js +3 -1
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.js +2 -2
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-page-styles.js +4 -1
- package/src/lib/graph-page-styles.js.map +1 -1
- package/src/lib/graph-product-paint.client.d.ts +92 -0
- package/src/lib/graph-product-paint.client.js +196 -0
- package/src/lib/graph-product-paint.client.js.map +1 -0
- package/src/lib/graph-products.d.ts +54 -6
- package/src/lib/graph-products.js +62 -9
- package/src/lib/graph-products.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +12 -12
- package/src/lib/graph-render-model.js +12 -14
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +13 -1
- package/src/lib/graph-visualizer.client.js +27 -4
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.d.ts +3 -3
- package/src/lib/graph-visualizer.js +13 -12
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/runtime-goal-comparison.js +1 -1
- package/src/lib/runtime-goal-comparison.js.map +1 -1
- package/src/lib/runtime-html-page.js +3 -2
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-layout.js +1 -1
- package/src/lib/runtime-layout.js.map +1 -1
- package/src/lib/runtime-viewer-adapter.d.ts +4 -1
- package/src/lib/runtime-viewer-adapter.js +6 -13
- package/src/lib/runtime-viewer-adapter.js.map +1 -1
|
@@ -1 +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,2DAAoD;AAEpD;;;;GAIG;AACH,MAAa,eAAe;IACxB,GAAG;QACC,OAAO,CACH,IAAI,CAAC,IAAI,EAAE;YACX,IAAI,CAAC,MAAM,EAAE;YACb,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,IAAI,EAAE;YACX,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,aAAa,EAAE;YACpB,IAAI,CAAC,KAAK,EAAE;YACZ,IAAI,CAAC,KAAK,EAAE,CACf,CAAC;IACN,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;;;;;;;;;;uFAuBwE,CAAC;IACpF,CAAC;IAEO,MAAM;QACV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mJAqCoI,CAAC;IAChJ,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;;;;;;;;;;mGAsBoF,CAAC;IAChG,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;uDAcwC,CAAC;IACpD,CAAC;IAED,iFAAiF;IACzE,aAAa;QACjB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;kEA4BmD,CAAC;IAC/D,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;uFAawE,CAAC;IACpF,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,KAAK;QACT,MAAM,KAAK,GAAG,6BAA6B,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,KAAK,MAAM,KAAK,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,GAAG,KAAK,QAAQ,KAAK,GAAG,CAAC;QACvC,MAAM,IAAI,GAAG,mGAAmG,CAAC;QACjH,OAAO;;;6CAG8B,KAAK;qCACb,iCAAa;qCACb,iCAAa,mCAAmC,IAAI;uCAClD,iCAAa,OAAO,KAAK;kCAC9B,iCAAa,wEAAwE,CAAC;IACpH,CAAC;IAEO,KAAK;QACT,OAAO;;;;;;;;;;sCAUuB,CAAC;IACnC,CAAC;CACJ;AA1ND,0CA0NC","sourcesContent":["import { STRIPED_CLASS } from './graph-color-modes';\n\n/**\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 (\n this.base() +\n this.drawer() +\n this.controls() +\n this.main() +\n this.overlays() +\n this.filterPopover() +\n this.graph() +\n this.cards()\n );\n }\n\n private base(): string {\n return `\n :root {\n --wp-side-bg: #15161b; --wp-side-control: #1e1f25; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;\n --wp-side-hover: #26272f; --wp-side-line: #3a3c46; --wp-accent: #8b3cf0; --wp-accent-hover: #a46bf7;\n --wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #faf9f5;\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-pulldown { position: relative; }\n .wp-pill-trigger { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 10px;\n width: 100%; padding: 7px 16px; border-radius: 999px; border: 1px solid var(--wp-side-line);\n background: var(--wp-side-control); }\n .wp-pill-trigger:hover, .wp-pill-trigger[aria-expanded=\"true\"] { border-color: var(--wp-accent-hover); }\n .wp-pill-text { flex: 1 1 auto; min-width: 0; }\n .wp-caret { color: var(--wp-side-muted); }\n .wp-menu { position: absolute; z-index: 35; left: 0; right: 0; top: calc(100% + 6px); display: grid; gap: 2px; padding: 6px;\n background: var(--wp-side-control); border: 1px solid var(--wp-side-line); border-radius: 12px; box-shadow: 0 10px 28px #0008; }\n .wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 6px 10px; border-radius: 8px; }\n .wp-mode:hover { background: var(--wp-side-hover); }\n .wp-mode[aria-checked=\"true\"] { background: #2a2333; box-shadow: inset 3px 0 0 var(--wp-accent); }\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-filter-btn { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;\n align-items: center; padding: 7px 12px; border-radius: 8px; border: 1px solid var(--wp-side-line);\n background: var(--wp-side-control); font-size: 13px; font-weight: 600; }\n .wp-filter-btn:hover, .wp-filter-btn[aria-expanded=\"true\"] { border-color: var(--wp-accent-hover); }\n .wp-badge { min-width: 18px; padding: 1px 6px; border-radius: 999px; background: var(--wp-accent); color: #fff;\n font-size: 11.5px; text-align: center; box-sizing: border-box; }\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: var(--wp-side-control); 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-pill-trigger:focus-visible, .wp-filter-btn:focus-visible, .wp-chip:focus-visible,\n .wp-primary:focus-visible, .wp-pill button: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-hover); 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 .wp-pills { display: flex; gap: 6px; flex-wrap: wrap; }\n .wp-pills:empty { display: none; }\n .wp-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 10px; border-radius: 999px;\n background: #f1e8fe; color: #4a1d8a; border: 1px solid #d6bcfa; font-size: 12.5px; }\n .wp-pill button { all: unset; cursor: pointer; width: 18px; height: 18px; display: inline-grid; place-items: center;\n border-radius: 50%; color: #4a1d8a; }\n .wp-pill button:hover { background: #e2cffc; }`;\n }\n\n /** The Filter popover: three groups, chips, and the \"Show N projects\" footer. */\n private filterPopover(): string {\n return `\n .wp-filter-pop { left: 12px; top: 56px; width: min(380px, calc(100% - 24px)); max-height: calc(100% - 80px); overflow: auto; }\n .wp-fgroup { border: 0; margin: 0 0 12px; padding: 0; display: grid; gap: 4px; }\n .wp-fgroup legend { padding: 0; margin-bottom: 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;\n color: var(--wp-muted); font-weight: 600; }\n .wp-fgroup legend small { text-transform: none; letter-spacing: 0; font-weight: 400; }\n .wp-fgroup:disabled .wp-scope { opacity: 0.45; cursor: not-allowed; }\n .wp-scope { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 6px; cursor: pointer; }\n .wp-scope:hover { background: #f4f1fb; }\n .wp-scope input { accent-color: var(--wp-accent); margin: 0; }\n .wp-scope-text { flex: 1 1 auto; display: grid; }\n .wp-scope-sub { font-size: 11.5px; color: var(--wp-muted); line-height: 1.3; }\n .wp-chip-swatch { vertical-align: -1px; margin-right: 5px; }\n .wp-count { color: var(--wp-muted); font-size: 12px; font-variant-numeric: tabular-nums; }\n .wp-scope-reason { margin: 2px 6px 0; font-size: 12px; color: var(--wp-muted); }\n .wp-chips { display: flex; flex-wrap: wrap; gap: 6px; }\n .wp-chip { all: unset; box-sizing: border-box; cursor: pointer; padding: 3px 11px; border-radius: 999px;\n border: 1px solid var(--wp-line); background: var(--wp-surface); font-size: 12.5px; }\n .wp-chip:hover { border-color: var(--wp-accent-hover); }\n .wp-chip[aria-pressed=\"true\"] { background: var(--wp-accent); border-color: var(--wp-accent); color: #fff; }\n .wp-chip:disabled { opacity: 0.45; cursor: default; }\n .wp-impact-kinds { margin: 6px 0 4px; }\n .wp-side .wp-chip:not([aria-pressed=\"true\"]) { background: var(--wp-side-control); border-color: var(--wp-side-line); }\n .wp-filter-pop footer { display: flex; justify-content: space-between; align-items: center; gap: 8px;\n padding-top: 10px; border-top: 1px solid var(--wp-line); }\n .wp-text-btn { all: unset; cursor: pointer; color: var(--wp-muted); font-size: 12.5px; text-decoration: underline; }\n .wp-primary { all: unset; box-sizing: border-box; cursor: pointer; padding: 7px 14px; border-radius: 8px;\n background: var(--wp-accent); color: #fff; font-weight: 600; font-size: 13px; }\n .wp-primary:hover { background: var(--wp-accent-hover); }`;\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.\n *\n * The hover glow and the lock outline belong to the OUTER box only. A nested Runtime box (one\n * runtime plus a specialization) is an HTML-like table, so after the node's own outline Graphviz\n * emits a `<polygon>` for the base-colored cell and a `<path>` for the inner `STYLE=\"rounded\"`\n * table — matching only `polygon` here is what let the glow stroke the inner box (#1158). So the\n * glow is suppressed on EVERY shape after the node's first, whatever its element type.\n *\n * A striped (multi-runtime) box is the one exception to \"the first shape is the outline\":\n * Graphviz draws one filled polygon per stripe first and the outline LAST, as an unfilled polygon.\n * graph-color-modes.ts stamps those boxes `wp-striped`, and for them the outline is that last,\n * `fill=\"none\"` polygon; the stripes keep their thin dividers.\n */\n private graph(): string {\n const shape = ':is(polygon, ellipse, path)';\n const later = `${shape} ~ ${shape}`;\n const first = `${shape}:not(${later})`;\n const glow = 'stroke: var(--wp-accent); stroke-width: 5; filter: drop-shadow(0 0 6px rgba(139, 60, 240, 0.85));';\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 > ${later} { stroke: none; stroke-width: 0; filter: none; }\n #graph g.wp-node-clickable.${STRIPED_CLASS}:hover > polygon:not([fill=\"none\"]) { stroke: #000000; stroke-width: 0.5; filter: none; }\n #graph g.wp-node-clickable.${STRIPED_CLASS}:hover > polygon[fill=\"none\"] { ${glow} }\n #graph g.node.wp-locked:not(.${STRIPED_CLASS}) > ${first},\n #graph g.node.wp-locked.${STRIPED_CLASS} > polygon[fill=\"none\"] { stroke: var(--wp-accent); 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
|
+
{"version":3,"file":"graph-page-styles.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-styles.ts"],"names":[],"mappings":";;;AAAA,2DAAoD;AAEpD;;;;GAIG;AACH,MAAa,eAAe;IACxB,GAAG;QACC,OAAO,CACH,IAAI,CAAC,IAAI,EAAE;YACX,IAAI,CAAC,MAAM,EAAE;YACb,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,IAAI,EAAE;YACX,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,aAAa,EAAE;YACpB,IAAI,CAAC,KAAK,EAAE;YACZ,IAAI,CAAC,KAAK,EAAE,CACf,CAAC;IACN,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;;;;;;;;;;uFAuBwE,CAAC;IACpF,CAAC;IAEO,MAAM;QACV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mJAwCoI,CAAC;IAChJ,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;;;;;;;;;;mGAsBoF,CAAC;IAChG,CAAC;IAEO,IAAI;QACR,OAAO;;;;;;;;;;;;;;uDAcwC,CAAC;IACpD,CAAC;IAED,iFAAiF;IACzE,aAAa;QACjB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;kEA4BmD,CAAC;IAC/D,CAAC;IAEO,QAAQ;QACZ,OAAO;;;;;;;;;;;;;uFAawE,CAAC;IACpF,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,KAAK;QACT,MAAM,KAAK,GAAG,6BAA6B,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,KAAK,MAAM,KAAK,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,GAAG,KAAK,QAAQ,KAAK,GAAG,CAAC;QACvC,MAAM,IAAI,GAAG,mGAAmG,CAAC;QACjH,OAAO;;;6CAG8B,KAAK;qCACb,iCAAa;qCACb,iCAAa,mCAAmC,IAAI;uCAClD,iCAAa,OAAO,KAAK;kCAC9B,iCAAa,wEAAwE,CAAC;IACpH,CAAC;IAEO,KAAK;QACT,OAAO;;;;;;;;;;sCAUuB,CAAC;IACnC,CAAC;CACJ;AA7ND,0CA6NC","sourcesContent":["import { STRIPED_CLASS } from './graph-color-modes';\n\n/**\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 (\n this.base() +\n this.drawer() +\n this.controls() +\n this.main() +\n this.overlays() +\n this.filterPopover() +\n this.graph() +\n this.cards()\n );\n }\n\n private base(): string {\n return `\n :root {\n --wp-side-bg: #15161b; --wp-side-control: #1e1f25; --wp-side-fg: #e9eaf0; --wp-side-muted: #8b8fa0;\n --wp-side-hover: #26272f; --wp-side-line: #3a3c46; --wp-accent: #8b3cf0; --wp-accent-hover: #a46bf7;\n --wp-fg: #1b1e27; --wp-muted: #5d6475; --wp-line: #d9dce5; --wp-surface: #ffffff; --wp-canvas: #faf9f5;\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-pulldown { position: relative; }\n .wp-pill-trigger { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 10px;\n width: 100%; padding: 7px 16px; border-radius: 999px; border: 1px solid var(--wp-side-line);\n background: var(--wp-side-control); }\n .wp-pill-trigger:hover, .wp-pill-trigger[aria-expanded=\"true\"] { border-color: var(--wp-accent-hover); }\n .wp-pill-text { flex: 1 1 auto; min-width: 0; }\n .wp-caret { color: var(--wp-side-muted); }\n .wp-menu { position: absolute; z-index: 35; left: 0; right: 0; top: calc(100% + 6px); display: grid; gap: 2px; padding: 6px;\n background: var(--wp-side-control); border: 1px solid var(--wp-side-line); border-radius: 12px; box-shadow: 0 10px 28px #0008; }\n .wp-mode { all: unset; box-sizing: border-box; cursor: pointer; display: block; padding: 6px 10px; border-radius: 8px; }\n .wp-mode:hover { background: var(--wp-side-hover); }\n .wp-mode[aria-checked=\"true\"] { background: #2a2333; box-shadow: inset 3px 0 0 var(--wp-accent); }\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-filter-btn { all: unset; box-sizing: border-box; cursor: pointer; display: flex; justify-content: space-between;\n align-items: center; padding: 7px 12px; border-radius: 8px; border: 1px solid var(--wp-side-line);\n background: var(--wp-side-control); font-size: 13px; font-weight: 600; }\n .wp-filter-btn:hover, .wp-filter-btn[aria-expanded=\"true\"] { border-color: var(--wp-accent-hover); }\n .wp-badge { min-width: 18px; padding: 1px 6px; border-radius: 999px; background: var(--wp-accent); color: #fff;\n font-size: 11.5px; text-align: center; box-sizing: border-box; }\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-product-toggle { cursor: pointer; }\n .wp-product-toggle input { flex: none; margin: 0; accent-color: var(--wp-accent); cursor: pointer; }\n .wp-product-note { margin: 2px 0 0; font-size: 11.5px; color: var(--wp-side-muted); }\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: var(--wp-side-control); 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-pill-trigger:focus-visible, .wp-filter-btn:focus-visible, .wp-chip:focus-visible,\n .wp-primary:focus-visible, .wp-pill button: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-hover); 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 .wp-pills { display: flex; gap: 6px; flex-wrap: wrap; }\n .wp-pills:empty { display: none; }\n .wp-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 10px; border-radius: 999px;\n background: #f1e8fe; color: #4a1d8a; border: 1px solid #d6bcfa; font-size: 12.5px; }\n .wp-pill button { all: unset; cursor: pointer; width: 18px; height: 18px; display: inline-grid; place-items: center;\n border-radius: 50%; color: #4a1d8a; }\n .wp-pill button:hover { background: #e2cffc; }`;\n }\n\n /** The Filter popover: three groups, chips, and the \"Show N projects\" footer. */\n private filterPopover(): string {\n return `\n .wp-filter-pop { left: 12px; top: 56px; width: min(380px, calc(100% - 24px)); max-height: calc(100% - 80px); overflow: auto; }\n .wp-fgroup { border: 0; margin: 0 0 12px; padding: 0; display: grid; gap: 4px; }\n .wp-fgroup legend { padding: 0; margin-bottom: 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;\n color: var(--wp-muted); font-weight: 600; }\n .wp-fgroup legend small { text-transform: none; letter-spacing: 0; font-weight: 400; }\n .wp-fgroup:disabled .wp-scope { opacity: 0.45; cursor: not-allowed; }\n .wp-scope { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 6px; cursor: pointer; }\n .wp-scope:hover { background: #f4f1fb; }\n .wp-scope input { accent-color: var(--wp-accent); margin: 0; }\n .wp-scope-text { flex: 1 1 auto; display: grid; }\n .wp-scope-sub { font-size: 11.5px; color: var(--wp-muted); line-height: 1.3; }\n .wp-chip-swatch { vertical-align: -1px; margin-right: 5px; }\n .wp-count { color: var(--wp-muted); font-size: 12px; font-variant-numeric: tabular-nums; }\n .wp-scope-reason { margin: 2px 6px 0; font-size: 12px; color: var(--wp-muted); }\n .wp-chips { display: flex; flex-wrap: wrap; gap: 6px; }\n .wp-chip { all: unset; box-sizing: border-box; cursor: pointer; padding: 3px 11px; border-radius: 999px;\n border: 1px solid var(--wp-line); background: var(--wp-surface); font-size: 12.5px; }\n .wp-chip:hover { border-color: var(--wp-accent-hover); }\n .wp-chip[aria-pressed=\"true\"] { background: var(--wp-accent); border-color: var(--wp-accent); color: #fff; }\n .wp-chip:disabled { opacity: 0.45; cursor: default; }\n .wp-impact-kinds { margin: 6px 0 4px; }\n .wp-side .wp-chip:not([aria-pressed=\"true\"]) { background: var(--wp-side-control); border-color: var(--wp-side-line); }\n .wp-filter-pop footer { display: flex; justify-content: space-between; align-items: center; gap: 8px;\n padding-top: 10px; border-top: 1px solid var(--wp-line); }\n .wp-text-btn { all: unset; cursor: pointer; color: var(--wp-muted); font-size: 12.5px; text-decoration: underline; }\n .wp-primary { all: unset; box-sizing: border-box; cursor: pointer; padding: 7px 14px; border-radius: 8px;\n background: var(--wp-accent); color: #fff; font-weight: 600; font-size: 13px; }\n .wp-primary:hover { background: var(--wp-accent-hover); }`;\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, .wp-legend-popout .wp-product-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.\n *\n * The hover glow and the lock outline belong to the OUTER box only. A nested Runtime box (one\n * runtime plus a specialization) is an HTML-like table, so after the node's own outline Graphviz\n * emits a `<polygon>` for the base-colored cell and a `<path>` for the inner `STYLE=\"rounded\"`\n * table — matching only `polygon` here is what let the glow stroke the inner box (#1158). So the\n * glow is suppressed on EVERY shape after the node's first, whatever its element type.\n *\n * A striped (multi-runtime) box is the one exception to \"the first shape is the outline\":\n * Graphviz draws one filled polygon per stripe first and the outline LAST, as an unfilled polygon.\n * graph-color-modes.ts stamps those boxes `wp-striped`, and for them the outline is that last,\n * `fill=\"none\"` polygon; the stripes keep their thin dividers.\n */\n private graph(): string {\n const shape = ':is(polygon, ellipse, path)';\n const later = `${shape} ~ ${shape}`;\n const first = `${shape}:not(${later})`;\n const glow = 'stroke: var(--wp-accent); stroke-width: 5; filter: drop-shadow(0 0 6px rgba(139, 60, 240, 0.85));';\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 > ${later} { stroke: none; stroke-width: 0; filter: none; }\n #graph g.wp-node-clickable.${STRIPED_CLASS}:hover > polygon:not([fill=\"none\"]) { stroke: #000000; stroke-width: 0.5; filter: none; }\n #graph g.wp-node-clickable.${STRIPED_CLASS}:hover > polygon[fill=\"none\"] { ${glow} }\n #graph g.node.wp-locked:not(.${STRIPED_CLASS}) > ${first},\n #graph g.node.wp-locked.${STRIPED_CLASS} > polygon[fill=\"none\"] { stroke: var(--wp-accent); 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"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Which of the five looks a Product-mode node gets for one ON set. */
|
|
2
|
+
declare enum WpProductPaintKind {
|
|
3
|
+
/** The node belongs to no product: white, dashed border. */
|
|
4
|
+
NONE = "none",
|
|
5
|
+
/** Every product it carries is OFF: light grey, still visible. */
|
|
6
|
+
OFF = "off",
|
|
7
|
+
/** Exactly one of its products is ON: solid in that product's fixed color. */
|
|
8
|
+
SOLID = "solid",
|
|
9
|
+
/** Two or more of its products are ON: one stripe per ON product. */
|
|
10
|
+
STRIPES = "stripes",
|
|
11
|
+
/** Every product is ON and the node carries every one: the neutral shared fill. */
|
|
12
|
+
SHARED = "shared"
|
|
13
|
+
}
|
|
14
|
+
/** How one node is painted in Product mode. Data-only. */
|
|
15
|
+
declare class WpProductPaint {
|
|
16
|
+
readonly kind: WpProductPaintKind;
|
|
17
|
+
/** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */
|
|
18
|
+
readonly colors: string[];
|
|
19
|
+
/** The text color that reads on the fill. */
|
|
20
|
+
readonly fontColor: string;
|
|
21
|
+
constructor(kind: WpProductPaintKind,
|
|
22
|
+
/** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */
|
|
23
|
+
colors: string[],
|
|
24
|
+
/** The text color that reads on the fill. */
|
|
25
|
+
fontColor: string);
|
|
26
|
+
}
|
|
27
|
+
/** Which products are ON when a page loads (#1319): the same answer on every load, never persisted. */
|
|
28
|
+
declare class WpProductSelection {
|
|
29
|
+
/** The first two products in legend order. */
|
|
30
|
+
initialOn(order: string[]): Set<string>;
|
|
31
|
+
}
|
|
32
|
+
/** Decides and writes every Product-mode statement for one ON set. Immutable: a toggle builds a new one. */
|
|
33
|
+
declare class WpProductPainter {
|
|
34
|
+
private readonly legend;
|
|
35
|
+
private readonly on;
|
|
36
|
+
/** graph-products.ts PRODUCT_SHARED_FILL. */
|
|
37
|
+
static readonly SHARED_FILL = "#c9ced8";
|
|
38
|
+
/** graph-products.ts PRODUCT_NONE_FILL / PRODUCT_NONE_BORDER. */
|
|
39
|
+
static readonly NONE_FILL = "#ffffff";
|
|
40
|
+
static readonly NONE_BORDER = "#8a90a0";
|
|
41
|
+
/** graph-products.ts PRODUCT_OFF_FILL / PRODUCT_OFF_BORDER. */
|
|
42
|
+
static readonly OFF_FILL = "#eef0f4";
|
|
43
|
+
static readonly OFF_BORDER = "#b4b9c4";
|
|
44
|
+
/** graph-color-modes.ts DARK_TEXT / LIGHT_TEXT / STRIPED_CLASS. */
|
|
45
|
+
static readonly DARK_TEXT = "#1a1c22";
|
|
46
|
+
static readonly LIGHT_TEXT = "#ffffff";
|
|
47
|
+
static readonly STRIPED_CLASS = "wp-striped";
|
|
48
|
+
/** graph-products.ts PRODUCTS_ON_AT_LOAD. */
|
|
49
|
+
static readonly ON_AT_LOAD = 2;
|
|
50
|
+
private readonly colors;
|
|
51
|
+
/**
|
|
52
|
+
* @param legend the products the legend lists, in its order, each with its FIXED palette color —
|
|
53
|
+
* a color is never reassigned to the ON set, so it always means the same product
|
|
54
|
+
* @param on the products switched ON
|
|
55
|
+
*/
|
|
56
|
+
constructor(legend: ProductColorJson[], on: ReadonlySet<string>);
|
|
57
|
+
/** The paint for a node carrying `products`. */
|
|
58
|
+
paint(products: string[]): WpProductPaint;
|
|
59
|
+
/**
|
|
60
|
+
* The color a cross-product dependency is dashed in (#1204): the first product it crosses into
|
|
61
|
+
* that is ON; null draws it plain.
|
|
62
|
+
*/
|
|
63
|
+
edgeColor(crossesInto: string[]): string | null;
|
|
64
|
+
/** A node's Product-mode statement. */
|
|
65
|
+
nodeDot(template: ProductTemplateJson, products: string[]): string;
|
|
66
|
+
/** An edge's Product-mode statement: dashed in its ON target product's color, else plain. */
|
|
67
|
+
edgeDot(edge: RenderEdgeJson): string;
|
|
68
|
+
private isOn;
|
|
69
|
+
/** Every product is ON — of a legend listing two or more (a lone product is its own color). */
|
|
70
|
+
private allOn;
|
|
71
|
+
private attrs;
|
|
72
|
+
/** Equal vertical stripes, the last taking the remainder — the multi-runtime box's renderer. */
|
|
73
|
+
private stripes;
|
|
74
|
+
/**
|
|
75
|
+
* A queue, clock, datastore or vendor keeps its kind statement; only its fill changes. Graphviz
|
|
76
|
+
* cylinders cannot stripe, so several ON products are a gradient; no product is dashed.
|
|
77
|
+
*/
|
|
78
|
+
private context;
|
|
79
|
+
private contextFill;
|
|
80
|
+
}
|
|
81
|
+
/** The Product legend's checkboxes (#1319): one per product, in the drawer AND the popped-out legend. */
|
|
82
|
+
declare class WpProductToggles {
|
|
83
|
+
/** The products the legend lists, in its order, once each. */
|
|
84
|
+
products(): string[];
|
|
85
|
+
/** Each checkbox switches its product ON or OFF. */
|
|
86
|
+
wire(onChange: (product: string, on: boolean) => void): void;
|
|
87
|
+
/** Every copy of every checkbox shows the ON set. */
|
|
88
|
+
sync(on: ReadonlySet<string>): void;
|
|
89
|
+
/** A painter over the legend's products with their FIXED palette colors, for a snapshot of `on`. */
|
|
90
|
+
painter(palette: ProductColorJson[], on: ReadonlySet<string>): WpProductPainter;
|
|
91
|
+
private inputs;
|
|
92
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The Product color mode's paint, decided IN THE BROWSER from the products switched ON (#1319).
|
|
4
|
+
*
|
|
5
|
+
* Each product's legend row is a toggle. Painting every declared product at once gave a workspace with
|
|
6
|
+
* eight products eight saturated colors plus stripes, which hid exactly what the mode is for: one
|
|
7
|
+
* product's footprint, or where two overlap. So the page keeps an ON set — the first two products in
|
|
8
|
+
* legend (sorted-name) order at EVERY load, never persisted — and repaints from it without
|
|
9
|
+
* regenerating anything: the page already carries each node's products and the ONE palette (#1244).
|
|
10
|
+
*
|
|
11
|
+
* Prepended to graph-visualizer.client.js like graph-mode-state.client.ts. The painter is pure (no
|
|
12
|
+
* DOM), so a spec runs it in a bare VM; WpProductToggles is the only class here that touches the page. The colors it writes are graph-products.ts's (a spec pins that they agree);
|
|
13
|
+
* they are spelled here because this script is not a module and cannot import them.
|
|
14
|
+
*/
|
|
15
|
+
/** Which of the five looks a Product-mode node gets for one ON set. */
|
|
16
|
+
var WpProductPaintKind;
|
|
17
|
+
(function (WpProductPaintKind) {
|
|
18
|
+
/** The node belongs to no product: white, dashed border. */
|
|
19
|
+
WpProductPaintKind["NONE"] = "none";
|
|
20
|
+
/** Every product it carries is OFF: light grey, still visible. */
|
|
21
|
+
WpProductPaintKind["OFF"] = "off";
|
|
22
|
+
/** Exactly one of its products is ON: solid in that product's fixed color. */
|
|
23
|
+
WpProductPaintKind["SOLID"] = "solid";
|
|
24
|
+
/** Two or more of its products are ON: one stripe per ON product. */
|
|
25
|
+
WpProductPaintKind["STRIPES"] = "stripes";
|
|
26
|
+
/** Every product is ON and the node carries every one: the neutral shared fill. */
|
|
27
|
+
WpProductPaintKind["SHARED"] = "shared";
|
|
28
|
+
})(WpProductPaintKind || (WpProductPaintKind = {}));
|
|
29
|
+
/** How one node is painted in Product mode. Data-only. */
|
|
30
|
+
class WpProductPaint {
|
|
31
|
+
kind;
|
|
32
|
+
colors;
|
|
33
|
+
fontColor;
|
|
34
|
+
constructor(kind,
|
|
35
|
+
/** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */
|
|
36
|
+
colors,
|
|
37
|
+
/** The text color that reads on the fill. */
|
|
38
|
+
fontColor) {
|
|
39
|
+
this.kind = kind;
|
|
40
|
+
this.colors = colors;
|
|
41
|
+
this.fontColor = fontColor;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Which products are ON when a page loads (#1319): the same answer on every load, never persisted. */
|
|
45
|
+
class WpProductSelection {
|
|
46
|
+
/** The first two products in legend order. */
|
|
47
|
+
initialOn(order) {
|
|
48
|
+
return new Set(order.slice(0, WpProductPainter.ON_AT_LOAD));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Decides and writes every Product-mode statement for one ON set. Immutable: a toggle builds a new one. */
|
|
52
|
+
class WpProductPainter {
|
|
53
|
+
legend;
|
|
54
|
+
on;
|
|
55
|
+
/** graph-products.ts PRODUCT_SHARED_FILL. */
|
|
56
|
+
static SHARED_FILL = '#c9ced8';
|
|
57
|
+
/** graph-products.ts PRODUCT_NONE_FILL / PRODUCT_NONE_BORDER. */
|
|
58
|
+
static NONE_FILL = '#ffffff';
|
|
59
|
+
static NONE_BORDER = '#8a90a0';
|
|
60
|
+
/** graph-products.ts PRODUCT_OFF_FILL / PRODUCT_OFF_BORDER. */
|
|
61
|
+
static OFF_FILL = '#eef0f4';
|
|
62
|
+
static OFF_BORDER = '#b4b9c4';
|
|
63
|
+
/** graph-color-modes.ts DARK_TEXT / LIGHT_TEXT / STRIPED_CLASS. */
|
|
64
|
+
static DARK_TEXT = '#1a1c22';
|
|
65
|
+
static LIGHT_TEXT = '#ffffff';
|
|
66
|
+
static STRIPED_CLASS = 'wp-striped';
|
|
67
|
+
/** graph-products.ts PRODUCTS_ON_AT_LOAD. */
|
|
68
|
+
static ON_AT_LOAD = 2;
|
|
69
|
+
colors = new Map();
|
|
70
|
+
/**
|
|
71
|
+
* @param legend the products the legend lists, in its order, each with its FIXED palette color —
|
|
72
|
+
* a color is never reassigned to the ON set, so it always means the same product
|
|
73
|
+
* @param on the products switched ON
|
|
74
|
+
*/
|
|
75
|
+
constructor(legend, on) {
|
|
76
|
+
this.legend = legend;
|
|
77
|
+
this.on = on;
|
|
78
|
+
for (const color of legend)
|
|
79
|
+
this.colors.set(color.product, color);
|
|
80
|
+
}
|
|
81
|
+
/** The paint for a node carrying `products`. */
|
|
82
|
+
paint(products) {
|
|
83
|
+
if (products.length === 0)
|
|
84
|
+
return new WpProductPaint(WpProductPaintKind.NONE, [], WpProductPainter.DARK_TEXT);
|
|
85
|
+
const on = products.filter((product) => this.isOn(product));
|
|
86
|
+
if (on.length === 0)
|
|
87
|
+
return new WpProductPaint(WpProductPaintKind.OFF, [], WpProductPainter.DARK_TEXT);
|
|
88
|
+
if (this.allOn() && this.legend.every((color) => products.includes(color.product)))
|
|
89
|
+
return new WpProductPaint(WpProductPaintKind.SHARED, [], WpProductPainter.DARK_TEXT);
|
|
90
|
+
const colors = on.map((product) => this.colors.get(product));
|
|
91
|
+
if (colors.length === 1)
|
|
92
|
+
return new WpProductPaint(WpProductPaintKind.SOLID, [colors[0].color], colors[0].fontColor);
|
|
93
|
+
return new WpProductPaint(WpProductPaintKind.STRIPES, colors.map((color) => color.color), WpProductPainter.DARK_TEXT);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The color a cross-product dependency is dashed in (#1204): the first product it crosses into
|
|
97
|
+
* that is ON; null draws it plain.
|
|
98
|
+
*/
|
|
99
|
+
edgeColor(crossesInto) {
|
|
100
|
+
const into = crossesInto.find((product) => this.isOn(product));
|
|
101
|
+
return into === undefined ? null : this.colors.get(into).color;
|
|
102
|
+
}
|
|
103
|
+
/** A node's Product-mode statement. */
|
|
104
|
+
nodeDot(template, products) {
|
|
105
|
+
const paint = this.paint(products);
|
|
106
|
+
if (template.kind === 'context')
|
|
107
|
+
return this.context(template.head, paint);
|
|
108
|
+
const label = paint.fontColor === WpProductPainter.LIGHT_TEXT ? template.lightLabel : template.darkLabel;
|
|
109
|
+
return `${template.head}${this.attrs(paint)}, label=<${label}>];\n`;
|
|
110
|
+
}
|
|
111
|
+
/** An edge's Product-mode statement: dashed in its ON target product's color, else plain. */
|
|
112
|
+
edgeDot(edge) {
|
|
113
|
+
const color = this.edgeColor(edge.crossesInto);
|
|
114
|
+
if (color === null)
|
|
115
|
+
return edge.dot;
|
|
116
|
+
return `${edge.dot.slice(0, -2)} [style=dashed, color="${color}", penwidth=2];\n`;
|
|
117
|
+
}
|
|
118
|
+
isOn(product) {
|
|
119
|
+
return this.on.has(product) && this.colors.has(product);
|
|
120
|
+
}
|
|
121
|
+
/** Every product is ON — of a legend listing two or more (a lone product is its own color). */
|
|
122
|
+
allOn() {
|
|
123
|
+
return this.legend.length > 1 && this.legend.every((color) => this.on.has(color.product));
|
|
124
|
+
}
|
|
125
|
+
attrs(paint) {
|
|
126
|
+
const dark = WpProductPainter.DARK_TEXT;
|
|
127
|
+
if (paint.kind === WpProductPaintKind.NONE)
|
|
128
|
+
return `style="filled,dashed", fillcolor="${WpProductPainter.NONE_FILL}", fontcolor="${dark}", color="${WpProductPainter.NONE_BORDER}", penwidth=1.5`;
|
|
129
|
+
if (paint.kind === WpProductPaintKind.OFF)
|
|
130
|
+
return `style="filled", fillcolor="${WpProductPainter.OFF_FILL}", fontcolor="${dark}", color="${WpProductPainter.OFF_BORDER}"`;
|
|
131
|
+
if (paint.kind === WpProductPaintKind.SHARED)
|
|
132
|
+
return `style="filled", fillcolor="${WpProductPainter.SHARED_FILL}", fontcolor="${dark}"`;
|
|
133
|
+
if (paint.kind === WpProductPaintKind.SOLID)
|
|
134
|
+
return `style="filled", fillcolor="${paint.colors[0]}", fontcolor="${paint.fontColor}"`;
|
|
135
|
+
return `${this.stripes(paint.colors)}, fontcolor="${dark}"`;
|
|
136
|
+
}
|
|
137
|
+
/** Equal vertical stripes, the last taking the remainder — the multi-runtime box's renderer. */
|
|
138
|
+
stripes(colors) {
|
|
139
|
+
const share = (1 / colors.length).toFixed(3);
|
|
140
|
+
const weighted = colors.map((color, index) => index === colors.length - 1 ? color : `${color};${share}`);
|
|
141
|
+
return `style="striped", fillcolor="${weighted.join(':')}", class="${WpProductPainter.STRIPED_CLASS}"`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A queue, clock, datastore or vendor keeps its kind statement; only its fill changes. Graphviz
|
|
145
|
+
* cylinders cannot stripe, so several ON products are a gradient; no product is dashed.
|
|
146
|
+
*/
|
|
147
|
+
context(dot, paint) {
|
|
148
|
+
const recolored = dot.replace(/fillcolor="[^"]*"/, `fillcolor="${this.contextFill(paint)}"`);
|
|
149
|
+
return paint.kind === WpProductPaintKind.NONE ? recolored.replace(/style="[^"]*"/, 'style="filled,dashed"') : recolored;
|
|
150
|
+
}
|
|
151
|
+
contextFill(paint) {
|
|
152
|
+
if (paint.kind === WpProductPaintKind.NONE)
|
|
153
|
+
return WpProductPainter.NONE_FILL;
|
|
154
|
+
if (paint.kind === WpProductPaintKind.OFF)
|
|
155
|
+
return WpProductPainter.OFF_FILL;
|
|
156
|
+
if (paint.kind === WpProductPaintKind.SHARED)
|
|
157
|
+
return WpProductPainter.SHARED_FILL;
|
|
158
|
+
return paint.colors.join(':');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** The Product legend's checkboxes (#1319): one per product, in the drawer AND the popped-out legend. */
|
|
162
|
+
class WpProductToggles {
|
|
163
|
+
/** The products the legend lists, in its order, once each. */
|
|
164
|
+
products() {
|
|
165
|
+
const order = [];
|
|
166
|
+
this.inputs().forEach((input) => {
|
|
167
|
+
const product = input.dataset['wpProductToggle'] ?? '';
|
|
168
|
+
if (product !== '' && !order.includes(product))
|
|
169
|
+
order.push(product);
|
|
170
|
+
});
|
|
171
|
+
return order;
|
|
172
|
+
}
|
|
173
|
+
/** Each checkbox switches its product ON or OFF. */
|
|
174
|
+
wire(onChange) {
|
|
175
|
+
this.inputs().forEach((input) => {
|
|
176
|
+
input.addEventListener('change', () => onChange(input.dataset['wpProductToggle'] ?? '', input.checked));
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Every copy of every checkbox shows the ON set. */
|
|
180
|
+
sync(on) {
|
|
181
|
+
this.inputs().forEach((input) => {
|
|
182
|
+
input.checked = on.has(input.dataset['wpProductToggle'] ?? '');
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/** A painter over the legend's products with their FIXED palette colors, for a snapshot of `on`. */
|
|
186
|
+
painter(palette, on) {
|
|
187
|
+
const legend = this.products()
|
|
188
|
+
.map((product) => palette.find((color) => color.product === product))
|
|
189
|
+
.filter((color) => color !== undefined);
|
|
190
|
+
return new WpProductPainter(legend, new Set(on));
|
|
191
|
+
}
|
|
192
|
+
inputs() {
|
|
193
|
+
return document.querySelectorAll('[data-wp-product-toggle]');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=graph-product-paint.client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-product-paint.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-product-paint.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The Product color mode's paint, decided IN THE BROWSER from the products switched ON (#1319).\n *\n * Each product's legend row is a toggle. Painting every declared product at once gave a workspace with\n * eight products eight saturated colors plus stripes, which hid exactly what the mode is for: one\n * product's footprint, or where two overlap. So the page keeps an ON set — the first two products in\n * legend (sorted-name) order at EVERY load, never persisted — and repaints from it without\n * regenerating anything: the page already carries each node's products and the ONE palette (#1244).\n *\n * Prepended to graph-visualizer.client.js like graph-mode-state.client.ts. The painter is pure (no\n * DOM), so a spec runs it in a bare VM; WpProductToggles is the only class here that touches the page. The colors it writes are graph-products.ts's (a spec pins that they agree);\n * they are spelled here because this script is not a module and cannot import them.\n */\n\n/** Which of the five looks a Product-mode node gets for one ON set. */\nenum WpProductPaintKind {\n /** The node belongs to no product: white, dashed border. */\n NONE = 'none',\n /** Every product it carries is OFF: light grey, still visible. */\n OFF = 'off',\n /** Exactly one of its products is ON: solid in that product's fixed color. */\n SOLID = 'solid',\n /** Two or more of its products are ON: one stripe per ON product. */\n STRIPES = 'stripes',\n /** Every product is ON and the node carries every one: the neutral shared fill. */\n SHARED = 'shared',\n}\n\n/** How one node is painted in Product mode. Data-only. */\nclass WpProductPaint {\n constructor(\n readonly kind: WpProductPaintKind,\n /** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */\n readonly colors: string[],\n /** The text color that reads on the fill. */\n readonly fontColor: string,\n ) {}\n}\n\n/** Which products are ON when a page loads (#1319): the same answer on every load, never persisted. */\nclass WpProductSelection {\n /** The first two products in legend order. */\n initialOn(order: string[]): Set<string> {\n return new Set(order.slice(0, WpProductPainter.ON_AT_LOAD));\n }\n}\n\n/** Decides and writes every Product-mode statement for one ON set. Immutable: a toggle builds a new one. */\nclass WpProductPainter {\n /** graph-products.ts PRODUCT_SHARED_FILL. */\n static readonly SHARED_FILL = '#c9ced8';\n /** graph-products.ts PRODUCT_NONE_FILL / PRODUCT_NONE_BORDER. */\n static readonly NONE_FILL = '#ffffff';\n static readonly NONE_BORDER = '#8a90a0';\n /** graph-products.ts PRODUCT_OFF_FILL / PRODUCT_OFF_BORDER. */\n static readonly OFF_FILL = '#eef0f4';\n static readonly OFF_BORDER = '#b4b9c4';\n /** graph-color-modes.ts DARK_TEXT / LIGHT_TEXT / STRIPED_CLASS. */\n static readonly DARK_TEXT = '#1a1c22';\n static readonly LIGHT_TEXT = '#ffffff';\n static readonly STRIPED_CLASS = 'wp-striped';\n /** graph-products.ts PRODUCTS_ON_AT_LOAD. */\n static readonly ON_AT_LOAD = 2;\n\n private readonly colors = new Map<string, ProductColorJson>();\n\n /**\n * @param legend the products the legend lists, in its order, each with its FIXED palette color —\n * a color is never reassigned to the ON set, so it always means the same product\n * @param on the products switched ON\n */\n constructor(\n private readonly legend: ProductColorJson[],\n private readonly on: ReadonlySet<string>,\n ) {\n for (const color of legend) this.colors.set(color.product, color);\n }\n\n /** The paint for a node carrying `products`. */\n paint(products: string[]): WpProductPaint {\n if (products.length === 0) return new WpProductPaint(WpProductPaintKind.NONE, [], WpProductPainter.DARK_TEXT);\n const on = products.filter((product: string): boolean => this.isOn(product));\n if (on.length === 0) return new WpProductPaint(WpProductPaintKind.OFF, [], WpProductPainter.DARK_TEXT);\n if (this.allOn() && this.legend.every((color: ProductColorJson): boolean => products.includes(color.product)))\n return new WpProductPaint(WpProductPaintKind.SHARED, [], WpProductPainter.DARK_TEXT);\n const colors = on.map((product: string): ProductColorJson => this.colors.get(product) as ProductColorJson);\n if (colors.length === 1) return new WpProductPaint(WpProductPaintKind.SOLID, [colors[0].color], colors[0].fontColor);\n return new WpProductPaint(\n WpProductPaintKind.STRIPES,\n colors.map((color: ProductColorJson): string => color.color),\n WpProductPainter.DARK_TEXT,\n );\n }\n\n /**\n * The color a cross-product dependency is dashed in (#1204): the first product it crosses into\n * that is ON; null draws it plain.\n */\n edgeColor(crossesInto: string[]): string | null {\n const into = crossesInto.find((product: string): boolean => this.isOn(product));\n return into === undefined ? null : (this.colors.get(into) as ProductColorJson).color;\n }\n\n /** A node's Product-mode statement. */\n nodeDot(template: ProductTemplateJson, products: string[]): string {\n const paint = this.paint(products);\n if (template.kind === 'context') return this.context(template.head, paint);\n const label = paint.fontColor === WpProductPainter.LIGHT_TEXT ? template.lightLabel : template.darkLabel;\n return `${template.head}${this.attrs(paint)}, label=<${label}>];\\n`;\n }\n\n /** An edge's Product-mode statement: dashed in its ON target product's color, else plain. */\n edgeDot(edge: RenderEdgeJson): string {\n const color = this.edgeColor(edge.crossesInto);\n if (color === null) return edge.dot;\n return `${edge.dot.slice(0, -2)} [style=dashed, color=\"${color}\", penwidth=2];\\n`;\n }\n\n private isOn(product: string): boolean {\n return this.on.has(product) && this.colors.has(product);\n }\n\n /** Every product is ON — of a legend listing two or more (a lone product is its own color). */\n private allOn(): boolean {\n return this.legend.length > 1 && this.legend.every((color: ProductColorJson): boolean => this.on.has(color.product));\n }\n\n private attrs(paint: WpProductPaint): string {\n const dark = WpProductPainter.DARK_TEXT;\n if (paint.kind === WpProductPaintKind.NONE)\n return `style=\"filled,dashed\", fillcolor=\"${WpProductPainter.NONE_FILL}\", fontcolor=\"${dark}\", color=\"${WpProductPainter.NONE_BORDER}\", penwidth=1.5`;\n if (paint.kind === WpProductPaintKind.OFF)\n return `style=\"filled\", fillcolor=\"${WpProductPainter.OFF_FILL}\", fontcolor=\"${dark}\", color=\"${WpProductPainter.OFF_BORDER}\"`;\n if (paint.kind === WpProductPaintKind.SHARED) return `style=\"filled\", fillcolor=\"${WpProductPainter.SHARED_FILL}\", fontcolor=\"${dark}\"`;\n if (paint.kind === WpProductPaintKind.SOLID)\n return `style=\"filled\", fillcolor=\"${paint.colors[0]}\", fontcolor=\"${paint.fontColor}\"`;\n return `${this.stripes(paint.colors)}, fontcolor=\"${dark}\"`;\n }\n\n /** Equal vertical stripes, the last taking the remainder — the multi-runtime box's renderer. */\n private stripes(colors: string[]): string {\n const share = (1 / colors.length).toFixed(3);\n const weighted = colors.map((color: string, index: number): string =>\n index === colors.length - 1 ? color : `${color};${share}`,\n );\n return `style=\"striped\", fillcolor=\"${weighted.join(':')}\", class=\"${WpProductPainter.STRIPED_CLASS}\"`;\n }\n\n /**\n * A queue, clock, datastore or vendor keeps its kind statement; only its fill changes. Graphviz\n * cylinders cannot stripe, so several ON products are a gradient; no product is dashed.\n */\n private context(dot: string, paint: WpProductPaint): string {\n const recolored = dot.replace(/fillcolor=\"[^\"]*\"/, `fillcolor=\"${this.contextFill(paint)}\"`);\n return paint.kind === WpProductPaintKind.NONE ? recolored.replace(/style=\"[^\"]*\"/, 'style=\"filled,dashed\"') : recolored;\n }\n\n private contextFill(paint: WpProductPaint): string {\n if (paint.kind === WpProductPaintKind.NONE) return WpProductPainter.NONE_FILL;\n if (paint.kind === WpProductPaintKind.OFF) return WpProductPainter.OFF_FILL;\n if (paint.kind === WpProductPaintKind.SHARED) return WpProductPainter.SHARED_FILL;\n return paint.colors.join(':');\n }\n}\n\n/** The Product legend's checkboxes (#1319): one per product, in the drawer AND the popped-out legend. */\nclass WpProductToggles {\n /** The products the legend lists, in its order, once each. */\n products(): string[] {\n const order: string[] = [];\n this.inputs().forEach((input: HTMLInputElement) => {\n const product = input.dataset['wpProductToggle'] ?? '';\n if (product !== '' && !order.includes(product)) order.push(product);\n });\n return order;\n }\n\n /** Each checkbox switches its product ON or OFF. */\n wire(onChange: (product: string, on: boolean) => void): void {\n this.inputs().forEach((input: HTMLInputElement) => {\n input.addEventListener('change', () => onChange(input.dataset['wpProductToggle'] ?? '', input.checked));\n });\n }\n\n /** Every copy of every checkbox shows the ON set. */\n sync(on: ReadonlySet<string>): void {\n this.inputs().forEach((input: HTMLInputElement) => {\n input.checked = on.has(input.dataset['wpProductToggle'] ?? '');\n });\n }\n\n /** A painter over the legend's products with their FIXED palette colors, for a snapshot of `on`. */\n painter(palette: ProductColorJson[], on: ReadonlySet<string>): WpProductPainter {\n const legend = this.products()\n .map((product: string): ProductColorJson | undefined => palette.find((color: ProductColorJson): boolean => color.product === product))\n .filter((color: ProductColorJson | undefined): color is ProductColorJson => color !== undefined);\n return new WpProductPainter(legend, new Set(on));\n }\n\n private inputs(): NodeListOf<HTMLInputElement> {\n return document.querySelectorAll<HTMLInputElement>('[data-wp-product-toggle]');\n }\n}\n"]}
|
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
* between them. A service whose project carries no tag is in no product.
|
|
18
18
|
* - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each
|
|
19
19
|
* product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name
|
|
20
|
-
* order. Both viewers build it over the FULL declared product set, so they agree
|
|
21
|
-
*
|
|
20
|
+
* order. Both viewers build it over the FULL declared product set, so they agree, and a color is
|
|
21
|
+
* never reassigned when a product is toggled OFF.
|
|
22
|
+
* - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each
|
|
23
|
+
* product's legend row is a toggle and the page repaints in the browser from the ON set
|
|
24
|
+
* (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed
|
|
25
|
+
* there, not here.
|
|
22
26
|
* - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
|
|
23
|
-
* dashed in the target product's color in the Product color mode.
|
|
27
|
+
* dashed in the target product's color in the Product color mode while that product is ON.
|
|
24
28
|
*
|
|
25
29
|
* Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
|
|
26
30
|
* the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
|
|
@@ -42,11 +46,50 @@ export declare class ProductPaletteSlot {
|
|
|
42
46
|
}
|
|
43
47
|
/** The eight product colors, indexed by slot. Distinct from every runtime and role color. */
|
|
44
48
|
export declare const PRODUCT_PALETTE_SLOTS: readonly ProductPaletteSlot[];
|
|
45
|
-
/** A box shared by EVERY product. */
|
|
49
|
+
/** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */
|
|
46
50
|
export declare const PRODUCT_SHARED_FILL = "#c9ced8";
|
|
47
51
|
/** A box in no product: white, with a dashed border. */
|
|
48
52
|
export declare const PRODUCT_NONE_FILL = "#ffffff";
|
|
49
53
|
export declare const PRODUCT_NONE_BORDER = "#8a90a0";
|
|
54
|
+
/**
|
|
55
|
+
* A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays
|
|
56
|
+
* visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.
|
|
57
|
+
*/
|
|
58
|
+
export declare const PRODUCT_OFF_FILL = "#eef0f4";
|
|
59
|
+
export declare const PRODUCT_OFF_BORDER = "#b4b9c4";
|
|
60
|
+
/**
|
|
61
|
+
* How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.
|
|
62
|
+
* Always this many, on every load — the selection is never persisted.
|
|
63
|
+
*/
|
|
64
|
+
export declare const PRODUCTS_ON_AT_LOAD = 2;
|
|
65
|
+
/** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */
|
|
66
|
+
export declare enum ProductTemplateKind {
|
|
67
|
+
/** A project or service box: the page writes its whole statement. */
|
|
68
|
+
BOX = "box",
|
|
69
|
+
/** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */
|
|
70
|
+
CONTEXT = "context"
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.
|
|
74
|
+
* A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT
|
|
75
|
+
* statement is `head` (the kind statement) with its fill replaced.
|
|
76
|
+
*/
|
|
77
|
+
export declare class ProductTemplate {
|
|
78
|
+
readonly kind: ProductTemplateKind;
|
|
79
|
+
/** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
|
|
80
|
+
readonly head: string;
|
|
81
|
+
/** BOX: the HTML label written in dark text; '' for CONTEXT. */
|
|
82
|
+
readonly darkLabel: string;
|
|
83
|
+
/** BOX: the HTML label written in light text; '' for CONTEXT. */
|
|
84
|
+
readonly lightLabel: string;
|
|
85
|
+
constructor(kind: ProductTemplateKind,
|
|
86
|
+
/** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
|
|
87
|
+
head: string,
|
|
88
|
+
/** BOX: the HTML label written in dark text; '' for CONTEXT. */
|
|
89
|
+
darkLabel: string,
|
|
90
|
+
/** BOX: the HTML label written in light text; '' for CONTEXT. */
|
|
91
|
+
lightLabel: string);
|
|
92
|
+
}
|
|
50
93
|
export declare class ProductPalette {
|
|
51
94
|
/** Every declared product, sorted, each with its color. */
|
|
52
95
|
readonly colors: ProductColor[];
|
|
@@ -72,13 +115,18 @@ export declare class ProductPalette {
|
|
|
72
115
|
* Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge
|
|
73
116
|
* A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the
|
|
74
117
|
* product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.
|
|
118
|
+
* Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.
|
|
75
119
|
* Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):
|
|
76
120
|
* with `product-tags` ON, generation already refused every crossing the matrix does not allow, so
|
|
77
121
|
* every crossing drawn is an allowed one.
|
|
78
122
|
*/
|
|
79
123
|
export declare class ProductBoundary {
|
|
80
|
-
/**
|
|
81
|
-
|
|
124
|
+
/**
|
|
125
|
+
* The products the edge crosses into, in the target's order: the target's products its source
|
|
126
|
+
* lacks, else (a target narrower than its source) every product of the target. Empty when the
|
|
127
|
+
* target carries every product of the source. The page draws the edge in the first one that is ON.
|
|
128
|
+
*/
|
|
129
|
+
crossedInto(from: readonly string[], to: readonly string[]): string[];
|
|
82
130
|
}
|
|
83
131
|
/**
|
|
84
132
|
* The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).
|
|
@@ -18,16 +18,20 @@
|
|
|
18
18
|
* between them. A service whose project carries no tag is in no product.
|
|
19
19
|
* - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each
|
|
20
20
|
* product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name
|
|
21
|
-
* order. Both viewers build it over the FULL declared product set, so they agree
|
|
22
|
-
*
|
|
21
|
+
* order. Both viewers build it over the FULL declared product set, so they agree, and a color is
|
|
22
|
+
* never reassigned when a product is toggled OFF.
|
|
23
|
+
* - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each
|
|
24
|
+
* product's legend row is a toggle and the page repaints in the browser from the ON set
|
|
25
|
+
* (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed
|
|
26
|
+
* there, not here.
|
|
23
27
|
* - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
|
|
24
|
-
* dashed in the target product's color in the Product color mode.
|
|
28
|
+
* dashed in the target product's color in the Product color mode while that product is ON.
|
|
25
29
|
*
|
|
26
30
|
* Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
|
|
27
31
|
* the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
|
|
28
32
|
*/
|
|
29
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductBoundary = exports.ProductPalette = exports.PRODUCT_NONE_BORDER = exports.PRODUCT_NONE_FILL = exports.PRODUCT_SHARED_FILL = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = void 0;
|
|
34
|
+
exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductBoundary = exports.ProductPalette = exports.ProductTemplate = exports.ProductTemplateKind = exports.PRODUCTS_ON_AT_LOAD = exports.PRODUCT_OFF_BORDER = exports.PRODUCT_OFF_FILL = exports.PRODUCT_NONE_BORDER = exports.PRODUCT_NONE_FILL = exports.PRODUCT_SHARED_FILL = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = void 0;
|
|
31
35
|
const node_crypto_1 = require("node:crypto");
|
|
32
36
|
// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than
|
|
33
37
|
// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle
|
|
@@ -67,11 +71,54 @@ exports.PRODUCT_PALETTE_SLOTS = [
|
|
|
67
71
|
new ProductPaletteSlot('#8e5ea2', LIGHT_TEXT),
|
|
68
72
|
new ProductPaletteSlot('#ff9da7', DARK_TEXT),
|
|
69
73
|
];
|
|
70
|
-
/** A box shared by EVERY product. */
|
|
74
|
+
/** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */
|
|
71
75
|
exports.PRODUCT_SHARED_FILL = '#c9ced8';
|
|
72
76
|
/** A box in no product: white, with a dashed border. */
|
|
73
77
|
exports.PRODUCT_NONE_FILL = '#ffffff';
|
|
74
78
|
exports.PRODUCT_NONE_BORDER = '#8a90a0';
|
|
79
|
+
/**
|
|
80
|
+
* A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays
|
|
81
|
+
* visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.
|
|
82
|
+
*/
|
|
83
|
+
exports.PRODUCT_OFF_FILL = '#eef0f4';
|
|
84
|
+
exports.PRODUCT_OFF_BORDER = '#b4b9c4';
|
|
85
|
+
/**
|
|
86
|
+
* How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.
|
|
87
|
+
* Always this many, on every load — the selection is never persisted.
|
|
88
|
+
*/
|
|
89
|
+
exports.PRODUCTS_ON_AT_LOAD = 2;
|
|
90
|
+
/** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */
|
|
91
|
+
var ProductTemplateKind;
|
|
92
|
+
(function (ProductTemplateKind) {
|
|
93
|
+
/** A project or service box: the page writes its whole statement. */
|
|
94
|
+
ProductTemplateKind["BOX"] = "box";
|
|
95
|
+
/** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */
|
|
96
|
+
ProductTemplateKind["CONTEXT"] = "context";
|
|
97
|
+
})(ProductTemplateKind || (exports.ProductTemplateKind = ProductTemplateKind = {}));
|
|
98
|
+
/**
|
|
99
|
+
* What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.
|
|
100
|
+
* A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT
|
|
101
|
+
* statement is `head` (the kind statement) with its fill replaced.
|
|
102
|
+
*/
|
|
103
|
+
class ProductTemplate {
|
|
104
|
+
kind;
|
|
105
|
+
head;
|
|
106
|
+
darkLabel;
|
|
107
|
+
lightLabel;
|
|
108
|
+
constructor(kind,
|
|
109
|
+
/** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
|
|
110
|
+
head,
|
|
111
|
+
/** BOX: the HTML label written in dark text; '' for CONTEXT. */
|
|
112
|
+
darkLabel,
|
|
113
|
+
/** BOX: the HTML label written in light text; '' for CONTEXT. */
|
|
114
|
+
lightLabel) {
|
|
115
|
+
this.kind = kind;
|
|
116
|
+
this.head = head;
|
|
117
|
+
this.darkLabel = darkLabel;
|
|
118
|
+
this.lightLabel = lightLabel;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.ProductTemplate = ProductTemplate;
|
|
75
122
|
class ProductPalette {
|
|
76
123
|
/** Every declared product, sorted, each with its color. */
|
|
77
124
|
colors;
|
|
@@ -136,18 +183,24 @@ exports.ProductPalette = ProductPalette;
|
|
|
136
183
|
* Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge
|
|
137
184
|
* A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the
|
|
138
185
|
* product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.
|
|
186
|
+
* Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.
|
|
139
187
|
* Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):
|
|
140
188
|
* with `product-tags` ON, generation already refused every crossing the matrix does not allow, so
|
|
141
189
|
* every crossing drawn is an allowed one.
|
|
142
190
|
*/
|
|
143
191
|
class ProductBoundary {
|
|
144
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* The products the edge crosses into, in the target's order: the target's products its source
|
|
194
|
+
* lacks, else (a target narrower than its source) every product of the target. Empty when the
|
|
195
|
+
* target carries every product of the source. The page draws the edge in the first one that is ON.
|
|
196
|
+
*/
|
|
145
197
|
crossedInto(from, to) {
|
|
146
198
|
if (from.length === 0 || to.length === 0)
|
|
147
|
-
return
|
|
199
|
+
return [];
|
|
148
200
|
if (from.every((product) => to.includes(product)))
|
|
149
|
-
return
|
|
150
|
-
|
|
201
|
+
return [];
|
|
202
|
+
const added = to.filter((product) => !from.includes(product));
|
|
203
|
+
return added.length > 0 ? added : [...to];
|
|
151
204
|
}
|
|
152
205
|
}
|
|
153
206
|
exports.ProductBoundary = ProductBoundary;
|