Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/EditorCanvas/Canvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export default function Canvas() {
const noteRect = {
x: note.x,
y: note.y,
width: noteWidth,
width: note.width ?? noteWidth,
height: note.height,
};
if (shouldAddElement(noteRect, element)) {
Expand Down
150 changes: 146 additions & 4 deletions src/components/EditorCanvas/Note.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,25 @@ import {
useSelect,
useNotes,
useSaveState,
useTransform,
useSettings,
} from "../../hooks";
import { useTranslation } from "react-i18next";
import { noteWidth, noteRadius, noteFold } from "../../data/constants";

export default function Note({ data, onPointerDown }) {
const [editField, setEditField] = useState({});
const [hovered, setHovered] = useState(false);
const [resizing, setResizing] = useState(false);
const initialWidthRef = useRef(data.width ?? noteWidth);
const initialXRef = useRef(data.x);
const { layout } = useLayout();
const { t } = useTranslation();
const { setSaveState } = useSaveState();
const { updateNote, deleteNote } = useNotes();
const { setUndoStack, setRedoStack } = useUndoRedo();
const { transform } = useTransform();
const { settings } = useSettings();
const {
selectedElement,
setSelectedElement,
Expand Down Expand Up @@ -169,6 +176,9 @@ export default function Note({ data, onPointerDown }) {
);
}, [selectedElement, data, bulkSelectedElements]);

const width = data.width ?? noteWidth;
const MIN_NOTE_WIDTH = 120;

return (
<g
onPointerEnter={(e) => e.isPrimary && setHovered(true)}
Expand All @@ -181,11 +191,11 @@ export default function Note({ data, onPointerDown }) {
onDoubleClick={edit}
>
<path
d={`M${data.x + noteFold} ${data.y} L${data.x + noteWidth - noteRadius} ${
d={`M${data.x + noteFold} ${data.y} L${data.x + width - noteRadius} ${
data.y
} A${noteRadius} ${noteRadius} 0 0 1 ${data.x + noteWidth} ${data.y + noteRadius} L${data.x + noteWidth} ${
} A${noteRadius} ${noteRadius} 0 0 1 ${data.x + width} ${data.y + noteRadius} L${data.x + width} ${
data.y + data.height - noteRadius
} A${noteRadius} ${noteRadius} 0 0 1 ${data.x + noteWidth - noteRadius} ${data.y + data.height} L${
} A${noteRadius} ${noteRadius} 0 0 1 ${data.x + width - noteRadius} ${data.y + data.height} L${
data.x + noteRadius
} ${data.y + data.height} A${noteRadius} ${noteRadius} 0 0 1 ${data.x} ${
data.y + data.height - noteRadius
Expand Down Expand Up @@ -220,10 +230,142 @@ export default function Note({ data, onPointerDown }) {
strokeLinejoin="round"
strokeWidth="2"
/>

{!layout.readOnly && !data.locked && hovered && (
<g style={{ pointerEvents: "none" }}>
<circle
cx={data.x}
cy={data.y + data.height / 2}
r={6}
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
stroke="#5891db"
strokeWidth={2}
opacity={1}
/>
<circle
cx={data.x + width}
cy={data.y + data.height / 2}
r={6}
fill={settings.mode === "light" ? "white" : "rgb(28, 31, 35)"}
stroke="#5891db"
strokeWidth={2}
opacity={1}
/>
</g>
)}
{!layout.readOnly && !data.locked && (
<rect
x={data.x - 4}
y={data.y + 8}
width={8}
height={Math.max(0, data.height - 16)}
fill="transparent"
stroke="transparent"
style={{ cursor: "ew-resize" }}
onPointerDown={(e) => {
e.stopPropagation();
initialWidthRef.current = data.width ?? noteWidth;
initialXRef.current = data.x;
setResizing(true);
e.currentTarget.setPointerCapture?.(e.pointerId);
}}
onPointerMove={(e) => {
if (!resizing) return;
const delta = e.movementX / (transform?.zoom || 1);
const currentWidth = data.width ?? noteWidth;
let proposedWidth = currentWidth - delta;
let proposedX = data.x + delta;
if (proposedWidth < MIN_NOTE_WIDTH) {
const clampDelta = currentWidth - MIN_NOTE_WIDTH;
proposedWidth = MIN_NOTE_WIDTH;
proposedX = data.x + clampDelta;
}
if (proposedWidth !== data.width || proposedX !== data.x) {
updateNote(data.id, { width: proposedWidth, x: proposedX });
}
}}
onPointerUp={(e) => {
if (!resizing) return;
setResizing(false);
e.stopPropagation();
const finalWidth = data.width ?? noteWidth;
const finalX = data.x;
const startWidth = initialWidthRef.current;
const startX = initialXRef.current;
if (finalWidth !== startWidth || finalX !== startX) {
setUndoStack((prev) => [
...prev,
{
action: Action.EDIT,
element: ObjectType.NOTE,
nid: data.id,
undo: { width: startWidth, x: startX },
redo: { width: finalWidth, x: finalX },
message: t("edit_note", {
noteTitle: data.title,
extra: "[width/x]",
}),
},
]);
setRedoStack([]);
}
}}
/>
)}

{!layout.readOnly && !data.locked && (
<rect
x={data.x + width - 4}
y={data.y + 8}
width={8}
height={Math.max(0, data.height - 16)}
fill="transparent"
stroke="transparent"
style={{ cursor: "ew-resize" }}
onPointerDown={(e) => {
e.stopPropagation();
initialWidthRef.current = data.width ?? noteWidth;
setResizing(true);
e.currentTarget.setPointerCapture?.(e.pointerId);
}}
onPointerMove={(e) => {
if (!resizing) return;
const delta = e.movementX / (transform?.zoom || 1);
const next = Math.max(MIN_NOTE_WIDTH, (data.width ?? noteWidth) + delta);
if (next !== data.width) {
updateNote(data.id, { width: next });
}
}}
onPointerUp={(e) => {
if (!resizing) return;
setResizing(false);
e.stopPropagation();
const finalWidth = data.width ?? noteWidth;
const startWidth = initialWidthRef.current;
if (finalWidth !== startWidth) {
setUndoStack((prev) => [
...prev,
{
action: Action.EDIT,
element: ObjectType.NOTE,
nid: data.id,
undo: { width: startWidth },
redo: { width: finalWidth },
message: t("edit_note", {
noteTitle: data.title,
extra: "[width]",
}),
},
]);
setRedoStack([]);
}
}}
/>
)}
<foreignObject
x={data.x}
y={data.y}
width={noteWidth}
width={width}
height={data.height}
onPointerDown={onPointerDown}
>
Expand Down
5 changes: 4 additions & 1 deletion src/components/EditorHeader/ControlPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,10 @@ export default function ControlPanel({
notes.forEach((note) => {
minMaxXY.minX = Math.min(minMaxXY.minX, note.x);
minMaxXY.minY = Math.min(minMaxXY.minY, note.y);
minMaxXY.maxX = Math.max(minMaxXY.maxX, note.x + noteWidth);
minMaxXY.maxX = Math.max(
minMaxXY.maxX,
note.x + (note.width ?? noteWidth),
);
minMaxXY.maxY = Math.max(minMaxXY.maxY, note.y + note.height);
});

Expand Down
11 changes: 6 additions & 5 deletions src/components/Thumbnail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,14 @@ export default function Thumbnail({ diagram, i, zoom, theme }) {
const x = n.x;
const y = n.y;
const h = n.height;
const w = n.width ?? noteWidth;
return (
<g key={n.id}>
<path
d={`M${x + noteFold} ${y} L${x + noteWidth - noteRadius} ${y} A${noteRadius} ${noteRadius} 0 0 1 ${
x + noteWidth
} ${y + noteRadius} L${x + noteWidth} ${y + h - noteRadius} A${noteRadius} ${noteRadius} 0 0 1 ${
x + noteWidth - noteRadius
d={`M${x + noteFold} ${y} L${x + w - noteRadius} ${y} A${noteRadius} ${noteRadius} 0 0 1 ${
x + w
} ${y + noteRadius} L${x + w} ${y + h - noteRadius} A${noteRadius} ${noteRadius} 0 0 1 ${
x + w - noteRadius
} ${y + h} L${x + noteRadius} ${y + h} A${noteRadius} ${noteRadius} 0 0 1 ${x} ${
y + h - noteRadius
} L${x} ${y + noteFold}`}
Expand All @@ -146,7 +147,7 @@ export default function Thumbnail({ diagram, i, zoom, theme }) {
strokeLinejoin="round"
strokeWidth="0.5"
/>
<foreignObject x={x} y={y} width={noteWidth} height={h}>
<foreignObject x={x} y={y} width={w} height={h}>
<div className="text-gray-900 w-full h-full px-4 py-2">
<label htmlFor={`note_${n.id}`} className="ms-4">
{n.title}
Expand Down
3 changes: 2 additions & 1 deletion src/context/NotesContext.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, useState } from "react";
import { Action, ObjectType, defaultNoteTheme } from "../data/constants";
import { Action, ObjectType, defaultNoteTheme, noteWidth } from "../data/constants";
import { useUndoRedo, useTransform, useSelect } from "../hooks";
import { Toast } from "@douyinfe/semi-ui";
import { useTranslation } from "react-i18next";
Expand Down Expand Up @@ -33,6 +33,7 @@ export default function NotesContextProvider({ children }) {
locked: false,
color: defaultNoteTheme,
height,
width: noteWidth,
},
]);
}
Expand Down