@principal-ai/file-city-react 0.5.40 → 0.5.42
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/components/FileCity3D/FileCity3D.d.ts +8 -2
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +129 -40
- package/dist/components/FileCityExplorer/AddToAreaModal.d.ts +14 -0
- package/dist/components/FileCityExplorer/AddToAreaModal.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/AddToAreaModal.js +140 -0
- package/dist/components/FileCityExplorer/AddToScopeModal.d.ts +14 -0
- package/dist/components/FileCityExplorer/AddToScopeModal.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/AddToScopeModal.js +176 -0
- package/dist/components/FileCityExplorer/FileCityExplorer.d.ts +30 -0
- package/dist/components/FileCityExplorer/FileCityExplorer.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/FileCityExplorer.js +1045 -0
- package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts +10 -0
- package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/ScopeInfoOverlay.js +73 -0
- package/dist/components/FileCityExplorer/index.d.ts +3 -0
- package/dist/components/FileCityExplorer/index.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/index.js +1 -0
- package/dist/components/FileCityExplorer/layers.d.ts +16 -0
- package/dist/components/FileCityExplorer/layers.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/layers.js +61 -0
- package/dist/components/FileCityExplorer/model.d.ts +32 -0
- package/dist/components/FileCityExplorer/model.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/model.js +14 -0
- package/dist/components/FileCityExplorer/pathConversion.d.ts +19 -0
- package/dist/components/FileCityExplorer/pathConversion.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/pathConversion.js +26 -0
- package/dist/components/FileCityExplorer/scopeTreePaths.d.ts +21 -0
- package/dist/components/FileCityExplorer/scopeTreePaths.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/scopeTreePaths.js +42 -0
- package/dist/components/FileCityExplorer/styles.d.ts +9 -0
- package/dist/components/FileCityExplorer/styles.d.ts.map +1 -0
- package/dist/components/FileCityExplorer/styles.js +28 -0
- package/dist/utils/folderElevatedPanels.d.ts +3 -1
- package/dist/utils/folderElevatedPanels.d.ts.map +1 -1
- package/dist/utils/folderElevatedPanels.js +13 -2
- package/package.json +2 -1
- package/src/components/FileCity3D/FileCity3D.tsx +200 -52
- package/src/components/FileCityExplorer/AddToAreaModal.tsx +273 -0
- package/src/components/FileCityExplorer/AddToScopeModal.tsx +320 -0
- package/src/components/FileCityExplorer/FileCityExplorer.tsx +1457 -0
- package/src/components/FileCityExplorer/ScopeInfoOverlay.tsx +229 -0
- package/src/components/FileCityExplorer/index.ts +2 -0
- package/src/components/FileCityExplorer/layers.ts +72 -0
- package/src/components/FileCityExplorer/model.ts +35 -0
- package/src/components/FileCityExplorer/pathConversion.ts +32 -0
- package/src/components/FileCityExplorer/scopeTreePaths.ts +52 -0
- package/src/components/FileCityExplorer/styles.ts +34 -0
- package/src/stories/2D3DComparison.stories.tsx +13 -2
- package/src/stories/ElevatedScopePanels.stories.tsx +295 -0
- package/src/stories/FileCity3D.stories.tsx +24 -3
- package/src/stories/FileCityExplorer.stories.tsx +2474 -0
- package/src/stories/FileCityExplorerComponent.stories.tsx +59 -0
- package/src/stories/LeaderLineSnippetOverlay.stories.tsx +306 -0
- package/src/utils/folderElevatedPanels.ts +15 -2
- package/src/stories/ScopeOverlay.stories.tsx +0 -1610
|
@@ -68,8 +68,14 @@ export interface ElevatedScopePanel {
|
|
|
68
68
|
* size derived from the panel's footprint. Always clamped to fit the tile.
|
|
69
69
|
*/
|
|
70
70
|
labelSize?: number;
|
|
71
|
+
/** Optional secondary label rendered above the main label in a smaller font. */
|
|
72
|
+
displayLabel?: string;
|
|
73
|
+
/** Hex color for the display label (default `labelColor` or white). */
|
|
74
|
+
displayLabelColor?: string;
|
|
71
75
|
/** Click handler. When set, the slab becomes interactive and shows a pointer cursor. */
|
|
72
|
-
onClick?: () => void;
|
|
76
|
+
onClick?: (event: MouseEvent) => void;
|
|
77
|
+
/** Double-click handler. The slab becomes interactive (pointer cursor) when either onClick or onDoubleClick is set. */
|
|
78
|
+
onDoubleClick?: (event: MouseEvent) => void;
|
|
73
79
|
}
|
|
74
80
|
/** Pattern for files that should render flat (e.g., lock files, generated files) */
|
|
75
81
|
export interface FlatPattern {
|
|
@@ -179,7 +185,7 @@ export interface FileCity3DProps {
|
|
|
179
185
|
/** Height of the container */
|
|
180
186
|
height?: number | string;
|
|
181
187
|
/** Callback when a building is clicked */
|
|
182
|
-
onBuildingClick?: (building: CityBuilding) => void;
|
|
188
|
+
onBuildingClick?: (building: CityBuilding, event: MouseEvent) => void;
|
|
183
189
|
/** CSS class name */
|
|
184
190
|
className?: string;
|
|
185
191
|
/** Inline styles */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
8
8
|
* Supports animated transition from 2D (flat) to 3D (grown buildings).
|
|
9
9
|
*/
|
|
10
10
|
import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react';
|
|
11
|
+
import { useTheme } from '@principal-ade/industry-theme';
|
|
11
12
|
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
|
12
13
|
import { useSpring } from '@react-spring/three';
|
|
13
14
|
import { MapControls, PerspectiveCamera, Text } from '@react-three/drei';
|
|
@@ -377,7 +378,7 @@ function BorderHighlights({ buildings, centerOffset, highlightLayers, growProgre
|
|
|
377
378
|
const currentTime = clock.elapsedTime * 1000;
|
|
378
379
|
const animStartTime = startTimeRef.current ?? currentTime;
|
|
379
380
|
borderEdgeData.forEach((edge, idx) => {
|
|
380
|
-
const { x, z, fullHeight, staggerDelayMs, buildingIndex, color,
|
|
381
|
+
const { x, z, fullHeight, staggerDelayMs, buildingIndex, color, borderWidth, edgeType, width, depth } = edge;
|
|
381
382
|
// Get height multiplier from shared ref (for collapse animation)
|
|
382
383
|
const heightMultiplier = heightMultipliersRef.current?.[buildingIndex] ?? 1;
|
|
383
384
|
// Calculate per-building animation progress
|
|
@@ -656,7 +657,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
656
657
|
e.stopPropagation();
|
|
657
658
|
if (e.instanceId !== undefined && e.instanceId < buildingData.length) {
|
|
658
659
|
const data = buildingData[e.instanceId];
|
|
659
|
-
onClick?.(data.building);
|
|
660
|
+
onClick?.(data.building, e.nativeEvent);
|
|
660
661
|
}
|
|
661
662
|
}, [buildingData, onClick]);
|
|
662
663
|
if (buildingData.length === 0)
|
|
@@ -1034,6 +1035,68 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
|
|
|
1034
1035
|
});
|
|
1035
1036
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1036
1037
|
}, [isFlat]); // Only animate when isFlat changes, not when focusTarget/citySize/etc change
|
|
1038
|
+
// Animate the camera when focusTarget changes (works in both 2D and 3D).
|
|
1039
|
+
// - 3D + target: frame the directory using the same math as the 2D→3D path
|
|
1040
|
+
// - 3D + null: ease back to the overview position
|
|
1041
|
+
// - 2D + target: pan top-down camera over the directory and zoom to fit it
|
|
1042
|
+
// - 2D + null: return to the centered top-down overview
|
|
1043
|
+
useEffect(() => {
|
|
1044
|
+
if (!hasAppliedInitial.current)
|
|
1045
|
+
return;
|
|
1046
|
+
let newPos;
|
|
1047
|
+
if (isFlat) {
|
|
1048
|
+
if (focusTarget) {
|
|
1049
|
+
const perspCam = camera;
|
|
1050
|
+
const aspect = perspCam.aspect || 1;
|
|
1051
|
+
const fovRad = (50 * Math.PI) / 180;
|
|
1052
|
+
const tanHalfFov = Math.tan(fovRad / 2);
|
|
1053
|
+
const effectiveAspect = Math.min(1, aspect);
|
|
1054
|
+
// Same framing math as calculateFlatCameraHeight, but using the focus
|
|
1055
|
+
// region's size instead of citySize so the directory fills the view.
|
|
1056
|
+
const height = (focusTarget.size / (2 * tanHalfFov * effectiveAspect)) * 1.08;
|
|
1057
|
+
newPos = {
|
|
1058
|
+
x: focusTarget.x,
|
|
1059
|
+
y: height,
|
|
1060
|
+
z: focusTarget.z + 0.001,
|
|
1061
|
+
targetX: focusTarget.x,
|
|
1062
|
+
targetY: 0,
|
|
1063
|
+
targetZ: focusTarget.z,
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
newPos = getInitial2DPosition();
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
else if (focusTarget) {
|
|
1071
|
+
newPos = {
|
|
1072
|
+
x: focusTarget.x,
|
|
1073
|
+
y: Math.max(focusTarget.size * 1.5, 40),
|
|
1074
|
+
z: focusTarget.z + Math.max(focusTarget.size * 2, 50),
|
|
1075
|
+
targetX: focusTarget.x,
|
|
1076
|
+
targetY: 0,
|
|
1077
|
+
targetZ: focusTarget.z,
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
newPos = {
|
|
1082
|
+
x: 0,
|
|
1083
|
+
y: maxBuildingHeight > 0 ? Math.max(citySize * 1.1, maxBuildingHeight * 2.5) : citySize * 1.1,
|
|
1084
|
+
z: citySize * 1.3,
|
|
1085
|
+
targetX: 0,
|
|
1086
|
+
targetY: 0,
|
|
1087
|
+
targetZ: 0,
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
api.start({
|
|
1091
|
+
camX: newPos.x,
|
|
1092
|
+
camY: newPos.y,
|
|
1093
|
+
camZ: newPos.z,
|
|
1094
|
+
lookX: newPos.targetX,
|
|
1095
|
+
lookY: newPos.targetY,
|
|
1096
|
+
lookZ: newPos.targetZ,
|
|
1097
|
+
});
|
|
1098
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1099
|
+
}, [focusTarget, isFlat]);
|
|
1037
1100
|
// Update camera each frame
|
|
1038
1101
|
useFrame(() => {
|
|
1039
1102
|
frameCount.current++;
|
|
@@ -1126,11 +1189,6 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
|
|
|
1126
1189
|
}
|
|
1127
1190
|
// Handle position animation
|
|
1128
1191
|
else if (isAnimatingRef.current) {
|
|
1129
|
-
const springY = camY.get();
|
|
1130
|
-
const currentY = camera.position.y;
|
|
1131
|
-
if (Math.abs(springY - currentY) > 1) {
|
|
1132
|
-
console.log('[useFrame] Spring animating - springY:', springY, 'currentY:', currentY);
|
|
1133
|
-
}
|
|
1134
1192
|
camera.position.set(camX.get(), camY.get(), camZ.get());
|
|
1135
1193
|
controlsRef.current.target.set(lookX.get(), lookY.get(), lookZ.get());
|
|
1136
1194
|
controlsRef.current.update();
|
|
@@ -1426,14 +1484,17 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
|
|
|
1426
1484
|
}), [controlsConfig.oneFingerTouch, controlsConfig.twoFingerTouch]);
|
|
1427
1485
|
return (_jsxs(_Fragment, { children: [_jsx(PerspectiveCamera, { makeDefault: true, fov: 50, near: 1, far: citySize * 10 }), _jsx(MapControls, { ref: controlsRef, enableDamping: true, dampingFactor: 0.05, minDistance: 10, maxDistance: citySize * 3, maxPolarAngle: Math.PI / 2.1, mouseButtons: mouseButtons, touches: touches, enableZoom: controlsConfig.wheel !== 'pan', panSpeed: controlsConfig.panSpeed ?? 1, rotateSpeed: controlsConfig.rotateSpeed ?? 1, zoomSpeed: controlsConfig.zoomSpeed ?? 1 })] }));
|
|
1428
1486
|
}, (prevProps, nextProps) => {
|
|
1429
|
-
// Custom comparison:
|
|
1430
|
-
//
|
|
1487
|
+
// Custom comparison: re-render when isFlat, citySize, maxBuildingHeight,
|
|
1488
|
+
// cameraControls, or focusTarget change. focusTarget is included so the
|
|
1489
|
+
// useEffect that animates the camera on focus changes actually fires.
|
|
1431
1490
|
return (prevProps.isFlat === nextProps.isFlat &&
|
|
1432
1491
|
prevProps.citySize === nextProps.citySize &&
|
|
1433
1492
|
prevProps.maxBuildingHeight === nextProps.maxBuildingHeight &&
|
|
1434
|
-
prevProps.cameraControls === nextProps.cameraControls
|
|
1493
|
+
prevProps.cameraControls === nextProps.cameraControls &&
|
|
1494
|
+
prevProps.focusTarget === nextProps.focusTarget);
|
|
1435
1495
|
});
|
|
1436
1496
|
function InfoPanel({ building }) {
|
|
1497
|
+
const { theme } = useTheme();
|
|
1437
1498
|
if (!building)
|
|
1438
1499
|
return null;
|
|
1439
1500
|
const fileName = building.path.split('/').pop();
|
|
@@ -1442,55 +1503,56 @@ function InfoPanel({ building }) {
|
|
|
1442
1503
|
position: 'absolute',
|
|
1443
1504
|
bottom: 16,
|
|
1444
1505
|
left: 60,
|
|
1445
|
-
background:
|
|
1446
|
-
border:
|
|
1447
|
-
borderRadius:
|
|
1506
|
+
background: `color-mix(in oklab, ${theme.colors.background} 90%, transparent)`,
|
|
1507
|
+
border: `1px solid ${theme.colors.border}`,
|
|
1508
|
+
borderRadius: theme.radii[4],
|
|
1448
1509
|
padding: '12px 16px',
|
|
1449
|
-
color:
|
|
1450
|
-
fontSize:
|
|
1451
|
-
fontFamily:
|
|
1510
|
+
color: theme.colors.text,
|
|
1511
|
+
fontSize: theme.fontSizes[1],
|
|
1512
|
+
fontFamily: theme.fonts.monospace,
|
|
1452
1513
|
maxWidth: 400,
|
|
1453
1514
|
pointerEvents: 'none',
|
|
1454
|
-
}, children: [_jsx("div", { style: { fontWeight:
|
|
1455
|
-
color:
|
|
1515
|
+
}, children: [_jsx("div", { style: { fontWeight: theme.fontWeights.semibold, marginBottom: 4 }, children: fileName }), _jsx("div", { style: { color: theme.colors.textMuted, fontSize: theme.fontSizes[0] }, children: dirPath }), _jsxs("div", { style: {
|
|
1516
|
+
color: theme.colors.textTertiary,
|
|
1456
1517
|
fontSize: 11,
|
|
1457
1518
|
marginTop: 4,
|
|
1458
1519
|
display: 'flex',
|
|
1459
|
-
gap:
|
|
1520
|
+
gap: theme.space[3],
|
|
1460
1521
|
}, children: [building.lineCount !== undefined && (_jsxs("span", { children: [building.lineCount.toLocaleString(), " lines"] })), building.size !== undefined && _jsxs("span", { children: [(building.size / 1024).toFixed(1), " KB"] })] })] }));
|
|
1461
1522
|
}
|
|
1462
1523
|
function ControlsOverlay({ isFlat, onToggle, onResetCamera, onLookDown }) {
|
|
1524
|
+
const { theme } = useTheme();
|
|
1463
1525
|
const buttonStyle = {
|
|
1464
|
-
background:
|
|
1465
|
-
border:
|
|
1466
|
-
borderRadius:
|
|
1526
|
+
background: `color-mix(in oklab, ${theme.colors.background} 90%, transparent)`,
|
|
1527
|
+
border: `1px solid ${theme.colors.border}`,
|
|
1528
|
+
borderRadius: theme.radii[4],
|
|
1467
1529
|
padding: '10px',
|
|
1468
|
-
color:
|
|
1469
|
-
fontSize:
|
|
1530
|
+
color: theme.colors.text,
|
|
1531
|
+
fontSize: theme.fontSizes[1],
|
|
1470
1532
|
cursor: 'pointer',
|
|
1471
1533
|
display: 'flex',
|
|
1472
1534
|
alignItems: 'center',
|
|
1473
1535
|
justifyContent: 'center',
|
|
1474
1536
|
width: 40,
|
|
1475
1537
|
height: 40,
|
|
1476
|
-
fontWeight:
|
|
1538
|
+
fontWeight: theme.fontWeights.medium,
|
|
1477
1539
|
};
|
|
1478
1540
|
return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: onToggle, style: {
|
|
1479
1541
|
...buttonStyle,
|
|
1480
1542
|
position: 'absolute',
|
|
1481
|
-
|
|
1482
|
-
left: 8,
|
|
1483
|
-
}, children: isFlat ? '3D' : '2D' }), _jsx("button", { onClick: onResetCamera, style: {
|
|
1484
|
-
...buttonStyle,
|
|
1485
|
-
position: 'absolute',
|
|
1486
|
-
top: 8,
|
|
1543
|
+
bottom: 8,
|
|
1487
1544
|
right: 8,
|
|
1488
|
-
},
|
|
1545
|
+
}, children: isFlat ? '3D' : '2D' }), _jsx("button", { onClick: onLookDown, style: {
|
|
1489
1546
|
...buttonStyle,
|
|
1490
1547
|
position: 'absolute',
|
|
1491
1548
|
bottom: 8,
|
|
1492
1549
|
left: 8,
|
|
1493
|
-
}, title: "Look down", children: "\u2B07" })
|
|
1550
|
+
}, title: "Look down", children: "\u2B07" }), _jsx("button", { onClick: onResetCamera, style: {
|
|
1551
|
+
...buttonStyle,
|
|
1552
|
+
position: 'absolute',
|
|
1553
|
+
bottom: 8,
|
|
1554
|
+
left: 56,
|
|
1555
|
+
}, title: "Reset View", children: "\u21BB" })] }));
|
|
1494
1556
|
}
|
|
1495
1557
|
function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding, selectedBuilding, growProgress, animationConfig, highlightLayers, isolationMode, heightScaling, linearScale, flatPatterns, focusDirectory, focusColor, adaptCameraToBuildings = false, elevatedScopePanels, cameraControls, onCameraReady, }) {
|
|
1496
1558
|
const centerOffset = useMemo(() => ({
|
|
@@ -1580,14 +1642,31 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
1580
1642
|
}
|
|
1581
1643
|
// Case 3: Switching between directories (dirA -> dirB)
|
|
1582
1644
|
if (prevFocus !== null && focusDirectory !== null) {
|
|
1583
|
-
//
|
|
1645
|
+
// Direct transition when the two directories are visually adjacent:
|
|
1646
|
+
// - parent ↔ child (one is a prefix of the other)
|
|
1647
|
+
// - immediate siblings (same parent folder)
|
|
1648
|
+
// In both cases the new directory is already in or near the current
|
|
1649
|
+
// view, so a zoom-out detour would feel like extra travel.
|
|
1650
|
+
const isDescendant = focusDirectory.startsWith(prevFocus + '/');
|
|
1651
|
+
const isAncestor = prevFocus.startsWith(focusDirectory + '/');
|
|
1652
|
+
const parentOf = (p) => {
|
|
1653
|
+
const i = p.lastIndexOf('/');
|
|
1654
|
+
return i >= 0 ? p.slice(0, i) : '';
|
|
1655
|
+
};
|
|
1656
|
+
const isSibling = parentOf(prevFocus) === parentOf(focusDirectory);
|
|
1657
|
+
if (isDescendant || isAncestor || isSibling) {
|
|
1658
|
+
setBuildingFocusDirectory(focusDirectory);
|
|
1659
|
+
setBuildingFocusColor(focusColor ?? null);
|
|
1660
|
+
setCameraFocusDirectory(focusDirectory);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
// Unrelated branches — keep the 3-phase out/in transition so the
|
|
1664
|
+
// long camera flight stays legible.
|
|
1584
1665
|
setCameraFocusDirectory(null);
|
|
1585
|
-
// Phase 2: After zoom-out, collapse/expand buildings with new color
|
|
1586
1666
|
const timer1 = setTimeout(() => {
|
|
1587
1667
|
setBuildingFocusDirectory(focusDirectory);
|
|
1588
1668
|
setBuildingFocusColor(focusColor ?? null);
|
|
1589
1669
|
}, 500);
|
|
1590
|
-
// Phase 3: After collapse settles, zoom camera into new directory
|
|
1591
1670
|
const timer2 = setTimeout(() => {
|
|
1592
1671
|
setCameraFocusDirectory(focusDirectory);
|
|
1593
1672
|
}, 1100); // 500ms zoom-out + 600ms collapse
|
|
@@ -1697,24 +1776,34 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
1697
1776
|
const tileMax = Math.min(w, d) / 2;
|
|
1698
1777
|
const requested = panel.labelSize ?? Math.min(w, d) / 6;
|
|
1699
1778
|
const labelSize = Math.max(4, Math.min(tileMax, requested));
|
|
1779
|
+
const interactive = Boolean(panel.onClick || panel.onDoubleClick);
|
|
1700
1780
|
const handleClick = panel.onClick
|
|
1701
1781
|
? (e) => {
|
|
1702
1782
|
e.stopPropagation();
|
|
1703
|
-
panel.onClick();
|
|
1783
|
+
panel.onClick(e.nativeEvent);
|
|
1784
|
+
}
|
|
1785
|
+
: undefined;
|
|
1786
|
+
const handleDoubleClick = panel.onDoubleClick
|
|
1787
|
+
? (e) => {
|
|
1788
|
+
e.stopPropagation();
|
|
1789
|
+
panel.onDoubleClick(e.nativeEvent);
|
|
1704
1790
|
}
|
|
1705
1791
|
: undefined;
|
|
1706
|
-
const handlePointerOver =
|
|
1792
|
+
const handlePointerOver = interactive
|
|
1707
1793
|
? (e) => {
|
|
1708
1794
|
e.stopPropagation();
|
|
1709
1795
|
document.body.style.cursor = 'pointer';
|
|
1710
1796
|
}
|
|
1711
1797
|
: undefined;
|
|
1712
|
-
const handlePointerOut =
|
|
1798
|
+
const handlePointerOut = interactive
|
|
1713
1799
|
? () => {
|
|
1714
1800
|
document.body.style.cursor = '';
|
|
1715
1801
|
}
|
|
1716
1802
|
: undefined;
|
|
1717
|
-
return (_jsxs("group", { children: [_jsxs("mesh", { position: [cx, y, cz], renderOrder: 10, onClick: handleClick, onPointerOver: handlePointerOver, onPointerOut: handlePointerOut, children: [_jsx("boxGeometry", { args: [w, t, d] }), _jsx("meshBasicMaterial", { color: panel.color, transparent: !isOpaque, opacity: opacity, depthWrite: isOpaque })] }), panel.
|
|
1803
|
+
return (_jsxs("group", { children: [_jsxs("mesh", { position: [cx, y, cz], renderOrder: 10, onClick: handleClick, onDoubleClick: handleDoubleClick, onPointerOver: handlePointerOver, onPointerOut: handlePointerOut, children: [_jsx("boxGeometry", { args: [w, t, d] }), _jsx("meshBasicMaterial", { color: panel.color, transparent: !isOpaque, opacity: opacity, depthWrite: isOpaque })] }), panel.displayLabel && (_jsxs(_Fragment, { children: [_jsxs(Text, { position: [cx, topY + 0.05, cz - labelSize * 0.6], rotation: [-Math.PI / 2, 0, 0], fontSize: labelSize, color: panel.displayLabelColor ?? panel.labelColor ?? '#ffffff', anchorX: "center", anchorY: "middle", maxWidth: w * 0.9, textAlign: "center", renderOrder: 11, frustumCulled: false, children: [panel.displayLabel, _jsx("meshBasicMaterial", { attach: "material", color: panel.displayLabelColor ?? panel.labelColor ?? '#ffffff', depthWrite: false, depthTest: false })] }), _jsxs("mesh", { position: [cx, topY + 0.06, cz - labelSize * 0.05], rotation: [-Math.PI / 2, 0, 0], renderOrder: 11, children: [_jsx("planeGeometry", { args: [
|
|
1804
|
+
Math.min(w * 0.9, panel.displayLabel.length * labelSize * 0.55),
|
|
1805
|
+
labelSize * 0.06,
|
|
1806
|
+
] }), _jsx("meshBasicMaterial", { color: panel.displayLabelColor ?? panel.labelColor ?? '#ffffff', depthWrite: false, depthTest: false, transparent: true })] })] })), panel.label && (_jsxs(Text, { position: [cx, topY + 0.05, cz + (panel.displayLabel ? labelSize * 0.6 : 0)], rotation: [-Math.PI / 2, 0, 0], fontSize: labelSize, color: panel.labelColor ?? '#ffffff', anchorX: "center", anchorY: "middle", maxWidth: w * 0.9, textAlign: "center", renderOrder: 11, frustumCulled: false, children: [panel.label, _jsx("meshBasicMaterial", { attach: "material", color: panel.labelColor ?? '#ffffff', depthWrite: false, depthTest: false })] }))] }, panel.id));
|
|
1718
1807
|
})] }));
|
|
1719
1808
|
}
|
|
1720
1809
|
/**
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ProjectArea } from './model';
|
|
3
|
+
export declare const AddToAreaModal: React.FC<{
|
|
4
|
+
path: string;
|
|
5
|
+
areas: readonly ProjectArea[];
|
|
6
|
+
areaName: string;
|
|
7
|
+
description: string;
|
|
8
|
+
onAreaNameChange: (value: string) => void;
|
|
9
|
+
onDescriptionChange: (value: string) => void;
|
|
10
|
+
onPickExisting: (areaName: string) => void;
|
|
11
|
+
onSubmit: () => void;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=AddToAreaModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddToAreaModal.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/AddToAreaModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAgQA,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTheme } from '@principal-ade/industry-theme';
|
|
4
|
+
import { AREA_PANEL_COLOR } from './layers';
|
|
5
|
+
import { makeSectionLabelStyle } from './styles';
|
|
6
|
+
export const AddToAreaModal = ({ path, areas, areaName, description, onAreaNameChange, onDescriptionChange, onPickExisting, onSubmit, onClose, }) => {
|
|
7
|
+
const { theme } = useTheme();
|
|
8
|
+
const sectionLabelStyle = makeSectionLabelStyle(theme);
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
const onKey = (e) => {
|
|
11
|
+
if (e.key === 'Escape')
|
|
12
|
+
onClose();
|
|
13
|
+
};
|
|
14
|
+
window.addEventListener('keydown', onKey);
|
|
15
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
16
|
+
}, [onClose]);
|
|
17
|
+
const trimmedName = areaName.trim();
|
|
18
|
+
const canSubmit = trimmedName.length > 0;
|
|
19
|
+
const targetArea = areas.find(a => a.name === trimmedName);
|
|
20
|
+
const alreadyClaimed = targetArea?.paths.includes(path) ?? false;
|
|
21
|
+
let actionLabel = 'Add';
|
|
22
|
+
if (alreadyClaimed)
|
|
23
|
+
actionLabel = 'Already added';
|
|
24
|
+
else if (!targetArea)
|
|
25
|
+
actionLabel = 'Create area';
|
|
26
|
+
else
|
|
27
|
+
actionLabel = 'Add path';
|
|
28
|
+
const sectionDivider = `1px solid ${theme.colors.backgroundSecondary}`;
|
|
29
|
+
const inputStyle = {
|
|
30
|
+
padding: '8px 10px',
|
|
31
|
+
background: theme.colors.backgroundDark ?? theme.colors.background,
|
|
32
|
+
color: theme.colors.text,
|
|
33
|
+
border: `1px solid ${theme.colors.border}`,
|
|
34
|
+
borderRadius: theme.radii[2],
|
|
35
|
+
fontFamily: theme.fonts.monospace,
|
|
36
|
+
fontSize: theme.fontSizes[1],
|
|
37
|
+
};
|
|
38
|
+
return (_jsx("div", { onClick: onClose, style: {
|
|
39
|
+
position: 'fixed',
|
|
40
|
+
inset: 0,
|
|
41
|
+
background: 'rgba(0, 0, 0, 0.55)',
|
|
42
|
+
display: 'flex',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
justifyContent: 'center',
|
|
45
|
+
zIndex: 1000,
|
|
46
|
+
fontFamily: theme.fonts.body,
|
|
47
|
+
}, children: _jsxs("div", { onClick: e => e.stopPropagation(), style: {
|
|
48
|
+
width: 520,
|
|
49
|
+
maxHeight: 'min(80vh, 700px)',
|
|
50
|
+
display: 'flex',
|
|
51
|
+
flexDirection: 'column',
|
|
52
|
+
background: theme.colors.background,
|
|
53
|
+
color: theme.colors.text,
|
|
54
|
+
borderRadius: theme.radii[4],
|
|
55
|
+
border: `1px solid ${theme.colors.border}`,
|
|
56
|
+
boxShadow: theme.shadows[4],
|
|
57
|
+
overflow: 'hidden',
|
|
58
|
+
}, children: [_jsxs("div", { style: {
|
|
59
|
+
padding: '14px 18px',
|
|
60
|
+
borderBottom: sectionDivider,
|
|
61
|
+
display: 'flex',
|
|
62
|
+
justifyContent: 'space-between',
|
|
63
|
+
alignItems: 'flex-start',
|
|
64
|
+
gap: theme.space[3],
|
|
65
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: sectionLabelStyle, children: "Add to area" }), _jsx("div", { style: {
|
|
66
|
+
fontFamily: theme.fonts.monospace,
|
|
67
|
+
fontSize: theme.fontSizes[0],
|
|
68
|
+
color: theme.colors.textMuted,
|
|
69
|
+
marginTop: 6,
|
|
70
|
+
wordBreak: 'break-all',
|
|
71
|
+
}, children: path })] }), _jsx("button", { onClick: onClose, style: {
|
|
72
|
+
background: 'transparent',
|
|
73
|
+
border: 'none',
|
|
74
|
+
color: theme.colors.textTertiary,
|
|
75
|
+
fontSize: theme.fontSizes[3],
|
|
76
|
+
cursor: 'pointer',
|
|
77
|
+
lineHeight: 1,
|
|
78
|
+
padding: 0,
|
|
79
|
+
}, "aria-label": "Close", children: "\u00D7" })] }), _jsxs("div", { style: {
|
|
80
|
+
padding: '14px 18px',
|
|
81
|
+
borderBottom: sectionDivider,
|
|
82
|
+
display: 'flex',
|
|
83
|
+
flexDirection: 'column',
|
|
84
|
+
gap: theme.space[3],
|
|
85
|
+
}, children: [_jsxs("label", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx("span", { style: sectionLabelStyle, children: "Area" }), _jsx("input", { type: "text", value: areaName, list: "area-name-options", autoFocus: true, placeholder: "e.g. Documentation", onChange: e => onAreaNameChange(e.target.value), onKeyDown: e => {
|
|
86
|
+
if (e.key === 'Enter' && canSubmit && !alreadyClaimed)
|
|
87
|
+
onSubmit();
|
|
88
|
+
}, style: inputStyle }), _jsx("datalist", { id: "area-name-options", children: areas.map(a => (_jsx("option", { value: a.name }, a.name))) })] }), !targetArea && trimmedName.length > 0 && (_jsxs("label", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx("span", { style: sectionLabelStyle, children: "Description (optional)" }), _jsx("input", { type: "text", value: description, placeholder: "Why this area exists, what it covers", onChange: e => onDescriptionChange(e.target.value), onKeyDown: e => {
|
|
89
|
+
if (e.key === 'Enter' && canSubmit)
|
|
90
|
+
onSubmit();
|
|
91
|
+
}, style: { ...inputStyle, fontFamily: theme.fonts.body } })] })), _jsxs("div", { style: { display: 'flex', justifyContent: 'flex-end', gap: theme.space[2] }, children: [_jsx("button", { onClick: onClose, style: {
|
|
92
|
+
padding: '8px 14px',
|
|
93
|
+
background: 'transparent',
|
|
94
|
+
color: theme.colors.textSecondary,
|
|
95
|
+
border: `1px solid ${theme.colors.border}`,
|
|
96
|
+
borderRadius: theme.radii[2],
|
|
97
|
+
cursor: 'pointer',
|
|
98
|
+
fontSize: theme.fontSizes[1],
|
|
99
|
+
}, children: "Cancel" }), _jsx("button", { onClick: onSubmit, disabled: !canSubmit || alreadyClaimed, style: {
|
|
100
|
+
padding: '8px 14px',
|
|
101
|
+
background: !canSubmit || alreadyClaimed
|
|
102
|
+
? theme.colors.backgroundSecondary
|
|
103
|
+
: theme.colors.textMuted,
|
|
104
|
+
color: !canSubmit || alreadyClaimed
|
|
105
|
+
? theme.colors.muted
|
|
106
|
+
: theme.colors.background,
|
|
107
|
+
border: `1px solid ${theme.colors.border}`,
|
|
108
|
+
borderRadius: theme.radii[2],
|
|
109
|
+
cursor: !canSubmit || alreadyClaimed ? 'not-allowed' : 'pointer',
|
|
110
|
+
fontSize: theme.fontSizes[1],
|
|
111
|
+
fontWeight: theme.fontWeights.medium,
|
|
112
|
+
}, children: actionLabel })] })] }), _jsxs("div", { style: { padding: '14px 18px', overflowY: 'auto', flex: 1 }, children: [_jsx("div", { style: sectionLabelStyle, children: "Existing areas (click to prefill)" }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: theme.space[2] }, children: [areas.length === 0 && (_jsx("div", { style: { fontSize: theme.fontSizes[0], color: theme.colors.textTertiary, fontStyle: 'italic' }, children: "No areas yet. Type a name above to create the first one." })), areas.map(area => {
|
|
113
|
+
const claims = area.paths.includes(path);
|
|
114
|
+
return (_jsxs("button", { onClick: () => onPickExisting(area.name), title: claims ? 'Area already claims this path' : 'Prefill the area name', style: {
|
|
115
|
+
fontSize: theme.fontSizes[0],
|
|
116
|
+
padding: '6px 10px',
|
|
117
|
+
background: claims ? theme.colors.background : theme.colors.backgroundSecondary,
|
|
118
|
+
color: claims ? theme.colors.muted : theme.colors.text,
|
|
119
|
+
border: `1px solid ${theme.colors.border}`,
|
|
120
|
+
borderRadius: theme.radii[2],
|
|
121
|
+
cursor: 'pointer',
|
|
122
|
+
textAlign: 'left',
|
|
123
|
+
display: 'flex',
|
|
124
|
+
alignItems: 'center',
|
|
125
|
+
gap: theme.space[2],
|
|
126
|
+
opacity: claims ? 0.6 : 1,
|
|
127
|
+
}, children: [_jsx("span", { style: {
|
|
128
|
+
width: 10,
|
|
129
|
+
height: 10,
|
|
130
|
+
borderRadius: theme.radii[1],
|
|
131
|
+
background: AREA_PANEL_COLOR,
|
|
132
|
+
border: `1px dashed ${theme.colors.textMuted}`,
|
|
133
|
+
flexShrink: 0,
|
|
134
|
+
} }), _jsx("span", { style: { fontFamily: theme.fonts.monospace }, children: area.name }), _jsxs("span", { style: {
|
|
135
|
+
marginLeft: 'auto',
|
|
136
|
+
fontSize: theme.fontSizes[0],
|
|
137
|
+
color: theme.colors.textTertiary,
|
|
138
|
+
}, children: [area.paths.length, " path", area.paths.length === 1 ? '' : 's'] }), claims && _jsx("span", { style: { marginLeft: theme.space[1], fontSize: theme.fontSizes[0] }, children: "\u2713" })] }, area.name));
|
|
139
|
+
})] })] })] }) }));
|
|
140
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Scope } from './model';
|
|
3
|
+
export declare const AddToScopeModal: React.FC<{
|
|
4
|
+
path: string;
|
|
5
|
+
scopes: readonly Scope[];
|
|
6
|
+
scopeId: string;
|
|
7
|
+
namespaceName: string;
|
|
8
|
+
onScopeIdChange: (value: string) => void;
|
|
9
|
+
onNamespaceNameChange: (value: string) => void;
|
|
10
|
+
onPickExisting: (scopeId: string, namespaceName: string) => void;
|
|
11
|
+
onSubmit: () => void;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=AddToScopeModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddToScopeModal.d.ts","sourceRoot":"","sources":["../../../src/components/FileCityExplorer/AddToScopeModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGrC,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAgTA,CAAC"}
|