Skip to content

Commit a3b1d88

Browse files
committed
improve stuff
1 parent 2165bd7 commit a3b1d88

14 files changed

Lines changed: 462 additions & 137 deletions

File tree

docs/book/logo.png

12.1 KB
Loading

docs/hyperbook.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"name": "docs",
33
"version": "0.0.0",
4+
"logo": "logo.png",
45
"description": "Learningmap Documentation",
56
"license": "cc-by-sa",
67
"author": {

packages/learningmap/src/EditorDrawer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export const EditorDrawer: React.FC<EditorDrawerProps> = ({
115115
setLocalNode((prev: Node<NodeData> | null) => ({
116116
...prev!,
117117
data: { ...prev!.data, [field]: value },
118+
className: field === "color" ? value : prev!.className
118119
}));
119120
};
120121

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React from "react";
2-
import { Menu, MenuButton, MenuItem, SubMenu } from "@szhsin/react-menu";
2+
import { Menu, MenuButton, MenuDivider, MenuItem, SubMenu } from "@szhsin/react-menu";
33
import "@szhsin/react-menu/dist/index.css";
44
import '@szhsin/react-menu/dist/transitions/zoom.css';
5-
import { Save, Plus, Bug, Settings, Eye, Menu as MenuI, FolderOpen, Download, ImageDown } from "lucide-react";
5+
import { Plus, Bug, Settings, Eye, Menu as MenuI, FolderOpen, Download, ImageDown, ExternalLink } from "lucide-react";
66
import { getTranslations } from "./translations";
77

88
interface EditorToolbarProps {
9-
saved: boolean;
109
debugMode: boolean;
1110
previewMode: boolean;
1211
showCompletionNeeds: boolean;
@@ -19,15 +18,12 @@ interface EditorToolbarProps {
1918
onSetShowUnlockAfter: (checked: boolean) => void;
2019
onAddNewNode: (type: "task" | "topic" | "image" | "text") => void;
2120
onOpenSettingsDrawer: () => void;
22-
onSave: () => void;
2321
onDownlad: () => void;
2422
onOpen: () => void;
25-
onExportSVG: () => void;
2623
language?: string;
2724
}
2825

2926
export const EditorToolbar: React.FC<EditorToolbarProps> = ({
30-
saved,
3127
debugMode,
3228
previewMode,
3329
showCompletionNeeds,
@@ -40,59 +36,63 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
4036
onSetShowUnlockAfter,
4137
onAddNewNode,
4238
onOpenSettingsDrawer,
43-
onSave,
4439
onDownlad,
4540
onOpen,
46-
onExportSVG,
4741
language = "en",
4842
}) => {
4943
const t = getTranslations(language);
50-
44+
5145
return (
52-
<div className="editor-toolbar">
53-
<div className="toolbar-group">
54-
<Menu menuButton={<MenuButton disabled={previewMode} className="toolbar-button"><Plus size={16} /> <span className="toolbar-label">{t.nodes}</span></MenuButton>}>
55-
<MenuItem onClick={() => onAddNewNode("task")}>{t.addTask}</MenuItem>
56-
<MenuItem onClick={() => onAddNewNode("topic")}>{t.addTopic}</MenuItem>
57-
<MenuItem onClick={() => onAddNewNode("image")}>{t.addImage}</MenuItem>
58-
<MenuItem onClick={() => onAddNewNode("text")}>{t.addText}</MenuItem>
59-
</Menu>
60-
<button disabled={previewMode} onClick={onOpenSettingsDrawer} className="toolbar-button">
61-
<Settings size={16} /> <span className="toolbar-label">{t.settings}</span>
62-
</button>
63-
</div>
64-
<div className="toolbar-group">
65-
<Menu menuButton={<MenuButton className="toolbar-button"><MenuI /></MenuButton>}>
66-
<SubMenu className={`${debugMode ? "active" : ""}`} label={<><Bug size={16} /> <span>{t.debug}</span></>}>
67-
<MenuItem type="checkbox" checked={debugMode} onClick={onToggleDebugMode}>
68-
{t.enableDebugMode}
46+
<div className="editor-toolbar">
47+
<div className="toolbar-group">
48+
<Menu menuButton={<MenuButton disabled={previewMode} className="toolbar-button"><Plus size={16} /> <span className="toolbar-label">{t.nodes}</span></MenuButton>}>
49+
<MenuItem onClick={() => onAddNewNode("task")}>{t.addTask}</MenuItem>
50+
<MenuItem onClick={() => onAddNewNode("topic")}>{t.addTopic}</MenuItem>
51+
<MenuItem onClick={() => onAddNewNode("image")}>{t.addImage}</MenuItem>
52+
<MenuItem onClick={() => onAddNewNode("text")}>{t.addText}</MenuItem>
53+
</Menu>
54+
<button disabled={previewMode} onClick={onOpenSettingsDrawer} className="toolbar-button">
55+
<Settings size={16} /> <span className="toolbar-label">{t.settings}</span>
56+
</button>
57+
</div>
58+
<div className="toolbar-group">
59+
<Menu menuButton={<MenuButton className="toolbar-button"><MenuI /></MenuButton>}>
60+
<MenuItem onClick={onOpen}>
61+
<FolderOpen size={16} /> <span>{t.open}</span>
62+
</MenuItem>
63+
<MenuItem onClick={onDownlad}>
64+
<Download size={16} /> <span>{t.download}</span>
65+
</MenuItem>
66+
<MenuDivider />
67+
<SubMenu className={`${debugMode ? "active" : ""}`} label={<><Bug size={16} /> <span>{t.debug}</span></>}>
68+
<MenuItem type="checkbox" checked={debugMode} onClick={onToggleDebugMode}>
69+
{t.enableDebugMode}
70+
</MenuItem>
71+
<MenuItem type="checkbox" checked={showCompletionNeeds} onClick={e => onSetShowCompletionNeeds(e.checked ?? false)} disabled={!debugMode}>
72+
{t.showCompletionNeedsEdges}
73+
</MenuItem>
74+
<MenuItem type="checkbox" checked={showCompletionOptional} onClick={e => onSetShowCompletionOptional(e.checked ?? false)} disabled={!debugMode}>
75+
{t.showCompletionOptionalEdges}
76+
</MenuItem>
77+
<MenuItem type="checkbox" checked={showUnlockAfter} onClick={e => onSetShowUnlockAfter(e.checked ?? false)} disabled={!debugMode}>
78+
{t.showUnlockAfterEdges}
79+
</MenuItem>
80+
</SubMenu>
81+
<MenuItem onClick={onTogglePreviewMode} className={`${previewMode ? "active" : ""}`}>
82+
<Eye size={16} /> <span>{t.preview}</span>
6983
</MenuItem>
70-
<MenuItem type="checkbox" checked={showCompletionNeeds} onClick={e => onSetShowCompletionNeeds(e.checked ?? false)} disabled={!debugMode}>
71-
{t.showCompletionNeedsEdges}
84+
<MenuDivider />
85+
<MenuItem href="https://openpatch.org" target="_blank" rel="noopener noreferrer">
86+
<ExternalLink size={16} /> <span>OpenPatch</span>
7287
</MenuItem>
73-
<MenuItem type="checkbox" checked={showCompletionOptional} onClick={e => onSetShowCompletionOptional(e.checked ?? false)} disabled={!debugMode}>
74-
{t.showCompletionOptionalEdges}
88+
<MenuItem href="https://github.com/openpatch/learningmap" target="_blank" rel="noopener noreferrer">
89+
<ExternalLink size={16} /> <span>GitHub</span>
7590
</MenuItem>
76-
<MenuItem type="checkbox" checked={showUnlockAfter} onClick={e => onSetShowUnlockAfter(e.checked ?? false)} disabled={!debugMode}>
77-
{t.showUnlockAfterEdges}
91+
<MenuItem href="https://fosstodon.org/@openpatch" target="_blank" rel="noopener noreferrer">
92+
<ExternalLink size={16} /> <span>Mastodon</span>
7893
</MenuItem>
79-
</SubMenu>
80-
<MenuItem onClick={onTogglePreviewMode} className={`${previewMode ? "active" : ""}`}>
81-
<Eye size={16} /> <span>{t.preview}</span>
82-
</MenuItem>
83-
<MenuItem onClick={onSave} className={!saved ? "active" : ""} disabled={saved}>
84-
<Save size={16} /> <span>{t.save}{!saved ? "*" : ""}</span>
85-
</MenuItem>
86-
<MenuItem onClick={onDownlad}>
87-
<Download size={16} /> <span>{t.download}</span>
88-
</MenuItem>
89-
<MenuItem onClick={onOpen}>
90-
<FolderOpen size={16} /> <span>{t.open}</span>
91-
</MenuItem>
92-
{false && <MenuItem onClick={onExportSVG}>
93-
<ImageDown size={16} /> <span>{t.exportAsSVG}</span>
94-
</MenuItem>}
95-
</Menu>
94+
</Menu>
95+
</div>
9696
</div>
97-
</div>
98-
);};
97+
);
98+
};

packages/learningmap/src/LearningMapEditor.tsx

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ import {
1414
ControlButton,
1515
OnNodesChange,
1616
OnEdgesChange,
17-
getNodesBounds,
18-
getViewportForBounds,
1917
Panel,
2018
OnSelectionChangeFunc,
2119
ReactFlowProvider,
2220
} from "@xyflow/react";
23-
import { toSvg } from "html-to-image";
2421
import { EditorDrawer } from "./EditorDrawer";
2522
import { EdgeDrawer } from "./EdgeDrawer";
2623
import { TaskNode } from "./nodes/TaskNode";
@@ -56,6 +53,12 @@ export interface LearningMapEditorProps {
5653
onChange?: (data: RoadmapData) => void;
5754
}
5855

56+
const getDefaultFilename = () => {
57+
const now = new Date();
58+
const pad = (n: number) => n.toString().padStart(2, '0');
59+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
60+
};
61+
5962
export function LearningMapEditor({
6063
roadmapData,
6164
language = "en",
@@ -111,9 +114,6 @@ export function LearningMapEditor({
111114
const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
112115
const [edgeDrawerOpen, setEdgeDrawerOpen] = useState(false);
113116

114-
// Track Shift key state
115-
const [shiftPressed, setShiftPressed] = useState(false);
116-
117117
useEffect(() => {
118118
const parsedRoadmap = parseRoadmapData(roadmapData || "");
119119
loadRoadmapStateIntoReactFlowState(parsedRoadmap);
@@ -129,6 +129,7 @@ export function LearningMapEditor({
129129
const rawNodes = nodesArr.map((n) => ({
130130
...n,
131131
draggable: true,
132+
className: n.data.color ? n.data.color : n.className,
132133
data: { ...n.data },
133134
}));
134135

@@ -413,7 +414,7 @@ export function LearningMapEditor({
413414
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(roadmapState, null, 2));
414415
const downloadAnchorNode = document.createElement('a');
415416
downloadAnchorNode.setAttribute("href", dataStr);
416-
downloadAnchorNode.setAttribute("download", `${roadmapState.settings.title ?? new Date().toString()}.learningmap`);
417+
downloadAnchorNode.setAttribute("download", `${roadmapState.settings.title?.trim() ?? getDefaultFilename()}.learningmap`);
417418
document.body.appendChild(downloadAnchorNode); // required for firefox
418419
downloadAnchorNode.click();
419420
downloadAnchorNode.remove();
@@ -428,42 +429,10 @@ export function LearningMapEditor({
428429
type: "default",
429430
};
430431

431-
const handleExportSVG = useCallback(async () => {
432-
const nodesBounds = getNodesBounds(nodes);
433-
const imageWidth = nodesBounds.width;
434-
const imageHeight = nodesBounds.height;
435-
let viewport = getViewportForBounds(nodesBounds, imageWidth, imageHeight, 0.1, 5);
436-
437-
const dom = document.querySelector(".react-flow__viewport") as HTMLElement;
438-
if (!dom) return;
439-
440-
toSvg(dom, {
441-
backgroundColor: settings?.background?.color || "#ffffff",
442-
width: imageWidth,
443-
height: imageHeight,
444-
style: {
445-
transform: `translate(${viewport.x / 2.0}px, ${viewport.y / 2.0}px) scale(${viewport.zoom})`,
446-
width: `${imageWidth}px`,
447-
height: `${imageHeight}px`,
448-
}
449-
}).then((dataUrl) => {
450-
const downloadAnchorNode = document.createElement('a');
451-
downloadAnchorNode.setAttribute("href", dataUrl);
452-
downloadAnchorNode.setAttribute("download", "roadmap.svg");
453-
document.body.appendChild(downloadAnchorNode); // required for firefox
454-
downloadAnchorNode.click();
455-
downloadAnchorNode.remove();
456-
457-
// Restore old viewport
458-
}).catch((err) => {
459-
alert(t.failedToExportSVG + err.message);
460-
});
461-
}, [nodes, roadmapState, t]);
462-
463432
const handleOpen = useCallback(() => {
464433
const input = document.createElement('input');
465434
input.type = 'file';
466-
input.accept = '.json,application/json';
435+
input.accept = '.learningmap,application/json';
467436
input.onchange = (e: any) => {
468437
const file = e.target.files[0];
469438
if (!file) return;
@@ -544,7 +513,6 @@ export function LearningMapEditor({
544513

545514
useEffect(() => {
546515
const handleKeyDown = (e: KeyboardEvent) => {
547-
if (e.key === "Shift") setShiftPressed(true);
548516
//save shortcut
549517
if ((e.ctrlKey || e.metaKey) && e.key === 's' && !e.shiftKey) {
550518
e.preventDefault();
@@ -600,21 +568,15 @@ export function LearningMapEditor({
600568
setHelpOpen(false);
601569
}
602570
};
603-
const handleKeyUp = (e: KeyboardEvent) => {
604-
if (e.key === "Shift") setShiftPressed(false);
605-
};
606571
window.addEventListener("keydown", handleKeyDown);
607-
window.addEventListener("keyup", handleKeyUp);
608572
return () => {
609573
window.removeEventListener("keydown", handleKeyDown);
610-
window.removeEventListener("keyup", handleKeyUp);
611574
};
612575
}, [handleSave, handleUndo, handleRedo, addNewNode, helpOpen, setHelpOpen, togglePreviewMode, toggleDebugMode]);
613576

614577
return (
615578
<>
616579
<EditorToolbar
617-
saved={saved}
618580
debugMode={debugMode}
619581
previewMode={previewMode}
620582
showCompletionNeeds={showCompletionNeeds}
@@ -627,10 +589,8 @@ export function LearningMapEditor({
627589
onSetShowUnlockAfter={handleSetShowUnlockAfter}
628590
onAddNewNode={addNewNode}
629591
onOpenSettingsDrawer={handleOpenSettingsDrawer}
630-
onSave={handleSave}
631592
onDownlad={handleDownload}
632593
onOpen={handleOpen}
633-
onExportSVG={handleExportSVG}
634594
language={effectiveLanguage}
635595
/>
636596
{previewMode && <LearningMap roadmapData={roadmapState} language={effectiveLanguage} />}
@@ -650,16 +610,7 @@ export function LearningMapEditor({
650610
/>
651611
)}
652612
<ReactFlow
653-
nodes={nodes.map(n => {
654-
const className = [];
655-
if (n.data?.color) {
656-
className.push(n.data.color);
657-
}
658-
return {
659-
...n,
660-
className: className.join(" ")
661-
};
662-
})}
613+
nodes={nodes}
663614
edges={edges}
664615
onEdgesChange={handleEdgesChange}
665616
onNodeDoubleClick={onNodeClick}
@@ -671,7 +622,6 @@ export function LearningMapEditor({
671622
selectionOnDrag={false}
672623
edgeTypes={edgeTypes}
673624
fitView
674-
snapToGrid={!shiftPressed}
675625
proOptions={{ hideAttribution: true }}
676626
defaultEdgeOptions={defaultEdgeOptions}
677627
nodesDraggable={true}
@@ -694,8 +644,8 @@ export function LearningMapEditor({
694644
<Info />
695645
</ControlButton>
696646
</Controls>
697-
{!saved && <Panel position="top-right" title={t.unsavedChanges} onClick={() => { handleSave(); }}>
698-
<ShieldAlert size={32} color="red" />
647+
{!saved && <Panel position="bottom-right" title={t.unsavedChanges} onClick={() => { handleSave(); }}>
648+
<ShieldAlert size={32} color="var(--learningmap-color-coal)" />
699649
</Panel>}
700650
{selectedNodeIds.length > 1 && <MultiNodePanel nodes={nodes.filter(n => selectedNodeIds.includes(n.id))} onUpdate={updateNodes} />}
701651
</ReactFlow>

packages/learningmap/src/WelcomeMessage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { FolderOpen, Plus, Info } from "lucide-react";
33
import { getTranslations } from "./translations";
4+
import logo from "./logo.svg";
45

56
interface WelcomeMessageProps {
67
onOpenFile: () => void;
@@ -20,7 +21,9 @@ export const WelcomeMessage: React.FC<WelcomeMessageProps> = ({
2021
return (
2122
<div className="welcome-message">
2223
<div className="welcome-content">
23-
<h1 className="welcome-title">{t.welcomeTitle}</h1>
24+
<h1 className="welcome-title">
25+
<img src={logo} alt="Logo" className="welcome-logo" />
26+
{t.welcomeTitle}</h1>
2427
<p className="welcome-subtitle">{t.welcomeSubtitle}</p>
2528
<div className="welcome-actions">
2629
<button onClick={onOpenFile} className="primary-button">
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module "*.svg" {
2+
const content: any;
3+
export default content;
4+
}
23.6 KB
Binary file not shown.
3.78 KB
Binary file not shown.

0 commit comments

Comments
 (0)