@visualli/core 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -105
- package/dist/chunk-25AJTPMS.js +68 -0
- package/dist/chunk-25AJTPMS.js.map +1 -0
- package/dist/{chunk-6NXADJK2.js → chunk-7NJWM7ZE.js} +84 -117
- package/dist/chunk-7NJWM7ZE.js.map +1 -0
- package/dist/chunk-KVSM3APM.js +96 -0
- package/dist/chunk-KVSM3APM.js.map +1 -0
- package/dist/{chunk-PDP5CVUL.js → chunk-LOQLNYB3.js} +2 -2
- package/dist/chunk-LV3EXUQY.js +753 -0
- package/dist/chunk-LV3EXUQY.js.map +1 -0
- package/dist/chunk-N7HCYULH.js +12 -0
- package/dist/chunk-N7HCYULH.js.map +1 -0
- package/dist/{chunk-I75TMOE4.js → chunk-P6PKWVYF.js} +2 -2
- package/dist/chunk-TPLJJKAU.js +151 -0
- package/dist/chunk-TPLJJKAU.js.map +1 -0
- package/dist/chunk-UAMEJG2C.js +267 -0
- package/dist/chunk-UAMEJG2C.js.map +1 -0
- package/dist/{chunk-LJYSKSCE.js → chunk-W2CAOOJJ.js} +6 -6
- package/dist/chunk-W2CAOOJJ.js.map +1 -0
- package/dist/config/index.cjs +250 -349
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.d.cts +107 -66
- package/dist/config/index.d.ts +107 -66
- package/dist/config/index.js +52 -34
- package/dist/constants/index.cjs +14 -333
- package/dist/constants/index.cjs.map +1 -1
- package/dist/constants/index.d.cts +5 -243
- package/dist/constants/index.d.ts +5 -243
- package/dist/constants/index.js +5 -75
- package/dist/designSystem-BOrZHfEq.d.cts +150 -0
- package/dist/designSystem-BOrZHfEq.d.ts +150 -0
- package/dist/document-BFBwn93X.d.ts +26 -0
- package/dist/document-MEJEfxl3.d.cts +26 -0
- package/dist/ideaSize-CLDKeyqD.d.cts +11 -0
- package/dist/ideaSize-DxxGy2E8.d.ts +11 -0
- package/dist/index-BmILeKj6.d.cts +99 -0
- package/dist/index-CcPRwVUL.d.ts +99 -0
- package/dist/index.cjs +1636 -961
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +350 -27
- package/dist/index.d.ts +350 -27
- package/dist/index.js +434 -179
- package/dist/index.js.map +1 -1
- package/dist/{document-B3hnNr-F.d.cts → layer-B2A3Ki48.d.cts} +2 -16
- package/dist/{document-B3hnNr-F.d.ts → layer-B2A3Ki48.d.ts} +2 -16
- package/dist/layout/index.d.cts +2 -1
- package/dist/layout/index.d.ts +2 -1
- package/dist/mindmap-C3Qr0qKW.d.cts +182 -0
- package/dist/mindmap-DHeVa86G.d.ts +182 -0
- package/dist/parser/index.cjs +745 -179
- package/dist/parser/index.cjs.map +1 -1
- package/dist/parser/index.d.cts +23 -8
- package/dist/parser/index.d.ts +23 -8
- package/dist/parser/index.js +7 -2
- package/dist/performance/index.cjs +13 -0
- package/dist/performance/index.cjs.map +1 -1
- package/dist/performance/index.js +2 -2
- package/dist/theme/index.cjs +884 -0
- package/dist/theme/index.cjs.map +1 -0
- package/dist/theme/index.d.cts +2 -0
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.js +53 -0
- package/dist/types/index.d.cts +4 -2
- package/dist/types/index.d.ts +4 -2
- package/dist/viewport/index.cjs +17 -6
- package/dist/viewport/index.cjs.map +1 -1
- package/dist/viewport/index.d.cts +6 -4
- package/dist/viewport/index.d.ts +6 -4
- package/dist/viewport/index.js +2 -2
- package/licenses/visualli-design-system/LICENSE +9 -0
- package/licenses/visualli-design-system/NOTICE.md +6 -0
- package/package.json +12 -4
- package/src/config/README.md +2 -4
- package/src/config/USAGE_EXAMPLES.md +0 -4
- package/src/config/index.ts +3 -4
- package/src/constants/index.ts +1 -1
- package/src/constants/layout.ts +11 -0
- package/src/constants/performanceConstants.ts +8 -27
- package/src/generated/README.md +12 -0
- package/src/generated/designSystem.ts +758 -0
- package/src/generated/geometry/blob.ts +99 -0
- package/src/generated/geometry/blobShapes.ts +4 -0
- package/src/generated/geometry/color.ts +37 -0
- package/src/generated/geometry/container.ts +84 -0
- package/src/generated/geometry/detail.ts +22 -0
- package/src/generated/geometry/idea.ts +56 -0
- package/src/generated/geometry/interaction.ts +122 -0
- package/src/generated/geometry/motion.ts +140 -0
- package/src/index.ts +18 -1
- package/src/parser/configUtils.ts +12 -19
- package/src/parser/visualliConverter.ts +71 -112
- package/src/parser/visualliParser.ts +16 -1
- package/src/rendering/culling.ts +16 -12
- package/src/rendering/ideaSize.ts +21 -0
- package/src/rendering/nodeGeometry.ts +97 -0
- package/src/theme/index.ts +136 -0
- package/src/types/document.ts +9 -0
- package/src/types/layer.ts +1 -1
- package/src/types/mindmap.ts +13 -0
- package/src/utils/navigation.ts +33 -63
- package/src/viewport/viewportUtils.ts +6 -4
- package/dist/animations/index.cjs +0 -146
- package/dist/animations/index.cjs.map +0 -1
- package/dist/animations/index.d.cts +0 -81
- package/dist/animations/index.d.ts +0 -81
- package/dist/animations/index.js +0 -35
- package/dist/chunk-6NXADJK2.js.map +0 -1
- package/dist/chunk-F36SMC3K.js +0 -106
- package/dist/chunk-F36SMC3K.js.map +0 -1
- package/dist/chunk-G5H3P4YZ.js +0 -273
- package/dist/chunk-G5H3P4YZ.js.map +0 -1
- package/dist/chunk-LDJRGT2Q.js +0 -163
- package/dist/chunk-LDJRGT2Q.js.map +0 -1
- package/dist/chunk-LJYSKSCE.js.map +0 -1
- package/dist/chunk-LNTJTDB2.js +0 -349
- package/dist/chunk-LNTJTDB2.js.map +0 -1
- package/dist/mindmap-DWS0WZT_.d.cts +0 -118
- package/dist/mindmap-DWS0WZT_.d.ts +0 -118
- package/src/animations/constants.ts +0 -72
- package/src/animations/easing.ts +0 -70
- package/src/animations/index.ts +0 -2
- package/src/config/blobShapes.ts +0 -146
- package/src/config/textScaling.ts +0 -54
- package/src/constants/design.ts +0 -281
- /package/dist/{chunk-PDP5CVUL.js.map → chunk-LOQLNYB3.js.map} +0 -0
- /package/dist/{chunk-I75TMOE4.js.map → chunk-P6PKWVYF.js.map} +0 -0
- /package/dist/{animations → theme}/index.js.map +0 -0
package/README.md
CHANGED
|
@@ -7,13 +7,13 @@ Framework-agnostic core logic, types, and algorithms for Visualli. Zero React /
|
|
|
7
7
|
| Module | What it provides |
|
|
8
8
|
|--------|------------------|
|
|
9
9
|
| **Types** | Full TypeScript interfaces for `VisualliDocument`, `VisualliLayer`, `FlatNode`, `ViewportState`, `RenderConfig`, `Connection`, and more |
|
|
10
|
-
| **Parser** | Parse `.visualli` JSONL files
|
|
11
|
-
| **Layout** | Circular and linear
|
|
12
|
-
| **
|
|
10
|
+
| **Parser** | Parse `.visualli` JSONL files and convert layers to flat nodes: each idea sized for its label and coloured by the design system's rules, placed where the file puts it |
|
|
11
|
+
| **Layout** | Circular and linear layouts for files whose ideas carry no positions |
|
|
12
|
+
| **Design system** | The Visualli design system's rules, copied verbatim from `design-system/` (tokens, blob geometry, motion, idea size, colours, containers, fit, peek, trail, level of detail) |
|
|
13
|
+
| **Viewport** | Pure pan/zoom/bounds math — world↔screen coordinate transforms, fit to view |
|
|
13
14
|
| **Spatial index** | RBush-backed O(log n) spatial index for viewport culling of large node graphs |
|
|
14
|
-
| **Animations** | Easing functions (cubic, quartic, sine, bezier LUT) and timing phase constants |
|
|
15
15
|
| **Performance** | rAF-based FPS monitor, memory monitor, profiler |
|
|
16
|
-
| **Constants** |
|
|
16
|
+
| **Constants** | Zoom limits, FPS targets, render config |
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
@@ -36,6 +36,10 @@ const doc = parseVisualliFile(rawJsonlString);
|
|
|
36
36
|
// Get the root layer
|
|
37
37
|
const rootLayerId = [...doc.layers.keys()][0];
|
|
38
38
|
const nodes = getNodesForLayer(doc, rootLayerId); // FlatNode[]
|
|
39
|
+
|
|
40
|
+
// Ideas are sized for their labels (the design system's idea.ts). Renderers pass their own text measurement in the
|
|
41
|
+
// label face; without one, an approximation is used.
|
|
42
|
+
const sized = getNodesForLayer(doc, rootLayerId, { measure: (text, px) => ctx.measureText(text).width, labelScale: 1 });
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
### Apply layouts
|
|
@@ -75,25 +79,15 @@ index.bulkLoad(nodes.map(n => ({
|
|
|
75
79
|
const visible = index.query(viewportBounds); // string[] — node IDs
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
###
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
import { easeInOutCubic, easeOutCubic, createCubicBezier } from '@visualli/core';
|
|
82
|
-
|
|
83
|
-
const t = easeInOutCubic(progress); // 0..1 → 0..1
|
|
84
|
-
const custom = createCubicBezier(0.4, 0, 0.2, 1); // CSS timing function
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Colors & design tokens
|
|
82
|
+
### Colours
|
|
88
83
|
|
|
89
84
|
```ts
|
|
90
|
-
import {
|
|
91
|
-
getColorForLevel, getThemeBackground,
|
|
92
|
-
LEVEL_COLOR_ARRAY, DS_COLORS, BRAND_COLORS,
|
|
93
|
-
} from '@visualli/core';
|
|
85
|
+
import { ideaColor, ideaStyle } from '@visualli/core';
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
ideaColor('teal', 0); // { topic: 'teal', custom: null } — a topic name
|
|
88
|
+
ideaColor(undefined, 2); // { topic: 'iris', custom: null } — no colour: topics in sibling order
|
|
89
|
+
ideaColor('#b7e7f3', 0); // { topic: null, custom: { fill: '#b7e7f3', ring: '#8fb4be' } } — drawn as given
|
|
90
|
+
ideaStyle('dark', node); // { fill, ring } in a theme
|
|
97
91
|
```
|
|
98
92
|
|
|
99
93
|
## Module Reference
|
|
@@ -104,7 +98,7 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
104
98
|
|--------|-------------|
|
|
105
99
|
| `VisualliDocument` | Top-level document (layers Map, extensions Map) |
|
|
106
100
|
| `VisualliLayer` | A single layer: level, nodes, connections, containers |
|
|
107
|
-
| `FlatNode` | Renderable
|
|
101
|
+
| `FlatNode` | Renderable idea: position, size for its label (`width` / `height`, `kind`), title, colour (`topic` or `custom`), level, parentId |
|
|
108
102
|
| `NodeMap` | `Map<string, FlatNode>` |
|
|
109
103
|
| `ViewportState` | `centerX/Y`, `zoomLevel`, `rotation`, `visibleBounds` |
|
|
110
104
|
| `RenderConfig` | Quality level, FPS target, culling flag, render mode |
|
|
@@ -116,15 +110,15 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
116
110
|
|--------|-------------|
|
|
117
111
|
| `parseVisualliFile(str)` | Parse JSONL → `VisualliDocument` |
|
|
118
112
|
| `loadVisualliFileFromFile(file)` | Parse a browser `File` object |
|
|
119
|
-
| `getNodesForLayer(doc, layerId)` | `FlatNode[]` for one layer |
|
|
120
|
-
| `convertVisualliToFlatNodes(doc)` | All layers → `NodeMap` |
|
|
113
|
+
| `getNodesForLayer(doc, layerId, opts?)` | `FlatNode[]` for one layer (`opts`: `measure`, `labelScale` for sizing ideas) |
|
|
114
|
+
| `convertVisualliToFlatNodes(doc, opts?)` | All layers → `NodeMap` |
|
|
121
115
|
| `getChildLayers(doc, layerId)` | Direct child layers |
|
|
122
116
|
|
|
123
117
|
### `layout/`
|
|
124
118
|
|
|
125
119
|
| Export | Description |
|
|
126
120
|
|--------|-------------|
|
|
127
|
-
| `applyCircularLayout(nodes)` | Radial arrangement |
|
|
121
|
+
| `applyCircularLayout(nodes)` | Radial arrangement (used when the file gives no positions) |
|
|
128
122
|
| `applyLinearHorizontalLayout(nodes)` | Left-to-right tree |
|
|
129
123
|
| `applyLinearVerticalLayout(nodes)` | Top-to-bottom tree |
|
|
130
124
|
| `resolveCollisions(nodes)` | Push apart overlapping nodes |
|
|
@@ -137,7 +131,7 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
137
131
|
| `zoomViewport(delta, vp, px, py, w, h)` | Zoom to cursor |
|
|
138
132
|
| `panViewport(dx, dy, vp)` | Translate camera |
|
|
139
133
|
| `setViewportCenter(x, y, vp)` | Teleport center |
|
|
140
|
-
| `clampZoom(level)` | Clamp to `[ZOOM_MIN, ZOOM_MAX]` |
|
|
134
|
+
| `clampZoom(level, fit?)` | Clamp to `[ZOOM_MIN, ZOOM_MAX]` × the layer's fit scale (the design system's limits are relative to the fit) |
|
|
141
135
|
| `worldToScreen(x, y, vp, w, h)` | World → screen px |
|
|
142
136
|
| `screenToWorld(x, y, vp, w, h)` | Screen px → world |
|
|
143
137
|
|
|
@@ -145,12 +139,11 @@ const bg = getThemeBackground(isDark); // canvas background
|
|
|
145
139
|
|
|
146
140
|
| Export | Description |
|
|
147
141
|
|--------|-------------|
|
|
148
|
-
| `ZOOM_MIN` / `ZOOM_MAX` | `0.3` / `5.0` |
|
|
149
|
-
| `ZOOM_NAV_IN_THRESHOLD` | `2.7` — zoom level that triggers drill-in |
|
|
150
|
-
| `ZOOM_NAV_OUT_THRESHOLD` | `0.4` — zoom level that triggers back navigation |
|
|
151
|
-
| `LEVEL_COLOR_ARRAY` | 10-color palette indexed by node level |
|
|
142
|
+
| `ZOOM_MIN` / `ZOOM_MAX` | `0.3` / `5.0` — the design system's `VIEW`, relative to the layer's fit |
|
|
152
143
|
| `DEFAULT_RENDER_CONFIG` | Baseline quality settings |
|
|
153
|
-
|
|
144
|
+
|
|
145
|
+
Motion, idea size, colours, fit, peek placement and the depth trail are the design system's rules, exported verbatim
|
|
146
|
+
(`MOTION`, `layerReveal`, `IDEA`, `ideaSize`, `labelGrowth`, `ideaColor`, `layerBounds`, `fitView`, `peekPosition`, `TRAIL` …).
|
|
154
147
|
|
|
155
148
|
## TypeScript
|
|
156
149
|
|
|
@@ -178,86 +171,25 @@ npm run typecheck # tsc --noEmit (0 errors expected)
|
|
|
178
171
|
|
|
179
172
|
```
|
|
180
173
|
src/
|
|
181
|
-
├──
|
|
182
|
-
│ ├──
|
|
183
|
-
│
|
|
184
|
-
│ ├── layer.ts VisualliLayer, LayerNode, LayerConnection, LayerContainer …
|
|
185
|
-
│ ├── extension.ts VisualliExtension
|
|
186
|
-
│ ├── document.ts VisualliDocument
|
|
187
|
-
│ ├── schema.ts JSON-schema-aligned types + re-exports
|
|
188
|
-
│ ├── processing.ts ConnectionState, ProcessingState, type guards
|
|
189
|
-
│ └── sse.ts SSEEvent discriminated union + Zod validation + helpers
|
|
190
|
-
│
|
|
191
|
-
├── layout/ Pure layout algorithms (no side-effects)
|
|
192
|
-
│ ├── circularLayout.ts applyCircularLayout, calculateOptimalRadiusPercentage
|
|
193
|
-
│ ├── linearLayout.ts applyLinearHorizontalLayout, applyLinearVerticalLayout
|
|
194
|
-
│ └── layoutUtils.ts getNodeBounds, resolveCollisions, optimizeLayout
|
|
195
|
-
│
|
|
196
|
-
├── parser/ File parsing and data conversion
|
|
197
|
-
│ ├── visualliParser.ts parseVisualliFile, loadVisualliFile, getChildLayers …
|
|
198
|
-
│ ├── visualliConverter.ts convertVisualliToFlatNodes, resolveNodeOverlaps …
|
|
199
|
-
│ ├── mindmapUtils.ts flattenNodes, getVisibleNodes, calculateDistance …
|
|
200
|
-
│ └── configUtils.ts createMindMapConfig, generateSampleConfig
|
|
201
|
-
│
|
|
202
|
-
├── services/ Network utilities (fetch, SSE, logging)
|
|
203
|
-
│ ├── api.ts initApi, apiRequest, ApiError, mindmapApi, correlationId helpers
|
|
204
|
-
│ ├── logger.ts Logger class (buffered remote + console)
|
|
205
|
-
│ └── sseClient.ts createSSEClient (fetch ReadableStream, reconnection)
|
|
206
|
-
│
|
|
207
|
-
├── constants/ Design tokens, performance thresholds, render config
|
|
208
|
-
│ ├── performanceConstants.ts FPS targets, zoom limits, node dimensions …
|
|
209
|
-
│ ├── design.ts DS_COLORS, LEVEL_COLORS, BRAND_COLORS, helpers …
|
|
210
|
-
│ └── renderConfig.ts DEFAULT_RENDER_CONFIG, FEATURE_FLAGS, helpers …
|
|
211
|
-
│
|
|
212
|
-
├── viewport/ Coordinate math and pan/zoom operations
|
|
213
|
-
│ └── viewportUtils.ts calculateViewportBounds, worldToScreen, zoomViewport …
|
|
174
|
+
├── generated/ The Visualli design system, generated from design-system/ (never edited by hand)
|
|
175
|
+
│ ├── designSystem.ts TOKENS, METRICS, TYPE_STYLES, CANVAS_STYLE (from css/spec.css)
|
|
176
|
+
│ └── geometry/ blob, motion, idea, color, container, interaction, detail — copied verbatim
|
|
214
177
|
│
|
|
178
|
+
├── types/ TypeScript interfaces (FlatNode, VisualliDocument, VisualliLayer, ViewportState …)
|
|
179
|
+
├── parser/ parseVisualliFile; convertLayerToFlatNodes / getNodesForLayer (sizes, colours, positions)
|
|
180
|
+
├── layout/ Circular and linear layouts, for layers whose ideas carry no positions
|
|
181
|
+
├── rendering/ nodeGeometry (radii, rings, connector endpoints), ideaSize (idea.ts + measurement), culling
|
|
182
|
+
├── theme/ Theme and comfort resolution; ideaColor / ideaStyle (the design system's colour rule)
|
|
183
|
+
├── utils/ Layer navigation and fit to view (layerBounds / fitView)
|
|
184
|
+
├── viewport/ Coordinate math and pan / zoom (limits relative to the layer's fit)
|
|
185
|
+
├── constants/ Zoom limits, performance thresholds, render config
|
|
215
186
|
├── performance/ FPS / memory monitoring (browser APIs, no React)
|
|
216
|
-
│ └── performanceMonitor.ts FPSMonitor, MemoryMonitor, PerformanceProfiler
|
|
217
|
-
│
|
|
218
|
-
├── animations/ Easing functions and timing constants
|
|
219
|
-
│ ├── easing.ts easeInOutCubic, createCubicBezier …
|
|
220
|
-
│ └── constants.ts ANIMATION_DURATION, ANIMATION_PHASES, COOLDOWN …
|
|
221
|
-
│
|
|
222
187
|
└── spatial/ RBush spatial index for O(log n) viewport culling
|
|
223
|
-
└── spatialIndex.ts RBushSpatialIndex, ISpatialIndex, BoundingBox
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
## Installation
|
|
227
|
-
|
|
228
|
-
```bash
|
|
229
|
-
npm install @visualli/core
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
> **Dependencies:** `zod` (schema validation) · `rbush` (spatial index)
|
|
233
|
-
|
|
234
|
-
## Quick Start
|
|
235
|
-
|
|
236
|
-
```ts
|
|
237
|
-
// Parse a .visualli document
|
|
238
|
-
import { parseVisualliFile, convertVisualliToFlatNodes } from '@visualli/core/parser';
|
|
239
|
-
|
|
240
|
-
const doc = parseVisualliFile(rawJsonlString);
|
|
241
|
-
const nodeMap = convertVisualliToFlatNodes(doc);
|
|
242
|
-
|
|
243
|
-
// Apply a circular layout
|
|
244
|
-
import { applyCircularLayout } from '@visualli/core/layout';
|
|
245
|
-
const nodes = [...nodeMap.values()];
|
|
246
|
-
applyCircularLayout(nodes);
|
|
247
|
-
|
|
248
|
-
// Viewport math
|
|
249
|
-
import { zoomViewport } from '@visualli/core/viewport';
|
|
250
|
-
const next = zoomViewport(0.1, currentViewport, pivotX, pivotY);
|
|
251
|
-
|
|
252
|
-
// API client
|
|
253
|
-
import { initApi, mindmapApi } from '@visualli/core/services';
|
|
254
|
-
initApi({ baseUrl: 'https://api.example.com/api' });
|
|
255
|
-
const list = await mindmapApi.getAllMindmaps();
|
|
256
188
|
```
|
|
257
189
|
|
|
258
190
|
## Design Principles
|
|
259
191
|
|
|
260
192
|
- **No React** — no hooks, no JSX, no Context
|
|
261
|
-
- **No DOM manipulation** — pure data transforms;
|
|
262
|
-
- **No
|
|
193
|
+
- **No DOM manipulation** — pure data transforms; text measurement is passed in by the renderer
|
|
194
|
+
- **No design decisions of its own** — every visual rule comes from the Visualli design system (`design-system/`)
|
|
263
195
|
- **Tree-shakeable** — import from sub-paths to avoid bundling unused modules
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/generated/geometry/idea.ts
|
|
2
|
+
var LABEL_GROWTH = { idea: 1.3, connector: 1.6 };
|
|
3
|
+
var IDEA = {
|
|
4
|
+
/** Width an idea starts at… */
|
|
5
|
+
baseWidth: { root: 240, node: 200, mini: 120 },
|
|
6
|
+
/** …and the widest it gets before it grows taller instead. */
|
|
7
|
+
maxWidth: { root: 400, node: 360, mini: 200 },
|
|
8
|
+
/** Width grows in steps of this. */
|
|
9
|
+
step: 20,
|
|
10
|
+
/** Height = width × aspect (at least `minRy` × 2), more if the label needs it. */
|
|
11
|
+
aspect: 0.74,
|
|
12
|
+
minRy: 64,
|
|
13
|
+
/** The label's box is the idea's width less this (22 each side). */
|
|
14
|
+
labelInset: 44,
|
|
15
|
+
/** The idea widens until its label fits in this many lines. */
|
|
16
|
+
labelLines: 3,
|
|
17
|
+
/** Label size (before the label scale and zoom growth), weight from the canvas type styles. */
|
|
18
|
+
labelSize: { root: 30, node: 22, mini: 15 },
|
|
19
|
+
labelLineHeight: 1.15,
|
|
20
|
+
/** The label may fill at most this share of the idea's height. */
|
|
21
|
+
labelFillHeight: 0.7
|
|
22
|
+
};
|
|
23
|
+
function wrapLines(text, maxWidth, measure) {
|
|
24
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
25
|
+
const lines = [];
|
|
26
|
+
let cur = "";
|
|
27
|
+
for (const w of words) {
|
|
28
|
+
const next = cur ? `${cur} ${w}` : w;
|
|
29
|
+
if (cur && measure(next) > maxWidth) {
|
|
30
|
+
lines.push(cur);
|
|
31
|
+
cur = w;
|
|
32
|
+
} else cur = next;
|
|
33
|
+
}
|
|
34
|
+
if (cur) lines.push(cur);
|
|
35
|
+
return lines.length ? lines : [""];
|
|
36
|
+
}
|
|
37
|
+
function ideaSize(kind, label, measure, labelScale = 1) {
|
|
38
|
+
const px = IDEA.labelSize[kind] * labelScale * LABEL_GROWTH.idea;
|
|
39
|
+
const fit = (w) => wrapLines(label, w - IDEA.labelInset, (t) => measure(t, px));
|
|
40
|
+
const base = IDEA.baseWidth[kind], max = IDEA.maxWidth[kind];
|
|
41
|
+
let width = base, lines = fit(width);
|
|
42
|
+
while (lines.length > IDEA.labelLines && width < max) {
|
|
43
|
+
width = Math.min(max, width + IDEA.step);
|
|
44
|
+
lines = fit(width);
|
|
45
|
+
}
|
|
46
|
+
const rx = width / 2;
|
|
47
|
+
const ry = Math.max(IDEA.minRy, rx * IDEA.aspect, lines.length * px * IDEA.labelLineHeight / 2 / IDEA.labelFillHeight);
|
|
48
|
+
return { width, height: ry * 2, rx, ry, lines };
|
|
49
|
+
}
|
|
50
|
+
function labelGrowth(scale) {
|
|
51
|
+
const inv = 1 / Math.max(scale, 1e-4);
|
|
52
|
+
return { idea: Math.max(1, Math.min(inv, LABEL_GROWTH.idea)), connector: Math.max(1, Math.min(inv, LABEL_GROWTH.connector)) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/rendering/ideaSize.ts
|
|
56
|
+
var approximateMeasure = (text, px) => text.length * px * 0.5;
|
|
57
|
+
var ideaKindOf = (layer, index) => layer.level === 0 && (layer.layout ?? "radial") === "radial" && index === 0 ? "root" : "node";
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
LABEL_GROWTH,
|
|
61
|
+
IDEA,
|
|
62
|
+
wrapLines,
|
|
63
|
+
ideaSize,
|
|
64
|
+
labelGrowth,
|
|
65
|
+
approximateMeasure,
|
|
66
|
+
ideaKindOf
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=chunk-25AJTPMS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generated/geometry/idea.ts","../src/rendering/ideaSize.ts"],"sourcesContent":["// Generated from visualli.ai design-system/components/src/idea.ts — do not edit here.\n/* An idea's size and its label. An idea grows to show its whole label: it starts at its base width and widens in\n steps until the label fits in `labelLines` lines; at its widest it grows taller instead, so a label is never cut.\n Labels (and connector labels) grow a little when the map is zoomed out, so they stay readable. Every renderer sizes\n ideas with these rules, measuring text with its own font engine (`measure`). No dependencies, so it can travel alone. */\n\nexport type IdeaKind = 'root' | 'node' | 'mini';\n\n/** How much labels grow when zoomed out (`scale` = the map's zoom): never smaller than their size, up to these caps.\n * Idea labels and container names use `idea`; connector labels `connector`. Below 100% they grow; above, they don't. */\nexport const LABEL_GROWTH = { idea: 1.3, connector: 1.6 } as const;\n\nexport const IDEA = {\n /** Width an idea starts at… */ baseWidth: { root: 240, node: 200, mini: 120 },\n /** …and the widest it gets before it grows taller instead. */ maxWidth: { root: 400, node: 360, mini: 200 },\n /** Width grows in steps of this. */ step: 20,\n /** Height = width × aspect (at least `minRy` × 2), more if the label needs it. */ aspect: 0.74, minRy: 64,\n /** The label's box is the idea's width less this (22 each side). */ labelInset: 44,\n /** The idea widens until its label fits in this many lines. */ labelLines: 3,\n /** Label size (before the label scale and zoom growth), weight from the canvas type styles. */ labelSize: { root: 30, node: 22, mini: 15 },\n labelLineHeight: 1.15,\n /** The label may fill at most this share of the idea's height. */ labelFillHeight: 0.7,\n} as const;\n\n/** Greedy word wrap of `text` into lines no wider than `maxWidth` (a single word longer than that gets its own line). */\nexport function wrapLines(text: string, maxWidth: number, measure: (s: string) => number): string[] {\n const words = text.split(/\\s+/).filter(Boolean);\n const lines: string[] = [];\n let cur = '';\n for (const w of words) {\n const next = cur ? `${cur} ${w}` : w;\n if (cur && measure(next) > maxWidth) { lines.push(cur); cur = w; } else cur = next;\n }\n if (cur) lines.push(cur);\n return lines.length ? lines : [''];\n}\n\n/** An idea's size for its label: width, half-width / half-height for the blob, and the label's lines. The label is\n * measured at its largest drawn size — its type size × `labelScale` (Comfort: larger text) × LABEL_GROWTH.idea (zoomed\n * out) — so it fits at every zoom and the idea never changes size while zooming. `measure(text, px)` returns the\n * text's width in px in the label's face at `px`. */\nexport function ideaSize(kind: IdeaKind, label: string, measure: (text: string, px: number) => number, labelScale = 1) {\n const px = IDEA.labelSize[kind] * labelScale * LABEL_GROWTH.idea;\n const fit = (w: number) => wrapLines(label, w - IDEA.labelInset, (t) => measure(t, px));\n const base: number = IDEA.baseWidth[kind], max: number = IDEA.maxWidth[kind];\n let width = base, lines = fit(width);\n while (lines.length > IDEA.labelLines && width < max) { width = Math.min(max, width + IDEA.step); lines = fit(width); }\n const rx = width / 2;\n const ry = Math.max(IDEA.minRy, rx * IDEA.aspect, (lines.length * px * IDEA.labelLineHeight) / 2 / IDEA.labelFillHeight);\n return { width, height: ry * 2, rx, ry, lines };\n}\n\nexport function labelGrowth(scale: number): { idea: number; connector: number } {\n const inv = 1 / Math.max(scale, 0.0001);\n return { idea: Math.max(1, Math.min(inv, LABEL_GROWTH.idea)), connector: Math.max(1, Math.min(inv, LABEL_GROWTH.connector)) };\n}\n","// ─── Idea size and label growth ──────────────────────────────────────────────\n//\n// The design system's idea.ts (copied verbatim into generated/geometry): an idea\n// grows to show its whole label, and labels grow a little when zoomed out. The\n// SDK only supplies the text measurement (its font engine) and picks the kind.\n\nimport { IDEA, LABEL_GROWTH, ideaSize, labelGrowth, wrapLines, type IdeaKind } from '../generated/geometry/idea.js';\nimport type { VisualliLayer } from '../types/layer.js';\n\nexport { IDEA, LABEL_GROWTH, ideaSize, labelGrowth, wrapLines };\nexport type { IdeaKind };\n\n/** Width in px of `text` in the idea label's face at `px` (the label's weight; readable type adds its tracking). */\nexport type IdeaMeasure = (text: string, px: number) => number;\n\n/** Measurement without a font engine (server, tests): half an em per character. Renderers pass the real face. */\nexport const approximateMeasure: IdeaMeasure = (text, px) => text.length * px * 0.5;\n\n/** The size kind of the `index`-th idea of a layer: the centre of the root radial layer is the root, the rest ideas. */\nexport const ideaKindOf = (layer: Pick<VisualliLayer, 'level' | 'layout'>, index: number): IdeaKind =>\n layer.level === 0 && (layer.layout ?? 'radial') === 'radial' && index === 0 ? 'root' : 'node';\n"],"mappings":";AAUO,IAAM,eAAe,EAAE,MAAM,KAAK,WAAW,IAAI;AAEjD,IAAM,OAAO;AAAA;AAAA,EACc,WAAW,EAAE,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI;AAAA;AAAA,EACd,UAAU,EAAE,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI;AAAA;AAAA,EACtE,MAAM;AAAA;AAAA,EACwC,QAAQ;AAAA,EAAM,OAAO;AAAA;AAAA,EACnC,YAAY;AAAA;AAAA,EACjB,YAAY;AAAA;AAAA,EACoB,WAAW,EAAE,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG;AAAA,EAC1I,iBAAiB;AAAA;AAAA,EACkD,iBAAiB;AACtF;AAGO,SAAS,UAAU,MAAc,UAAkB,SAA0C;AAClG,QAAM,QAAQ,KAAK,MAAM,KAAK,EAAE,OAAO,OAAO;AAC9C,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAM;AACV,aAAW,KAAK,OAAO;AACrB,UAAM,OAAO,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK;AACnC,QAAI,OAAO,QAAQ,IAAI,IAAI,UAAU;AAAE,YAAM,KAAK,GAAG;AAAG,YAAM;AAAA,IAAG,MAAO,OAAM;AAAA,EAChF;AACA,MAAI,IAAK,OAAM,KAAK,GAAG;AACvB,SAAO,MAAM,SAAS,QAAQ,CAAC,EAAE;AACnC;AAMO,SAAS,SAAS,MAAgB,OAAe,SAA+C,aAAa,GAAG;AACrH,QAAM,KAAK,KAAK,UAAU,IAAI,IAAI,aAAa,aAAa;AAC5D,QAAM,MAAM,CAAC,MAAc,UAAU,OAAO,IAAI,KAAK,YAAY,CAAC,MAAM,QAAQ,GAAG,EAAE,CAAC;AACtF,QAAM,OAAe,KAAK,UAAU,IAAI,GAAG,MAAc,KAAK,SAAS,IAAI;AAC3E,MAAI,QAAQ,MAAM,QAAQ,IAAI,KAAK;AACnC,SAAO,MAAM,SAAS,KAAK,cAAc,QAAQ,KAAK;AAAE,YAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI;AAAG,YAAQ,IAAI,KAAK;AAAA,EAAG;AACtH,QAAM,KAAK,QAAQ;AACnB,QAAM,KAAK,KAAK,IAAI,KAAK,OAAO,KAAK,KAAK,QAAS,MAAM,SAAS,KAAK,KAAK,kBAAmB,IAAI,KAAK,eAAe;AACvH,SAAO,EAAE,OAAO,QAAQ,KAAK,GAAG,IAAI,IAAI,MAAM;AAChD;AAEO,SAAS,YAAY,OAAoD;AAC9E,QAAM,MAAM,IAAI,KAAK,IAAI,OAAO,IAAM;AACtC,SAAO,EAAE,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,aAAa,IAAI,CAAC,GAAG,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,aAAa,SAAS,CAAC,EAAE;AAC9H;;;ACvCO,IAAM,qBAAkC,CAAC,MAAM,OAAO,KAAK,SAAS,KAAK;AAGzE,IAAM,aAAa,CAAC,OAAgD,UACzE,MAAM,UAAU,MAAM,MAAM,UAAU,cAAc,YAAY,UAAU,IAAI,SAAS;","names":[]}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-
|
|
2
|
+
approximateMeasure,
|
|
3
|
+
ideaKindOf,
|
|
4
|
+
ideaSize
|
|
5
|
+
} from "./chunk-25AJTPMS.js";
|
|
6
|
+
import {
|
|
7
|
+
LAYOUT_SPACING
|
|
8
|
+
} from "./chunk-N7HCYULH.js";
|
|
6
9
|
import {
|
|
7
10
|
applyCircularLayout,
|
|
8
11
|
applyLinearHorizontalLayout,
|
|
9
12
|
applyLinearVerticalLayout,
|
|
10
13
|
calculateOptimalRadiusPercentage
|
|
11
14
|
} from "./chunk-GQMFBYLK.js";
|
|
15
|
+
import {
|
|
16
|
+
ideaColor
|
|
17
|
+
} from "./chunk-KVSM3APM.js";
|
|
18
|
+
import {
|
|
19
|
+
TOKENS,
|
|
20
|
+
TOPICS
|
|
21
|
+
} from "./chunk-LV3EXUQY.js";
|
|
12
22
|
|
|
13
23
|
// src/parser/visualliParser.ts
|
|
14
24
|
function toVisualliLayer(schemaLayer) {
|
|
@@ -75,6 +85,13 @@ function parseVisualliFile(content) {
|
|
|
75
85
|
}
|
|
76
86
|
break;
|
|
77
87
|
}
|
|
88
|
+
case "extension": {
|
|
89
|
+
const id = obj["id"];
|
|
90
|
+
if (typeof id === "string") {
|
|
91
|
+
(doc.extensions ?? (doc.extensions = {}))[id] = Array.isArray(obj["data"]) ? obj["data"] : [];
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
78
95
|
default:
|
|
79
96
|
break;
|
|
80
97
|
}
|
|
@@ -112,6 +129,11 @@ function loadVisualliFileFromFile(file) {
|
|
|
112
129
|
reader.readAsText(file);
|
|
113
130
|
});
|
|
114
131
|
}
|
|
132
|
+
function getSemanticAnchors(doc) {
|
|
133
|
+
const raw = doc.extensions?.["semantic-anchors"];
|
|
134
|
+
if (!Array.isArray(raw)) return [];
|
|
135
|
+
return raw.filter((a) => !!a && typeof a.word === "string" && typeof a.description === "string");
|
|
136
|
+
}
|
|
115
137
|
function getChildLayers(doc, nodeId) {
|
|
116
138
|
const result = [];
|
|
117
139
|
for (const layer of doc.layers.values()) {
|
|
@@ -169,26 +191,10 @@ function resolveNodeOverlaps(nodes, maxIterations = 10) {
|
|
|
169
191
|
if (!any) break;
|
|
170
192
|
}
|
|
171
193
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
"#F54A57",
|
|
177
|
-
"#FF6C4D",
|
|
178
|
-
"#F28C16",
|
|
179
|
-
"#FFD347",
|
|
180
|
-
"#12C7D3",
|
|
181
|
-
"#7F7F7F",
|
|
182
|
-
"#8D8D8D",
|
|
183
|
-
"#12C7D3"
|
|
184
|
-
];
|
|
185
|
-
function getRandomColorForNode(nodeId) {
|
|
186
|
-
let hash = 0;
|
|
187
|
-
for (let i = 0; i < nodeId.length; i++) {
|
|
188
|
-
hash = nodeId.charCodeAt(i) + ((hash << 5) - hash);
|
|
189
|
-
}
|
|
190
|
-
const index = Math.abs(hash) % COLOR_PALETTE.length;
|
|
191
|
-
return COLOR_PALETTE[index];
|
|
194
|
+
function hasFilePositions(layer) {
|
|
195
|
+
const ps = layer.nodes.map((n) => n.position);
|
|
196
|
+
if (!ps.length || !ps.every((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y))) return false;
|
|
197
|
+
return ps.length === 1 || ps.some((p) => p.x !== ps[0].x || p.y !== ps[0].y);
|
|
192
198
|
}
|
|
193
199
|
function applyFormation(nodes, formation) {
|
|
194
200
|
if (nodes.length === 0) return;
|
|
@@ -252,44 +258,34 @@ function proxyRadial(proxies, radius = 500) {
|
|
|
252
258
|
proxies[i].y = radius * Math.sin(angle);
|
|
253
259
|
}
|
|
254
260
|
}
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
261
|
+
function makeFlatNode(node, layer, doc, siblingIndex, opts) {
|
|
262
|
+
const label = Array.isArray(node.data.label) ? node.data.label.join(" ") : node.data.label || "Untitled";
|
|
263
|
+
const branchCount = countLayersBeneath(doc, node.id);
|
|
264
|
+
const kind = ideaKindOf(layer, siblingIndex);
|
|
265
|
+
const size = ideaSize(kind, label, opts.measure ?? approximateMeasure, opts.labelScale ?? 1);
|
|
266
|
+
const { topic, custom } = ideaColor(node.data.color, siblingIndex);
|
|
259
267
|
return {
|
|
260
|
-
id:
|
|
268
|
+
id: node.id,
|
|
261
269
|
parentId: layer.parentNodeId ?? null,
|
|
262
|
-
x,
|
|
263
|
-
y,
|
|
270
|
+
x: node.position?.x ?? 0,
|
|
271
|
+
y: node.position?.y ?? 0,
|
|
264
272
|
level: layer.level,
|
|
265
273
|
title: label,
|
|
266
|
-
description: summary,
|
|
267
|
-
color,
|
|
268
|
-
|
|
269
|
-
|
|
274
|
+
description: node.data.summary || "",
|
|
275
|
+
color: node.data.color ?? "",
|
|
276
|
+
...topic ? { topic } : {},
|
|
277
|
+
...custom ? { custom } : {},
|
|
278
|
+
kind,
|
|
279
|
+
width: size.width,
|
|
280
|
+
height: size.height,
|
|
270
281
|
isExpanded: branchCount > 0,
|
|
271
282
|
branchCount,
|
|
272
283
|
createdAt: /* @__PURE__ */ new Date(),
|
|
273
284
|
updatedAt: /* @__PURE__ */ new Date()
|
|
274
285
|
};
|
|
275
286
|
}
|
|
276
|
-
function
|
|
287
|
+
function layoutLayerWithContainers(layer, flatNodes) {
|
|
277
288
|
const PADDING = 80;
|
|
278
|
-
const flatNodes = [];
|
|
279
|
-
for (const node of layer.nodes) {
|
|
280
|
-
const label = Array.isArray(node.data.label) ? node.data.label.join(" ") : node.data.label || "Untitled";
|
|
281
|
-
const branchCount = countLayersBeneath(doc, node.id);
|
|
282
|
-
flatNodes.push(
|
|
283
|
-
makeFlatNode(
|
|
284
|
-
node.id,
|
|
285
|
-
label,
|
|
286
|
-
node.data.summary || "",
|
|
287
|
-
layer,
|
|
288
|
-
node.data.color || getRandomColorForNode(node.id),
|
|
289
|
-
branchCount
|
|
290
|
-
)
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
289
|
const byId = new Map(flatNodes.map((n) => [n.id, n]));
|
|
294
290
|
const containerNodeIds = /* @__PURE__ */ new Set();
|
|
295
291
|
const containerGroups = /* @__PURE__ */ new Map();
|
|
@@ -302,11 +298,8 @@ function convertLayerWithContainers(layer, doc) {
|
|
|
302
298
|
containerGroups.set(container.id, group);
|
|
303
299
|
}
|
|
304
300
|
const proxies = [];
|
|
305
|
-
for (const
|
|
306
|
-
if (!containerNodeIds.has(
|
|
307
|
-
const nodeWidth = calculateNodeWidth(node.data.label);
|
|
308
|
-
proxies.push({ id: node.id, x: 0, y: 0, width: nodeWidth, height: 80 });
|
|
309
|
-
}
|
|
301
|
+
for (const n of flatNodes) {
|
|
302
|
+
if (!containerNodeIds.has(n.id)) proxies.push({ id: n.id, x: 0, y: 0, width: n.width, height: n.height });
|
|
310
303
|
}
|
|
311
304
|
for (const container of layer.containers) {
|
|
312
305
|
const group = containerGroups.get(container.id);
|
|
@@ -333,54 +326,40 @@ function convertLayerWithContainers(layer, doc) {
|
|
|
333
326
|
}
|
|
334
327
|
}
|
|
335
328
|
}
|
|
336
|
-
resolveNodeOverlaps(flatNodes);
|
|
337
|
-
return flatNodes;
|
|
338
329
|
}
|
|
339
|
-
function
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
flatNodes
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
branchCount,
|
|
355
|
-
0,
|
|
356
|
-
0
|
|
357
|
-
)
|
|
358
|
-
);
|
|
330
|
+
function layoutLayer(layer, flatNodes) {
|
|
331
|
+
const layout = layer.layout || "radial";
|
|
332
|
+
if (layout === "linear-horizontal") {
|
|
333
|
+
applyLinearHorizontalLayout(flatNodes, { centerX: 0, centerY: 0 });
|
|
334
|
+
} else if (layout === "linear-vertical") {
|
|
335
|
+
applyLinearVerticalLayout(flatNodes, { centerX: 0, centerY: 0 });
|
|
336
|
+
} else {
|
|
337
|
+
const pct = calculateOptimalRadiusPercentage(flatNodes.length, 2e3, 2e3, 200, 30);
|
|
338
|
+
applyCircularLayout(flatNodes, {
|
|
339
|
+
radiusPercentage: pct,
|
|
340
|
+
containerWidth: 2e3,
|
|
341
|
+
containerHeight: 2e3,
|
|
342
|
+
centerX: 0,
|
|
343
|
+
centerY: 0
|
|
344
|
+
});
|
|
359
345
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const pct = calculateOptimalRadiusPercentage(flatNodes.length, 2e3, 2e3, 200, 30);
|
|
368
|
-
applyCircularLayout(flatNodes, {
|
|
369
|
-
radiusPercentage: pct,
|
|
370
|
-
containerWidth: 2e3,
|
|
371
|
-
containerHeight: 2e3,
|
|
372
|
-
centerX: 0,
|
|
373
|
-
centerY: 0
|
|
374
|
-
});
|
|
375
|
-
}
|
|
346
|
+
}
|
|
347
|
+
function convertLayerToFlatNodes(layer, doc, opts = {}) {
|
|
348
|
+
const flatNodes = layer.nodes.map((node, i) => makeFlatNode(node, layer, doc, i, opts));
|
|
349
|
+
if (flatNodes.length === 0 || hasFilePositions(layer)) return flatNodes;
|
|
350
|
+
for (const n of flatNodes) {
|
|
351
|
+
n.x = 0;
|
|
352
|
+
n.y = 0;
|
|
376
353
|
}
|
|
354
|
+
if ((layer.containers ?? []).length > 0) layoutLayerWithContainers(layer, flatNodes);
|
|
355
|
+
else layoutLayer(layer, flatNodes);
|
|
377
356
|
resolveNodeOverlaps(flatNodes);
|
|
378
357
|
return flatNodes;
|
|
379
358
|
}
|
|
380
|
-
function convertVisualliToFlatNodes(doc) {
|
|
359
|
+
function convertVisualliToFlatNodes(doc, opts = {}) {
|
|
381
360
|
const map = /* @__PURE__ */ new Map();
|
|
382
361
|
for (const layer of doc.layers.values()) {
|
|
383
|
-
for (const node of convertLayerToFlatNodes(layer, doc)) {
|
|
362
|
+
for (const node of convertLayerToFlatNodes(layer, doc, opts)) {
|
|
384
363
|
map.set(node.id, node);
|
|
385
364
|
}
|
|
386
365
|
}
|
|
@@ -392,9 +371,9 @@ function convertVisualliToFlatNodes(doc) {
|
|
|
392
371
|
}
|
|
393
372
|
return map;
|
|
394
373
|
}
|
|
395
|
-
function getNodesForLayer(doc, layerId) {
|
|
374
|
+
function getNodesForLayer(doc, layerId, opts = {}) {
|
|
396
375
|
const layer = doc.layers.get(layerId);
|
|
397
|
-
return layer ? convertLayerToFlatNodes(layer, doc) : [];
|
|
376
|
+
return layer ? convertLayerToFlatNodes(layer, doc, opts) : [];
|
|
398
377
|
}
|
|
399
378
|
|
|
400
379
|
// src/parser/mindmapUtils.ts
|
|
@@ -446,6 +425,8 @@ function calculateDistance(x1, y1, x2, y2) {
|
|
|
446
425
|
}
|
|
447
426
|
|
|
448
427
|
// src/parser/configUtils.ts
|
|
428
|
+
var levelColor = (level) => TOKENS.light[`topic-${TOPICS[level % TOPICS.length]}`];
|
|
429
|
+
var levelColors = (count) => Object.fromEntries(Array.from({ length: count }, (_, i) => [`level${i}`, levelColor(i)]));
|
|
449
430
|
function createMindMapConfig(nodes, topLevelConnections, title = "Untitled Mindmap") {
|
|
450
431
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
451
432
|
return {
|
|
@@ -459,12 +440,7 @@ function createMindMapConfig(nodes, topLevelConnections, title = "Untitled Mindm
|
|
|
459
440
|
nodes,
|
|
460
441
|
topLevelConnections,
|
|
461
442
|
settings: {
|
|
462
|
-
defaultColors:
|
|
463
|
-
level0: LEVEL_COLORS.level0,
|
|
464
|
-
level1: LEVEL_COLORS.level1,
|
|
465
|
-
level2: LEVEL_COLORS.level2,
|
|
466
|
-
level3: LEVEL_COLORS.level3
|
|
467
|
-
},
|
|
443
|
+
defaultColors: levelColors(4),
|
|
468
444
|
layout: {
|
|
469
445
|
nodeSpacing: LAYOUT_SPACING.nodeSpacing,
|
|
470
446
|
levelSpacing: LAYOUT_SPACING.levelSpacing
|
|
@@ -491,7 +467,7 @@ function generateLevelNodes(parentId, level, maxLevels, xOffset = 0, yOffset = 0
|
|
|
491
467
|
level,
|
|
492
468
|
x,
|
|
493
469
|
y,
|
|
494
|
-
color:
|
|
470
|
+
color: levelColor(level),
|
|
495
471
|
relationshipLabel: level > 0 ? "child" : void 0,
|
|
496
472
|
parent: parentId ?? void 0
|
|
497
473
|
};
|
|
@@ -520,17 +496,7 @@ function generateSampleConfig(maxLevels = 9) {
|
|
|
520
496
|
nodes,
|
|
521
497
|
topLevelConnections,
|
|
522
498
|
settings: {
|
|
523
|
-
defaultColors:
|
|
524
|
-
level0: LEVEL_COLORS.level0,
|
|
525
|
-
level1: LEVEL_COLORS.level1,
|
|
526
|
-
level2: LEVEL_COLORS.level2,
|
|
527
|
-
level3: LEVEL_COLORS.level3,
|
|
528
|
-
level4: LEVEL_COLORS.level4,
|
|
529
|
-
level5: LEVEL_COLORS.level5,
|
|
530
|
-
level6: LEVEL_COLORS.level6,
|
|
531
|
-
level7: LEVEL_COLORS.level7,
|
|
532
|
-
level8: LEVEL_COLORS.level8
|
|
533
|
-
},
|
|
499
|
+
defaultColors: levelColors(9),
|
|
534
500
|
layout: {
|
|
535
501
|
nodeSpacing: LAYOUT_SPACING.nodeSpacing,
|
|
536
502
|
levelSpacing: LAYOUT_SPACING.levelSpacing
|
|
@@ -545,6 +511,7 @@ export {
|
|
|
545
511
|
fetchAndParseVisualli,
|
|
546
512
|
parseVisualliFromMongoDB,
|
|
547
513
|
loadVisualliFileFromFile,
|
|
514
|
+
getSemanticAnchors,
|
|
548
515
|
getChildLayers,
|
|
549
516
|
countLayersBeneath,
|
|
550
517
|
resolveNodeOverlaps,
|
|
@@ -559,4 +526,4 @@ export {
|
|
|
559
526
|
createMindMapConfig,
|
|
560
527
|
generateSampleConfig
|
|
561
528
|
};
|
|
562
|
-
//# sourceMappingURL=chunk-
|
|
529
|
+
//# sourceMappingURL=chunk-7NJWM7ZE.js.map
|