Skip to content

Commit 311ef76

Browse files
committed
Extract link/tag/AI suggestion dialogs; add clear-selection; tweak hero spacing and title sizes
- Add LinkDialog, TagDialog and AiSuggestionsDialog components and replace the inline dialogs in FormatToolbar. - Restore dialog logic to toolbar via props and small helper handlers (tag input/remove handlers). - Add clearSelection callback in Editor to deselect nodes/edges and exit node editing; pass it to Menubar. - Invoke clearSelection when opening Menubar menus (project, edit, profile, theme) to avoid stale selection/edit state. - Update homepage hero: increase logo top/bottom padding and break heading into multiple lines for improved layout. - Adjust title typography size variants in primitives to reduce heading sizes.
1 parent b9839c2 commit 311ef76

File tree

8 files changed

+411
-202
lines changed

8 files changed

+411
-202
lines changed

src/app/(home)/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default function Home() {
3939
<AppInsightService />
4040
<Image
4141
alt="MindMapFlow logo"
42-
className="h-auto w-full max-w-[480px] object-contain pt-8 pb-4"
42+
className="h-auto w-full max-w-[480px] object-contain pt-24 pb-20"
4343
height={260}
4444
sizes="(max-width: 640px) 80vw, 480px"
4545
src="/mindmapflow_logo_with_text.png"
@@ -48,7 +48,11 @@ export default function Home() {
4848

4949
<div className="inline-block max-w-3xl text-center justify-center">
5050
<span className={title({ class: "text-heading" })}>
51-
Organize Your Ideas, Simplify Your Workflow, Enhance Productivity
51+
Organize Your Ideas,
52+
<br />
53+
Simplify Your Workflow,
54+
<br />
55+
Enhance Productivity
5256
</span>
5357
</div>
5458

src/components/editor/editor.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,60 @@ export default function Editor() {
13941394
[nodes, setEdges, onNodesChange],
13951395
);
13961396

1397+
const clearSelection = useCallback(() => {
1398+
setSelectedNodeIds([]);
1399+
setSelectedNodeId(null);
1400+
setSelectedEdgeId(null);
1401+
1402+
setNodes((prevNodes) => {
1403+
let didChange = false;
1404+
1405+
const nextNodes = prevNodes.map((node) => {
1406+
const shouldDeselect = node.selected;
1407+
const shouldExitEditing = Boolean(node.data?.isEditing);
1408+
1409+
if (!shouldDeselect && !shouldExitEditing) {
1410+
return node;
1411+
}
1412+
1413+
didChange = true;
1414+
1415+
return {
1416+
...node,
1417+
...(shouldDeselect ? { selected: false } : {}),
1418+
data:
1419+
shouldExitEditing && node.data
1420+
? { ...node.data, isEditing: false }
1421+
: node.data,
1422+
};
1423+
});
1424+
1425+
return didChange ? nextNodes : prevNodes;
1426+
});
1427+
1428+
setEdges((prevEdges) => {
1429+
let didChange = false;
1430+
1431+
const nextEdges = prevEdges.map((edge) => {
1432+
if (!edge.selected) {
1433+
return edge;
1434+
}
1435+
1436+
didChange = true;
1437+
1438+
return { ...edge, selected: false };
1439+
});
1440+
1441+
return didChange ? nextEdges : prevEdges;
1442+
});
1443+
}, [
1444+
setEdges,
1445+
setNodes,
1446+
setSelectedEdgeId,
1447+
setSelectedNodeId,
1448+
setSelectedNodeIds,
1449+
]);
1450+
13971451
useKeyboardShortcuts({
13981452
onDelete: handleDeleteNodeOrEdge,
13991453
onSearch: handleSearchFocus,
@@ -1411,6 +1465,7 @@ export default function Editor() {
14111465
return (
14121466
<div className="flex flex-col h-full flex-1 overflow-hidden">
14131467
<Menubar
1468+
onClearSelection={clearSelection}
14141469
onCopyJsonToClipboard={copyJsonToClipboard}
14151470
onNewProject={onNewProject}
14161471
/>

src/components/editor/menubar.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ import { MindmapExportError, renderMindmapToPng } from "@/utils/mindmap-export";
3535
interface MenubarProps {
3636
onNewProject: () => void;
3737
onCopyJsonToClipboard: () => void;
38+
onClearSelection: () => void;
3839
}
3940

4041
export const Menubar = ({
4142
onNewProject,
4243
onCopyJsonToClipboard,
44+
onClearSelection,
4345
}: MenubarProps) => {
4446
const { getNodes } = useReactFlow();
4547
const { isFullScreen } = useEditor(); // Modified: removed editorVersion
@@ -165,18 +167,22 @@ export const Menubar = ({
165167
}, [isSaved]);
166168

167169
const handleProjectMenuClick = (event: React.MouseEvent<HTMLElement>) => {
170+
onClearSelection();
168171
setAnchorEl(event.currentTarget);
169172
};
170173

171174
const handleEditMenuClick = (event: React.MouseEvent<HTMLElement>) => {
175+
onClearSelection();
172176
setEditAnchorEl(event.currentTarget);
173177
};
174178
const handleProfileMenuClick = (event: React.MouseEvent<HTMLElement>) => {
179+
onClearSelection();
175180
setProfileAnchorEl(event.currentTarget);
176181
};
177182
const handleThemeMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
178183
event.preventDefault();
179184
event.stopPropagation();
185+
onClearSelection();
180186
setThemeAnchorEl(event.currentTarget);
181187
};
182188
const handleThemeMenuKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
@@ -186,6 +192,7 @@ export const Menubar = ({
186192
event.key === " "
187193
) {
188194
event.preventDefault();
195+
onClearSelection();
189196
setThemeAnchorEl(event.currentTarget as HTMLElement);
190197
}
191198
};
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"use client";
2+
3+
import type { AiSubnodeSuggestion } from "@/services/ai-suggestion-service";
4+
5+
import { useMemo } from "react";
6+
import {
7+
Box,
8+
Button,
9+
Dialog,
10+
DialogActions,
11+
DialogContent,
12+
DialogTitle,
13+
Typography,
14+
} from "@mui/material";
15+
16+
interface AiSuggestionsDialogProps {
17+
open: boolean;
18+
parentTitle: string;
19+
suggestions: AiSubnodeSuggestion[] | null;
20+
onApprove: () => void;
21+
onCancel: () => void;
22+
}
23+
24+
const countTotalSuggestions = (items: AiSubnodeSuggestion[]): number =>
25+
items.reduce(
26+
(total, item) =>
27+
total + 1 + (item.children ? countTotalSuggestions(item.children) : 0),
28+
0,
29+
);
30+
31+
const renderSuggestionTree = (
32+
items: AiSubnodeSuggestion[],
33+
depth = 0,
34+
): JSX.Element[] =>
35+
items.map((item, index) => (
36+
<Box
37+
key={`${depth}-${index}-${item.title}`}
38+
sx={{ ml: depth * 2, mb: 1.5 }}
39+
>
40+
<Typography variant="subtitle2">&bull; {item.title}</Typography>
41+
{item.children && item.children.length > 0 && (
42+
<Box
43+
sx={{
44+
mt: 0.75,
45+
display: "flex",
46+
flexDirection: "column",
47+
gap: 1,
48+
}}
49+
>
50+
{renderSuggestionTree(item.children, depth + 1)}
51+
</Box>
52+
)}
53+
</Box>
54+
));
55+
56+
export function AiSuggestionsDialog({
57+
open,
58+
parentTitle,
59+
suggestions,
60+
onApprove,
61+
onCancel,
62+
}: AiSuggestionsDialogProps) {
63+
const totalSuggestionCount = useMemo(() => {
64+
if (!suggestions || suggestions.length === 0) {
65+
return 0;
66+
}
67+
68+
return countTotalSuggestions(suggestions);
69+
}, [suggestions]);
70+
71+
const hasSuggestions = Boolean(suggestions && suggestions.length > 0);
72+
73+
return (
74+
<Dialog fullWidth maxWidth="sm" open={open} onClose={onCancel}>
75+
<DialogTitle>Review AI Suggestions</DialogTitle>
76+
<DialogContent dividers>
77+
<Typography sx={{ mb: 1 }} variant="body2">
78+
{parentTitle
79+
? `Review AI suggestions generated for "${parentTitle}".`
80+
: "Review AI suggestions."}
81+
</Typography>
82+
<Typography color="text.secondary" sx={{ mb: 2 }} variant="body2">
83+
{totalSuggestionCount > 0
84+
? `AI prepared ${totalSuggestionCount} new node${
85+
totalSuggestionCount === 1 ? "" : "s"
86+
}.`
87+
: "No nodes will be added."}
88+
</Typography>
89+
{hasSuggestions ? (
90+
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
91+
{renderSuggestionTree(suggestions)}
92+
</Box>
93+
) : (
94+
<Typography color="text.secondary" variant="body2">
95+
No suggestions to display.
96+
</Typography>
97+
)}
98+
</DialogContent>
99+
<DialogActions>
100+
<Button onClick={onCancel}>Discard</Button>
101+
<Button
102+
color="primary"
103+
disabled={!hasSuggestions}
104+
variant="contained"
105+
onClick={onApprove}
106+
>
107+
Add Suggestions
108+
</Button>
109+
</DialogActions>
110+
</Dialog>
111+
);
112+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"use client";
2+
3+
import {
4+
useCallback,
5+
type ChangeEvent,
6+
type KeyboardEvent,
7+
type RefObject,
8+
} from "react";
9+
import {
10+
Button,
11+
Dialog,
12+
DialogActions,
13+
DialogContent,
14+
DialogTitle,
15+
TextField,
16+
} from "@mui/material";
17+
18+
interface LinkDialogProps {
19+
open: boolean;
20+
hasExistingLink: boolean;
21+
linkValue: string;
22+
linkError: string | null;
23+
inputRef: RefObject<HTMLInputElement>;
24+
onClose: () => void;
25+
onRemove: () => void;
26+
onSave: () => void;
27+
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
28+
}
29+
30+
export function LinkDialog({
31+
open,
32+
hasExistingLink,
33+
linkValue,
34+
linkError,
35+
inputRef,
36+
onClose,
37+
onRemove,
38+
onSave,
39+
onChange,
40+
}: LinkDialogProps) {
41+
const handleKeyDown = useCallback(
42+
(event: KeyboardEvent<HTMLInputElement>) => {
43+
if (event.key === "Enter") {
44+
event.preventDefault();
45+
onSave();
46+
}
47+
},
48+
[onSave],
49+
);
50+
51+
return (
52+
<Dialog fullWidth maxWidth="xs" open={open} onClose={onClose}>
53+
<DialogTitle>{hasExistingLink ? "Edit Link" : "Add Link"}</DialogTitle>
54+
<DialogContent>
55+
<TextField
56+
fullWidth
57+
error={Boolean(linkError)}
58+
helperText={
59+
linkError ??
60+
"HTTP or HTTPS links are supported. We'll add https:// when missing."
61+
}
62+
inputRef={inputRef}
63+
label="URL"
64+
margin="dense"
65+
placeholder="https://example.com"
66+
type="url"
67+
value={linkValue}
68+
onChange={onChange}
69+
onKeyDown={handleKeyDown}
70+
/>
71+
</DialogContent>
72+
<DialogActions>
73+
{hasExistingLink && (
74+
<Button color="error" onClick={onRemove}>
75+
Remove Link
76+
</Button>
77+
)}
78+
<Button onClick={onClose}>Cancel</Button>
79+
<Button variant="contained" onClick={onSave}>
80+
Save
81+
</Button>
82+
</DialogActions>
83+
</Dialog>
84+
);
85+
}

0 commit comments

Comments
 (0)