@reddb-io/ui 0.1.1
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/LICENSE +17 -0
- package/bin/red-ui.js +259 -0
- package/build/CNAME +1 -0
- package/build/_app/env.js +1 -0
- package/build/_app/immutable/assets/0.C63jY5jX.css +1 -0
- package/build/_app/immutable/assets/10.Dblyk0Kr.css +1 -0
- package/build/_app/immutable/assets/EmptyState.RXcH7ryE.css +1 -0
- package/build/_app/immutable/assets/Workspace.BCzG1DsH.css +1 -0
- package/build/_app/immutable/assets/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2 +0 -0
- package/build/_app/immutable/assets/inter-cyrillic-wght-normal.DqGufNeO.woff2 +0 -0
- package/build/_app/immutable/assets/inter-greek-ext-wght-normal.DlzME5K_.woff2 +0 -0
- package/build/_app/immutable/assets/inter-greek-wght-normal.CkhJZR-_.woff2 +0 -0
- package/build/_app/immutable/assets/inter-latin-ext-wght-normal.DO1Apj_S.woff2 +0 -0
- package/build/_app/immutable/assets/inter-latin-wght-normal.Dx4kXJAl.woff2 +0 -0
- package/build/_app/immutable/assets/inter-vietnamese-wght-normal.CBcvBZtf.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2 +0 -0
- package/build/_app/immutable/chunks/3j4cZ58b.js +1 -0
- package/build/_app/immutable/chunks/69_IOA4Y.js +1 -0
- package/build/_app/immutable/chunks/ALqaDrNQ.js +2 -0
- package/build/_app/immutable/chunks/BGcIe2ep.js +2 -0
- package/build/_app/immutable/chunks/BMHKbvs8.js +1 -0
- package/build/_app/immutable/chunks/BQHbwf54.js +916 -0
- package/build/_app/immutable/chunks/Bgso8cDH.js +311 -0
- package/build/_app/immutable/chunks/Bmgx1mfv.js +310 -0
- package/build/_app/immutable/chunks/BrvvOVaJ.js +1 -0
- package/build/_app/immutable/chunks/Bzak7iHL.js +1 -0
- package/build/_app/immutable/chunks/D9CTkMjK.js +1 -0
- package/build/_app/immutable/chunks/DIeogL5L.js +1 -0
- package/build/_app/immutable/chunks/DXY_Rxa7.js +113 -0
- package/build/_app/immutable/chunks/DhEqZVGG.js +1 -0
- package/build/_app/immutable/chunks/Ov8N-vxt.js +1 -0
- package/build/_app/immutable/chunks/oiO4RLqp.js +1 -0
- package/build/_app/immutable/entry/app.D_IVtpdv.js +2 -0
- package/build/_app/immutable/entry/start.3oC0UQ_Q.js +1 -0
- package/build/_app/immutable/nodes/0.CHPiFxDo.js +1 -0
- package/build/_app/immutable/nodes/1.BC_4YZ0h.js +1 -0
- package/build/_app/immutable/nodes/10.B7DMKSNc.js +60 -0
- package/build/_app/immutable/nodes/2.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/3.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/4.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/5.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/6.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/7.BZX9Iird.js +134 -0
- package/build/_app/immutable/nodes/8.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/9.CAG5_cGP.js +1 -0
- package/build/_app/version.json +1 -0
- package/build/favicon.png +0 -0
- package/build/index.html +59 -0
- package/dist/embed/RedUiEmbedRoot-BGoLxJf1.js +26562 -0
- package/dist/embed/RedUiEmbedRoot-BGoLxJf1.js.map +1 -0
- package/dist/embed/bundle-mjs-UuVRz3O4.js +3047 -0
- package/dist/embed/bundle-mjs-UuVRz3O4.js.map +1 -0
- package/dist/embed/core-QuRYvMt8.js +118 -0
- package/dist/embed/core-QuRYvMt8.js.map +1 -0
- package/dist/embed/index-B6ZQhCsu.js +577 -0
- package/dist/embed/index-B6ZQhCsu.js.map +1 -0
- package/dist/embed/index-client-ItCEU64C.js +3087 -0
- package/dist/embed/index-client-ItCEU64C.js.map +1 -0
- package/dist/embed/index-fad77a13.esm-BqPj0saH.js +1318 -0
- package/dist/embed/index-fad77a13.esm-BqPj0saH.js.map +1 -0
- package/dist/embed/index.js +11 -0
- package/dist/embed/index.js.map +1 -0
- package/dist/embed/sigma-node-border.esm-CGtlgqmo.js +432 -0
- package/dist/embed/sigma-node-border.esm-CGtlgqmo.js.map +1 -0
- package/dist/embed/sigma.esm-y0qgY-OC.js +2355 -0
- package/dist/embed/sigma.esm-y0qgY-OC.js.map +1 -0
- package/dist/embed/ui.css +1 -0
- package/package.json +101 -0
- package/src/app.css +238 -0
- package/src/lib/ActivityIndicator.svelte +100 -0
- package/src/lib/ClusterNode.svelte +119 -0
- package/src/lib/ClusterView.svelte +541 -0
- package/src/lib/CollectionHistory.svelte +266 -0
- package/src/lib/CollectionsView.svelte +14 -0
- package/src/lib/CommandPalette.svelte +237 -0
- package/src/lib/ConnectDropdown.svelte +222 -0
- package/src/lib/EmptyState.svelte +27 -0
- package/src/lib/LiveChanges.svelte +195 -0
- package/src/lib/MasterPasswordDialog.svelte +168 -0
- package/src/lib/PageHeader.svelte +32 -0
- package/src/lib/PendingChangesPanel.svelte +153 -0
- package/src/lib/QueryEditor.svelte +122 -0
- package/src/lib/ResultsPane.svelte +747 -0
- package/src/lib/SchemaTree.svelte +193 -0
- package/src/lib/SecurityView.svelte +255 -0
- package/src/lib/ShortcutOverlay.svelte +104 -0
- package/src/lib/Splash.svelte +46 -0
- package/src/lib/StatusBar.svelte +163 -0
- package/src/lib/SubNav.svelte +62 -0
- package/src/lib/Topbar.svelte +119 -0
- package/src/lib/Workspace.svelte +247 -0
- package/src/lib/activity.svelte.ts +91 -0
- package/src/lib/build-info.ts +41 -0
- package/src/lib/capability.ts +221 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/collection-catalog.ts +116 -0
- package/src/lib/collection-pages.ts +33 -0
- package/src/lib/connection-history.ts +301 -0
- package/src/lib/connections.svelte.ts +493 -0
- package/src/lib/embed/RedUiEmbedRoot.svelte +31 -0
- package/src/lib/embed/index.ts +234 -0
- package/src/lib/graph-loader/contract.ts +336 -0
- package/src/lib/graph-loader/drilldown.ts +334 -0
- package/src/lib/graph-loader/index.ts +13 -0
- package/src/lib/index.ts +114 -0
- package/src/lib/pending-changes.svelte.ts +149 -0
- package/src/lib/query-tabs.svelte.ts +130 -0
- package/src/lib/reddb/boot-params.ts +254 -0
- package/src/lib/reddb/cdc-stream-client.ts +234 -0
- package/src/lib/reddb/client.ts +698 -0
- package/src/lib/reddb/connection-bootstrap.ts +260 -0
- package/src/lib/reddb/connection-provider.contract.ts +72 -0
- package/src/lib/reddb/connection-provider.ts +72 -0
- package/src/lib/reddb/index.ts +14 -0
- package/src/lib/reddb/injected-client-provider.ts +76 -0
- package/src/lib/reddb/local-url-provider.ts +245 -0
- package/src/lib/reddb/secure-store.ts +41 -0
- package/src/lib/reddb/server-capabilities.ts +71 -0
- package/src/lib/reddb/tauri-encrypted-store.ts +64 -0
- package/src/lib/reddb/transports.ts +89 -0
- package/src/lib/reddb/types.ts +32 -0
- package/src/lib/reddb/uri.ts +54 -0
- package/src/lib/reddb/web-encrypted-store.ts +201 -0
- package/src/lib/renderers/DiffRenderer.svelte +184 -0
- package/src/lib/renderers/DocumentRenderer.svelte +134 -0
- package/src/lib/renderers/GraphRenderer.svelte +1328 -0
- package/src/lib/renderers/HypertableRenderer.svelte +253 -0
- package/src/lib/renderers/JsonRenderer.svelte +30 -0
- package/src/lib/renderers/KvRenderer.svelte +150 -0
- package/src/lib/renderers/QueueRenderer.svelte +286 -0
- package/src/lib/renderers/StatsRenderer.svelte +123 -0
- package/src/lib/renderers/TableRenderer.svelte +440 -0
- package/src/lib/renderers/VectorRenderer.svelte +388 -0
- package/src/lib/renderers/diff-render.ts +122 -0
- package/src/lib/renderers/diff.ts +9 -0
- package/src/lib/renderers/document-render.ts +64 -0
- package/src/lib/renderers/document.ts +9 -0
- package/src/lib/renderers/graph-render.ts +631 -0
- package/src/lib/renderers/graph-sigma.ts +395 -0
- package/src/lib/renderers/graph.ts +9 -0
- package/src/lib/renderers/hypertable-render.ts +320 -0
- package/src/lib/renderers/hypertable.ts +9 -0
- package/src/lib/renderers/index.ts +42 -0
- package/src/lib/renderers/json-render.ts +89 -0
- package/src/lib/renderers/json.ts +10 -0
- package/src/lib/renderers/kv-render.ts +193 -0
- package/src/lib/renderers/kv.ts +9 -0
- package/src/lib/renderers/queue-render.ts +197 -0
- package/src/lib/renderers/queue.ts +9 -0
- package/src/lib/renderers/registry.ts +68 -0
- package/src/lib/renderers/stats-render.ts +73 -0
- package/src/lib/renderers/stats.ts +9 -0
- package/src/lib/renderers/table-render.ts +156 -0
- package/src/lib/renderers/table.ts +11 -0
- package/src/lib/renderers/types.ts +33 -0
- package/src/lib/renderers/vector-render.ts +154 -0
- package/src/lib/renderers/vector.ts +9 -0
- package/src/lib/router.svelte.ts +163 -0
- package/src/lib/secureStore.svelte.ts +148 -0
- package/src/lib/shadow-dom-spike/ShadowDomSpikeCore.svelte +198 -0
- package/src/lib/shadow-dom-spike/mount-shadow-dom-spike.ts +94 -0
- package/src/lib/shortcuts.ts +22 -0
- package/src/lib/surface.ts +80 -0
- package/src/lib/tabs.svelte.ts +115 -0
- package/src/lib/theme.svelte.ts +63 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
// Graph → sigma.js adapter. Pure, DOM-free, and — critically — free of any
|
|
2
|
+
// `sigma` import: sigma touches WebGL globals at module load, which throws in
|
|
3
|
+
// the node test environment. The Svelte component dynamically imports sigma in
|
|
4
|
+
// the browser and wires these helpers to its reducers/events. Everything here
|
|
5
|
+
// is unit-testable against a plain `graphology.Graph`.
|
|
6
|
+
|
|
7
|
+
import Graph from "graphology";
|
|
8
|
+
import {
|
|
9
|
+
colorForCommunity,
|
|
10
|
+
type GraphEdge,
|
|
11
|
+
type GraphLayout,
|
|
12
|
+
type GraphNode,
|
|
13
|
+
} from "./graph-render";
|
|
14
|
+
|
|
15
|
+
// ─── type → colour / size contract (moved out of the component so the sigma
|
|
16
|
+
// adapter and the SVG fallback share one source of truth) ───────────────
|
|
17
|
+
|
|
18
|
+
/** Literal type colours so the renderer never depends on resolving CSS vars. */
|
|
19
|
+
export function colorForType(type: string): string {
|
|
20
|
+
switch (type) {
|
|
21
|
+
case "tale":
|
|
22
|
+
return "#ff2056"; // accent
|
|
23
|
+
case "character":
|
|
24
|
+
return "#7dd3fc"; // sky-300
|
|
25
|
+
case "location":
|
|
26
|
+
return "#a3e635"; // lime-400
|
|
27
|
+
case "object":
|
|
28
|
+
return "#fbbf24"; // amber-400
|
|
29
|
+
case "archetype":
|
|
30
|
+
return "#a78bfa"; // violet-400
|
|
31
|
+
default:
|
|
32
|
+
return "#94a3b8"; // slate-400
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function sizeForType(type: string): number {
|
|
37
|
+
if (type === "tale") return 8;
|
|
38
|
+
if (type === "character") return 5;
|
|
39
|
+
if (type === "location" || type === "object") return 4;
|
|
40
|
+
return 3;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function nodeVisualRadius(type: string, incomingScale: number): number {
|
|
44
|
+
const base = Math.max(0.35, Math.min(1.25, sizeForType(type) * 0.14));
|
|
45
|
+
return Math.max(0.35, Math.min(2.5, base * incomingScale));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Map the unit-ish visual radius onto a sigma pixel size. Sigma sizes are in
|
|
49
|
+
// screen pixels at camera ratio 1; the factor keeps hubs prominent without the
|
|
50
|
+
// graph turning into a field of touching discs.
|
|
51
|
+
const SIGMA_SIZE_FACTOR = 3.4;
|
|
52
|
+
|
|
53
|
+
export function sigmaNodeSize(type: string, incomingScale: number): number {
|
|
54
|
+
return nodeVisualRadius(type, incomingScale) * SIGMA_SIZE_FACTOR;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── colour utilities (sigma's WebGL colour parser handles hex/rgb(a) but not
|
|
58
|
+
// hsl(), so community tints are converted up front) ─────────────────────
|
|
59
|
+
|
|
60
|
+
interface Rgb {
|
|
61
|
+
r: number;
|
|
62
|
+
g: number;
|
|
63
|
+
b: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hslToRgb(h: number, s: number, l: number): Rgb {
|
|
67
|
+
const sat = s / 100;
|
|
68
|
+
const light = l / 100;
|
|
69
|
+
const c = (1 - Math.abs(2 * light - 1)) * sat;
|
|
70
|
+
const hp = (((h % 360) + 360) % 360) / 60;
|
|
71
|
+
const x = c * (1 - Math.abs((hp % 2) - 1));
|
|
72
|
+
let r = 0;
|
|
73
|
+
let g = 0;
|
|
74
|
+
let b = 0;
|
|
75
|
+
if (hp >= 0 && hp < 1) [r, g, b] = [c, x, 0];
|
|
76
|
+
else if (hp < 2) [r, g, b] = [x, c, 0];
|
|
77
|
+
else if (hp < 3) [r, g, b] = [0, c, x];
|
|
78
|
+
else if (hp < 4) [r, g, b] = [0, x, c];
|
|
79
|
+
else if (hp < 5) [r, g, b] = [x, 0, c];
|
|
80
|
+
else [r, g, b] = [c, 0, x];
|
|
81
|
+
const m = light - c / 2;
|
|
82
|
+
return {
|
|
83
|
+
r: Math.round((r + m) * 255),
|
|
84
|
+
g: Math.round((g + m) * 255),
|
|
85
|
+
b: Math.round((b + m) * 255),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseHsl(value: string): Rgb | null {
|
|
90
|
+
// Matches `hsl(200 62% 60%)` and the legacy comma form.
|
|
91
|
+
const m = value.match(
|
|
92
|
+
/hsl\(\s*([\d.]+)\s*[, ]\s*([\d.]+)%\s*[, ]\s*([\d.]+)%/i
|
|
93
|
+
);
|
|
94
|
+
if (!m) return null;
|
|
95
|
+
return hslToRgb(Number(m[1]), Number(m[2]), Number(m[3]));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function rgbString({ r, g, b }: Rgb): string {
|
|
99
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Community tint as an rgb() string sigma can parse (input may be hsl()). */
|
|
103
|
+
export function sigmaColorForCommunity(
|
|
104
|
+
community: number,
|
|
105
|
+
dark: boolean = true
|
|
106
|
+
): string {
|
|
107
|
+
const hsl = colorForCommunity(community, dark);
|
|
108
|
+
const rgb = parseHsl(hsl);
|
|
109
|
+
return rgb ? rgbString(rgb) : hsl;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Wrap a known rgb()/hsl()/hex colour with an alpha for dimming. */
|
|
113
|
+
export function withAlpha(color: string, alpha: number): string {
|
|
114
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
115
|
+
const hsl = color.startsWith("hsl") ? parseHsl(color) : null;
|
|
116
|
+
if (hsl) return `rgba(${hsl.r}, ${hsl.g}, ${hsl.b}, ${a})`;
|
|
117
|
+
const rgb = color.match(/rgb\(\s*([\d.]+)[, ]+([\d.]+)[, ]+([\d.]+)/i);
|
|
118
|
+
if (rgb) return `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${a})`;
|
|
119
|
+
const hex = color.replace("#", "");
|
|
120
|
+
if (/^[0-9a-f]{6}$/i.test(hex)) {
|
|
121
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
122
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
123
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
124
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
125
|
+
}
|
|
126
|
+
return color;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── theme palette consumed by the reducers ────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export interface SigmaThemeColors {
|
|
132
|
+
background: string;
|
|
133
|
+
edge: string;
|
|
134
|
+
focusEdge: string;
|
|
135
|
+
focusNodeStroke: string;
|
|
136
|
+
selectedNodeStroke: string;
|
|
137
|
+
nodeStroke: string;
|
|
138
|
+
warn: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const FOCUS_EDGE_COLOR = "#d6a72c";
|
|
142
|
+
export const FOCUS_NODE_STROKE_COLOR = "#f4c84a";
|
|
143
|
+
|
|
144
|
+
// ─── graphology graph attributes ────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
export interface SigmaNodeAttributes {
|
|
147
|
+
x: number;
|
|
148
|
+
y: number;
|
|
149
|
+
size: number;
|
|
150
|
+
/** Resting size; the focus reducer scales from this. */
|
|
151
|
+
baseSize: number;
|
|
152
|
+
/** Community tint (rgb string) — the primary fill. */
|
|
153
|
+
color: string;
|
|
154
|
+
/** Dimmed fill used when another node holds focus. */
|
|
155
|
+
mutedColor: string;
|
|
156
|
+
/** Type-based stroke, preserved for legibility (NodeBorderProgram). */
|
|
157
|
+
borderColor: string;
|
|
158
|
+
label: string;
|
|
159
|
+
nodeType: string;
|
|
160
|
+
community: number;
|
|
161
|
+
orphan: boolean;
|
|
162
|
+
/** Drives `defaultNodeType` → NodeBorderProgram. */
|
|
163
|
+
type: "border";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface SigmaEdgeAttributes {
|
|
167
|
+
size: number;
|
|
168
|
+
color: string;
|
|
169
|
+
/** Original edge label, kept so the reducer can surface it on focus only. */
|
|
170
|
+
edgeLabel: string;
|
|
171
|
+
label: string;
|
|
172
|
+
type: "line";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface BuildSigmaGraphParams {
|
|
176
|
+
nodes: GraphNode[];
|
|
177
|
+
edges: GraphEdge[];
|
|
178
|
+
layout: GraphLayout;
|
|
179
|
+
sizeScales: Map<string, number>;
|
|
180
|
+
orphanIds: Set<string>;
|
|
181
|
+
dark?: boolean;
|
|
182
|
+
edgeColor?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Build a `graphology.Graph` ready to hand to a sigma instance: community tint
|
|
187
|
+
* as fill, type colour as border, layout positions, degree-scaled sizes. This
|
|
188
|
+
* is the adapter the issue asks to cover — it carries every visual contract
|
|
189
|
+
* sigma's reducers later read.
|
|
190
|
+
*/
|
|
191
|
+
export function buildSigmaGraph(
|
|
192
|
+
params: BuildSigmaGraphParams
|
|
193
|
+
): Graph<SigmaNodeAttributes, SigmaEdgeAttributes> {
|
|
194
|
+
const {
|
|
195
|
+
nodes,
|
|
196
|
+
edges,
|
|
197
|
+
layout,
|
|
198
|
+
sizeScales,
|
|
199
|
+
orphanIds,
|
|
200
|
+
dark = true,
|
|
201
|
+
edgeColor = "#3a424d",
|
|
202
|
+
} = params;
|
|
203
|
+
|
|
204
|
+
const graph = new Graph<SigmaNodeAttributes, SigmaEdgeAttributes>({
|
|
205
|
+
type: "directed",
|
|
206
|
+
multi: true,
|
|
207
|
+
allowSelfLoops: true,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
for (const node of nodes) {
|
|
211
|
+
if (graph.hasNode(node.id)) continue;
|
|
212
|
+
const type = String(node.data.node_type ?? "node");
|
|
213
|
+
const incomingScale = sizeScales.get(node.id) ?? 1;
|
|
214
|
+
const pos = layout.get(node.id);
|
|
215
|
+
const size = sigmaNodeSize(type, incomingScale);
|
|
216
|
+
const fill = pos
|
|
217
|
+
? sigmaColorForCommunity(pos.community, dark)
|
|
218
|
+
: colorForType(type);
|
|
219
|
+
graph.addNode(node.id, {
|
|
220
|
+
// Flip y so the WebGL scene matches the SVG fallback's downward growth.
|
|
221
|
+
x: pos?.x ?? 50,
|
|
222
|
+
y: -(pos?.y ?? 50),
|
|
223
|
+
size,
|
|
224
|
+
baseSize: size,
|
|
225
|
+
color: fill,
|
|
226
|
+
mutedColor: withAlpha(fill, 0.14),
|
|
227
|
+
borderColor: colorForType(type),
|
|
228
|
+
label: node.label,
|
|
229
|
+
nodeType: type,
|
|
230
|
+
community: pos?.community ?? -1,
|
|
231
|
+
orphan: orphanIds.has(node.id),
|
|
232
|
+
type: "border",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const edge of edges) {
|
|
237
|
+
if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
|
|
238
|
+
if (graph.hasEdge(edge.id)) continue;
|
|
239
|
+
graph.addEdgeWithKey(edge.id, edge.source, edge.target, {
|
|
240
|
+
size: 1,
|
|
241
|
+
color: withAlpha(edgeColor, 0.34),
|
|
242
|
+
edgeLabel: edge.label ?? "",
|
|
243
|
+
label: "",
|
|
244
|
+
type: "line",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return graph;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ─── focus neighbourhood (parity with the canvas focus highlight) ───────────
|
|
252
|
+
|
|
253
|
+
export interface GraphFocus {
|
|
254
|
+
nodes: Set<string>;
|
|
255
|
+
edges: Set<string>;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The focused node plus its immediate neighbours and the edges between them —
|
|
260
|
+
* the set the renderer keeps lit while everything else dims.
|
|
261
|
+
*/
|
|
262
|
+
export function computeGraphFocus(
|
|
263
|
+
graph: Graph<SigmaNodeAttributes, SigmaEdgeAttributes>,
|
|
264
|
+
focusId: string | null
|
|
265
|
+
): GraphFocus {
|
|
266
|
+
const nodes = new Set<string>();
|
|
267
|
+
const edges = new Set<string>();
|
|
268
|
+
if (!focusId || !graph.hasNode(focusId)) return { nodes, edges };
|
|
269
|
+
nodes.add(focusId);
|
|
270
|
+
graph.forEachEdge(focusId, (edge, _attrs, source, target) => {
|
|
271
|
+
edges.add(edge);
|
|
272
|
+
nodes.add(source);
|
|
273
|
+
nodes.add(target);
|
|
274
|
+
});
|
|
275
|
+
return { nodes, edges };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ─── reducers (pure; the component closes over reactive state and delegates) ─
|
|
279
|
+
|
|
280
|
+
export interface SigmaReducerState {
|
|
281
|
+
focusId: string | null;
|
|
282
|
+
focus: GraphFocus;
|
|
283
|
+
selectedNodeId: string | null;
|
|
284
|
+
/** Focus-pulse progress, 0 (rest) → 1 (peak). Held at 0 for reduced motion. */
|
|
285
|
+
pulse: number;
|
|
286
|
+
colors: SigmaThemeColors;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface SigmaNodeDisplay {
|
|
290
|
+
size: number;
|
|
291
|
+
color: string;
|
|
292
|
+
borderColor: string;
|
|
293
|
+
label: string | null;
|
|
294
|
+
forceLabel: boolean;
|
|
295
|
+
zIndex: number;
|
|
296
|
+
hidden: boolean;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export interface SigmaEdgeDisplay {
|
|
300
|
+
size: number;
|
|
301
|
+
color: string;
|
|
302
|
+
label: string | null;
|
|
303
|
+
forceLabel: boolean;
|
|
304
|
+
zIndex: number;
|
|
305
|
+
hidden: boolean;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const NODE_LABEL_MAX = 36;
|
|
309
|
+
const EDGE_LABEL_MAX = 28;
|
|
310
|
+
const NEIGHBOUR_LABEL_LIMIT = 36;
|
|
311
|
+
|
|
312
|
+
/** Per-node display override mirroring the old canvas draw loop's focus maths. */
|
|
313
|
+
export function reduceSigmaNode(
|
|
314
|
+
id: string,
|
|
315
|
+
attrs: SigmaNodeAttributes,
|
|
316
|
+
state: SigmaReducerState
|
|
317
|
+
): SigmaNodeDisplay {
|
|
318
|
+
const { focus, focusId, selectedNodeId, pulse, colors } = state;
|
|
319
|
+
const hasFocus = focusId !== null;
|
|
320
|
+
const isPrimary = focusId === id;
|
|
321
|
+
const isNeighbor = focus.nodes.has(id);
|
|
322
|
+
const lit = !hasFocus || isNeighbor;
|
|
323
|
+
|
|
324
|
+
const scale = !hasFocus
|
|
325
|
+
? 1
|
|
326
|
+
: isPrimary
|
|
327
|
+
? 2.2 + pulse * 0.38
|
|
328
|
+
: isNeighbor
|
|
329
|
+
? 1.58
|
|
330
|
+
: 0.9;
|
|
331
|
+
|
|
332
|
+
const borderColor = isPrimary
|
|
333
|
+
? colors.focusNodeStroke
|
|
334
|
+
: attrs.orphan
|
|
335
|
+
? colors.warn
|
|
336
|
+
: selectedNodeId === id
|
|
337
|
+
? colors.selectedNodeStroke
|
|
338
|
+
: attrs.borderColor;
|
|
339
|
+
|
|
340
|
+
const showLabel =
|
|
341
|
+
hasFocus &&
|
|
342
|
+
(isPrimary || (focus.nodes.size <= NEIGHBOUR_LABEL_LIMIT && isNeighbor));
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
size: attrs.baseSize * scale,
|
|
346
|
+
color: lit ? attrs.color : attrs.mutedColor,
|
|
347
|
+
borderColor,
|
|
348
|
+
label: showLabel ? attrs.label.slice(0, NODE_LABEL_MAX) : "",
|
|
349
|
+
forceLabel: showLabel,
|
|
350
|
+
zIndex: isPrimary ? 3 : isNeighbor ? 2 : 1,
|
|
351
|
+
hidden: false,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Per-edge display override: dim everything, light up the focused fan + label. */
|
|
356
|
+
export function reduceSigmaEdge(
|
|
357
|
+
id: string,
|
|
358
|
+
attrs: SigmaEdgeAttributes,
|
|
359
|
+
state: SigmaReducerState
|
|
360
|
+
): SigmaEdgeDisplay {
|
|
361
|
+
const { focus, focusId, colors } = state;
|
|
362
|
+
const hasFocus = focusId !== null;
|
|
363
|
+
const focused = focus.edges.has(id);
|
|
364
|
+
|
|
365
|
+
if (!hasFocus) {
|
|
366
|
+
return {
|
|
367
|
+
size: attrs.size,
|
|
368
|
+
color: withAlpha(colors.edge, 0.34),
|
|
369
|
+
label: "",
|
|
370
|
+
forceLabel: false,
|
|
371
|
+
zIndex: 0,
|
|
372
|
+
hidden: false,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (focused) {
|
|
377
|
+
return {
|
|
378
|
+
size: 2.2,
|
|
379
|
+
color: colors.focusEdge,
|
|
380
|
+
label: attrs.edgeLabel ? attrs.edgeLabel.slice(0, EDGE_LABEL_MAX) : "",
|
|
381
|
+
forceLabel: Boolean(attrs.edgeLabel),
|
|
382
|
+
zIndex: 2,
|
|
383
|
+
hidden: false,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
size: attrs.size,
|
|
389
|
+
color: withAlpha(colors.edge, 0.055),
|
|
390
|
+
label: "",
|
|
391
|
+
forceLabel: false,
|
|
392
|
+
zIndex: 0,
|
|
393
|
+
hidden: false,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CapabilityRenderer } from './types'
|
|
2
|
+
import { hasGraphShape } from './graph-render'
|
|
3
|
+
import GraphRenderer from './GraphRenderer.svelte'
|
|
4
|
+
|
|
5
|
+
export const graphRenderer: CapabilityRenderer = {
|
|
6
|
+
capability: 'graph',
|
|
7
|
+
renders: (result) => hasGraphShape(result),
|
|
8
|
+
component: GraphRenderer,
|
|
9
|
+
}
|