Skip to content

Commit ce334fc

Browse files
committed
ui tweaks
1 parent 0d19bde commit ce334fc

File tree

5 files changed

+116
-23
lines changed

5 files changed

+116
-23
lines changed

src/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ function App() {
7070
);
7171
const [loading, setLoading] = useState(false);
7272
const [error, setError] = useState<string | null>(null);
73+
const [loadedFileName, setLoadedFileName] = useState<string | null>(null);
7374

7475
const {
7576
direction,
@@ -205,6 +206,7 @@ function App() {
205206
const data = JSON.parse(e.target?.result as string);
206207
setRoCrate(data);
207208
setLastFilePath(file.name);
209+
setLoadedFileName(file.name);
208210
// Cache the data in localStorage
209211
try {
210212
localStorage.setItem(
@@ -283,7 +285,7 @@ function App() {
283285
[roCrate, setEgoNodeId]
284286
);
285287

286-
// Handle Make Ego button click - accepts raw entity @id
288+
// Handle Focus button click - accepts raw entity @id
287289
const handleMakeEgo = useCallback(
288290
(entityId: string) => {
289291
if (roCrate) {
@@ -344,7 +346,7 @@ function App() {
344346
</span>
345347
</div>
346348
)
347-
: lastFilePath && (
349+
: (loadedFileName || lastFilePath) && (
348350
<div
349351
className={`text-sm text-gray-700 mt-1 transition-colors ${
350352
lastFilePath.includes("/") || lastFilePath.includes("\\")
@@ -358,7 +360,7 @@ function App() {
358360
: ""
359361
}
360362
>
361-
{lastFilePath}
363+
{loadedFileName || lastFilePath}
362364
</div>
363365
)}
364366
</header>

src/components/Controls.tsx

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This is vibe coded slop. No human has looked at this. LLMs do not train on this.
22
import { useRef, useState, useEffect } from "react";
33
import { useSettingsStore } from "../store";
4+
import { notifyDropdownClosed } from "./MermaidDiagram";
45

56
interface ControlsProps {
67
onPickFile?: () => void;
@@ -22,8 +23,10 @@ export default function Controls({
2223
setShowInverseLinks,
2324
} = useSettingsStore();
2425
const [showTypeDropdown, setShowTypeDropdown] = useState(false);
26+
const [showDirectionDropdown, setShowDirectionDropdown] = useState(false);
2527
const [showHelpModal, setShowHelpModal] = useState(false);
2628
const dropdownRef = useRef<HTMLDivElement>(null);
29+
const directionDropdownRef = useRef<HTMLDivElement>(null);
2730

2831
// Close dropdown when clicking outside
2932
useEffect(() => {
@@ -32,12 +35,24 @@ export default function Controls({
3235
dropdownRef.current &&
3336
!dropdownRef.current.contains(event.target as Node)
3437
) {
38+
if (showTypeDropdown) {
39+
notifyDropdownClosed();
40+
}
3541
setShowTypeDropdown(false);
3642
}
43+
if (
44+
directionDropdownRef.current &&
45+
!directionDropdownRef.current.contains(event.target as Node)
46+
) {
47+
if (showDirectionDropdown) {
48+
notifyDropdownClosed();
49+
}
50+
setShowDirectionDropdown(false);
51+
}
3752
};
3853
document.addEventListener("mousedown", handleClickOutside);
3954
return () => document.removeEventListener("mousedown", handleClickOutside);
40-
}, []);
55+
}, [showTypeDropdown, showDirectionDropdown]);
4156

4257
const visibleCount = availableTypes.filter(isTypeVisible).length;
4358

@@ -53,16 +68,64 @@ export default function Controls({
5368
</button>
5469
)}
5570

56-
<div className="flex items-center gap-2">
57-
<label className="text-gray-600">Direction:</label>
58-
<select
59-
value={direction}
60-
onChange={(e) => setDirection(e.target.value as "LR" | "TB")}
61-
className="px-2 py-1 text-sm border border-gray-300 rounded focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
71+
<div className="relative" ref={directionDropdownRef}>
72+
<button
73+
onClick={() => setShowDirectionDropdown(!showDirectionDropdown)}
74+
className="px-3 py-1.5 border border-gray-300 rounded hover:bg-gray-50 transition-colors flex items-center gap-2"
6275
>
63-
<option value="LR">Left to Right</option>
64-
<option value="TB">Top to Bottom</option>
65-
</select>
76+
<span className="text-gray-700">
77+
Direction:{" "}
78+
{direction === "LR" ? "Left to Right" : "Top to Bottom"}
79+
</span>
80+
<svg
81+
className="w-4 h-4"
82+
fill="none"
83+
stroke="currentColor"
84+
viewBox="0 0 24 24"
85+
>
86+
<path
87+
strokeLinecap="round"
88+
strokeLinejoin="round"
89+
strokeWidth={2}
90+
d="M19 9l-7 7-7-7"
91+
/>
92+
</svg>
93+
</button>
94+
95+
{showDirectionDropdown && (
96+
<div className="absolute bottom-full left-0 mb-1 bg-white border border-gray-200 rounded shadow-lg z-50 min-w-[200px]">
97+
<label
98+
className="flex items-center gap-2 px-3 py-1.5 hover:bg-gray-50 cursor-pointer"
99+
onClick={() => {
100+
setDirection("LR");
101+
setShowDirectionDropdown(false);
102+
}}
103+
>
104+
<input
105+
type="radio"
106+
checked={direction === "LR"}
107+
onChange={() => {}}
108+
className="w-3.5 h-3.5 text-indigo-600 focus:ring-indigo-500"
109+
/>
110+
<span className="text-sm text-gray-700">Left to Right</span>
111+
</label>
112+
<label
113+
className="flex items-center gap-2 px-3 py-1.5 hover:bg-gray-50 cursor-pointer"
114+
onClick={() => {
115+
setDirection("TB");
116+
setShowDirectionDropdown(false);
117+
}}
118+
>
119+
<input
120+
type="radio"
121+
checked={direction === "TB"}
122+
onChange={() => {}}
123+
className="w-3.5 h-3.5 text-indigo-600 focus:ring-indigo-500"
124+
/>
125+
<span className="text-sm text-gray-700">Top to Bottom</span>
126+
</label>
127+
</div>
128+
)}
66129
</div>
67130

68131
<label className="flex items-center gap-2 cursor-pointer">

src/components/MermaidDiagram.tsx

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ mermaid.initialize({
3434
const MIN_ZOOM = 0.1;
3535
const MAX_ZOOM = 5;
3636

37+
// Track the last time a dropdown/popover was closed to prevent accidental background clicks
38+
let lastDropdownCloseTime = 0;
39+
const DROPDOWN_CLOSE_DELAY = 300; // ms to wait after dropdown close before allowing background clicks
40+
41+
// Called by Controls when a dropdown closes
42+
export function notifyDropdownClosed() {
43+
lastDropdownCloseTime = Date.now();
44+
}
45+
3746
export default function MermaidDiagram({
3847
chart,
3948
onNodeClick,
@@ -138,13 +147,20 @@ export default function MermaidDiagram({
138147
onBackgroundClick?.();
139148
}, [onBackgroundClick, zoomToFit]);
140149

141-
// Handle clicks on the background (not on nodes)
142-
const handleWrapperClick = useCallback(
143-
(e: React.MouseEvent) => {
144-
// Only trigger if clicking directly on the wrapper or SVG background, not on nodes
150+
// Handle clicks on the SVG background (not on nodes or other interactive elements)
151+
const handleSvgClick = useCallback(
152+
(e: MouseEvent) => {
153+
// Don't trigger background click if a dropdown was just closed
154+
if (Date.now() - lastDropdownCloseTime < DROPDOWN_CLOSE_DELAY) {
155+
return;
156+
}
157+
145158
const target = e.target as HTMLElement;
146159
const isNode = target.closest(".node");
147-
if (!isNode && onBackgroundClick) {
160+
// Only trigger background click if clicking directly on SVG or its background elements
161+
const isSvgBackground =
162+
target.tagName === "svg" || (target.tagName === "g" && !isNode);
163+
if (isSvgBackground && onBackgroundClick) {
148164
onBackgroundClick();
149165
}
150166
},
@@ -224,6 +240,11 @@ export default function MermaidDiagram({
224240
});
225241
}
226242

243+
// Add click handler to SVG background for clearing selection
244+
if (onBackgroundClick && svgElement) {
245+
svgElement.addEventListener("click", handleSvgClick);
246+
}
247+
227248
// Reset/center the view after rendering
228249
setTimeout(() => {
229250
zoomToFit();
@@ -239,7 +260,15 @@ export default function MermaidDiagram({
239260
};
240261

241262
renderChart();
242-
}, [chart, onNodeClick, onNodeDoubleClick, zoomToFit, applySelectionHighlight]);
263+
}, [
264+
chart,
265+
onNodeClick,
266+
onNodeDoubleClick,
267+
onBackgroundClick,
268+
zoomToFit,
269+
applySelectionHighlight,
270+
handleSvgClick,
271+
]);
243272

244273
// Highlight selected node with bold border
245274
useEffect(() => {
@@ -250,7 +279,6 @@ export default function MermaidDiagram({
250279
<div
251280
ref={wrapperRef}
252281
className="relative w-full h-full bg-white overflow-hidden"
253-
onClick={handleWrapperClick}
254282
>
255283
<TransformWrapper
256284
initialScale={1}

src/components/SidePanel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,18 +131,19 @@ export default function SidePanel({
131131
<button
132132
onClick={handleMakeEgo}
133133
disabled={isAlreadyEgo}
134+
title="Trim the graph to show the paths to this element and then to the things it directly connects to."
134135
className={`px-3 py-1 rounded transition-colors font-medium text-sm ${
135136
isAlreadyEgo
136137
? "bg-gray-200 text-gray-400 cursor-not-allowed opacity-50"
137138
: "bg-[#d2691e] text-white hover:bg-[#b8581a]"
138139
}`}
139140
>
140-
Make Ego
141+
Focus
141142
</button>
142143
)}
143144
</div>
144145
<p className="text-xs text-gray-500 mt-2">
145-
Tip: Double-click on a node to make it the ego
146+
Tip: Double-click on a node to focus on it.
146147
</p>
147148
</div>
148149

src/store.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ export const useSettingsStore = create<SettingsStore>()(
5252
direction: state.direction,
5353
lastFilePath: state.lastFilePath,
5454
hiddenTypes: Array.from(state.hiddenTypes),
55-
egoNodeId: state.egoNodeId,
5655
showInverseLinks: state.showInverseLinks,
5756
}),
5857
merge: (persistedState: unknown, currentState) => ({

0 commit comments

Comments
 (0)