@behindthemusictree/genre-tree-view 5.0.0 → 6.4.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/dist/index.cjs +665 -600
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -19
- package/dist/index.d.ts +29 -19
- package/dist/index.js +684 -619
- package/dist/index.js.map +1 -1
- package/dist/styles.css +173 -69
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -51,7 +51,6 @@ module.exports = __toCommonJS(src_exports);
|
|
|
51
51
|
// src/GenreTree.tsx
|
|
52
52
|
var import_react8 = require("react");
|
|
53
53
|
var d3 = __toESM(require("d3"), 1);
|
|
54
|
-
var import_md6 = require("react-icons/md");
|
|
55
54
|
|
|
56
55
|
// src/NodeHelper.tsx
|
|
57
56
|
var import_server = __toESM(require("react-dom/server"), 1);
|
|
@@ -75,6 +74,9 @@ var MENU_WIDTH = 130;
|
|
|
75
74
|
var PER_TREE_ACCENT_DOT = true;
|
|
76
75
|
var ROOT_TINT_RATIO = 0.08;
|
|
77
76
|
var POP_SECTOR_TINT_RATIO = 0.8;
|
|
77
|
+
var UNDERGROUND_DARKEN_RATIO = 0.62;
|
|
78
|
+
var UNDERGROUND_SECTOR_FILL_OPACITY = 0.15;
|
|
79
|
+
var SCENE_SECTOR_OUTER_RADIUS = 1e5;
|
|
78
80
|
var CENTER_NODE_NAME = "Mainstream Pop";
|
|
79
81
|
var TREE_COLORS = [
|
|
80
82
|
"#4F46E5",
|
|
@@ -156,6 +158,11 @@ function isMainstreamPopLineage(index, nodeId) {
|
|
|
156
158
|
const topRoot = index.ancestorsOf(nodeId)[0] ?? index.byId.get(nodeId);
|
|
157
159
|
return topRoot?.name === CENTER_NODE_NAME;
|
|
158
160
|
}
|
|
161
|
+
function shadeSurface(hex, ratio) {
|
|
162
|
+
const channel = (offset) => parseInt(hex.slice(offset, offset + 2), 16);
|
|
163
|
+
const toHex = (value) => Math.round(value * ratio).toString(16).padStart(2, "0");
|
|
164
|
+
return `#${toHex(channel(1))}${toHex(channel(3))}${toHex(channel(5))}`;
|
|
165
|
+
}
|
|
159
166
|
function getGenreNodeStyle(index, nodeId) {
|
|
160
167
|
if (isMainstreamPopLineage(index, nodeId)) {
|
|
161
168
|
return { fill: tintSurface("#ffffff", POP_SECTOR_TINT_RATIO), textColor: TEXT_COLOR };
|
|
@@ -174,6 +181,9 @@ var MIN_NODE_WIDTH = 180;
|
|
|
174
181
|
var MAX_NODE_WIDTH = 700;
|
|
175
182
|
var MIN_NODE_HEIGHT = 35;
|
|
176
183
|
var MAX_NODE_HEIGHT = 120;
|
|
184
|
+
var NODE_SIZE_REFERENCE_COUNT = 100;
|
|
185
|
+
var HIGH_DENSITY_NODE_MAX_FLOOR_SCALE = 1.5;
|
|
186
|
+
var LOW_DENSITY_DOT_MAX_RADIUS = 70;
|
|
177
187
|
var TOOLBAR_MENU_X_GAP = 4;
|
|
178
188
|
var ACTIONS_OVERLAY_WIDTH = MENU_WIDTH + TOOLBAR_MENU_X_GAP;
|
|
179
189
|
var ACTIONS_OVERLAY_HEIGHT = RECT_BASE_DIMENSIONS.HEIGHT * 7;
|
|
@@ -188,23 +198,47 @@ var HORIZONTAL_SEPARATION_BETWEEN_NODES = (NODE_DIMENSIONS.WIDTH + HORIZONTAL_SE
|
|
|
188
198
|
var VERTICAL_SEPARATION_BETWEEN_NODES = NODE_DIMENSIONS.HEIGHT + VERTICAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
189
199
|
var VERTICAL_ORIENTATION_DEPTH_SEPARATION = VERTICAL_SEPARATION_BETWEEN_NODES * 2 * DEPTH_AXIS_SPACING_FACTOR;
|
|
190
200
|
var SIBLING_SEPARATION_BETWEEN_NODES = MAX_NODE_WIDTH + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
191
|
-
function
|
|
192
|
-
|
|
201
|
+
function getDensityAdjustedMax(baseMax, min, nodeCount) {
|
|
202
|
+
const safeNodeCount = Number.isFinite(nodeCount) ? Math.max(1, nodeCount) : 1;
|
|
203
|
+
const availableRangeScale = Math.min(1, NODE_SIZE_REFERENCE_COUNT / safeNodeCount);
|
|
204
|
+
const denseTreeFloor = min * HIGH_DENSITY_NODE_MAX_FLOOR_SCALE;
|
|
205
|
+
return denseTreeFloor + (baseMax - denseTreeFloor) * availableRangeScale;
|
|
206
|
+
}
|
|
207
|
+
function getDensityAdjustedDotMax(nodeCount) {
|
|
208
|
+
const safeNodeCount = Number.isFinite(nodeCount) ? Math.max(1, nodeCount) : 1;
|
|
209
|
+
const inverseDensityRadius = NODE_DOT_MAX_RADIUS * NODE_SIZE_REFERENCE_COUNT / safeNodeCount;
|
|
210
|
+
const denseTreeFloor = NODE_DOT_MIN_RADIUS * HIGH_DENSITY_NODE_MAX_FLOOR_SCALE;
|
|
211
|
+
return Math.max(denseTreeFloor, Math.min(LOW_DENSITY_DOT_MAX_RADIUS, inverseDensityRadius));
|
|
212
|
+
}
|
|
213
|
+
function getItemCountRange(nodes, options = {}) {
|
|
193
214
|
const counts = nodes.map((node) => node.itemCount);
|
|
194
|
-
|
|
215
|
+
const validScaleMax = options.itemCountScaleMax !== void 0 && Number.isFinite(options.itemCountScaleMax) && options.itemCountScaleMax >= 0;
|
|
216
|
+
const nodeCount = options.nodeCountForSizing ?? nodes.length;
|
|
217
|
+
const observedMin = counts.length === 0 ? 0 : Math.min(...counts);
|
|
218
|
+
const observedMax = counts.length === 0 ? 0 : Math.max(...counts);
|
|
219
|
+
const includeDensityCaps = options.nodeCountForSizing !== void 0 || validScaleMax;
|
|
220
|
+
return {
|
|
221
|
+
min: validScaleMax ? 0 : observedMin,
|
|
222
|
+
max: validScaleMax ? options.itemCountScaleMax : observedMax,
|
|
223
|
+
...includeDensityCaps && {
|
|
224
|
+
maxNodeWidth: getDensityAdjustedMax(MAX_NODE_WIDTH, MIN_NODE_WIDTH, nodeCount),
|
|
225
|
+
maxNodeHeight: getDensityAdjustedMax(MAX_NODE_HEIGHT, MIN_NODE_HEIGHT, nodeCount),
|
|
226
|
+
maxDotRadius: getDensityAdjustedDotMax(nodeCount)
|
|
227
|
+
}
|
|
228
|
+
};
|
|
195
229
|
}
|
|
196
230
|
function logarithmicPosition(itemCount, range) {
|
|
197
231
|
const { min, max } = range;
|
|
198
232
|
const logMin = Math.log1p(Math.max(min, 0));
|
|
199
233
|
const logMax = Math.log1p(Math.max(max, 0));
|
|
200
234
|
const logValue = Math.log1p(Math.max(itemCount, 0));
|
|
201
|
-
return logMax > logMin ? (logValue - logMin) / (logMax - logMin) : 0;
|
|
235
|
+
return logMax > logMin ? Math.max(0, Math.min(1, (logValue - logMin) / (logMax - logMin))) : 0;
|
|
202
236
|
}
|
|
203
237
|
function calculateNodeDimensions(itemCount, range) {
|
|
204
238
|
const t = logarithmicPosition(itemCount, range);
|
|
205
239
|
return {
|
|
206
|
-
WIDTH: Math.round(MIN_NODE_WIDTH + t * (MAX_NODE_WIDTH - MIN_NODE_WIDTH)),
|
|
207
|
-
HEIGHT: Math.round(MIN_NODE_HEIGHT + t * (MAX_NODE_HEIGHT - MIN_NODE_HEIGHT))
|
|
240
|
+
WIDTH: Math.round(MIN_NODE_WIDTH + t * ((range.maxNodeWidth ?? MAX_NODE_WIDTH) - MIN_NODE_WIDTH)),
|
|
241
|
+
HEIGHT: Math.round(MIN_NODE_HEIGHT + t * ((range.maxNodeHeight ?? MAX_NODE_HEIGHT) - MIN_NODE_HEIGHT))
|
|
208
242
|
};
|
|
209
243
|
}
|
|
210
244
|
var NODE_MIN_BORDER_WIDTH = 1;
|
|
@@ -244,10 +278,13 @@ function calculateRootLabelFontSize(layoutFontSize, zoomScale) {
|
|
|
244
278
|
return Math.max(layoutFontSize, ROOT_LABEL_MIN_SCREEN_PX / zoomScale);
|
|
245
279
|
}
|
|
246
280
|
var NODE_DOT_MIN_RADIUS = 3;
|
|
247
|
-
var NODE_DOT_MAX_RADIUS =
|
|
281
|
+
var NODE_DOT_MAX_RADIUS = 24;
|
|
248
282
|
var NODE_DOT_LABEL_GAP = 6;
|
|
283
|
+
var NODE_LAYOUT_SLOT_RADIUS = 8;
|
|
249
284
|
function calculateNodeDotRadius(itemCount, range) {
|
|
250
|
-
|
|
285
|
+
const minArea = NODE_DOT_MIN_RADIUS ** 2;
|
|
286
|
+
const maxArea = (range.maxDotRadius ?? NODE_DOT_MAX_RADIUS) ** 2;
|
|
287
|
+
return Math.sqrt(minArea + logarithmicPosition(itemCount, range) * (maxArea - minArea));
|
|
251
288
|
}
|
|
252
289
|
var WHEEL_RADIUS = 260;
|
|
253
290
|
var WHEEL_VIEWPORT_HEIGHT = WHEEL_RADIUS * 2 + MAX_NODE_HEIGHT / 2;
|
|
@@ -281,19 +318,22 @@ var PAN_MIN_VISIBLE_PX = 80;
|
|
|
281
318
|
var PAN_ZOOM_SETTLE_MS = 100;
|
|
282
319
|
var DEFAULT_VIEWPORT_CENTER_GUARD_MS = 1e3;
|
|
283
320
|
var INFO_PANEL_WIDTH = 280;
|
|
321
|
+
var INFO_PANEL_SHEET_MAX_WIDTH = 767;
|
|
322
|
+
var INFO_PANEL_SHEET_PEEK_FRACTION = 0.4;
|
|
323
|
+
var INFO_PANEL_SHEET_DRAG_THRESHOLD_PX = 32;
|
|
284
324
|
|
|
285
325
|
// src/NodeHelper.tsx
|
|
286
326
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
287
327
|
function buildTreeHierarchyStructure(d3Lib, nodes) {
|
|
288
328
|
return d3Lib.stratify().id((d) => d.id).parentId((d) => d.parentId)(nodes);
|
|
289
329
|
}
|
|
290
|
-
function calculateLocalRootDimensions(d3Lib, nodes) {
|
|
330
|
+
function calculateLocalRootDimensions(d3Lib, nodes, options = {}) {
|
|
291
331
|
const root = buildTreeHierarchyStructure(d3Lib, nodes);
|
|
292
|
-
const range = getItemCountRange(root.descendants().map((d) => d.data));
|
|
332
|
+
const range = getItemCountRange(root.descendants().map((d) => d.data), options);
|
|
293
333
|
return calculateNodeDimensions(root.data.itemCount, range);
|
|
294
334
|
}
|
|
295
|
-
function calculateRootAnchorClearance(d3Lib, nodes, rootItemCount, rootItemCountRange, axis) {
|
|
296
|
-
const localExtent = calculateLocalRootDimensions(d3Lib, nodes)[axis];
|
|
335
|
+
function calculateRootAnchorClearance(d3Lib, nodes, rootItemCount, rootItemCountRange, axis, options = {}) {
|
|
336
|
+
const localExtent = calculateLocalRootDimensions(d3Lib, nodes, options)[axis];
|
|
297
337
|
const displayExtent = calculateNodeDimensions(rootItemCount, rootItemCountRange)[axis];
|
|
298
338
|
return (localExtent + displayExtent) / 2;
|
|
299
339
|
}
|
|
@@ -377,13 +417,31 @@ function appendPaths(d3Lib, svg, treeData, itemCountRange, orientation = "horizo
|
|
|
377
417
|
}
|
|
378
418
|
|
|
379
419
|
// src/tree-renderer.ts
|
|
380
|
-
function
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
const
|
|
420
|
+
function getLayoutSpacings(itemCountRange, depthSpacingScale = 1) {
|
|
421
|
+
const maxWidth = itemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH;
|
|
422
|
+
const maxHeight = itemCountRange.maxNodeHeight ?? MAX_NODE_HEIGHT;
|
|
423
|
+
const verticalSeparation = maxHeight + VERTICAL_SEPARATION_BETWEEN_RECTANGLES;
|
|
424
|
+
return {
|
|
425
|
+
horizontalDepth: (maxWidth + ACTIONS_OVERLAY_WIDTH + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES) * DEPTH_AXIS_SPACING_FACTOR * depthSpacingScale,
|
|
426
|
+
verticalBreadth: verticalSeparation,
|
|
427
|
+
verticalDepth: verticalSeparation * 2 * DEPTH_AXIS_SPACING_FACTOR * depthSpacingScale,
|
|
428
|
+
verticalSibling: maxWidth + HORIZONTAL_SEPARATION_BETWEEN_RECTANGLES
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function calculateSvgDimensions(d3Lib, treeData, orientation = "horizontal", hideRoot = false, depthSpacingScale = 1, suppliedItemCountRange) {
|
|
432
|
+
const treeNodes = treeData.descendants();
|
|
433
|
+
const nodes = treeNodes;
|
|
434
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
435
|
+
treeNodes.map((d) => d.data)
|
|
436
|
+
);
|
|
437
|
+
const maxNodeDimensions = {
|
|
438
|
+
WIDTH: itemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH,
|
|
439
|
+
HEIGHT: itemCountRange.maxNodeHeight ?? MAX_NODE_HEIGHT
|
|
440
|
+
};
|
|
384
441
|
const maximumLevel = d3Lib.max(nodes, (d) => d.depth);
|
|
385
|
-
const
|
|
386
|
-
const
|
|
442
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
443
|
+
const depthSeparation = layoutSpacings.verticalDepth;
|
|
444
|
+
const depthSeparationHorizontal = layoutSpacings.horizontalDepth;
|
|
387
445
|
if (isVerticalOrientation(orientation)) {
|
|
388
446
|
const svgHeight2 = hideRoot ? maximumLevel * depthSeparation : maximumLevel * depthSeparation + maxNodeDimensions.HEIGHT + ACTIONS_OVERLAY_HEIGHT / 2;
|
|
389
447
|
const rootWidth = calculateNodeDimensions(
|
|
@@ -445,10 +503,15 @@ function calculateSvgDimensions(d3Lib, treeData, orientation = "horizontal", hid
|
|
|
445
503
|
const svgWidth = maximumLevel * depthSeparationHorizontal + maxNodeDimensions.WIDTH + ACTIONS_OVERLAY_WIDTH;
|
|
446
504
|
return { svgWidth, svgHeight, highestVerticalCoordinate, rootDepthOffset: 0 };
|
|
447
505
|
}
|
|
448
|
-
function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation = "horizontal", rootDepthOffset = 0, svgWidth = 0, depthSpacingScale = 1) {
|
|
506
|
+
function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation = "horizontal", rootDepthOffset = 0, svgWidth = 0, depthSpacingScale = 1, suppliedItemCountRange) {
|
|
507
|
+
const treeNodes = treeData.descendants();
|
|
508
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
509
|
+
treeNodes.map((d) => d.data)
|
|
510
|
+
);
|
|
511
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
449
512
|
if (isVerticalOrientation(orientation)) {
|
|
450
513
|
const maximumLevel = d3Lib.max(treeData.descendants(), (d) => d.depth);
|
|
451
|
-
const depthSeparation =
|
|
514
|
+
const depthSeparation = layoutSpacings.verticalDepth;
|
|
452
515
|
treeData.each(function(d) {
|
|
453
516
|
d.x = d.x + highestVerticalCoordinate;
|
|
454
517
|
d.y = depthAxisSign(orientation) === -1 ? (maximumLevel - d.depth) * depthSeparation : d.depth * depthSeparation + rootDepthOffset;
|
|
@@ -470,24 +533,30 @@ function setupTreeLayout(d3Lib, treeData, highestVerticalCoordinate, orientation
|
|
|
470
533
|
});
|
|
471
534
|
return treeData;
|
|
472
535
|
}
|
|
473
|
-
function createTreeLayout(d3Lib, root, orientation = "horizontal", depthSpacingScale = 1) {
|
|
536
|
+
function createTreeLayout(d3Lib, root, orientation = "horizontal", depthSpacingScale = 1, suppliedItemCountRange) {
|
|
537
|
+
const nodes = root.descendants();
|
|
538
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
539
|
+
nodes.map((d) => d.data)
|
|
540
|
+
);
|
|
541
|
+
const layoutSpacings = getLayoutSpacings(itemCountRange, depthSpacingScale);
|
|
474
542
|
const nodeSize = isVerticalOrientation(orientation) ? [
|
|
475
|
-
|
|
476
|
-
|
|
543
|
+
layoutSpacings.verticalSibling,
|
|
544
|
+
layoutSpacings.verticalDepth
|
|
477
545
|
] : [
|
|
478
|
-
|
|
479
|
-
|
|
546
|
+
layoutSpacings.verticalBreadth,
|
|
547
|
+
layoutSpacings.horizontalDepth
|
|
480
548
|
];
|
|
481
549
|
const treeLayout = d3Lib.tree().nodeSize(nodeSize).separation(() => 1);
|
|
482
550
|
return treeLayout(root);
|
|
483
551
|
}
|
|
484
|
-
function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNodeId, reparentForbiddenIds, rootColor, callbacks, orientation = "horizontal", hideRoot = false, selectedNodeId = null) {
|
|
552
|
+
function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNodeId, reparentForbiddenIds, rootColor, callbacks, orientation = "horizontal", hideRoot = false, selectedNodeId = null, suppliedItemCountRange) {
|
|
485
553
|
const { onReparentTargetSelect, onNodeClick } = callbacks;
|
|
486
554
|
if (!svgRef.current) {
|
|
487
555
|
throw new Error("SVG reference is null");
|
|
488
556
|
}
|
|
489
|
-
const
|
|
490
|
-
|
|
557
|
+
const treeNodes = treeData.descendants();
|
|
558
|
+
const itemCountRange = suppliedItemCountRange ?? getItemCountRange(
|
|
559
|
+
treeNodes.map((d) => d.data)
|
|
491
560
|
);
|
|
492
561
|
const svg = d3Lib.select(svgRef.current).append("svg").attr("width", svgWidth).attr("height", svgHeight).style("overflow", "visible").append("g");
|
|
493
562
|
const safeRootId = treeData.data.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -634,6 +703,48 @@ function renderTree(d3Lib, svgRef, treeData, svgWidth, svgHeight, reparentingNod
|
|
|
634
703
|
return svg;
|
|
635
704
|
}
|
|
636
705
|
|
|
706
|
+
// src/FloatingControls.tsx
|
|
707
|
+
var import_ui = require("@behindthemusictree/ui");
|
|
708
|
+
|
|
709
|
+
// src/BusyIndicator.tsx
|
|
710
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
711
|
+
function BusyIndicator() {
|
|
712
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gtv-busy", role: "status", "aria-live": "polite", children: [
|
|
713
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gtv-busy-ring", "aria-hidden": "true" }),
|
|
714
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gtv-skeleton-sr-only", children: "Updating genre tree\u2026" })
|
|
715
|
+
] });
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/FloatingControls.tsx
|
|
719
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
720
|
+
function FloatingControls({ panZoom, onFit, orientation, zoomControls, leading, busy }) {
|
|
721
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
722
|
+
"div",
|
|
723
|
+
{
|
|
724
|
+
className: [
|
|
725
|
+
"gtv-wheel-floating-controls",
|
|
726
|
+
orientation === "horizontal" && "gtv-wheel-floating-controls--horizontal"
|
|
727
|
+
].filter(Boolean).join(" "),
|
|
728
|
+
children: [
|
|
729
|
+
busy && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BusyIndicator, {}),
|
|
730
|
+
leading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "gtv-floating-controls-leading", children: leading }),
|
|
731
|
+
zoomControls !== "never" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
732
|
+
import_ui.ZoomControls,
|
|
733
|
+
{
|
|
734
|
+
onZoomIn: panZoom.zoomIn,
|
|
735
|
+
onZoomOut: panZoom.zoomOut,
|
|
736
|
+
onFit,
|
|
737
|
+
canZoomIn: panZoom.canZoomIn,
|
|
738
|
+
canZoomOut: panZoom.canZoomOut,
|
|
739
|
+
orientation,
|
|
740
|
+
className: zoomControls === "auto" ? "gtv-zoom-controls gtv-zoom-controls--auto" : "gtv-zoom-controls"
|
|
741
|
+
}
|
|
742
|
+
)
|
|
743
|
+
]
|
|
744
|
+
}
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
637
748
|
// src/use-pan-zoom.ts
|
|
638
749
|
var import_react = require("react");
|
|
639
750
|
|
|
@@ -1032,9 +1143,10 @@ function usePanZoom(viewportRef, wheelZoom = "always", stageRef, options = {}) {
|
|
|
1032
1143
|
const newScale = clampZoomScale(Math.max(targetScale, currentScale), minScaleRef.current);
|
|
1033
1144
|
const centerX = (rect.left + rect.width / 2 - viewportRect.left - view.x) / currentScale;
|
|
1034
1145
|
const centerY = (rect.top + rect.height / 2 - viewportRect.top - view.y) / currentScale;
|
|
1035
|
-
const availableCenterX =
|
|
1146
|
+
const availableCenterX = obscured?.side === "left" ? obscured.width + (viewportRect.width - obscured.width) / 2 : obscured?.side === "right" ? (viewportRect.width - obscured.width) / 2 : viewportRect.width / 2;
|
|
1147
|
+
const availableCenterY = obscured?.side === "bottom" ? (viewportRect.height - obscured.height) / 2 : viewportRect.height / 2;
|
|
1036
1148
|
const targetPanX = availableCenterX - centerX * newScale;
|
|
1037
|
-
const targetPanY =
|
|
1149
|
+
const targetPanY = availableCenterY - centerY * newScale;
|
|
1038
1150
|
zoomAnimationRef.current = null;
|
|
1039
1151
|
if (animationFrameIdRef.current !== null) {
|
|
1040
1152
|
cancelAnimationFrame(animationFrameIdRef.current);
|
|
@@ -1188,6 +1300,9 @@ function resolveInfoPanelSide() {
|
|
|
1188
1300
|
}
|
|
1189
1301
|
function resolveInfoPanelObscuredArea(element, viewport, panelWidth) {
|
|
1190
1302
|
if (!element || !viewport) return null;
|
|
1303
|
+
if (viewport.clientWidth > 0 && viewport.clientWidth <= INFO_PANEL_SHEET_MAX_WIDTH) {
|
|
1304
|
+
return { side: "bottom", height: viewport.clientHeight * INFO_PANEL_SHEET_PEEK_FRACTION };
|
|
1305
|
+
}
|
|
1191
1306
|
return { width: panelWidth, side: resolveInfoPanelSide() };
|
|
1192
1307
|
}
|
|
1193
1308
|
|
|
@@ -1274,9 +1389,9 @@ var import_md4 = require("react-icons/md");
|
|
|
1274
1389
|
|
|
1275
1390
|
// src/InfoPanelExpandButton.tsx
|
|
1276
1391
|
var import_md = require("react-icons/md");
|
|
1277
|
-
var
|
|
1392
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1278
1393
|
function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
|
|
1279
|
-
return /* @__PURE__ */ (0,
|
|
1394
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1280
1395
|
"button",
|
|
1281
1396
|
{
|
|
1282
1397
|
type: "button",
|
|
@@ -1284,7 +1399,7 @@ function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
|
|
|
1284
1399
|
style,
|
|
1285
1400
|
onClick: () => onExpandedChange(!expanded),
|
|
1286
1401
|
"aria-label": expanded ? "Collapse panel" : "Expand panel",
|
|
1287
|
-
children: expanded ? /* @__PURE__ */ (0,
|
|
1402
|
+
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_md.MdCloseFullscreen, { className: "gtv-icon", size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_md.MdOpenInFull, { className: "gtv-icon", size: 16 })
|
|
1288
1403
|
}
|
|
1289
1404
|
);
|
|
1290
1405
|
}
|
|
@@ -1292,11 +1407,11 @@ function InfoPanelExpandButton({ expanded, onExpandedChange, style }) {
|
|
|
1292
1407
|
// src/InfoPanelSection.tsx
|
|
1293
1408
|
var import_react4 = require("react");
|
|
1294
1409
|
var import_md2 = require("react-icons/md");
|
|
1295
|
-
var
|
|
1410
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1296
1411
|
function InfoPanelSection({ title, children }) {
|
|
1297
1412
|
const [open, setOpen] = (0, import_react4.useState)(true);
|
|
1298
|
-
return /* @__PURE__ */ (0,
|
|
1299
|
-
/* @__PURE__ */ (0,
|
|
1413
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "gtv-info-panel-children", children: [
|
|
1414
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1300
1415
|
"button",
|
|
1301
1416
|
{
|
|
1302
1417
|
type: "button",
|
|
@@ -1304,8 +1419,8 @@ function InfoPanelSection({ title, children }) {
|
|
|
1304
1419
|
"aria-expanded": open,
|
|
1305
1420
|
onClick: () => setOpen((isOpen) => !isOpen),
|
|
1306
1421
|
children: [
|
|
1307
|
-
/* @__PURE__ */ (0,
|
|
1308
|
-
/* @__PURE__ */ (0,
|
|
1422
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "gtv-info-panel-children-title", children: title }),
|
|
1423
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_md2.MdExpandMore, { className: "gtv-icon gtv-info-panel-section-chevron", size: 16 })
|
|
1309
1424
|
]
|
|
1310
1425
|
}
|
|
1311
1426
|
),
|
|
@@ -1318,7 +1433,7 @@ var import_react5 = require("react");
|
|
|
1318
1433
|
var import_md3 = require("react-icons/md");
|
|
1319
1434
|
var import_fa = require("react-icons/fa");
|
|
1320
1435
|
var import_pi2 = require("react-icons/pi");
|
|
1321
|
-
var
|
|
1436
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1322
1437
|
function NodeToolbar({
|
|
1323
1438
|
node,
|
|
1324
1439
|
playingNodeId,
|
|
@@ -1339,9 +1454,9 @@ function NodeToolbar({
|
|
|
1339
1454
|
const isPlayable = node.itemCount > 0;
|
|
1340
1455
|
const isThisPlaying = playingNodeId === node.id;
|
|
1341
1456
|
const playLabel = isThisPlaying && playState === "playing" ? "Pause" : isThisPlaying && playState === "loading" ? "Loading..." : "Play";
|
|
1342
|
-
const playIcon = isThisPlaying && playState === "playing" ? /* @__PURE__ */ (0,
|
|
1343
|
-
return /* @__PURE__ */ (0,
|
|
1344
|
-
/* @__PURE__ */ (0,
|
|
1457
|
+
const playIcon = isThisPlaying && playState === "playing" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_fa.FaPause, { className: "gtv-icon", size: "0.6em" }) : isThisPlaying && playState === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_fa.FaSpinner, { className: "gtv-icon gtv-icon--spin", size: "0.6em" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_fa.FaPlay, { className: "gtv-icon", size: "0.6em" });
|
|
1458
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: ["gtv-toolbar", className].filter(Boolean).join(" "), children: [
|
|
1459
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1345
1460
|
"button",
|
|
1346
1461
|
{
|
|
1347
1462
|
type: "button",
|
|
@@ -1353,8 +1468,8 @@ function NodeToolbar({
|
|
|
1353
1468
|
children: playIcon
|
|
1354
1469
|
}
|
|
1355
1470
|
),
|
|
1356
|
-
isActionable && /* @__PURE__ */ (0,
|
|
1357
|
-
/* @__PURE__ */ (0,
|
|
1471
|
+
isActionable && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1472
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1358
1473
|
"button",
|
|
1359
1474
|
{
|
|
1360
1475
|
type: "button",
|
|
@@ -1362,13 +1477,13 @@ function NodeToolbar({
|
|
|
1362
1477
|
title: "Add sub-genre",
|
|
1363
1478
|
"aria-label": "Add sub-genre",
|
|
1364
1479
|
onClick: () => onAddChild?.(node.id),
|
|
1365
|
-
children: /* @__PURE__ */ (0,
|
|
1480
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_fa.FaPlus, { className: "gtv-icon", size: "0.6em" })
|
|
1366
1481
|
}
|
|
1367
1482
|
),
|
|
1368
1483
|
primaryExtraActions.map((action) => {
|
|
1369
1484
|
const enabled = action.enabled ? action.enabled(node) : true;
|
|
1370
1485
|
const label = action.label(node);
|
|
1371
|
-
return /* @__PURE__ */ (0,
|
|
1486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1372
1487
|
"button",
|
|
1373
1488
|
{
|
|
1374
1489
|
type: "button",
|
|
@@ -1382,8 +1497,8 @@ function NodeToolbar({
|
|
|
1382
1497
|
action.key
|
|
1383
1498
|
);
|
|
1384
1499
|
}),
|
|
1385
|
-
/* @__PURE__ */ (0,
|
|
1386
|
-
/* @__PURE__ */ (0,
|
|
1500
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "gtv-toolbar-overflow", children: [
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1387
1502
|
"button",
|
|
1388
1503
|
{
|
|
1389
1504
|
type: "button",
|
|
@@ -1393,11 +1508,11 @@ function NodeToolbar({
|
|
|
1393
1508
|
"aria-haspopup": "menu",
|
|
1394
1509
|
"aria-expanded": menuOpen,
|
|
1395
1510
|
onClick: () => setMenuOpen((open) => !open),
|
|
1396
|
-
children: /* @__PURE__ */ (0,
|
|
1511
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_md3.MdMoreVert, { size: "0.6em" })
|
|
1397
1512
|
}
|
|
1398
1513
|
),
|
|
1399
|
-
menuOpen && /* @__PURE__ */ (0,
|
|
1400
|
-
/* @__PURE__ */ (0,
|
|
1514
|
+
menuOpen && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "gtv-menu-card gtv-toolbar-overflow-menu", children: [
|
|
1515
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1401
1516
|
"button",
|
|
1402
1517
|
{
|
|
1403
1518
|
type: "button",
|
|
@@ -1407,12 +1522,12 @@ function NodeToolbar({
|
|
|
1407
1522
|
onRenameRequest?.(node);
|
|
1408
1523
|
},
|
|
1409
1524
|
children: [
|
|
1410
|
-
/* @__PURE__ */ (0,
|
|
1411
|
-
/* @__PURE__ */ (0,
|
|
1525
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_md3.MdModeEdit, { className: "gtv-icon", size: 13 }) }),
|
|
1526
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-label", children: "Rename" })
|
|
1412
1527
|
]
|
|
1413
1528
|
}
|
|
1414
1529
|
),
|
|
1415
|
-
/* @__PURE__ */ (0,
|
|
1530
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1416
1531
|
"button",
|
|
1417
1532
|
{
|
|
1418
1533
|
type: "button",
|
|
@@ -1422,14 +1537,14 @@ function NodeToolbar({
|
|
|
1422
1537
|
onReparentRequest?.(node);
|
|
1423
1538
|
},
|
|
1424
1539
|
children: [
|
|
1425
|
-
/* @__PURE__ */ (0,
|
|
1426
|
-
/* @__PURE__ */ (0,
|
|
1540
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_pi2.PiGraphFill, { className: "gtv-icon", size: 13 }) }),
|
|
1541
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-label", children: "Change parent" })
|
|
1427
1542
|
]
|
|
1428
1543
|
}
|
|
1429
1544
|
),
|
|
1430
1545
|
overflowExtraActions.map((action) => {
|
|
1431
1546
|
const enabled = action.enabled ? action.enabled(node) : true;
|
|
1432
|
-
return /* @__PURE__ */ (0,
|
|
1547
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1433
1548
|
"button",
|
|
1434
1549
|
{
|
|
1435
1550
|
type: "button",
|
|
@@ -1440,15 +1555,15 @@ function NodeToolbar({
|
|
|
1440
1555
|
action.onClick(event, node);
|
|
1441
1556
|
},
|
|
1442
1557
|
children: [
|
|
1443
|
-
/* @__PURE__ */ (0,
|
|
1444
|
-
/* @__PURE__ */ (0,
|
|
1558
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-icon", children: action.icon(node) }),
|
|
1559
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-label", children: action.label(node) })
|
|
1445
1560
|
]
|
|
1446
1561
|
},
|
|
1447
1562
|
action.key
|
|
1448
1563
|
);
|
|
1449
1564
|
}),
|
|
1450
|
-
/* @__PURE__ */ (0,
|
|
1451
|
-
/* @__PURE__ */ (0,
|
|
1565
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "gtv-menu-divider" }),
|
|
1566
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1452
1567
|
"button",
|
|
1453
1568
|
{
|
|
1454
1569
|
type: "button",
|
|
@@ -1458,8 +1573,8 @@ function NodeToolbar({
|
|
|
1458
1573
|
onDeleteRequest?.(node);
|
|
1459
1574
|
},
|
|
1460
1575
|
children: [
|
|
1461
|
-
/* @__PURE__ */ (0,
|
|
1462
|
-
/* @__PURE__ */ (0,
|
|
1576
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_fa.FaTrashAlt, { className: "gtv-icon", size: 13 }) }),
|
|
1577
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "gtv-menu-row-label", children: "Delete" })
|
|
1463
1578
|
]
|
|
1464
1579
|
}
|
|
1465
1580
|
)
|
|
@@ -1470,7 +1585,7 @@ function NodeToolbar({
|
|
|
1470
1585
|
}
|
|
1471
1586
|
|
|
1472
1587
|
// src/InfoPanel.tsx
|
|
1473
|
-
var
|
|
1588
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1474
1589
|
var MINI_NODE_MIN_DIAMETER = 14;
|
|
1475
1590
|
var MINI_NODE_MAX_DIAMETER = 32;
|
|
1476
1591
|
var MINI_NODE_MIN_FONT_SIZE = 10;
|
|
@@ -1502,10 +1617,6 @@ function InfoPanel({
|
|
|
1502
1617
|
onZoomIn,
|
|
1503
1618
|
actions,
|
|
1504
1619
|
readOnly = false,
|
|
1505
|
-
editMode = false,
|
|
1506
|
-
onEditModeChange,
|
|
1507
|
-
renderTitleEditor,
|
|
1508
|
-
renderParentsEditor,
|
|
1509
1620
|
renderDangerZone,
|
|
1510
1621
|
expanded = false,
|
|
1511
1622
|
onExpandedChange,
|
|
@@ -1513,13 +1624,32 @@ function InfoPanel({
|
|
|
1513
1624
|
hideExpandButton = false
|
|
1514
1625
|
}) {
|
|
1515
1626
|
const panelRef = (0, import_react6.useRef)(null);
|
|
1627
|
+
const [sheetFull, setSheetFull] = (0, import_react6.useState)(false);
|
|
1628
|
+
const grabberDragRef = (0, import_react6.useRef)(null);
|
|
1629
|
+
const onGrabberPointerDown = (event) => {
|
|
1630
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1631
|
+
grabberDragRef.current = { startY: event.clientY, dragged: false };
|
|
1632
|
+
};
|
|
1633
|
+
const onGrabberPointerUp = (event) => {
|
|
1634
|
+
const drag = grabberDragRef.current;
|
|
1635
|
+
if (!drag) return;
|
|
1636
|
+
const dy = event.clientY - drag.startY;
|
|
1637
|
+
if (Math.abs(dy) < INFO_PANEL_SHEET_DRAG_THRESHOLD_PX) return;
|
|
1638
|
+
drag.dragged = true;
|
|
1639
|
+
if (dy < 0) setSheetFull(true);
|
|
1640
|
+
else if (sheetFull) setSheetFull(false);
|
|
1641
|
+
else if (!hideClose) onClose();
|
|
1642
|
+
};
|
|
1643
|
+
const onGrabberClick = () => {
|
|
1644
|
+
const dragged = grabberDragRef.current?.dragged ?? false;
|
|
1645
|
+
grabberDragRef.current = null;
|
|
1646
|
+
if (!dragged) setSheetFull((full) => !full);
|
|
1647
|
+
};
|
|
1516
1648
|
(0, import_react6.useEffect)(() => {
|
|
1517
1649
|
panelRef.current?.toggleAttribute("inert", collapsed);
|
|
1518
1650
|
}, [collapsed]);
|
|
1519
|
-
const editing = editMode && !readOnly;
|
|
1520
|
-
const shownParent = editing && renderParentsEditor ? null : parentNode;
|
|
1521
1651
|
const miniTreeRange = getItemCountRange([
|
|
1522
|
-
...
|
|
1652
|
+
...parentNode ? [parentNode.node] : [],
|
|
1523
1653
|
node,
|
|
1524
1654
|
...childNodes.map((child) => child.node)
|
|
1525
1655
|
]);
|
|
@@ -1533,9 +1663,9 @@ function InfoPanel({
|
|
|
1533
1663
|
};
|
|
1534
1664
|
const miniNodeContent = (target, background, range) => {
|
|
1535
1665
|
const { diameter, fontSize } = miniNodeSize(target, range);
|
|
1536
|
-
return /* @__PURE__ */ (0,
|
|
1537
|
-
/* @__PURE__ */ (0,
|
|
1538
|
-
/* @__PURE__ */ (0,
|
|
1666
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1667
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "gtv-info-panel-mini-dot", style: { background, width: diameter, height: diameter } }),
|
|
1668
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "gtv-info-panel-mini-label", style: { fontSize }, children: target.name })
|
|
1539
1669
|
] });
|
|
1540
1670
|
};
|
|
1541
1671
|
const sortedChildNodes = [...childNodes].sort((a, b) => b.node.itemCount - a.node.itemCount);
|
|
@@ -1548,9 +1678,9 @@ function InfoPanel({
|
|
|
1548
1678
|
document.addEventListener("keydown", onKeyDown);
|
|
1549
1679
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
1550
1680
|
}, [expanded, onExpandedChange]);
|
|
1551
|
-
const dangerZone =
|
|
1552
|
-
const toolbar = actions ? /* @__PURE__ */ (0,
|
|
1553
|
-
const ancestors = ancestorNodes.length > 0 ? /* @__PURE__ */ (0,
|
|
1681
|
+
const dangerZone = !readOnly && renderDangerZone && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "gtv-info-panel-danger-zone", children: renderDangerZone(node) });
|
|
1682
|
+
const toolbar = actions ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NodeToolbar, { node, className: "gtv-info-panel-toolbar", ...actions }, node.id) : null;
|
|
1683
|
+
const ancestors = ancestorNodes.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoPanelSection, { title: "Ancestors", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "gtv-info-panel-children-list gtv-info-panel-ancestors-list", children: ancestorNodes.map(({ node: ancestor, fill: fill2 }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1554
1684
|
"button",
|
|
1555
1685
|
{
|
|
1556
1686
|
type: "button",
|
|
@@ -1560,76 +1690,85 @@ function InfoPanel({
|
|
|
1560
1690
|
children: miniNodeContent(ancestor, fill2, ancestorRange)
|
|
1561
1691
|
}
|
|
1562
1692
|
) }, ancestor.id)) }) }) : null;
|
|
1563
|
-
const family = /* @__PURE__ */ (0,
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1693
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoPanelSection, { title: "Family", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "gtv-info-panel-mini-tree", children: [
|
|
1694
|
+
parentNode && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "gtv-info-panel-mini-tree-parent", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1695
|
+
"button",
|
|
1696
|
+
{
|
|
1697
|
+
type: "button",
|
|
1698
|
+
className: "gtv-info-panel-child",
|
|
1699
|
+
title: parentNode.node.name,
|
|
1700
|
+
onClick: () => onSelectNode(parentNode.node.id),
|
|
1701
|
+
children: miniNodeContent(parentNode.node, parentNode.fill, miniTreeRange)
|
|
1702
|
+
}
|
|
1703
|
+
) }),
|
|
1704
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1705
|
+
"div",
|
|
1706
|
+
{
|
|
1707
|
+
className: `gtv-info-panel-mini-tree-node${childNodes.length === 0 ? " gtv-info-panel-mini-tree-node--leaf" : ""}`,
|
|
1708
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1709
|
+
"span",
|
|
1710
|
+
{
|
|
1711
|
+
className: "gtv-info-panel-child gtv-info-panel-mini-tree-current",
|
|
1712
|
+
title: node.name,
|
|
1713
|
+
children: miniNodeContent(node, fill, miniTreeRange)
|
|
1714
|
+
}
|
|
1715
|
+
)
|
|
1716
|
+
}
|
|
1717
|
+
),
|
|
1718
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "gtv-info-panel-mini-tree-children", children: [
|
|
1719
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
1720
|
+
childNodes.length,
|
|
1721
|
+
" ",
|
|
1722
|
+
childNodes.length <= 1 ? "child" : "children"
|
|
1723
|
+
] }),
|
|
1724
|
+
childNodes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { className: "gtv-info-panel-children-list", children: sortedChildNodes.map(({ node: child, fill: fill2 }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1567
1725
|
"button",
|
|
1568
1726
|
{
|
|
1569
1727
|
type: "button",
|
|
1570
1728
|
className: "gtv-info-panel-child",
|
|
1571
|
-
title:
|
|
1572
|
-
onClick: () => onSelectNode(
|
|
1573
|
-
children: miniNodeContent(
|
|
1574
|
-
}
|
|
1575
|
-
) }),
|
|
1576
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1577
|
-
"div",
|
|
1578
|
-
{
|
|
1579
|
-
className: `gtv-info-panel-mini-tree-node${childNodes.length === 0 ? " gtv-info-panel-mini-tree-node--leaf" : ""}`,
|
|
1580
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1581
|
-
"span",
|
|
1582
|
-
{
|
|
1583
|
-
className: "gtv-info-panel-child gtv-info-panel-mini-tree-current",
|
|
1584
|
-
title: node.name,
|
|
1585
|
-
children: miniNodeContent(node, fill, miniTreeRange)
|
|
1586
|
-
}
|
|
1587
|
-
)
|
|
1729
|
+
title: child.name,
|
|
1730
|
+
onClick: () => onSelectNode(child.id),
|
|
1731
|
+
children: miniNodeContent(child, fill2, miniTreeRange)
|
|
1588
1732
|
}
|
|
1589
|
-
),
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
] }),
|
|
1596
|
-
childNodes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "gtv-info-panel-children-list", children: sortedChildNodes.map(({ node: child, fill: fill2 }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1597
|
-
"button",
|
|
1598
|
-
{
|
|
1599
|
-
type: "button",
|
|
1600
|
-
className: "gtv-info-panel-child",
|
|
1601
|
-
title: child.name,
|
|
1602
|
-
onClick: () => onSelectNode(child.id),
|
|
1603
|
-
children: miniNodeContent(child, fill2, miniTreeRange)
|
|
1604
|
-
}
|
|
1605
|
-
) }, child.id)) })
|
|
1606
|
-
] })
|
|
1607
|
-
] }) })
|
|
1608
|
-
] });
|
|
1609
|
-
const itemCount = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "gtv-info-panel-fields", children: [
|
|
1610
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: itemCountLabel }),
|
|
1611
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: node.itemCount })
|
|
1733
|
+
) }, child.id)) })
|
|
1734
|
+
] })
|
|
1735
|
+
] }) }) });
|
|
1736
|
+
const itemCount = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("dl", { className: "gtv-info-panel-fields", children: [
|
|
1737
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dt", { children: itemCountLabel }),
|
|
1738
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("dd", { children: node.itemCount })
|
|
1612
1739
|
] });
|
|
1613
|
-
const { primary, aside } = renderInfoPanelBody ? renderInfoPanelBody(node, { toolbar, ancestors, family, itemCount }) : { primary: /* @__PURE__ */ (0,
|
|
1740
|
+
const { primary, aside } = renderInfoPanelBody ? renderInfoPanelBody(node, { toolbar, ancestors, family, itemCount }) : { primary: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1614
1741
|
toolbar,
|
|
1615
1742
|
ancestors,
|
|
1616
1743
|
family,
|
|
1617
1744
|
itemCount
|
|
1618
1745
|
] }) };
|
|
1619
1746
|
const hasAside = aside !== void 0 && aside !== null && aside !== false;
|
|
1620
|
-
return /* @__PURE__ */ (0,
|
|
1747
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1621
1748
|
"div",
|
|
1622
1749
|
{
|
|
1623
|
-
className: `gtv-info-panel gtv-info-panel--${side}${expanded ? " gtv-info-panel--expanded" : ""}${expanded && !hasAside ? " gtv-info-panel--expanded-solo" : ""}${collapsed ? " gtv-info-panel--collapsed" : ""}`,
|
|
1750
|
+
className: `gtv-info-panel gtv-info-panel--sheet${sheetFull ? " gtv-info-panel--sheet-full" : ""} gtv-info-panel--${side}${expanded ? " gtv-info-panel--expanded" : ""}${expanded && !hasAside ? " gtv-info-panel--expanded-solo" : ""}${collapsed ? " gtv-info-panel--collapsed" : ""}`,
|
|
1624
1751
|
ref: panelRef,
|
|
1625
1752
|
children: [
|
|
1626
|
-
/* @__PURE__ */ (0,
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1754
|
+
"button",
|
|
1755
|
+
{
|
|
1756
|
+
type: "button",
|
|
1757
|
+
className: "gtv-info-panel-grabber",
|
|
1758
|
+
"aria-label": sheetFull ? "Lower panel" : "Raise panel",
|
|
1759
|
+
"aria-expanded": sheetFull,
|
|
1760
|
+
onPointerDown: onGrabberPointerDown,
|
|
1761
|
+
onPointerUp: onGrabberPointerUp,
|
|
1762
|
+
onClick: onGrabberClick
|
|
1763
|
+
}
|
|
1764
|
+
),
|
|
1765
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1627
1766
|
"div",
|
|
1628
1767
|
{
|
|
1629
1768
|
className: "gtv-info-panel-header",
|
|
1630
1769
|
style: { background: fill, color: textColor },
|
|
1631
1770
|
children: [
|
|
1632
|
-
onPrevious && /* @__PURE__ */ (0,
|
|
1771
|
+
onPrevious && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1633
1772
|
"button",
|
|
1634
1773
|
{
|
|
1635
1774
|
type: "button",
|
|
@@ -1638,21 +1777,21 @@ function InfoPanel({
|
|
|
1638
1777
|
onClick: onPrevious,
|
|
1639
1778
|
"aria-label": previousLabel ? `Previous: ${previousLabel}` : "Previous",
|
|
1640
1779
|
title: previousLabel,
|
|
1641
|
-
children: /* @__PURE__ */ (0,
|
|
1780
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md4.MdArrowBack, { className: "gtv-icon", size: 16 })
|
|
1642
1781
|
}
|
|
1643
1782
|
),
|
|
1644
|
-
|
|
1783
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1645
1784
|
"span",
|
|
1646
1785
|
{
|
|
1647
1786
|
className: `gtv-info-panel-title${hideTitle ? " gtv-info-panel-title--hidden" : ""}`,
|
|
1648
1787
|
style: { color: textColor },
|
|
1649
1788
|
children: [
|
|
1650
1789
|
node.name,
|
|
1651
|
-
node.umbrella && /* @__PURE__ */ (0,
|
|
1790
|
+
node.umbrella && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "gtv-info-panel-umbrella-badge", children: "umbrella" })
|
|
1652
1791
|
]
|
|
1653
1792
|
}
|
|
1654
1793
|
),
|
|
1655
|
-
onZoomIn && /* @__PURE__ */ (0,
|
|
1794
|
+
onZoomIn && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1656
1795
|
"button",
|
|
1657
1796
|
{
|
|
1658
1797
|
type: "button",
|
|
@@ -1661,22 +1800,11 @@ function InfoPanel({
|
|
|
1661
1800
|
onClick: onZoomIn,
|
|
1662
1801
|
"aria-label": "Zoom in",
|
|
1663
1802
|
title: "Zoom in",
|
|
1664
|
-
children: /* @__PURE__ */ (0,
|
|
1665
|
-
}
|
|
1666
|
-
),
|
|
1667
|
-
onEditModeChange && !readOnly && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1668
|
-
"button",
|
|
1669
|
-
{
|
|
1670
|
-
type: "button",
|
|
1671
|
-
className: "gtv-info-panel-edit",
|
|
1672
|
-
style: { color: textColor },
|
|
1673
|
-
onClick: () => onEditModeChange(!editMode),
|
|
1674
|
-
"aria-pressed": editMode,
|
|
1675
|
-
children: editMode ? "Done" : "Edit"
|
|
1803
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md4.MdZoomIn, { className: "gtv-icon", size: 16 })
|
|
1676
1804
|
}
|
|
1677
1805
|
),
|
|
1678
|
-
onExpandedChange && !hideExpandButton && /* @__PURE__ */ (0,
|
|
1679
|
-
!hideClose && /* @__PURE__ */ (0,
|
|
1806
|
+
onExpandedChange && !hideExpandButton && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoPanelExpandButton, { expanded, onExpandedChange, style: { color: textColor } }),
|
|
1807
|
+
!hideClose && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1680
1808
|
"button",
|
|
1681
1809
|
{
|
|
1682
1810
|
type: "button",
|
|
@@ -1684,18 +1812,18 @@ function InfoPanel({
|
|
|
1684
1812
|
style: { color: textColor },
|
|
1685
1813
|
onClick: onClose,
|
|
1686
1814
|
"aria-label": "Close",
|
|
1687
|
-
children: /* @__PURE__ */ (0,
|
|
1815
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md4.MdClose, { className: "gtv-icon", size: 16 })
|
|
1688
1816
|
}
|
|
1689
1817
|
)
|
|
1690
1818
|
]
|
|
1691
1819
|
}
|
|
1692
1820
|
),
|
|
1693
|
-
/* @__PURE__ */ (0,
|
|
1694
|
-
/* @__PURE__ */ (0,
|
|
1821
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "gtv-info-panel-body", children: [
|
|
1822
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "gtv-info-panel-primary", children: [
|
|
1695
1823
|
primary,
|
|
1696
1824
|
expanded && !hasAside && dangerZone
|
|
1697
1825
|
] }),
|
|
1698
|
-
hasAside && /* @__PURE__ */ (0,
|
|
1826
|
+
hasAside && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "gtv-info-panel-aside", children: [
|
|
1699
1827
|
aside,
|
|
1700
1828
|
expanded && dangerZone
|
|
1701
1829
|
] })
|
|
@@ -1709,7 +1837,7 @@ function InfoPanel({
|
|
|
1709
1837
|
// src/OverlayPanel.tsx
|
|
1710
1838
|
var import_react7 = require("react");
|
|
1711
1839
|
var import_md5 = require("react-icons/md");
|
|
1712
|
-
var
|
|
1840
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1713
1841
|
function OverlayPanel({
|
|
1714
1842
|
title,
|
|
1715
1843
|
content,
|
|
@@ -1744,21 +1872,21 @@ function OverlayPanel({
|
|
|
1744
1872
|
expanded && !extraContent && "gtv-info-panel--expanded-solo",
|
|
1745
1873
|
collapsed && "gtv-info-panel--collapsed"
|
|
1746
1874
|
].filter(Boolean).join(" ");
|
|
1747
|
-
return /* @__PURE__ */ (0,
|
|
1748
|
-
/* @__PURE__ */ (0,
|
|
1749
|
-
/* @__PURE__ */ (0,
|
|
1750
|
-
onExpandedChange && !hideExpandButton && /* @__PURE__ */ (0,
|
|
1751
|
-
!hideClose && /* @__PURE__ */ (0,
|
|
1875
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className, ref: panelRef, children: [
|
|
1876
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "gtv-info-panel-header", children: [
|
|
1877
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: `gtv-info-panel-title${hideTitle ? " gtv-info-panel-title--hidden" : ""}`, children: title }),
|
|
1878
|
+
onExpandedChange && !hideExpandButton && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(InfoPanelExpandButton, { expanded, onExpandedChange }),
|
|
1879
|
+
!hideClose && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "gtv-info-panel-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_md5.MdClose, { className: "gtv-icon", size: 16 }) })
|
|
1752
1880
|
] }),
|
|
1753
|
-
/* @__PURE__ */ (0,
|
|
1754
|
-
/* @__PURE__ */ (0,
|
|
1755
|
-
extraContent && /* @__PURE__ */ (0,
|
|
1881
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "gtv-info-panel-body", children: [
|
|
1882
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "gtv-info-panel-aside gtv-overlay-content", children: content }),
|
|
1883
|
+
extraContent && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "gtv-info-panel-primary gtv-overlay-content", children: extraContent })
|
|
1756
1884
|
] })
|
|
1757
1885
|
] });
|
|
1758
1886
|
}
|
|
1759
1887
|
|
|
1760
1888
|
// src/GenreTree.tsx
|
|
1761
|
-
var
|
|
1889
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1762
1890
|
function GenreTree({
|
|
1763
1891
|
nodes,
|
|
1764
1892
|
className,
|
|
@@ -1785,11 +1913,9 @@ function GenreTree({
|
|
|
1785
1913
|
hideInfoPanelClose = false,
|
|
1786
1914
|
hideInfoPanelTitle = false,
|
|
1787
1915
|
itemCountLabel,
|
|
1916
|
+
itemCountScaleMax,
|
|
1917
|
+
nodeCountForSizing,
|
|
1788
1918
|
readOnly,
|
|
1789
|
-
editMode,
|
|
1790
|
-
onEditModeChange,
|
|
1791
|
-
renderTitleEditor,
|
|
1792
|
-
renderParentsEditor,
|
|
1793
1919
|
renderDangerZone,
|
|
1794
1920
|
expanded,
|
|
1795
1921
|
onExpandedChange,
|
|
@@ -1799,7 +1925,10 @@ function GenreTree({
|
|
|
1799
1925
|
wheelZoom,
|
|
1800
1926
|
defaultViewport,
|
|
1801
1927
|
onViewportChange,
|
|
1802
|
-
controlsOrientation = "vertical"
|
|
1928
|
+
controlsOrientation = "vertical",
|
|
1929
|
+
showZoomControls = "auto",
|
|
1930
|
+
floatingControlsLeading,
|
|
1931
|
+
busy
|
|
1803
1932
|
}) {
|
|
1804
1933
|
const index = useTreeIndex(nodes);
|
|
1805
1934
|
const svgRef = (0, import_react8.useRef)(null);
|
|
@@ -1807,6 +1936,10 @@ function GenreTree({
|
|
|
1807
1936
|
const stageRef = (0, import_react8.useRef)(null);
|
|
1808
1937
|
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, onViewportChange });
|
|
1809
1938
|
const { panel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId: selectedNodeIdProp, onSelectedNodeChange });
|
|
1939
|
+
const itemCountRange = (0, import_react8.useMemo)(
|
|
1940
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: nodeCountForSizing ?? nodes.length }),
|
|
1941
|
+
[nodes, itemCountScaleMax, nodeCountForSizing]
|
|
1942
|
+
);
|
|
1810
1943
|
const showNodeInfoRef = (0, import_react8.useRef)(showNodeInfo);
|
|
1811
1944
|
(0, import_react8.useEffect)(() => {
|
|
1812
1945
|
showNodeInfoRef.current = showNodeInfo;
|
|
@@ -1821,7 +1954,8 @@ function GenreTree({
|
|
|
1821
1954
|
d3,
|
|
1822
1955
|
root,
|
|
1823
1956
|
orientation,
|
|
1824
|
-
depthSpacingScale
|
|
1957
|
+
depthSpacingScale,
|
|
1958
|
+
itemCountRange
|
|
1825
1959
|
);
|
|
1826
1960
|
const {
|
|
1827
1961
|
svgWidth: width,
|
|
@@ -1833,7 +1967,8 @@ function GenreTree({
|
|
|
1833
1967
|
originalTreeData,
|
|
1834
1968
|
orientation,
|
|
1835
1969
|
hideRoot,
|
|
1836
|
-
depthSpacingScale
|
|
1970
|
+
depthSpacingScale,
|
|
1971
|
+
itemCountRange
|
|
1837
1972
|
);
|
|
1838
1973
|
const reshapedTreeData = setupTreeLayout(
|
|
1839
1974
|
d3,
|
|
@@ -1842,7 +1977,8 @@ function GenreTree({
|
|
|
1842
1977
|
orientation,
|
|
1843
1978
|
rootDepthOffset,
|
|
1844
1979
|
width,
|
|
1845
|
-
depthSpacingScale
|
|
1980
|
+
depthSpacingScale,
|
|
1981
|
+
itemCountRange
|
|
1846
1982
|
);
|
|
1847
1983
|
return {
|
|
1848
1984
|
treeData: reshapedTreeData,
|
|
@@ -1850,7 +1986,7 @@ function GenreTree({
|
|
|
1850
1986
|
svgWidth: width,
|
|
1851
1987
|
svgHeight: height
|
|
1852
1988
|
};
|
|
1853
|
-
}, [nodes, rootColor, orientation, hideRoot, depthSpacingScale]);
|
|
1989
|
+
}, [nodes, rootColor, orientation, hideRoot, depthSpacingScale, itemCountRange]);
|
|
1854
1990
|
const reparentForbiddenIds = (0, import_react8.useMemo)(() => {
|
|
1855
1991
|
if (!reparentingNodeId) return [];
|
|
1856
1992
|
const reparentingHierarchyNode = treeData.descendants().find((d) => d.data.id === reparentingNodeId);
|
|
@@ -1901,7 +2037,8 @@ function GenreTree({
|
|
|
1901
2037
|
},
|
|
1902
2038
|
orientation,
|
|
1903
2039
|
hideRoot,
|
|
1904
|
-
effectiveSelectedNodeId
|
|
2040
|
+
effectiveSelectedNodeId,
|
|
2041
|
+
itemCountRange
|
|
1905
2042
|
);
|
|
1906
2043
|
}, [
|
|
1907
2044
|
treeData,
|
|
@@ -1922,7 +2059,8 @@ function GenreTree({
|
|
|
1922
2059
|
additionalActions,
|
|
1923
2060
|
orientation,
|
|
1924
2061
|
hideRoot,
|
|
1925
|
-
effectiveSelectedNodeId
|
|
2062
|
+
effectiveSelectedNodeId,
|
|
2063
|
+
itemCountRange
|
|
1926
2064
|
]);
|
|
1927
2065
|
(0, import_react8.useEffect)(() => {
|
|
1928
2066
|
if (!interactive || !selectedNodeIdProp) return;
|
|
@@ -1955,7 +2093,7 @@ function GenreTree({
|
|
|
1955
2093
|
hasInitialFitRef.current = true;
|
|
1956
2094
|
panZoom.fitToFrame(elements, { silent: true });
|
|
1957
2095
|
});
|
|
1958
|
-
const svg = /* @__PURE__ */ (0,
|
|
2096
|
+
const svg = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1959
2097
|
"svg",
|
|
1960
2098
|
{
|
|
1961
2099
|
ref: svgRef,
|
|
@@ -1965,13 +2103,14 @@ function GenreTree({
|
|
|
1965
2103
|
}
|
|
1966
2104
|
);
|
|
1967
2105
|
if (!interactive) {
|
|
1968
|
-
return /* @__PURE__ */ (0,
|
|
2106
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className, children: svg });
|
|
1969
2107
|
}
|
|
1970
|
-
return /* @__PURE__ */ (0,
|
|
2108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1971
2109
|
"div",
|
|
1972
2110
|
{
|
|
1973
2111
|
ref: viewportRef,
|
|
1974
2112
|
className,
|
|
2113
|
+
"aria-busy": busy,
|
|
1975
2114
|
style: {
|
|
1976
2115
|
position: "relative",
|
|
1977
2116
|
overflow: "hidden",
|
|
@@ -1994,7 +2133,7 @@ function GenreTree({
|
|
|
1994
2133
|
},
|
|
1995
2134
|
onPointerDown: panZoom.handlePointerDown,
|
|
1996
2135
|
children: [
|
|
1997
|
-
/* @__PURE__ */ (0,
|
|
2136
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1998
2137
|
"div",
|
|
1999
2138
|
{
|
|
2000
2139
|
ref: stageRef,
|
|
@@ -2008,7 +2147,7 @@ function GenreTree({
|
|
|
2008
2147
|
children: svg
|
|
2009
2148
|
}
|
|
2010
2149
|
),
|
|
2011
|
-
panel && /* @__PURE__ */ (0,
|
|
2150
|
+
panel && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2012
2151
|
InfoPanel,
|
|
2013
2152
|
{
|
|
2014
2153
|
node: panel.node,
|
|
@@ -2058,10 +2197,6 @@ function GenreTree({
|
|
|
2058
2197
|
hideTitle: hideInfoPanelTitle,
|
|
2059
2198
|
itemCountLabel,
|
|
2060
2199
|
readOnly,
|
|
2061
|
-
editMode,
|
|
2062
|
-
onEditModeChange,
|
|
2063
|
-
renderTitleEditor,
|
|
2064
|
-
renderParentsEditor,
|
|
2065
2200
|
renderDangerZone,
|
|
2066
2201
|
expanded,
|
|
2067
2202
|
onExpandedChange,
|
|
@@ -2080,54 +2215,16 @@ function GenreTree({
|
|
|
2080
2215
|
} : void 0
|
|
2081
2216
|
}
|
|
2082
2217
|
),
|
|
2083
|
-
overlayPanel && /* @__PURE__ */ (0,
|
|
2084
|
-
/* @__PURE__ */ (0,
|
|
2085
|
-
|
|
2218
|
+
overlayPanel && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OverlayPanel, { ...overlayPanel }),
|
|
2219
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2220
|
+
FloatingControls,
|
|
2086
2221
|
{
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
"button",
|
|
2094
|
-
{
|
|
2095
|
-
type: "button",
|
|
2096
|
-
className: [
|
|
2097
|
-
"gtv-zoom-btn",
|
|
2098
|
-
!panZoom.canZoomIn && "gtv-zoom-btn--disabled"
|
|
2099
|
-
].filter(Boolean).join(" "),
|
|
2100
|
-
disabled: !panZoom.canZoomIn,
|
|
2101
|
-
onClick: panZoom.zoomIn,
|
|
2102
|
-
"aria-label": "Zoom in",
|
|
2103
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md6.MdZoomIn, { className: "gtv-icon", size: 18 })
|
|
2104
|
-
}
|
|
2105
|
-
),
|
|
2106
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2107
|
-
"button",
|
|
2108
|
-
{
|
|
2109
|
-
type: "button",
|
|
2110
|
-
className: [
|
|
2111
|
-
"gtv-zoom-btn",
|
|
2112
|
-
!panZoom.canZoomOut && "gtv-zoom-btn--disabled"
|
|
2113
|
-
].filter(Boolean).join(" "),
|
|
2114
|
-
disabled: !panZoom.canZoomOut,
|
|
2115
|
-
onClick: panZoom.zoomOut,
|
|
2116
|
-
"aria-label": "Zoom out",
|
|
2117
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md6.MdZoomOut, { className: "gtv-icon", size: 18 })
|
|
2118
|
-
}
|
|
2119
|
-
),
|
|
2120
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2121
|
-
"button",
|
|
2122
|
-
{
|
|
2123
|
-
type: "button",
|
|
2124
|
-
className: "gtv-zoom-btn",
|
|
2125
|
-
onClick: () => panZoom.fitToFrame(queryTreeContentElements(svgRef.current)),
|
|
2126
|
-
"aria-label": "Fit to frame",
|
|
2127
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_md6.MdFitScreen, { className: "gtv-icon", size: 18 })
|
|
2128
|
-
}
|
|
2129
|
-
)
|
|
2130
|
-
] })
|
|
2222
|
+
panZoom,
|
|
2223
|
+
orientation: controlsOrientation,
|
|
2224
|
+
zoomControls: showZoomControls,
|
|
2225
|
+
leading: floatingControlsLeading,
|
|
2226
|
+
busy,
|
|
2227
|
+
onFit: () => panZoom.fitToFrame(queryTreeContentElements(svgRef.current))
|
|
2131
2228
|
}
|
|
2132
2229
|
)
|
|
2133
2230
|
]
|
|
@@ -2137,7 +2234,6 @@ function GenreTree({
|
|
|
2137
2234
|
|
|
2138
2235
|
// src/GenreTreeWheelBase.tsx
|
|
2139
2236
|
var import_react9 = require("react");
|
|
2140
|
-
var import_md7 = require("react-icons/md");
|
|
2141
2237
|
var d32 = __toESM(require("d3"), 1);
|
|
2142
2238
|
|
|
2143
2239
|
// src/root-grouping.ts
|
|
@@ -2213,7 +2309,7 @@ function computeRotationForSelection(currentRotationDeg, targetAngle, landingAng
|
|
|
2213
2309
|
}
|
|
2214
2310
|
|
|
2215
2311
|
// src/GenreTreeWheelBase.tsx
|
|
2216
|
-
var
|
|
2312
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2217
2313
|
function WheelCore({
|
|
2218
2314
|
nodes,
|
|
2219
2315
|
className,
|
|
@@ -2238,11 +2334,9 @@ function WheelCore({
|
|
|
2238
2334
|
hideInfoPanelClose = false,
|
|
2239
2335
|
hideInfoPanelTitle = false,
|
|
2240
2336
|
itemCountLabel,
|
|
2337
|
+
itemCountScaleMax,
|
|
2338
|
+
nodeCountForSizing,
|
|
2241
2339
|
readOnly,
|
|
2242
|
-
editMode,
|
|
2243
|
-
onEditModeChange,
|
|
2244
|
-
renderTitleEditor,
|
|
2245
|
-
renderParentsEditor,
|
|
2246
2340
|
renderDangerZone,
|
|
2247
2341
|
expanded,
|
|
2248
2342
|
onExpandedChange,
|
|
@@ -2253,8 +2347,16 @@ function WheelCore({
|
|
|
2253
2347
|
defaultViewport,
|
|
2254
2348
|
onViewportChange,
|
|
2255
2349
|
controlsOrientation = "vertical",
|
|
2350
|
+
showZoomControls = "auto",
|
|
2351
|
+
floatingControlsLeading,
|
|
2352
|
+
busy,
|
|
2256
2353
|
selectedNodeId
|
|
2257
2354
|
}) {
|
|
2355
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
2356
|
+
const wheelSizeRange = (0, import_react9.useMemo)(
|
|
2357
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
2358
|
+
[nodes, itemCountScaleMax, fullNodeCountForSizing]
|
|
2359
|
+
);
|
|
2258
2360
|
const treeOrientation = direction === "left" ? "horizontal-anchored" : "vertical";
|
|
2259
2361
|
const landingAngle = direction === "left" ? 90 : 0;
|
|
2260
2362
|
const chipHalfExtentVar = direction === "left" ? "--gtv-wheel-chip-half-width" : "--gtv-wheel-chip-half-height";
|
|
@@ -2264,10 +2366,10 @@ function WheelCore({
|
|
|
2264
2366
|
return getGenreTreeColor(nodes.find((n) => n.id === rootId)?.wikidataId);
|
|
2265
2367
|
};
|
|
2266
2368
|
const wheelRadius = (0, import_react9.useMemo)(
|
|
2267
|
-
() => calculateWheelRadius(groups.length, MAX_NODE_WIDTH, WHEEL_RADIUS),
|
|
2268
|
-
[groups.length]
|
|
2369
|
+
() => calculateWheelRadius(groups.length, wheelSizeRange.maxNodeWidth ?? MAX_NODE_WIDTH, WHEEL_RADIUS),
|
|
2370
|
+
[groups.length, wheelSizeRange.maxNodeWidth]
|
|
2269
2371
|
);
|
|
2270
|
-
const wheelViewportHeight = wheelRadius * 2 + MAX_NODE_HEIGHT / 2;
|
|
2372
|
+
const wheelViewportHeight = wheelRadius * 2 + (wheelSizeRange.maxNodeHeight ?? MAX_NODE_HEIGHT) / 2;
|
|
2271
2373
|
const rootItemCountById = (0, import_react9.useMemo)(
|
|
2272
2374
|
() => new Map(groups.map((group) => [group.root.id, group.root.itemCount])),
|
|
2273
2375
|
[groups]
|
|
@@ -2314,16 +2416,18 @@ function WheelCore({
|
|
|
2314
2416
|
() => getItemCountRange(
|
|
2315
2417
|
groups.map((group) => ({
|
|
2316
2418
|
itemCount: rootItemCountById.get(group.root.id)
|
|
2317
|
-
}))
|
|
2419
|
+
})),
|
|
2420
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
2318
2421
|
),
|
|
2319
|
-
[groups, rootItemCountById]
|
|
2422
|
+
[groups, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
2320
2423
|
);
|
|
2321
2424
|
const rootChipHalfExtent = selectedGroup ? calculateRootAnchorClearance(
|
|
2322
2425
|
d32,
|
|
2323
2426
|
selectedGroup.nodes,
|
|
2324
2427
|
rootItemCountById.get(selectedGroup.root.id),
|
|
2325
2428
|
rootItemCountRange,
|
|
2326
|
-
direction === "left" ? "WIDTH" : "HEIGHT"
|
|
2429
|
+
direction === "left" ? "WIDTH" : "HEIGHT",
|
|
2430
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
2327
2431
|
) : 0;
|
|
2328
2432
|
const sectorFillGradient = (0, import_react9.useMemo)(
|
|
2329
2433
|
() => buildWheelSectorGradient(
|
|
@@ -2368,10 +2472,11 @@ function WheelCore({
|
|
|
2368
2472
|
);
|
|
2369
2473
|
showNodeInfo(targetNode, element, viewportRef.current, { notify: false });
|
|
2370
2474
|
}, [selectedNodeId, effectiveRootId, nodes, panel, showNodeInfo]);
|
|
2371
|
-
return /* @__PURE__ */ (0,
|
|
2475
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2372
2476
|
"div",
|
|
2373
2477
|
{
|
|
2374
2478
|
ref: viewportRef,
|
|
2479
|
+
"aria-busy": busy,
|
|
2375
2480
|
className: [
|
|
2376
2481
|
"gtv-wheel-container",
|
|
2377
2482
|
direction === "left" && "gtv-wheel-container--left",
|
|
@@ -2386,7 +2491,7 @@ function WheelCore({
|
|
|
2386
2491
|
},
|
|
2387
2492
|
onPointerDown: panZoom.handlePointerDown,
|
|
2388
2493
|
children: [
|
|
2389
|
-
/* @__PURE__ */ (0,
|
|
2494
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2390
2495
|
"div",
|
|
2391
2496
|
{
|
|
2392
2497
|
ref: stageRef,
|
|
@@ -2399,7 +2504,7 @@ function WheelCore({
|
|
|
2399
2504
|
transform: panZoom.transform,
|
|
2400
2505
|
transformOrigin: "0 0"
|
|
2401
2506
|
},
|
|
2402
|
-
children: /* @__PURE__ */ (0,
|
|
2507
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2403
2508
|
"div",
|
|
2404
2509
|
{
|
|
2405
2510
|
className: "gtv-wheel-stage",
|
|
@@ -2407,11 +2512,13 @@ function WheelCore({
|
|
|
2407
2512
|
[chipHalfExtentVar]: `${rootChipHalfExtent}px`
|
|
2408
2513
|
},
|
|
2409
2514
|
children: [
|
|
2410
|
-
selectedGroup && /* @__PURE__ */ (0,
|
|
2515
|
+
selectedGroup && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "gtv-wheel-tree-anchor", ref: treeAnchorRef, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2411
2516
|
GenreTree,
|
|
2412
2517
|
{
|
|
2413
2518
|
nodes: selectedGroup.nodes,
|
|
2414
2519
|
itemCountLabel,
|
|
2520
|
+
itemCountScaleMax,
|
|
2521
|
+
nodeCountForSizing: fullNodeCountForSizing,
|
|
2415
2522
|
orientation: treeOrientation,
|
|
2416
2523
|
hideRoot: true,
|
|
2417
2524
|
interactive: false,
|
|
@@ -2445,9 +2552,9 @@ function WheelCore({
|
|
|
2445
2552
|
},
|
|
2446
2553
|
selectedGroup.root.id
|
|
2447
2554
|
) }),
|
|
2448
|
-
/* @__PURE__ */ (0,
|
|
2449
|
-
centerLabel && /* @__PURE__ */ (0,
|
|
2450
|
-
/* @__PURE__ */ (0,
|
|
2555
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
|
|
2556
|
+
centerLabel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "gtv-wheel-center-label", children: centerLabel }),
|
|
2557
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2451
2558
|
"div",
|
|
2452
2559
|
{
|
|
2453
2560
|
className: "gtv-wheel",
|
|
@@ -2455,7 +2562,7 @@ function WheelCore({
|
|
|
2455
2562
|
"--gtv-wheel-rotation": `${rotationDeg}deg`
|
|
2456
2563
|
},
|
|
2457
2564
|
children: [
|
|
2458
|
-
sectorFillGradient && /* @__PURE__ */ (0,
|
|
2565
|
+
sectorFillGradient && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2459
2566
|
"div",
|
|
2460
2567
|
{
|
|
2461
2568
|
className: "gtv-wheel-sector-fill",
|
|
@@ -2464,7 +2571,7 @@ function WheelCore({
|
|
|
2464
2571
|
}
|
|
2465
2572
|
}
|
|
2466
2573
|
),
|
|
2467
|
-
groups.length > 1 && groups.map((group, index) => /* @__PURE__ */ (0,
|
|
2574
|
+
groups.length > 1 && groups.map((group, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2468
2575
|
"div",
|
|
2469
2576
|
{
|
|
2470
2577
|
className: "gtv-wheel-divider",
|
|
@@ -2487,12 +2594,12 @@ function WheelCore({
|
|
|
2487
2594
|
rootItemCount,
|
|
2488
2595
|
rootItemCountRange
|
|
2489
2596
|
);
|
|
2490
|
-
return /* @__PURE__ */ (0,
|
|
2597
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2491
2598
|
"div",
|
|
2492
2599
|
{
|
|
2493
2600
|
className: "gtv-wheel-slot",
|
|
2494
2601
|
style: { "--gtv-chip-angle": `${angle}deg` },
|
|
2495
|
-
children: /* @__PURE__ */ (0,
|
|
2602
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2496
2603
|
"button",
|
|
2497
2604
|
{
|
|
2498
2605
|
type: "button",
|
|
@@ -2528,8 +2635,8 @@ function WheelCore({
|
|
|
2528
2635
|
}
|
|
2529
2636
|
},
|
|
2530
2637
|
children: [
|
|
2531
|
-
PER_TREE_ACCENT_DOT && /* @__PURE__ */ (0,
|
|
2532
|
-
/* @__PURE__ */ (0,
|
|
2638
|
+
PER_TREE_ACCENT_DOT && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "gtv-wheel-chip-dot" }),
|
|
2639
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2533
2640
|
"span",
|
|
2534
2641
|
{
|
|
2535
2642
|
className: "gtv-node-label gtv-node-label--root",
|
|
@@ -2552,7 +2659,7 @@ function WheelCore({
|
|
|
2552
2659
|
)
|
|
2553
2660
|
}
|
|
2554
2661
|
),
|
|
2555
|
-
panel && /* @__PURE__ */ (0,
|
|
2662
|
+
panel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2556
2663
|
InfoPanel,
|
|
2557
2664
|
{
|
|
2558
2665
|
node: panel.node,
|
|
@@ -2599,10 +2706,6 @@ function WheelCore({
|
|
|
2599
2706
|
hideTitle: hideInfoPanelTitle,
|
|
2600
2707
|
itemCountLabel,
|
|
2601
2708
|
readOnly,
|
|
2602
|
-
editMode,
|
|
2603
|
-
onEditModeChange,
|
|
2604
|
-
renderTitleEditor,
|
|
2605
|
-
renderParentsEditor,
|
|
2606
2709
|
renderDangerZone,
|
|
2607
2710
|
expanded,
|
|
2608
2711
|
onExpandedChange,
|
|
@@ -2621,57 +2724,19 @@ function WheelCore({
|
|
|
2621
2724
|
} : void 0
|
|
2622
2725
|
}
|
|
2623
2726
|
),
|
|
2624
|
-
overlayPanel && /* @__PURE__ */ (0,
|
|
2625
|
-
/* @__PURE__ */ (0,
|
|
2626
|
-
|
|
2727
|
+
overlayPanel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OverlayPanel, { ...overlayPanel }),
|
|
2728
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2729
|
+
FloatingControls,
|
|
2627
2730
|
{
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
className: [
|
|
2638
|
-
"gtv-zoom-btn",
|
|
2639
|
-
!panZoom.canZoomIn && "gtv-zoom-btn--disabled"
|
|
2640
|
-
].filter(Boolean).join(" "),
|
|
2641
|
-
disabled: !panZoom.canZoomIn,
|
|
2642
|
-
onClick: panZoom.zoomIn,
|
|
2643
|
-
"aria-label": "Zoom in",
|
|
2644
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_md7.MdZoomIn, { className: "gtv-icon", size: 18 })
|
|
2645
|
-
}
|
|
2646
|
-
),
|
|
2647
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2648
|
-
"button",
|
|
2649
|
-
{
|
|
2650
|
-
type: "button",
|
|
2651
|
-
className: [
|
|
2652
|
-
"gtv-zoom-btn",
|
|
2653
|
-
!panZoom.canZoomOut && "gtv-zoom-btn--disabled"
|
|
2654
|
-
].filter(Boolean).join(" "),
|
|
2655
|
-
disabled: !panZoom.canZoomOut,
|
|
2656
|
-
onClick: panZoom.zoomOut,
|
|
2657
|
-
"aria-label": "Zoom out",
|
|
2658
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_md7.MdZoomOut, { className: "gtv-icon", size: 18 })
|
|
2659
|
-
}
|
|
2660
|
-
),
|
|
2661
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2662
|
-
"button",
|
|
2663
|
-
{
|
|
2664
|
-
type: "button",
|
|
2665
|
-
className: "gtv-zoom-btn",
|
|
2666
|
-
onClick: () => panZoom.fitToFrame([
|
|
2667
|
-
wheelCircleRef.current,
|
|
2668
|
-
...queryTreeContentElements(treeAnchorRef.current)
|
|
2669
|
-
]),
|
|
2670
|
-
"aria-label": "Fit to frame",
|
|
2671
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_md7.MdFitScreen, { className: "gtv-icon", size: 18 })
|
|
2672
|
-
}
|
|
2673
|
-
)
|
|
2674
|
-
] })
|
|
2731
|
+
panZoom,
|
|
2732
|
+
orientation: controlsOrientation,
|
|
2733
|
+
zoomControls: showZoomControls,
|
|
2734
|
+
leading: floatingControlsLeading,
|
|
2735
|
+
busy,
|
|
2736
|
+
onFit: () => panZoom.fitToFrame([
|
|
2737
|
+
wheelCircleRef.current,
|
|
2738
|
+
...queryTreeContentElements(treeAnchorRef.current)
|
|
2739
|
+
])
|
|
2675
2740
|
}
|
|
2676
2741
|
)
|
|
2677
2742
|
]
|
|
@@ -2680,37 +2745,50 @@ function WheelCore({
|
|
|
2680
2745
|
}
|
|
2681
2746
|
|
|
2682
2747
|
// src/GenreTreeWheel.tsx
|
|
2683
|
-
var
|
|
2748
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2684
2749
|
function GenreTreeWheel(props) {
|
|
2685
|
-
return /* @__PURE__ */ (0,
|
|
2750
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(WheelCore, { ...props, direction: "bottom" });
|
|
2686
2751
|
}
|
|
2687
2752
|
|
|
2688
2753
|
// src/GenreTreeWheelRight.tsx
|
|
2689
|
-
var
|
|
2754
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2690
2755
|
function GenreTreeWheelRight(props) {
|
|
2691
|
-
return /* @__PURE__ */ (0,
|
|
2756
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(WheelCore, { ...props, direction: "left" });
|
|
2692
2757
|
}
|
|
2693
2758
|
|
|
2694
2759
|
// src/GenreTreeWheelRadialBase.tsx
|
|
2695
2760
|
var import_react10 = require("react");
|
|
2696
|
-
var import_md8 = require("react-icons/md");
|
|
2697
2761
|
var d33 = __toESM(require("d3"), 1);
|
|
2698
2762
|
|
|
2699
2763
|
// src/defer-click.ts
|
|
2700
2764
|
function createDeferredClick() {
|
|
2701
2765
|
let timeoutId = null;
|
|
2766
|
+
let pendingKey = void 0;
|
|
2767
|
+
let doubledAt = -Infinity;
|
|
2702
2768
|
const cancel = () => {
|
|
2703
2769
|
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
2704
2770
|
timeoutId = null;
|
|
2705
2771
|
};
|
|
2706
|
-
const schedule = (action) => {
|
|
2772
|
+
const schedule = (action, onDouble, key) => {
|
|
2773
|
+
if (onDouble && timeoutId !== null && key === pendingKey) {
|
|
2774
|
+
cancel();
|
|
2775
|
+
doubledAt = Date.now();
|
|
2776
|
+
onDouble();
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2707
2779
|
cancel();
|
|
2780
|
+
pendingKey = key;
|
|
2708
2781
|
timeoutId = setTimeout(() => {
|
|
2709
2782
|
timeoutId = null;
|
|
2710
2783
|
action();
|
|
2711
2784
|
}, DOUBLE_CLICK_DELAY_MS);
|
|
2712
2785
|
};
|
|
2713
|
-
|
|
2786
|
+
const doubleClick = (onDouble) => {
|
|
2787
|
+
cancel();
|
|
2788
|
+
if (Date.now() - doubledAt < DOUBLE_CLICK_DELAY_MS) return;
|
|
2789
|
+
onDouble();
|
|
2790
|
+
};
|
|
2791
|
+
return { schedule, cancel, doubleClick };
|
|
2714
2792
|
}
|
|
2715
2793
|
|
|
2716
2794
|
// src/radial-wheel-geometry.ts
|
|
@@ -2877,6 +2955,7 @@ function pickEmptyLabelSpot(area, width, height, obstacles, prefer = () => true)
|
|
|
2877
2955
|
var POP_WEDGE_SPAN_DEGREES = 80;
|
|
2878
2956
|
var POP_SUBTREE_OUTER_MARGIN = 24;
|
|
2879
2957
|
var POP_HIERARCHY_ROOT_ABSOLUTE_DEPTH = 1;
|
|
2958
|
+
var DOT_RADIUS_TRANSITION_MS = 250;
|
|
2880
2959
|
function getRadialDepthRadius(depth, coreRootCircleRadius, depthSpacing) {
|
|
2881
2960
|
return coreRootCircleRadius + depth * depthSpacing;
|
|
2882
2961
|
}
|
|
@@ -2899,14 +2978,15 @@ function calculatePopSubtreeRadialExtent(hierarchy, coreRootCircleRadius = 0) {
|
|
|
2899
2978
|
POP_TREE_DEPTH_RADIAL_SPACING
|
|
2900
2979
|
) + MAX_NODE_WIDTH / 2 + POP_SUBTREE_OUTER_MARGIN;
|
|
2901
2980
|
}
|
|
2902
|
-
function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING,
|
|
2981
|
+
function computePopRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, coreRootCircleRadius, wedgeSpanDegrees = POP_WEDGE_SPAN_DEGREES, depthSpacing = POP_TREE_DEPTH_RADIAL_SPACING, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
2903
2982
|
return layoutWedge(
|
|
2904
2983
|
d3Lib,
|
|
2905
2984
|
hierarchy,
|
|
2906
2985
|
wedgeCenterAngleDegrees,
|
|
2907
2986
|
wedgeSpanDegrees,
|
|
2908
2987
|
popRadiusOfDepth(coreRootCircleRadius, depthSpacing),
|
|
2909
|
-
|
|
2988
|
+
"center",
|
|
2989
|
+
dotRadius
|
|
2910
2990
|
);
|
|
2911
2991
|
}
|
|
2912
2992
|
var MIN_DOT_GAP = 5.6;
|
|
@@ -2916,15 +2996,15 @@ function popRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
|
|
|
2916
2996
|
function coreRadiusOfDepth(coreRootCircleRadius, depthSpacing) {
|
|
2917
2997
|
return (depth) => getRadialDepthRadius(depth + 1, coreRootCircleRadius, depthSpacing);
|
|
2918
2998
|
}
|
|
2919
|
-
function measureWedge(_d3Lib, hierarchy, radiusOfDepth,
|
|
2920
|
-
const
|
|
2999
|
+
function measureWedge(_d3Lib, hierarchy, radiusOfDepth, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3000
|
+
const slotRadius = () => dotRadius;
|
|
2921
3001
|
const offsetFromParent = /* @__PURE__ */ new Map();
|
|
2922
3002
|
const shiftLevel = (level, by) => ({
|
|
2923
3003
|
left: { ...level.left, x: level.left.x + by },
|
|
2924
3004
|
right: { ...level.right, x: level.right.x + by }
|
|
2925
3005
|
});
|
|
2926
3006
|
const pack = (node) => {
|
|
2927
|
-
const own = { x: 0, dotRadius:
|
|
3007
|
+
const own = { x: 0, dotRadius: slotRadius() };
|
|
2928
3008
|
if (!node.children) return [{ left: own, right: own }];
|
|
2929
3009
|
const below = [];
|
|
2930
3010
|
const childOffsets = [];
|
|
@@ -2956,7 +3036,7 @@ function measureWedge(_d3Lib, hierarchy, radiusOfDepth, itemCountRange) {
|
|
|
2956
3036
|
let min = Infinity;
|
|
2957
3037
|
let max = -Infinity;
|
|
2958
3038
|
hierarchy.each((d) => {
|
|
2959
|
-
const inset = (
|
|
3039
|
+
const inset = (slotRadius() + MIN_DOT_GAP / 2) / radiusOfDepth(d.depth);
|
|
2960
3040
|
min = Math.min(min, d.x - inset);
|
|
2961
3041
|
max = Math.max(max, d.x + inset);
|
|
2962
3042
|
});
|
|
@@ -2966,13 +3046,44 @@ function measureWedge(_d3Lib, hierarchy, radiusOfDepth, itemCountRange) {
|
|
|
2966
3046
|
return max - min;
|
|
2967
3047
|
}
|
|
2968
3048
|
var MAX_WEDGE_STRETCH = 1.42;
|
|
2969
|
-
function
|
|
2970
|
-
|
|
3049
|
+
function splitAtBranching(hierarchy) {
|
|
3050
|
+
let branching = hierarchy;
|
|
3051
|
+
const chain = [];
|
|
3052
|
+
while (branching.children?.length === 1) {
|
|
3053
|
+
chain.push(branching);
|
|
3054
|
+
branching = branching.children[0];
|
|
3055
|
+
}
|
|
3056
|
+
const children = branching.children;
|
|
3057
|
+
return children && children.length > 1 ? { chain, branching, children } : null;
|
|
3058
|
+
}
|
|
3059
|
+
function layoutWedge(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, radiusOfDepth, slack = "center", dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3060
|
+
const neededRad = measureWedge(d3Lib, hierarchy, radiusOfDepth, dotRadius);
|
|
2971
3061
|
const wedgeSpanRad = wedgeSpanDegrees * Math.PI / 180;
|
|
2972
3062
|
const scale = Math.min(MAX_WEDGE_STRETCH, wedgeSpanRad / neededRad);
|
|
2973
|
-
const
|
|
3063
|
+
const wedgeCenterRad = wedgeCenterAngleDegrees * Math.PI / 180;
|
|
3064
|
+
const centeredStartRad = wedgeCenterRad - neededRad * scale / 2;
|
|
3065
|
+
const branchingSplit = slack === "between-children" ? splitAtBranching(hierarchy) : null;
|
|
3066
|
+
if (!branchingSplit) {
|
|
3067
|
+
hierarchy.each((d) => {
|
|
3068
|
+
const angleRad = centeredStartRad + d.x * scale;
|
|
3069
|
+
const radius = radiusOfDepth(d.depth);
|
|
3070
|
+
d.x = radius * Math.sin(angleRad);
|
|
3071
|
+
d.y = -radius * Math.cos(angleRad);
|
|
3072
|
+
});
|
|
3073
|
+
return hierarchy;
|
|
3074
|
+
}
|
|
3075
|
+
const { chain, branching, children } = branchingSplit;
|
|
3076
|
+
const gap = Math.max(0, wedgeSpanRad - neededRad * scale) / (children.length - 1);
|
|
3077
|
+
const wedgeStartRad = wedgeCenterRad - wedgeSpanRad / 2;
|
|
3078
|
+
const angleOffsetRad = /* @__PURE__ */ new Map();
|
|
3079
|
+
hierarchy.each((d) => angleOffsetRad.set(d, d.x * scale));
|
|
3080
|
+
children.forEach((child, i) => {
|
|
3081
|
+
child.each((d) => angleOffsetRad.set(d, angleOffsetRad.get(d) + i * gap));
|
|
3082
|
+
});
|
|
3083
|
+
const recenter = gap * (children.length - 1) / 2;
|
|
3084
|
+
[...chain, branching].forEach((d) => angleOffsetRad.set(d, angleOffsetRad.get(d) + recenter));
|
|
2974
3085
|
hierarchy.each((d) => {
|
|
2975
|
-
const angleRad = wedgeStartRad + d
|
|
3086
|
+
const angleRad = wedgeStartRad + angleOffsetRad.get(d);
|
|
2976
3087
|
const radius = radiusOfDepth(d.depth);
|
|
2977
3088
|
d.x = radius * Math.sin(angleRad);
|
|
2978
3089
|
d.y = -radius * Math.cos(angleRad);
|
|
@@ -3059,7 +3170,8 @@ function renderPopSubtree(d3Lib, svg, hierarchy, rootColor, reparentingNodeId, r
|
|
|
3059
3170
|
rootLinkOrigin,
|
|
3060
3171
|
isMainstreamSector = false,
|
|
3061
3172
|
selectedNodeId = null,
|
|
3062
|
-
nodeFillOf
|
|
3173
|
+
nodeFillOf,
|
|
3174
|
+
dotRadiusMemory
|
|
3063
3175
|
} = options;
|
|
3064
3176
|
const { onReparentTargetSelect, onNodeClick, onNodeDoubleClick, onNodeHover } = callbacks;
|
|
3065
3177
|
const isForbidden = (d) => reparentForbiddenIds.includes(d.data.id);
|
|
@@ -3122,13 +3234,18 @@ function renderPopSubtree(d3Lib, svg, hierarchy, rootColor, reparentingNodeId, r
|
|
|
3122
3234
|
);
|
|
3123
3235
|
d3Lib.select(this).attr("x", hitArea.x0).attr("y", hitArea.y0).attr("width", hitArea.x1 - hitArea.x0).attr("height", hitArea.y1 - hitArea.y0);
|
|
3124
3236
|
}).attr("fill", "transparent").attr("pointer-events", "all");
|
|
3125
|
-
|
|
3237
|
+
const targetDotRadius = (d) => calculateNodeDotRadius(d.data.itemCount, itemCountRange);
|
|
3238
|
+
const animateDots = dotRadiusMemory !== void 0 && !(typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches);
|
|
3239
|
+
const startDotRadius = (d) => (animateDots ? dotRadiusMemory?.get(d.data.id) : void 0) ?? targetDotRadius(d);
|
|
3240
|
+
nodes.append("circle").attr("class", "gtv-node-dot").attr("r", startDotRadius).attr("fill", nodeFill).attr(
|
|
3126
3241
|
"stroke",
|
|
3127
3242
|
(d) => isSelected(d) ? ACCENT_COLOR : isMainstreamSector ? TEXT_COLOR : SURFACE_BORDER_COLOR
|
|
3128
3243
|
).attr(
|
|
3129
3244
|
"stroke-width",
|
|
3130
3245
|
(d) => isSelected(d) ? calculateSelectedNodeBorderWidth(calculateNodeDotRadius(d.data.itemCount, itemCountRange)) : calculateNodeBorderWidth(d.data.itemCount, itemCountRange)
|
|
3131
|
-
).style("pointer-events", "none");
|
|
3246
|
+
).style("pointer-events", "none").filter((d) => startDotRadius(d) !== targetDotRadius(d)).transition().duration(DOT_RADIUS_TRANSITION_MS).ease(d3Lib.easeCubicOut).attr("r", targetDotRadius);
|
|
3247
|
+
nodes.each((d) => dotRadiusMemory?.set(d.data.id, targetDotRadius(d)));
|
|
3248
|
+
nodes.sort((a, b) => targetDotRadius(b) - targetDotRadius(a));
|
|
3132
3249
|
nodes.append("text").attr(
|
|
3133
3250
|
"class",
|
|
3134
3251
|
"gtv-node-label"
|
|
@@ -3169,12 +3286,12 @@ function renderPopSubtree(d3Lib, svg, hierarchy, rootColor, reparentingNodeId, r
|
|
|
3169
3286
|
group.on("click", (event) => {
|
|
3170
3287
|
if (reparentingNodeId) return;
|
|
3171
3288
|
const element = event.currentTarget;
|
|
3172
|
-
deferredClick.schedule(
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
onNodeDoubleClick?.(d.data);
|
|
3289
|
+
deferredClick.schedule(
|
|
3290
|
+
() => onNodeClick?.(d.data, event, element),
|
|
3291
|
+
() => onNodeDoubleClick?.(d.data)
|
|
3292
|
+
);
|
|
3177
3293
|
});
|
|
3294
|
+
group.on("dblclick", () => deferredClick.doubleClick(() => onNodeDoubleClick?.(d.data)));
|
|
3178
3295
|
});
|
|
3179
3296
|
}
|
|
3180
3297
|
function hideCollidingLabels(d3Lib, container, itemCountRange, zoomScale) {
|
|
@@ -3251,14 +3368,15 @@ function buildCoreHierarchy(d3Lib, coreNodes) {
|
|
|
3251
3368
|
});
|
|
3252
3369
|
return hierarchy;
|
|
3253
3370
|
}
|
|
3254
|
-
function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing,
|
|
3371
|
+
function computeCoreRadialLayout(d3Lib, hierarchy, wedgeCenterAngleDegrees, wedgeSpanDegrees, coreRootCircleRadius, depthSpacing, dotRadius = NODE_LAYOUT_SLOT_RADIUS) {
|
|
3255
3372
|
return layoutWedge(
|
|
3256
3373
|
d3Lib,
|
|
3257
3374
|
hierarchy,
|
|
3258
3375
|
wedgeCenterAngleDegrees,
|
|
3259
3376
|
wedgeSpanDegrees,
|
|
3260
3377
|
coreRadiusOfDepth(coreRootCircleRadius, depthSpacing),
|
|
3261
|
-
|
|
3378
|
+
"between-children",
|
|
3379
|
+
dotRadius
|
|
3262
3380
|
);
|
|
3263
3381
|
}
|
|
3264
3382
|
function calculateCoreSubtreeRadialExtent(hierarchy, depthSpacing, coreRootCircleRadius = 0) {
|
|
@@ -3298,7 +3416,7 @@ function splitRootGroupBySide(group) {
|
|
|
3298
3416
|
}
|
|
3299
3417
|
|
|
3300
3418
|
// src/GenreTreeWheelRadialBase.tsx
|
|
3301
|
-
var
|
|
3419
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
3302
3420
|
var LANDING_ANGLE = 90;
|
|
3303
3421
|
function computeContinuousAngles(groups, layout, previous) {
|
|
3304
3422
|
const angles = /* @__PURE__ */ new Map();
|
|
@@ -3332,11 +3450,9 @@ function WheelRadialCore({
|
|
|
3332
3450
|
hideInfoPanelClose = false,
|
|
3333
3451
|
hideInfoPanelTitle = false,
|
|
3334
3452
|
itemCountLabel,
|
|
3453
|
+
itemCountScaleMax,
|
|
3454
|
+
nodeCountForSizing,
|
|
3335
3455
|
readOnly,
|
|
3336
|
-
editMode,
|
|
3337
|
-
onEditModeChange,
|
|
3338
|
-
renderTitleEditor,
|
|
3339
|
-
renderParentsEditor,
|
|
3340
3456
|
renderDangerZone,
|
|
3341
3457
|
expanded,
|
|
3342
3458
|
onExpandedChange,
|
|
@@ -3347,8 +3463,12 @@ function WheelRadialCore({
|
|
|
3347
3463
|
defaultViewport,
|
|
3348
3464
|
onViewportChange,
|
|
3349
3465
|
controlsOrientation = "vertical",
|
|
3466
|
+
showZoomControls = "auto",
|
|
3467
|
+
floatingControlsLeading,
|
|
3468
|
+
busy,
|
|
3350
3469
|
selectedNodeId
|
|
3351
3470
|
}) {
|
|
3471
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
3352
3472
|
const index = useTreeIndex(nodes);
|
|
3353
3473
|
const groups = (0, import_react10.useMemo)(() => groupNodesByRoot(nodes), [nodes]);
|
|
3354
3474
|
const rootColorOf = (node) => getGenreTreeColor(index.byId.get(index.rootIdById.get(node.id) ?? node.id)?.wikidataId);
|
|
@@ -3401,11 +3521,15 @@ function WheelRadialCore({
|
|
|
3401
3521
|
() => getItemCountRange(
|
|
3402
3522
|
groups.map((group) => ({
|
|
3403
3523
|
itemCount: rootItemCountById.get(group.root.id)
|
|
3404
|
-
}))
|
|
3524
|
+
})),
|
|
3525
|
+
{ itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }
|
|
3405
3526
|
),
|
|
3406
|
-
[groups, rootItemCountById]
|
|
3527
|
+
[groups, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
3528
|
+
);
|
|
3529
|
+
const wheelItemCountRange = (0, import_react10.useMemo)(
|
|
3530
|
+
() => getItemCountRange(nodes, { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
3531
|
+
[nodes, itemCountScaleMax, fullNodeCountForSizing]
|
|
3407
3532
|
);
|
|
3408
|
-
const wheelItemCountRange = (0, import_react10.useMemo)(() => getItemCountRange(nodes), [nodes]);
|
|
3409
3533
|
const sectorSpanByRootId = (0, import_react10.useMemo)(() => {
|
|
3410
3534
|
const map = /* @__PURE__ */ new Map();
|
|
3411
3535
|
if (groups.length <= 1) return map;
|
|
@@ -3455,10 +3579,10 @@ function WheelRadialCore({
|
|
|
3455
3579
|
const chipClearanceFloor = (0, import_react10.useMemo)(
|
|
3456
3580
|
() => calculateWheelRadiusForAngles(
|
|
3457
3581
|
layout.map((slot) => slot.angle),
|
|
3458
|
-
MAX_NODE_WIDTH,
|
|
3582
|
+
wheelItemCountRange.maxNodeWidth ?? MAX_NODE_WIDTH,
|
|
3459
3583
|
WHEEL_RADIUS
|
|
3460
3584
|
),
|
|
3461
|
-
[layout]
|
|
3585
|
+
[layout, wheelItemCountRange.maxNodeWidth]
|
|
3462
3586
|
);
|
|
3463
3587
|
const maxCoreExtentDelta = (0, import_react10.useMemo)(() => {
|
|
3464
3588
|
let extent = 0;
|
|
@@ -3505,8 +3629,7 @@ function WheelRadialCore({
|
|
|
3505
3629
|
angle,
|
|
3506
3630
|
span,
|
|
3507
3631
|
wheelRadius,
|
|
3508
|
-
POP_TREE_DEPTH_RADIAL_SPACING
|
|
3509
|
-
wheelItemCountRange
|
|
3632
|
+
POP_TREE_DEPTH_RADIAL_SPACING
|
|
3510
3633
|
);
|
|
3511
3634
|
const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
|
|
3512
3635
|
const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-core-branch").attr("data-gtv-branch-index", branchIndex);
|
|
@@ -3622,10 +3745,11 @@ function WheelRadialCore({
|
|
|
3622
3745
|
};
|
|
3623
3746
|
});
|
|
3624
3747
|
}, [groups, continuousAngleByRootId, sectorSpanByRootId]);
|
|
3625
|
-
return /* @__PURE__ */ (0,
|
|
3748
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3626
3749
|
"div",
|
|
3627
3750
|
{
|
|
3628
3751
|
ref: viewportRef,
|
|
3752
|
+
"aria-busy": busy,
|
|
3629
3753
|
className: [
|
|
3630
3754
|
"gtv-wheel-container",
|
|
3631
3755
|
"gtv-wheel-container--radial",
|
|
@@ -3639,7 +3763,7 @@ function WheelRadialCore({
|
|
|
3639
3763
|
},
|
|
3640
3764
|
onPointerDown: panZoom.handlePointerDown,
|
|
3641
3765
|
children: [
|
|
3642
|
-
/* @__PURE__ */ (0,
|
|
3766
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3643
3767
|
"div",
|
|
3644
3768
|
{
|
|
3645
3769
|
ref: stageRef,
|
|
@@ -3652,9 +3776,9 @@ function WheelRadialCore({
|
|
|
3652
3776
|
transform: panZoom.transform,
|
|
3653
3777
|
transformOrigin: "0 0"
|
|
3654
3778
|
},
|
|
3655
|
-
children: /* @__PURE__ */ (0,
|
|
3656
|
-
/* @__PURE__ */ (0,
|
|
3657
|
-
/* @__PURE__ */ (0,
|
|
3779
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "gtv-wheel-stage", children: [
|
|
3780
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
|
|
3781
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gtv-wheel", children: sectorFills.map((sector) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3658
3782
|
"div",
|
|
3659
3783
|
{
|
|
3660
3784
|
className: "gtv-wheel-sector",
|
|
@@ -3666,7 +3790,7 @@ function WheelRadialCore({
|
|
|
3666
3790
|
},
|
|
3667
3791
|
`sector-${sector.rootId}`
|
|
3668
3792
|
)) }),
|
|
3669
|
-
/* @__PURE__ */ (0,
|
|
3793
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3670
3794
|
"svg",
|
|
3671
3795
|
{
|
|
3672
3796
|
ref: coreSvgRef,
|
|
@@ -3675,9 +3799,9 @@ function WheelRadialCore({
|
|
|
3675
3799
|
height: wheelRadius * 2
|
|
3676
3800
|
}
|
|
3677
3801
|
),
|
|
3678
|
-
centerLabel && /* @__PURE__ */ (0,
|
|
3679
|
-
/* @__PURE__ */ (0,
|
|
3680
|
-
dividerAngles.map((angle, index2) => /* @__PURE__ */ (0,
|
|
3802
|
+
centerLabel && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gtv-wheel-center-label", children: centerLabel }),
|
|
3803
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "gtv-wheel", children: [
|
|
3804
|
+
dividerAngles.map((angle, index2) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3681
3805
|
"div",
|
|
3682
3806
|
{
|
|
3683
3807
|
className: "gtv-wheel-divider",
|
|
@@ -3700,12 +3824,12 @@ function WheelRadialCore({
|
|
|
3700
3824
|
rootItemCount,
|
|
3701
3825
|
rootItemCountRange
|
|
3702
3826
|
);
|
|
3703
|
-
return /* @__PURE__ */ (0,
|
|
3827
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3704
3828
|
"div",
|
|
3705
3829
|
{
|
|
3706
3830
|
className: "gtv-wheel-slot",
|
|
3707
3831
|
style: { "--gtv-chip-angle": `${angle}deg` },
|
|
3708
|
-
children: /* @__PURE__ */ (0,
|
|
3832
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3709
3833
|
"button",
|
|
3710
3834
|
{
|
|
3711
3835
|
type: "button",
|
|
@@ -3738,8 +3862,8 @@ function WheelRadialCore({
|
|
|
3738
3862
|
}
|
|
3739
3863
|
},
|
|
3740
3864
|
children: [
|
|
3741
|
-
PER_TREE_ACCENT_DOT && /* @__PURE__ */ (0,
|
|
3742
|
-
/* @__PURE__ */ (0,
|
|
3865
|
+
PER_TREE_ACCENT_DOT && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "gtv-wheel-chip-dot" }),
|
|
3866
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3743
3867
|
"span",
|
|
3744
3868
|
{
|
|
3745
3869
|
className: "gtv-node-label gtv-node-label--root",
|
|
@@ -3758,7 +3882,7 @@ function WheelRadialCore({
|
|
|
3758
3882
|
] })
|
|
3759
3883
|
}
|
|
3760
3884
|
),
|
|
3761
|
-
panel && /* @__PURE__ */ (0,
|
|
3885
|
+
panel && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3762
3886
|
InfoPanel,
|
|
3763
3887
|
{
|
|
3764
3888
|
node: panel.node,
|
|
@@ -3804,10 +3928,6 @@ function WheelRadialCore({
|
|
|
3804
3928
|
hideTitle: hideInfoPanelTitle,
|
|
3805
3929
|
itemCountLabel,
|
|
3806
3930
|
readOnly,
|
|
3807
|
-
editMode,
|
|
3808
|
-
onEditModeChange,
|
|
3809
|
-
renderTitleEditor,
|
|
3810
|
-
renderParentsEditor,
|
|
3811
3931
|
renderDangerZone,
|
|
3812
3932
|
expanded,
|
|
3813
3933
|
onExpandedChange,
|
|
@@ -3826,54 +3946,16 @@ function WheelRadialCore({
|
|
|
3826
3946
|
} : void 0
|
|
3827
3947
|
}
|
|
3828
3948
|
),
|
|
3829
|
-
overlayPanel && /* @__PURE__ */ (0,
|
|
3830
|
-
/* @__PURE__ */ (0,
|
|
3831
|
-
|
|
3949
|
+
overlayPanel && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(OverlayPanel, { ...overlayPanel }),
|
|
3950
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3951
|
+
FloatingControls,
|
|
3832
3952
|
{
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
"button",
|
|
3840
|
-
{
|
|
3841
|
-
type: "button",
|
|
3842
|
-
className: [
|
|
3843
|
-
"gtv-zoom-btn",
|
|
3844
|
-
!panZoom.canZoomIn && "gtv-zoom-btn--disabled"
|
|
3845
|
-
].filter(Boolean).join(" "),
|
|
3846
|
-
disabled: !panZoom.canZoomIn,
|
|
3847
|
-
onClick: panZoom.zoomIn,
|
|
3848
|
-
"aria-label": "Zoom in",
|
|
3849
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_md8.MdZoomIn, { className: "gtv-icon", size: 18 })
|
|
3850
|
-
}
|
|
3851
|
-
),
|
|
3852
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3853
|
-
"button",
|
|
3854
|
-
{
|
|
3855
|
-
type: "button",
|
|
3856
|
-
className: [
|
|
3857
|
-
"gtv-zoom-btn",
|
|
3858
|
-
!panZoom.canZoomOut && "gtv-zoom-btn--disabled"
|
|
3859
|
-
].filter(Boolean).join(" "),
|
|
3860
|
-
disabled: !panZoom.canZoomOut,
|
|
3861
|
-
onClick: panZoom.zoomOut,
|
|
3862
|
-
"aria-label": "Zoom out",
|
|
3863
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_md8.MdZoomOut, { className: "gtv-icon", size: 18 })
|
|
3864
|
-
}
|
|
3865
|
-
),
|
|
3866
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3867
|
-
"button",
|
|
3868
|
-
{
|
|
3869
|
-
type: "button",
|
|
3870
|
-
className: "gtv-zoom-btn",
|
|
3871
|
-
onClick: () => panZoom.fitToFrame([wheelCircleRef.current, coreSvgRef.current]),
|
|
3872
|
-
"aria-label": "Fit to frame",
|
|
3873
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_md8.MdFitScreen, { className: "gtv-icon", size: 18 })
|
|
3874
|
-
}
|
|
3875
|
-
)
|
|
3876
|
-
] })
|
|
3953
|
+
panZoom,
|
|
3954
|
+
orientation: controlsOrientation,
|
|
3955
|
+
zoomControls: showZoomControls,
|
|
3956
|
+
leading: floatingControlsLeading,
|
|
3957
|
+
busy,
|
|
3958
|
+
onFit: () => panZoom.fitToFrame([wheelCircleRef.current, coreSvgRef.current])
|
|
3877
3959
|
}
|
|
3878
3960
|
)
|
|
3879
3961
|
]
|
|
@@ -3882,14 +3964,13 @@ function WheelRadialCore({
|
|
|
3882
3964
|
}
|
|
3883
3965
|
|
|
3884
3966
|
// src/GenreTreeWheelRadial.tsx
|
|
3885
|
-
var
|
|
3967
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
3886
3968
|
function GenreTreeWheelRadial(props) {
|
|
3887
|
-
return /* @__PURE__ */ (0,
|
|
3969
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(WheelRadialCore, { ...props });
|
|
3888
3970
|
}
|
|
3889
3971
|
|
|
3890
3972
|
// src/GenreTreeWheelRadialPopCoreBase.tsx
|
|
3891
3973
|
var import_react13 = require("react");
|
|
3892
|
-
var import_md10 = require("react-icons/md");
|
|
3893
3974
|
var d34 = __toESM(require("d3"), 1);
|
|
3894
3975
|
|
|
3895
3976
|
// src/use-focus-history.ts
|
|
@@ -3918,17 +3999,17 @@ function useFocusHistory(focusNodeId, isFocusable) {
|
|
|
3918
3999
|
|
|
3919
4000
|
// src/WheelBreadcrumb.tsx
|
|
3920
4001
|
var import_react12 = require("react");
|
|
3921
|
-
var
|
|
3922
|
-
var
|
|
4002
|
+
var import_md6 = require("react-icons/md");
|
|
4003
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3923
4004
|
var MAX_VISIBLE_ANCESTORS = 3;
|
|
3924
4005
|
var ANCESTORS_KEPT_WHEN_COLLAPSED = 2;
|
|
3925
4006
|
function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
|
|
3926
4007
|
const [expanded, setExpanded] = (0, import_react12.useState)(false);
|
|
3927
4008
|
const collapsed = !expanded && ancestors.length > MAX_VISIBLE_ANCESTORS;
|
|
3928
4009
|
const visibleAncestors = collapsed ? ancestors.slice(-ANCESTORS_KEPT_WHEN_COLLAPSED) : ancestors;
|
|
3929
|
-
const separator = /* @__PURE__ */ (0,
|
|
3930
|
-
return /* @__PURE__ */ (0,
|
|
3931
|
-
/* @__PURE__ */ (0,
|
|
4010
|
+
const separator = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-hidden": "true", children: "\u203A" });
|
|
4011
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("nav", { className: "gtv-wheel-breadcrumb", "aria-label": "Breadcrumb", children: [
|
|
4012
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3932
4013
|
"button",
|
|
3933
4014
|
{
|
|
3934
4015
|
type: "button",
|
|
@@ -3936,12 +4017,12 @@ function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
|
|
|
3936
4017
|
"aria-label": "All genres",
|
|
3937
4018
|
title: "All genres",
|
|
3938
4019
|
onClick: onHome,
|
|
3939
|
-
children: /* @__PURE__ */ (0,
|
|
4020
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_md6.MdHome, { className: "gtv-icon", size: 14 })
|
|
3940
4021
|
}
|
|
3941
4022
|
),
|
|
3942
4023
|
separator,
|
|
3943
|
-
collapsed && /* @__PURE__ */ (0,
|
|
3944
|
-
/* @__PURE__ */ (0,
|
|
4024
|
+
collapsed && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
4025
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3945
4026
|
"button",
|
|
3946
4027
|
{
|
|
3947
4028
|
type: "button",
|
|
@@ -3954,11 +4035,11 @@ function WheelBreadcrumb({ ancestors, currentName, onHome, onAncestor }) {
|
|
|
3954
4035
|
),
|
|
3955
4036
|
separator
|
|
3956
4037
|
] }),
|
|
3957
|
-
visibleAncestors.map((ancestor) => /* @__PURE__ */ (0,
|
|
3958
|
-
/* @__PURE__ */ (0,
|
|
4038
|
+
visibleAncestors.map((ancestor) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react12.Fragment, { children: [
|
|
4039
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "gtv-wheel-breadcrumb-crumb", onClick: () => onAncestor(ancestor.id), children: ancestor.name }),
|
|
3959
4040
|
separator
|
|
3960
4041
|
] }, ancestor.id)),
|
|
3961
|
-
/* @__PURE__ */ (0,
|
|
4042
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gtv-wheel-breadcrumb-current", "aria-current": "page", children: currentName })
|
|
3962
4043
|
] });
|
|
3963
4044
|
}
|
|
3964
4045
|
|
|
@@ -4019,7 +4100,7 @@ function resolvePopCoreFrame(nodes, focusNodeId) {
|
|
|
4019
4100
|
}
|
|
4020
4101
|
|
|
4021
4102
|
// src/GenreTreeWheelRadialPopCoreBase.tsx
|
|
4022
|
-
var
|
|
4103
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
4023
4104
|
var LANDING_ANGLE2 = 90;
|
|
4024
4105
|
var CHIP_GAP = 16;
|
|
4025
4106
|
var sum = (values) => values.reduce((total, value) => total + value, 0);
|
|
@@ -4055,11 +4136,9 @@ function WheelRadialPopCoreCore({
|
|
|
4055
4136
|
hideInfoPanelClose = false,
|
|
4056
4137
|
hideInfoPanelTitle = false,
|
|
4057
4138
|
itemCountLabel,
|
|
4139
|
+
itemCountScaleMax,
|
|
4140
|
+
nodeCountForSizing,
|
|
4058
4141
|
readOnly,
|
|
4059
|
-
editMode,
|
|
4060
|
-
onEditModeChange,
|
|
4061
|
-
renderTitleEditor,
|
|
4062
|
-
renderParentsEditor,
|
|
4063
4142
|
renderDangerZone,
|
|
4064
4143
|
expanded,
|
|
4065
4144
|
onExpandedChange,
|
|
@@ -4070,11 +4149,15 @@ function WheelRadialPopCoreCore({
|
|
|
4070
4149
|
defaultViewport,
|
|
4071
4150
|
onViewportChange,
|
|
4072
4151
|
controlsOrientation = "vertical",
|
|
4152
|
+
showZoomControls = "auto",
|
|
4153
|
+
floatingControlsLeading,
|
|
4154
|
+
busy,
|
|
4073
4155
|
allowWheelRotation = true,
|
|
4074
4156
|
selectedNodeId,
|
|
4075
4157
|
focusNodeId: requestedFocusNodeId = null,
|
|
4076
4158
|
onFocusChange
|
|
4077
4159
|
}) {
|
|
4160
|
+
const fullNodeCountForSizing = nodeCountForSizing ?? nodes.length;
|
|
4078
4161
|
const index = useTreeIndex(nodes);
|
|
4079
4162
|
const focusNodeId = (0, import_react13.useMemo)(
|
|
4080
4163
|
() => requestedFocusNodeId !== null && isZoomableNode(nodes, requestedFocusNodeId) ? requestedFocusNodeId : null,
|
|
@@ -4142,6 +4225,7 @@ function WheelRadialPopCoreCore({
|
|
|
4142
4225
|
}
|
|
4143
4226
|
const viewportRef = (0, import_react13.useRef)(null);
|
|
4144
4227
|
const stageRef = (0, import_react13.useRef)(null);
|
|
4228
|
+
const dotRadiusMemoryRef = (0, import_react13.useRef)(/* @__PURE__ */ new Map());
|
|
4145
4229
|
const panZoom = usePanZoom(viewportRef, wheelZoom, stageRef, { defaultViewport, onViewportChange });
|
|
4146
4230
|
const { panel: openPanel, showNodeInfo, closeNodeInfo } = useNodeInfoPanel({ selectedNodeId, onSelectedNodeChange });
|
|
4147
4231
|
const isInFrame = (id) => id === focusNodeId || centerSubtreeNodeIds.has(id) || ringRootById.has(id);
|
|
@@ -4213,8 +4297,8 @@ function WheelRadialPopCoreCore({
|
|
|
4213
4297
|
() => getItemCountRange([
|
|
4214
4298
|
...nodes,
|
|
4215
4299
|
...[...rootItemCountById.values()].map((itemCount) => ({ itemCount }))
|
|
4216
|
-
]),
|
|
4217
|
-
[nodes, rootItemCountById]
|
|
4300
|
+
], { itemCountScaleMax, nodeCountForSizing: fullNodeCountForSizing }),
|
|
4301
|
+
[nodes, rootItemCountById, itemCountScaleMax, fullNodeCountForSizing]
|
|
4218
4302
|
);
|
|
4219
4303
|
const isCenterMainstream = isMainstreamPopLineage(index, centerNode.id);
|
|
4220
4304
|
const centerNodeColor = isCenterMainstream ? "#ffffff" : getGenreNodeStyle(index, centerNode.id).fill;
|
|
@@ -4248,8 +4332,8 @@ function WheelRadialPopCoreCore({
|
|
|
4248
4332
|
centerSubtreeHierarchy,
|
|
4249
4333
|
0,
|
|
4250
4334
|
POP_TREE_DEPTH_RADIAL_SPACING
|
|
4251
|
-
) :
|
|
4252
|
-
[centerSubtreeHierarchy,
|
|
4335
|
+
) : wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS,
|
|
4336
|
+
[centerSubtreeHierarchy, wheelItemCountRange.maxDotRadius]
|
|
4253
4337
|
);
|
|
4254
4338
|
const maxCoreExtentDelta = (0, import_react13.useMemo)(() => {
|
|
4255
4339
|
let extent = 0;
|
|
@@ -4257,11 +4341,7 @@ function WheelRadialPopCoreCore({
|
|
|
4257
4341
|
core.forEach((hierarchy) => {
|
|
4258
4342
|
extent = Math.max(
|
|
4259
4343
|
extent,
|
|
4260
|
-
calculateCoreSubtreeRadialExtent(
|
|
4261
|
-
hierarchy,
|
|
4262
|
-
POP_TREE_DEPTH_RADIAL_SPACING,
|
|
4263
|
-
0
|
|
4264
|
-
)
|
|
4344
|
+
calculateCoreSubtreeRadialExtent(hierarchy, POP_TREE_DEPTH_RADIAL_SPACING, 0)
|
|
4265
4345
|
);
|
|
4266
4346
|
});
|
|
4267
4347
|
});
|
|
@@ -4275,7 +4355,8 @@ function WheelRadialPopCoreCore({
|
|
|
4275
4355
|
(radius) => new Map(
|
|
4276
4356
|
groups.map((group) => {
|
|
4277
4357
|
const { pop, core } = branchHierarchiesByRootId.get(group.root.id);
|
|
4278
|
-
const
|
|
4358
|
+
const dotRadius = wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS;
|
|
4359
|
+
const chipWidth = 2 * dotRadius;
|
|
4279
4360
|
return [
|
|
4280
4361
|
group.root.id,
|
|
4281
4362
|
{
|
|
@@ -4284,7 +4365,7 @@ function WheelRadialPopCoreCore({
|
|
|
4284
4365
|
d34,
|
|
4285
4366
|
hierarchy,
|
|
4286
4367
|
popRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
|
|
4287
|
-
|
|
4368
|
+
dotRadius
|
|
4288
4369
|
)
|
|
4289
4370
|
),
|
|
4290
4371
|
core: core.map(
|
|
@@ -4292,7 +4373,7 @@ function WheelRadialPopCoreCore({
|
|
|
4292
4373
|
d34,
|
|
4293
4374
|
hierarchy,
|
|
4294
4375
|
coreRadiusOfDepth(radius, POP_TREE_DEPTH_RADIAL_SPACING),
|
|
4295
|
-
|
|
4376
|
+
dotRadius
|
|
4296
4377
|
)
|
|
4297
4378
|
),
|
|
4298
4379
|
chip: 2 * Math.asin(Math.min(1, (chipWidth / 2 + CHIP_GAP) / radius))
|
|
@@ -4300,7 +4381,7 @@ function WheelRadialPopCoreCore({
|
|
|
4300
4381
|
];
|
|
4301
4382
|
})
|
|
4302
4383
|
),
|
|
4303
|
-
[groups, branchHierarchiesByRootId,
|
|
4384
|
+
[groups, branchHierarchiesByRootId, wheelItemCountRange.maxDotRadius]
|
|
4304
4385
|
);
|
|
4305
4386
|
const coreRootCircleRadius = (0, import_react13.useMemo)(
|
|
4306
4387
|
() => solveRingRadius(
|
|
@@ -4310,11 +4391,11 @@ function WheelRadialPopCoreCore({
|
|
|
4310
4391
|
),
|
|
4311
4392
|
Math.max(
|
|
4312
4393
|
WHEEL_POP_CORE_RADIUS,
|
|
4313
|
-
mainstreamCircleRadius + NODE_DOT_MAX_RADIUS + POP_SUBTREE_OUTER_MARGIN,
|
|
4394
|
+
mainstreamCircleRadius + (wheelItemCountRange.maxDotRadius ?? NODE_DOT_MAX_RADIUS) + POP_SUBTREE_OUTER_MARGIN,
|
|
4314
4395
|
popReachRequiredRadius
|
|
4315
4396
|
)
|
|
4316
4397
|
),
|
|
4317
|
-
[measureBranchNeedsAt, mainstreamCircleRadius, popReachRequiredRadius]
|
|
4398
|
+
[measureBranchNeedsAt, mainstreamCircleRadius, popReachRequiredRadius, wheelItemCountRange.maxDotRadius]
|
|
4318
4399
|
);
|
|
4319
4400
|
const branchNeedsByRootId = (0, import_react13.useMemo)(
|
|
4320
4401
|
() => measureBranchNeedsAt(coreRootCircleRadius),
|
|
@@ -4419,7 +4500,7 @@ function WheelRadialPopCoreCore({
|
|
|
4419
4500
|
coreRootCircleRadius,
|
|
4420
4501
|
span,
|
|
4421
4502
|
POP_TREE_DEPTH_RADIAL_SPACING,
|
|
4422
|
-
wheelItemCountRange
|
|
4503
|
+
wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS
|
|
4423
4504
|
);
|
|
4424
4505
|
const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
|
|
4425
4506
|
const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-pop-branch").attr("data-gtv-branch-index", branchIndex);
|
|
@@ -4463,6 +4544,7 @@ function WheelRadialPopCoreCore({
|
|
|
4463
4544
|
{
|
|
4464
4545
|
rootLinkOrigin,
|
|
4465
4546
|
selectedNodeId: panel?.node.id ?? null,
|
|
4547
|
+
dotRadiusMemory: dotRadiusMemoryRef.current,
|
|
4466
4548
|
nodeFillOf: (node) => getGenreNodeStyle(index, node.id).fill
|
|
4467
4549
|
}
|
|
4468
4550
|
);
|
|
@@ -4475,6 +4557,9 @@ function WheelRadialPopCoreCore({
|
|
|
4475
4557
|
);
|
|
4476
4558
|
const sectorGroup = originGroup.append("g").attr("class", "gtv-wheel-core-sector").attr("data-gtv-root-id", rootId);
|
|
4477
4559
|
branches.forEach(({ hierarchy, angle, span }, branchIndex) => {
|
|
4560
|
+
const undergroundIds = new Set(
|
|
4561
|
+
hierarchy.descendants().filter((d) => d.ancestors().some((a) => a.data.scene === "underground")).map((d) => d.data.id)
|
|
4562
|
+
);
|
|
4478
4563
|
const laidOut = computeCoreRadialLayout(
|
|
4479
4564
|
d34,
|
|
4480
4565
|
hierarchy,
|
|
@@ -4482,9 +4567,33 @@ function WheelRadialPopCoreCore({
|
|
|
4482
4567
|
span,
|
|
4483
4568
|
coreRootCircleRadius,
|
|
4484
4569
|
POP_TREE_DEPTH_RADIAL_SPACING,
|
|
4485
|
-
wheelItemCountRange
|
|
4570
|
+
wheelItemCountRange.maxDotRadius ?? NODE_LAYOUT_SLOT_RADIUS
|
|
4486
4571
|
);
|
|
4487
4572
|
const reparentForbiddenIds = reparentingNodeId ? laidOut.descendants().find((d) => d.data.id === reparentingNodeId)?.descendants().map((d) => d.data.id) ?? [] : [];
|
|
4573
|
+
const angleOf = (d) => {
|
|
4574
|
+
const deg = Math.atan2(d.x, -d.y) * 180 / Math.PI;
|
|
4575
|
+
return angle + ((deg - angle + 540) % 360 + 360) % 360 - 180;
|
|
4576
|
+
};
|
|
4577
|
+
const subtreeRange = (d) => {
|
|
4578
|
+
const angles = d.descendants().map(angleOf);
|
|
4579
|
+
return { min: Math.min(...angles), max: Math.max(...angles) };
|
|
4580
|
+
};
|
|
4581
|
+
laidOut.descendants().filter((d) => d.data.scene === "underground" && !d.ancestors().slice(1).some((a) => a.data.scene === "underground")).forEach((ug) => {
|
|
4582
|
+
const mine = subtreeRange(ug);
|
|
4583
|
+
const siblings = ug.parent?.children ?? [];
|
|
4584
|
+
const at = siblings.indexOf(ug);
|
|
4585
|
+
const prev = at > 0 ? siblings[at - 1] : void 0;
|
|
4586
|
+
const next = at >= 0 ? siblings[at + 1] : void 0;
|
|
4587
|
+
const startDeg = prev ? (subtreeRange(prev).max + mine.min) / 2 : angle - span / 2;
|
|
4588
|
+
const endDeg = next ? (mine.max + subtreeRange(next).min) / 2 : angle + span / 2;
|
|
4589
|
+
const arcPath = d34.arc()({
|
|
4590
|
+
innerRadius: coreRootCircleRadius,
|
|
4591
|
+
outerRadius: SCENE_SECTOR_OUTER_RADIUS,
|
|
4592
|
+
startAngle: startDeg * Math.PI / 180,
|
|
4593
|
+
endAngle: endDeg * Math.PI / 180
|
|
4594
|
+
});
|
|
4595
|
+
sectorGroup.append("path").attr("class", "gtv-wheel-scene-sector").attr("d", arcPath).attr("fill", shadeSurface(getGenreNodeColor(index, rootId), UNDERGROUND_DARKEN_RATIO)).attr("fill-opacity", UNDERGROUND_SECTOR_FILL_OPACITY);
|
|
4596
|
+
});
|
|
4488
4597
|
const branchGroup = sectorGroup.append("g").attr("class", "gtv-wheel-core-branch").attr("data-gtv-branch-index", branchIndex);
|
|
4489
4598
|
renderPopSubtree(
|
|
4490
4599
|
d34,
|
|
@@ -4527,7 +4636,11 @@ function WheelRadialPopCoreCore({
|
|
|
4527
4636
|
isCoreSector: true,
|
|
4528
4637
|
rootLinkOrigin,
|
|
4529
4638
|
selectedNodeId: panel?.node.id ?? null,
|
|
4530
|
-
|
|
4639
|
+
dotRadiusMemory: dotRadiusMemoryRef.current,
|
|
4640
|
+
nodeFillOf: (node) => {
|
|
4641
|
+
const fill = getGenreNodeStyle(index, node.id).fill;
|
|
4642
|
+
return undergroundIds.has(node.id) ? shadeSurface(fill, UNDERGROUND_DARKEN_RATIO) : fill;
|
|
4643
|
+
}
|
|
4531
4644
|
}
|
|
4532
4645
|
);
|
|
4533
4646
|
});
|
|
@@ -4582,6 +4695,7 @@ function WheelRadialPopCoreCore({
|
|
|
4582
4695
|
skipRootNode: true,
|
|
4583
4696
|
isMainstreamSector: isCenterMainstream,
|
|
4584
4697
|
selectedNodeId: panel?.node.id ?? null,
|
|
4698
|
+
dotRadiusMemory: dotRadiusMemoryRef.current,
|
|
4585
4699
|
nodeFillOf: (node) => getGenreNodeStyle(index, node.id).fill
|
|
4586
4700
|
}
|
|
4587
4701
|
);
|
|
@@ -4617,7 +4731,7 @@ function WheelRadialPopCoreCore({
|
|
|
4617
4731
|
originGroup.selectAll("g.gtv-root-label").select("text").style("pointer-events", "all").style("cursor", "pointer").on("click", (_event, d) => {
|
|
4618
4732
|
const chip = d.id === centerNode.id ? centerChipRef.current : viewportRef.current?.querySelector(`#group-${CSS.escape(d.id)}`);
|
|
4619
4733
|
chip?.click();
|
|
4620
|
-
}).on("dblclick", (_event, d) => focusOnNodeRef.current(d.id));
|
|
4734
|
+
}).on("dblclick", (_event, d) => chipClickRef.current.doubleClick(() => focusOnNodeRef.current(d.id)));
|
|
4621
4735
|
}, [
|
|
4622
4736
|
centerNode,
|
|
4623
4737
|
centerHasPopDisc,
|
|
@@ -4776,11 +4890,12 @@ function WheelRadialPopCoreCore({
|
|
|
4776
4890
|
)
|
|
4777
4891
|
};
|
|
4778
4892
|
});
|
|
4779
|
-
}, [groups, continuousAngleByRootId, sectorSpanByRootId]);
|
|
4780
|
-
return /* @__PURE__ */ (0,
|
|
4893
|
+
}, [groups, continuousAngleByRootId, sectorSpanByRootId, index]);
|
|
4894
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4781
4895
|
"div",
|
|
4782
4896
|
{
|
|
4783
4897
|
ref: viewportRef,
|
|
4898
|
+
"aria-busy": busy,
|
|
4784
4899
|
className: [
|
|
4785
4900
|
"gtv-wheel-container",
|
|
4786
4901
|
"gtv-wheel-container--radial",
|
|
@@ -4795,7 +4910,7 @@ function WheelRadialPopCoreCore({
|
|
|
4795
4910
|
},
|
|
4796
4911
|
onPointerDown: panZoom.handlePointerDown,
|
|
4797
4912
|
children: [
|
|
4798
|
-
/* @__PURE__ */ (0,
|
|
4913
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4799
4914
|
"div",
|
|
4800
4915
|
{
|
|
4801
4916
|
ref: stageRef,
|
|
@@ -4809,9 +4924,9 @@ function WheelRadialPopCoreCore({
|
|
|
4809
4924
|
transformOrigin: "0 0"
|
|
4810
4925
|
},
|
|
4811
4926
|
children: [
|
|
4812
|
-
/* @__PURE__ */ (0,
|
|
4813
|
-
/* @__PURE__ */ (0,
|
|
4814
|
-
isPopExpanded && /* @__PURE__ */ (0,
|
|
4927
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "gtv-wheel-stage", children: [
|
|
4928
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "gtv-wheel-circle", ref: wheelCircleRef }),
|
|
4929
|
+
isPopExpanded && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4815
4930
|
"div",
|
|
4816
4931
|
{
|
|
4817
4932
|
className: "gtv-wheel-middle-circle",
|
|
@@ -4820,8 +4935,8 @@ function WheelRadialPopCoreCore({
|
|
|
4820
4935
|
}
|
|
4821
4936
|
}
|
|
4822
4937
|
),
|
|
4823
|
-
/* @__PURE__ */ (0,
|
|
4824
|
-
sectorFills.map((sector) => /* @__PURE__ */ (0,
|
|
4938
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "gtv-wheel", children: [
|
|
4939
|
+
sectorFills.map((sector) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4825
4940
|
"div",
|
|
4826
4941
|
{
|
|
4827
4942
|
className: "gtv-wheel-sector",
|
|
@@ -4833,8 +4948,8 @@ function WheelRadialPopCoreCore({
|
|
|
4833
4948
|
},
|
|
4834
4949
|
`sector-${sector.rootId}`
|
|
4835
4950
|
)),
|
|
4836
|
-
/* @__PURE__ */ (0,
|
|
4837
|
-
isPopExpanded && /* @__PURE__ */ (0,
|
|
4951
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "gtv-wheel-inner-tint" }),
|
|
4952
|
+
isPopExpanded && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4838
4953
|
"div",
|
|
4839
4954
|
{
|
|
4840
4955
|
className: "gtv-wheel-middle-tint",
|
|
@@ -4844,7 +4959,7 @@ function WheelRadialPopCoreCore({
|
|
|
4844
4959
|
}
|
|
4845
4960
|
)
|
|
4846
4961
|
] }),
|
|
4847
|
-
/* @__PURE__ */ (0,
|
|
4962
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4848
4963
|
"svg",
|
|
4849
4964
|
{
|
|
4850
4965
|
ref: popSvgRef,
|
|
@@ -4853,7 +4968,7 @@ function WheelRadialPopCoreCore({
|
|
|
4853
4968
|
height: svgCanvasRadius * 2
|
|
4854
4969
|
}
|
|
4855
4970
|
),
|
|
4856
|
-
/* @__PURE__ */ (0,
|
|
4971
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "gtv-wheel-center-node", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4857
4972
|
"button",
|
|
4858
4973
|
{
|
|
4859
4974
|
ref: centerChipRef,
|
|
@@ -4885,14 +5000,14 @@ function WheelRadialPopCoreCore({
|
|
|
4885
5000
|
showNodeInfo(centerNode, chip, viewportRef.current);
|
|
4886
5001
|
onNodeClick?.(centerNode, nativeEvent);
|
|
4887
5002
|
}
|
|
4888
|
-
});
|
|
5003
|
+
}, () => focusOnNode(centerNode.id), centerNode.id);
|
|
4889
5004
|
},
|
|
4890
|
-
onDoubleClick: () => focusOnNode(centerNode.id),
|
|
5005
|
+
onDoubleClick: () => chipClickRef.current.doubleClick(() => focusOnNode(centerNode.id)),
|
|
4891
5006
|
"aria-label": centerNode.name
|
|
4892
5007
|
}
|
|
4893
5008
|
) }) }),
|
|
4894
|
-
/* @__PURE__ */ (0,
|
|
4895
|
-
dividerAngles.map((angle, index2) => /* @__PURE__ */ (0,
|
|
5009
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "gtv-wheel", children: [
|
|
5010
|
+
dividerAngles.map((angle, index2) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4896
5011
|
"div",
|
|
4897
5012
|
{
|
|
4898
5013
|
className: "gtv-wheel-divider",
|
|
@@ -4911,12 +5026,12 @@ function WheelRadialPopCoreCore({
|
|
|
4911
5026
|
group.root.id
|
|
4912
5027
|
);
|
|
4913
5028
|
const diameter = 2 * calculateNodeDotRadius(rootItemCount, wheelItemCountRange);
|
|
4914
|
-
return /* @__PURE__ */ (0,
|
|
5029
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4915
5030
|
"div",
|
|
4916
5031
|
{
|
|
4917
5032
|
className: "gtv-wheel-slot",
|
|
4918
5033
|
style: { "--gtv-chip-angle": `${angle}deg` },
|
|
4919
|
-
children: /* @__PURE__ */ (0,
|
|
5034
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "gtv-wheel-chip-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4920
5035
|
"button",
|
|
4921
5036
|
{
|
|
4922
5037
|
type: "button",
|
|
@@ -4947,9 +5062,9 @@ function WheelRadialPopCoreCore({
|
|
|
4947
5062
|
showNodeInfo(root, chip, viewportRef.current);
|
|
4948
5063
|
onNodeClick?.(root, nativeEvent);
|
|
4949
5064
|
}
|
|
4950
|
-
});
|
|
5065
|
+
}, () => focusOnNode(group.root.id), group.root.id);
|
|
4951
5066
|
},
|
|
4952
|
-
onDoubleClick: () => focusOnNode(group.root.id)
|
|
5067
|
+
onDoubleClick: () => chipClickRef.current.doubleClick(() => focusOnNode(group.root.id))
|
|
4953
5068
|
}
|
|
4954
5069
|
) })
|
|
4955
5070
|
},
|
|
@@ -4958,7 +5073,7 @@ function WheelRadialPopCoreCore({
|
|
|
4958
5073
|
})
|
|
4959
5074
|
] })
|
|
4960
5075
|
] }),
|
|
4961
|
-
regionLabel && index.byId.has(regionLabel.rootId) && /* @__PURE__ */ (0,
|
|
5076
|
+
regionLabel && index.byId.has(regionLabel.rootId) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4962
5077
|
"div",
|
|
4963
5078
|
{
|
|
4964
5079
|
ref: regionLabelRef,
|
|
@@ -4976,7 +5091,7 @@ function WheelRadialPopCoreCore({
|
|
|
4976
5091
|
]
|
|
4977
5092
|
}
|
|
4978
5093
|
),
|
|
4979
|
-
panel && /* @__PURE__ */ (0,
|
|
5094
|
+
panel && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4980
5095
|
InfoPanel,
|
|
4981
5096
|
{
|
|
4982
5097
|
node: panel.node,
|
|
@@ -5003,10 +5118,6 @@ function WheelRadialPopCoreCore({
|
|
|
5003
5118
|
hideTitle: hideInfoPanelTitle,
|
|
5004
5119
|
itemCountLabel,
|
|
5005
5120
|
readOnly,
|
|
5006
|
-
editMode,
|
|
5007
|
-
onEditModeChange,
|
|
5008
|
-
renderTitleEditor,
|
|
5009
|
-
renderParentsEditor,
|
|
5010
5121
|
renderDangerZone,
|
|
5011
5122
|
expanded,
|
|
5012
5123
|
onExpandedChange,
|
|
@@ -5026,8 +5137,8 @@ function WheelRadialPopCoreCore({
|
|
|
5026
5137
|
onZoomIn: onFocusChange && panel.node.id !== focusNodeId && isZoomableNode(nodes, panel.node.id) ? () => onFocusChange(panel.node.id, "zoom-in") : void 0
|
|
5027
5138
|
}
|
|
5028
5139
|
),
|
|
5029
|
-
overlayPanel && /* @__PURE__ */ (0,
|
|
5030
|
-
onFocusChange && focusNodeId !== null && /* @__PURE__ */ (0,
|
|
5140
|
+
overlayPanel && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OverlayPanel, { ...overlayPanel }),
|
|
5141
|
+
onFocusChange && focusNodeId !== null && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5031
5142
|
WheelBreadcrumb,
|
|
5032
5143
|
{
|
|
5033
5144
|
ancestors: index.ancestorsOf(focusNodeId).filter((ancestor) => isZoomableNode(nodes, ancestor.id)),
|
|
@@ -5043,56 +5154,18 @@ function WheelRadialPopCoreCore({
|
|
|
5043
5154
|
},
|
|
5044
5155
|
focusNodeId
|
|
5045
5156
|
),
|
|
5046
|
-
/* @__PURE__ */ (0,
|
|
5047
|
-
|
|
5157
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5158
|
+
FloatingControls,
|
|
5048
5159
|
{
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
className: [
|
|
5059
|
-
"gtv-zoom-btn",
|
|
5060
|
-
!panZoom.canZoomIn && "gtv-zoom-btn--disabled"
|
|
5061
|
-
].filter(Boolean).join(" "),
|
|
5062
|
-
disabled: !panZoom.canZoomIn,
|
|
5063
|
-
onClick: panZoom.zoomIn,
|
|
5064
|
-
"aria-label": "Zoom in",
|
|
5065
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_md10.MdZoomIn, { className: "gtv-icon", size: 18 })
|
|
5066
|
-
}
|
|
5067
|
-
),
|
|
5068
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
5069
|
-
"button",
|
|
5070
|
-
{
|
|
5071
|
-
type: "button",
|
|
5072
|
-
className: [
|
|
5073
|
-
"gtv-zoom-btn",
|
|
5074
|
-
!panZoom.canZoomOut && "gtv-zoom-btn--disabled"
|
|
5075
|
-
].filter(Boolean).join(" "),
|
|
5076
|
-
disabled: !panZoom.canZoomOut,
|
|
5077
|
-
onClick: panZoom.zoomOut,
|
|
5078
|
-
"aria-label": "Zoom out",
|
|
5079
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_md10.MdZoomOut, { className: "gtv-icon", size: 18 })
|
|
5080
|
-
}
|
|
5081
|
-
),
|
|
5082
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
5083
|
-
"button",
|
|
5084
|
-
{
|
|
5085
|
-
type: "button",
|
|
5086
|
-
className: "gtv-zoom-btn",
|
|
5087
|
-
onClick: () => panZoom.fitToFrame([
|
|
5088
|
-
wheelCircleRef.current,
|
|
5089
|
-
...queryTreeContentElements(popSvgRef.current)
|
|
5090
|
-
]),
|
|
5091
|
-
"aria-label": "Fit to frame",
|
|
5092
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_md10.MdFitScreen, { className: "gtv-icon", size: 18 })
|
|
5093
|
-
}
|
|
5094
|
-
)
|
|
5095
|
-
] })
|
|
5160
|
+
panZoom,
|
|
5161
|
+
orientation: controlsOrientation,
|
|
5162
|
+
zoomControls: showZoomControls,
|
|
5163
|
+
leading: floatingControlsLeading,
|
|
5164
|
+
busy,
|
|
5165
|
+
onFit: () => panZoom.fitToFrame([
|
|
5166
|
+
wheelCircleRef.current,
|
|
5167
|
+
...queryTreeContentElements(popSvgRef.current)
|
|
5168
|
+
])
|
|
5096
5169
|
}
|
|
5097
5170
|
)
|
|
5098
5171
|
]
|
|
@@ -5101,14 +5174,14 @@ function WheelRadialPopCoreCore({
|
|
|
5101
5174
|
}
|
|
5102
5175
|
|
|
5103
5176
|
// src/GenreTreeWheelRadialPopCore.tsx
|
|
5104
|
-
var
|
|
5177
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
5105
5178
|
function GenreTreeWheelRadialPopCore(props) {
|
|
5106
|
-
return /* @__PURE__ */ (0,
|
|
5179
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(WheelRadialPopCoreCore, { ...props });
|
|
5107
5180
|
}
|
|
5108
5181
|
|
|
5109
5182
|
// src/GenreTreeOutline.tsx
|
|
5110
5183
|
var import_react14 = require("react");
|
|
5111
|
-
var
|
|
5184
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
5112
5185
|
function createOutlineStore(initial) {
|
|
5113
5186
|
let state = initial;
|
|
5114
5187
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -5182,9 +5255,9 @@ function OutlineNodeView({
|
|
|
5182
5255
|
const { itemCount } = node;
|
|
5183
5256
|
const children = model.index.childrenByParentId.get(nodeId) ?? [];
|
|
5184
5257
|
const split = isRoot ? model.ringSplitById.get(nodeId) : void 0;
|
|
5185
|
-
const row = /* @__PURE__ */ (0,
|
|
5186
|
-
/* @__PURE__ */ (0,
|
|
5187
|
-
/* @__PURE__ */ (0,
|
|
5258
|
+
const row = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: ["gtv-outline-row", isSelected && "gtv-outline-row--selected"].filter(Boolean).join(" "), children: [
|
|
5259
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "gtv-outline-dot", style: { background: model.styleOf(node).fill } }),
|
|
5260
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
5188
5261
|
"button",
|
|
5189
5262
|
{
|
|
5190
5263
|
type: "button",
|
|
@@ -5201,16 +5274,16 @@ function OutlineNodeView({
|
|
|
5201
5274
|
children: node.name
|
|
5202
5275
|
}
|
|
5203
5276
|
),
|
|
5204
|
-
/* @__PURE__ */ (0,
|
|
5277
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "gtv-outline-count", children: itemCount })
|
|
5205
5278
|
] });
|
|
5206
5279
|
const itemProps = { model, store, callbacks };
|
|
5207
|
-
return /* @__PURE__ */ (0,
|
|
5208
|
-
/* @__PURE__ */ (0,
|
|
5209
|
-
isOpen && /* @__PURE__ */ (0,
|
|
5210
|
-
/* @__PURE__ */ (0,
|
|
5211
|
-
/* @__PURE__ */ (0,
|
|
5212
|
-
] }) : children.map((child) => /* @__PURE__ */ (0,
|
|
5213
|
-
] }) : /* @__PURE__ */ (0,
|
|
5280
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("li", { className: "gtv-outline-item", "data-gtv-node-id": nodeId, children: children.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("details", { open: isOpen, children: [
|
|
5281
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("summary", { onClick: (event) => handleSummaryClick(event, () => callbacks.toggleOpen(nodeId)), children: row }),
|
|
5282
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("ul", { className: "gtv-outline-list", children: split ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
5283
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OutlineSection, { rootId: nodeId, kind: "core", branches: split.coreBranches, ...itemProps }),
|
|
5284
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OutlineSection, { rootId: nodeId, kind: "pop", branches: split.popBranches, ...itemProps })
|
|
5285
|
+
] }) : children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OutlineNode, { nodeId: child.id, isRoot: false, ...itemProps }, child.id)) })
|
|
5286
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "gtv-outline-leaf", children: row }) });
|
|
5214
5287
|
}
|
|
5215
5288
|
var OutlineNode = (0, import_react14.memo)(OutlineNodeView);
|
|
5216
5289
|
var OutlineSection = (0, import_react14.memo)(function OutlineSection2({
|
|
@@ -5224,9 +5297,9 @@ var OutlineSection = (0, import_react14.memo)(function OutlineSection2({
|
|
|
5224
5297
|
const key = sectionKey(kind, rootId);
|
|
5225
5298
|
const isOpen = useOutlineState(store, (s) => s.openIds.has(key));
|
|
5226
5299
|
if (branches.length === 0) return null;
|
|
5227
|
-
return /* @__PURE__ */ (0,
|
|
5228
|
-
/* @__PURE__ */ (0,
|
|
5229
|
-
isOpen && /* @__PURE__ */ (0,
|
|
5300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("li", { className: "gtv-outline-item", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("details", { open: isOpen, children: [
|
|
5301
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("summary", { onClick: (event) => handleSummaryClick(event, () => callbacks.toggleOpen(key)), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "gtv-outline-section-label", children: kind === "core" ? "Core" : "Pop" }) }),
|
|
5302
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("ul", { className: "gtv-outline-list", children: branches.map(({ child }) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OutlineNode, { nodeId: child.id, isRoot: false, model, store, callbacks }, child.id)) })
|
|
5230
5303
|
] }) });
|
|
5231
5304
|
});
|
|
5232
5305
|
function GenreTreeOutline(props) {
|
|
@@ -5243,10 +5316,6 @@ function GenreTreeOutline(props) {
|
|
|
5243
5316
|
hideInfoPanelTitle = false,
|
|
5244
5317
|
itemCountLabel,
|
|
5245
5318
|
readOnly,
|
|
5246
|
-
editMode,
|
|
5247
|
-
onEditModeChange,
|
|
5248
|
-
renderTitleEditor,
|
|
5249
|
-
renderParentsEditor,
|
|
5250
5319
|
renderDangerZone,
|
|
5251
5320
|
expanded,
|
|
5252
5321
|
onExpandedChange,
|
|
@@ -5347,12 +5416,12 @@ function GenreTreeOutline(props) {
|
|
|
5347
5416
|
}, [selectedNodeId, store]);
|
|
5348
5417
|
const selectedNode = selectedId === null ? void 0 : index.byId.get(selectedId);
|
|
5349
5418
|
const itemProps = { model, store, callbacks };
|
|
5350
|
-
return /* @__PURE__ */ (0,
|
|
5351
|
-
/* @__PURE__ */ (0,
|
|
5352
|
-
/* @__PURE__ */ (0,
|
|
5353
|
-
model.detachedRoots.length > 0 && /* @__PURE__ */ (0,
|
|
5419
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: ["gtv-outline", selectedNode && "gtv-outline--panel-open", className].filter(Boolean).join(" "), children: [
|
|
5420
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "gtv-outline-scroll", ref: scrollRef, children: [
|
|
5421
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("ul", { className: "gtv-outline-list gtv-outline-list--roots", children: [model.centerRoot, ...model.ringRoots].map((root) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OutlineNode, { nodeId: root.id, isRoot: true, ...itemProps }, root.id)) }),
|
|
5422
|
+
model.detachedRoots.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("ul", { className: "gtv-outline-list gtv-outline-list--roots gtv-outline-list--detached", children: model.detachedRoots.map((root) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OutlineNode, { nodeId: root.id, isRoot: true, ...itemProps }, root.id)) })
|
|
5354
5423
|
] }),
|
|
5355
|
-
selectedNode && /* @__PURE__ */ (0,
|
|
5424
|
+
selectedNode && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
5356
5425
|
InfoPanel,
|
|
5357
5426
|
{
|
|
5358
5427
|
node: selectedNode,
|
|
@@ -5373,10 +5442,6 @@ function GenreTreeOutline(props) {
|
|
|
5373
5442
|
hideTitle: hideInfoPanelTitle,
|
|
5374
5443
|
itemCountLabel,
|
|
5375
5444
|
readOnly,
|
|
5376
|
-
editMode,
|
|
5377
|
-
onEditModeChange,
|
|
5378
|
-
renderTitleEditor,
|
|
5379
|
-
renderParentsEditor,
|
|
5380
5445
|
renderDangerZone,
|
|
5381
5446
|
expanded,
|
|
5382
5447
|
onExpandedChange,
|
|
@@ -5395,13 +5460,13 @@ function GenreTreeOutline(props) {
|
|
|
5395
5460
|
} : void 0
|
|
5396
5461
|
}
|
|
5397
5462
|
),
|
|
5398
|
-
overlayPanel && /* @__PURE__ */ (0,
|
|
5463
|
+
overlayPanel && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OverlayPanel, { ...overlayPanel })
|
|
5399
5464
|
] });
|
|
5400
5465
|
}
|
|
5401
5466
|
|
|
5402
5467
|
// src/GenreTreeSkeleton.tsx
|
|
5403
5468
|
var import_react15 = require("react");
|
|
5404
|
-
var
|
|
5469
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
5405
5470
|
var CARD_FILL = "#F4F4F5";
|
|
5406
5471
|
var CARD_BORDER_COLOR = "#E4E4E7";
|
|
5407
5472
|
var CONNECTOR_COLOR2 = "#D4D4D8";
|
|
@@ -5506,8 +5571,8 @@ function SkeletonCard({
|
|
|
5506
5571
|
card,
|
|
5507
5572
|
rootAccent
|
|
5508
5573
|
}) {
|
|
5509
|
-
return /* @__PURE__ */ (0,
|
|
5510
|
-
/* @__PURE__ */ (0,
|
|
5574
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
5575
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5511
5576
|
"rect",
|
|
5512
5577
|
{
|
|
5513
5578
|
x: card.x,
|
|
@@ -5521,7 +5586,7 @@ function SkeletonCard({
|
|
|
5521
5586
|
strokeWidth: rootAccent ? 1.5 : 1
|
|
5522
5587
|
}
|
|
5523
5588
|
),
|
|
5524
|
-
rootAccent && /* @__PURE__ */ (0,
|
|
5589
|
+
rootAccent && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5525
5590
|
"circle",
|
|
5526
5591
|
{
|
|
5527
5592
|
cx: card.x + 18,
|
|
@@ -5539,9 +5604,9 @@ function GenreTreeSkeleton() {
|
|
|
5539
5604
|
const animationName = `genre-tree-skeleton-sweep-${uid}`;
|
|
5540
5605
|
const shimmerBandClass = `genre-tree-skeleton-shimmer-band-${uid}`;
|
|
5541
5606
|
const sweepDistance = VIEWBOX_WIDTH + SHIMMER_BAND_WIDTH;
|
|
5542
|
-
return /* @__PURE__ */ (0,
|
|
5543
|
-
/* @__PURE__ */ (0,
|
|
5544
|
-
/* @__PURE__ */ (0,
|
|
5607
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "gtv-skeleton", children: [
|
|
5608
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
|
|
5609
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
5545
5610
|
"svg",
|
|
5546
5611
|
{
|
|
5547
5612
|
viewBox: `0 0 ${VIEWBOX_WIDTH} ${VIEWBOX_HEIGHT}`,
|
|
@@ -5550,7 +5615,7 @@ function GenreTreeSkeleton() {
|
|
|
5550
5615
|
className: "gtv-skeleton-svg",
|
|
5551
5616
|
"aria-hidden": "true",
|
|
5552
5617
|
children: [
|
|
5553
|
-
/* @__PURE__ */ (0,
|
|
5618
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("style", { children: `
|
|
5554
5619
|
@keyframes ${animationName} {
|
|
5555
5620
|
from { transform: translateX(0); }
|
|
5556
5621
|
to { transform: translateX(${sweepDistance}px); }
|
|
@@ -5564,9 +5629,9 @@ function GenreTreeSkeleton() {
|
|
|
5564
5629
|
}
|
|
5565
5630
|
}
|
|
5566
5631
|
` }),
|
|
5567
|
-
/* @__PURE__ */ (0,
|
|
5568
|
-
/* @__PURE__ */ (0,
|
|
5569
|
-
/* @__PURE__ */ (0,
|
|
5632
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("defs", { children: [
|
|
5633
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "1", y2: "0", children: [
|
|
5634
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5570
5635
|
"stop",
|
|
5571
5636
|
{
|
|
5572
5637
|
offset: "0%",
|
|
@@ -5574,7 +5639,7 @@ function GenreTreeSkeleton() {
|
|
|
5574
5639
|
stopOpacity: 0
|
|
5575
5640
|
}
|
|
5576
5641
|
),
|
|
5577
|
-
/* @__PURE__ */ (0,
|
|
5642
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5578
5643
|
"stop",
|
|
5579
5644
|
{
|
|
5580
5645
|
offset: "50%",
|
|
@@ -5582,7 +5647,7 @@ function GenreTreeSkeleton() {
|
|
|
5582
5647
|
stopOpacity: 0.9
|
|
5583
5648
|
}
|
|
5584
5649
|
),
|
|
5585
|
-
/* @__PURE__ */ (0,
|
|
5650
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5586
5651
|
"stop",
|
|
5587
5652
|
{
|
|
5588
5653
|
offset: "100%",
|
|
@@ -5591,8 +5656,8 @@ function GenreTreeSkeleton() {
|
|
|
5591
5656
|
}
|
|
5592
5657
|
)
|
|
5593
5658
|
] }),
|
|
5594
|
-
/* @__PURE__ */ (0,
|
|
5595
|
-
ALL_LINKS.map((link, i) => /* @__PURE__ */ (0,
|
|
5659
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("mask", { id: maskId, children: [
|
|
5660
|
+
ALL_LINKS.map((link, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5596
5661
|
"path",
|
|
5597
5662
|
{
|
|
5598
5663
|
d: connectorPath(link.from, link.to),
|
|
@@ -5602,7 +5667,7 @@ function GenreTreeSkeleton() {
|
|
|
5602
5667
|
},
|
|
5603
5668
|
`link-mask-${i}`
|
|
5604
5669
|
)),
|
|
5605
|
-
ALL_CARDS.map(({ card }, i) => /* @__PURE__ */ (0,
|
|
5670
|
+
ALL_CARDS.map(({ card }, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5606
5671
|
"rect",
|
|
5607
5672
|
{
|
|
5608
5673
|
x: card.x,
|
|
@@ -5617,7 +5682,7 @@ function GenreTreeSkeleton() {
|
|
|
5617
5682
|
))
|
|
5618
5683
|
] })
|
|
5619
5684
|
] }),
|
|
5620
|
-
ALL_LINKS.map((link, i) => /* @__PURE__ */ (0,
|
|
5685
|
+
ALL_LINKS.map((link, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5621
5686
|
"path",
|
|
5622
5687
|
{
|
|
5623
5688
|
d: connectorPath(link.from, link.to),
|
|
@@ -5627,8 +5692,8 @@ function GenreTreeSkeleton() {
|
|
|
5627
5692
|
},
|
|
5628
5693
|
`link-${i}`
|
|
5629
5694
|
)),
|
|
5630
|
-
ALL_CARDS.map(({ card, rootAccent }, i) => /* @__PURE__ */ (0,
|
|
5631
|
-
/* @__PURE__ */ (0,
|
|
5695
|
+
ALL_CARDS.map(({ card, rootAccent }, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SkeletonCard, { card, rootAccent }, `card-${i}`)),
|
|
5696
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("g", { mask: `url(#${maskId})`, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
5632
5697
|
"rect",
|
|
5633
5698
|
{
|
|
5634
5699
|
className: shimmerBandClass,
|
|
@@ -5647,7 +5712,7 @@ function GenreTreeSkeleton() {
|
|
|
5647
5712
|
|
|
5648
5713
|
// src/GenreTreeWheelSkeleton.tsx
|
|
5649
5714
|
var import_react16 = require("react");
|
|
5650
|
-
var
|
|
5715
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
5651
5716
|
var SHAPE_COLOR = "#D4D4D8";
|
|
5652
5717
|
var SECTOR_FILL_OPACITY = 0.22;
|
|
5653
5718
|
var INNER_TINT_OPACITY = 0.55;
|
|
@@ -5792,8 +5857,8 @@ function GenreTreeWheelSkeleton() {
|
|
|
5792
5857
|
sharedStartTime ??= animation.startTime ?? document.timeline.currentTime;
|
|
5793
5858
|
animation.startTime = sharedStartTime;
|
|
5794
5859
|
}, []);
|
|
5795
|
-
return /* @__PURE__ */ (0,
|
|
5796
|
-
/* @__PURE__ */ (0,
|
|
5860
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "gtv-wheel-skeleton", children: [
|
|
5861
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
5797
5862
|
"div",
|
|
5798
5863
|
{
|
|
5799
5864
|
className: "gtv-wheel-skeleton-sector-layer",
|
|
@@ -5801,8 +5866,8 @@ function GenreTreeWheelSkeleton() {
|
|
|
5801
5866
|
"aria-hidden": "true"
|
|
5802
5867
|
}
|
|
5803
5868
|
),
|
|
5804
|
-
/* @__PURE__ */ (0,
|
|
5805
|
-
/* @__PURE__ */ (0,
|
|
5869
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
|
|
5870
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
5806
5871
|
"svg",
|
|
5807
5872
|
{
|
|
5808
5873
|
viewBox: `${CANVAS_ORIGIN} ${CANVAS_ORIGIN} ${CANVAS_SIZE} ${CANVAS_SIZE}`,
|
|
@@ -5810,14 +5875,14 @@ function GenreTreeWheelSkeleton() {
|
|
|
5810
5875
|
className: "gtv-wheel-skeleton-svg",
|
|
5811
5876
|
"aria-hidden": "true",
|
|
5812
5877
|
children: [
|
|
5813
|
-
/* @__PURE__ */ (0,
|
|
5814
|
-
/* @__PURE__ */ (0,
|
|
5815
|
-
/* @__PURE__ */ (0,
|
|
5816
|
-
/* @__PURE__ */ (0,
|
|
5817
|
-
/* @__PURE__ */ (0,
|
|
5878
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("defs", { children: [
|
|
5879
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("radialGradient", { id: mainstreamId, children: [
|
|
5880
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("stop", { offset: "0%", stopColor: "#FFFFFF", stopOpacity: 0.45 }),
|
|
5881
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("stop", { offset: "65%", stopColor: "#FFFFFF", stopOpacity: 0.15 }),
|
|
5882
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("stop", { offset: "100%", stopColor: "#FFFFFF", stopOpacity: 0 })
|
|
5818
5883
|
] }),
|
|
5819
|
-
/* @__PURE__ */ (0,
|
|
5820
|
-
LINKS.map((link, i) => /* @__PURE__ */ (0,
|
|
5884
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("g", { id: shapesId, children: [
|
|
5885
|
+
LINKS.map((link, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
5821
5886
|
"path",
|
|
5822
5887
|
{
|
|
5823
5888
|
d: link.d,
|
|
@@ -5829,21 +5894,21 @@ function GenreTreeWheelSkeleton() {
|
|
|
5829
5894
|
},
|
|
5830
5895
|
`link-${i}`
|
|
5831
5896
|
)),
|
|
5832
|
-
DOTS.map((dot, i) => /* @__PURE__ */ (0,
|
|
5897
|
+
DOTS.map((dot, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: dot.point.x, cy: dot.point.y, r: dot.r, fill: "currentColor" }, `dot-${i}`))
|
|
5833
5898
|
] })
|
|
5834
5899
|
] }),
|
|
5835
|
-
/* @__PURE__ */ (0,
|
|
5836
|
-
/* @__PURE__ */ (0,
|
|
5837
|
-
/* @__PURE__ */ (0,
|
|
5900
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: 0, cy: 0, r: RING_RADIUS, fill: "#FFFFFF", fillOpacity: INNER_TINT_OPACITY }),
|
|
5901
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("circle", { cx: 0, cy: 0, r: MAINSTREAM_RADIUS, fill: `url(#${mainstreamId})` }),
|
|
5902
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("use", { href: `#${shapesId}`, color: SHAPE_COLOR })
|
|
5838
5903
|
]
|
|
5839
5904
|
}
|
|
5840
5905
|
),
|
|
5841
|
-
/* @__PURE__ */ (0,
|
|
5906
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "gtv-wheel-skeleton-shimmer", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { ref: wedgeRef, className: "gtv-wheel-skeleton-shimmer-wedge" }) })
|
|
5842
5907
|
] });
|
|
5843
5908
|
}
|
|
5844
5909
|
|
|
5845
5910
|
// src/GenreTreeOutlineSkeleton.tsx
|
|
5846
|
-
var
|
|
5911
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
5847
5912
|
var ROWS = [
|
|
5848
5913
|
{ depth: 0, width: 38 },
|
|
5849
5914
|
{ depth: 0, width: 26 },
|
|
@@ -5855,9 +5920,9 @@ var ROWS = [
|
|
|
5855
5920
|
{ depth: 0, width: 22 }
|
|
5856
5921
|
];
|
|
5857
5922
|
function GenreTreeOutlineSkeleton() {
|
|
5858
|
-
return /* @__PURE__ */ (0,
|
|
5859
|
-
/* @__PURE__ */ (0,
|
|
5860
|
-
ROWS.map((row, i) => /* @__PURE__ */ (0,
|
|
5923
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "gtv-skeleton gtv-outline-skeleton", children: [
|
|
5924
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "gtv-skeleton-sr-only", children: "Loading genre tree\u2026" }),
|
|
5925
|
+
ROWS.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
5861
5926
|
"div",
|
|
5862
5927
|
{
|
|
5863
5928
|
className: "gtv-outline-skeleton-row",
|
|
@@ -5870,13 +5935,13 @@ function GenreTreeOutlineSkeleton() {
|
|
|
5870
5935
|
}
|
|
5871
5936
|
|
|
5872
5937
|
// src/GenreTreeViewSkeleton.tsx
|
|
5873
|
-
var
|
|
5938
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
5874
5939
|
function GenreTreeViewSkeleton({ viewMode }) {
|
|
5875
5940
|
if (viewMode === "wheel" || viewMode === "pop-core") {
|
|
5876
|
-
return /* @__PURE__ */ (0,
|
|
5941
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "gtv-view-skeleton-wheel-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenreTreeWheelSkeleton, {}) });
|
|
5877
5942
|
}
|
|
5878
|
-
if (viewMode === "outline") return /* @__PURE__ */ (0,
|
|
5879
|
-
return /* @__PURE__ */ (0,
|
|
5943
|
+
if (viewMode === "outline") return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenreTreeOutlineSkeleton, {});
|
|
5944
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenreTreeSkeleton, {});
|
|
5880
5945
|
}
|
|
5881
5946
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5882
5947
|
0 && (module.exports = {
|