more undo redo for resize and panning

This commit is contained in:
1ilit 2023-09-19 15:50:00 +03:00
parent be66cf6e84
commit 16bc0edd36
8 changed files with 128 additions and 28 deletions

1
package-lock.json generated
View File

@ -22,6 +22,7 @@
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"jsonschema": "^1.4.1", "jsonschema": "^1.4.1",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"lodash": "^4.17.21",
"node-sql-parser": "^4.7.0", "node-sql-parser": "^4.7.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",

View File

@ -17,6 +17,7 @@
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"jsonschema": "^1.4.1", "jsonschema": "^1.4.1",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"lodash": "^4.17.21",
"node-sql-parser": "^4.7.0", "node-sql-parser": "^4.7.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",

View File

@ -46,9 +46,12 @@ export default function Canvas(props) {
tableId: -1, tableId: -1,
field: -2, field: -2,
}); });
const [panning, setPanning] = useState(false); const [panning, setPanning] = useState({ state: false, x: 0, y: 0 });
const [panOffset, setPanOffset] = useState({ x: 0, y: 0 }); const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
const [areaResize, setAreaResize] = useState({ id: -1, dir: "none" }); const [areaResize, setAreaResize] = useState({
id: -1,
dir: "none",
});
const [initCoords, setInitCoords] = useState({ const [initCoords, setInitCoords] = useState({
x: 0, x: 0,
y: 0, y: 0,
@ -114,7 +117,7 @@ export default function Canvas(props) {
endY: (e.clientY - offsetY) / settings.zoom - settings.pan.y, endY: (e.clientY - offsetY) / settings.zoom - settings.pan.y,
}); });
} else if ( } else if (
panning && panning.state &&
dragging.element === ObjectType.NONE && dragging.element === ObjectType.NONE &&
areaResize.id === -1 areaResize.id === -1
) { ) {
@ -150,7 +153,7 @@ export default function Canvas(props) {
let newHeight = initCoords.height; let newHeight = initCoords.height;
const mouseX = e.clientX / settings.zoom; const mouseX = e.clientX / settings.zoom;
const mouseY = e.clientY / settings.zoom; const mouseY = e.clientY / settings.zoom;
setPanning(false); setPanning({ state: false, x: 0, y: 0 });
if (areaResize.dir === "br") { if (areaResize.dir === "br") {
newWidth = initCoords.width + (mouseX - initCoords.mouseX); newWidth = initCoords.width + (mouseX - initCoords.mouseX);
newHeight = initCoords.height + (mouseY - initCoords.mouseY); newHeight = initCoords.height + (mouseY - initCoords.mouseY);
@ -187,7 +190,7 @@ export default function Canvas(props) {
}; };
const handleMouseDown = (e) => { const handleMouseDown = (e) => {
setPanning(true); setPanning({ state: true, ...settings.pan });
setPanOffset({ x: e.clientX, y: e.clientY }); setPanOffset({ x: e.clientX, y: e.clientY });
setCursor("grabbing"); setCursor("grabbing");
}; };
@ -214,6 +217,18 @@ export default function Canvas(props) {
} }
}; };
const didResize = (id) => {
return !(
areas[id].x === initCoords.x &&
areas[id].y === initCoords.y &&
areas[id].width === initCoords.width &&
areas[id].height === initCoords.height
);
};
const didPan = () =>
!(settings.pan.x === panning.x && settings.pan.y === panning.y);
const handleMouseUp = (e) => { const handleMouseUp = (e) => {
if (coordsDidUpdate(dragging.element)) { if (coordsDidUpdate(dragging.element)) {
setUndoStack((prev) => [ setUndoStack((prev) => [
@ -229,10 +244,44 @@ export default function Canvas(props) {
setRedoStack([]); setRedoStack([]);
} }
setDragging({ element: ObjectType.NONE, id: -1, prevX: 0, prevY: 0 }); setDragging({ element: ObjectType.NONE, id: -1, prevX: 0, prevY: 0 });
setPanning(false); // NOTE: consider just saving the offset to sub and add in undo redo
if (panning.state && didPan()) {
setUndoStack((prev) => [
...prev,
{
action: Action.PAN,
data: {
undo: { x: panning.x, y: panning.y },
redo: settings.pan,
},
},
]);
setRedoStack([]);
}
setPanning({ state: false, x: 0, y: 0 });
setCursor("default"); setCursor("default");
if (linking) handleLinking(); if (linking) handleLinking();
setLinking(false); setLinking(false);
if (areaResize.id !== -1 && didResize(areaResize.id)) {
setUndoStack((prev) => [
...prev,
{
action: Action.EDIT,
element: ObjectType.AREA,
data: {
undo: {
...areas[areaResize.id],
x: initCoords.x,
y: initCoords.y,
width: initCoords.width,
height: initCoords.height,
},
redo: areas[areaResize.id],
},
},
]);
setRedoStack([]);
}
setAreaResize({ id: -1, dir: "none" }); setAreaResize({ id: -1, dir: "none" });
setInitCoords({ setInitCoords({
x: 0, x: 0,

View File

@ -158,6 +158,25 @@ export default function ControlPanel(props) {
addArea(false, a.data); addArea(false, a.data);
} }
setRedoStack((prev) => [...prev, a]); setRedoStack((prev) => [...prev, a]);
} else if (a.action === Action.EDIT) {
if (a.element === ObjectType.AREA) {
setAreas((prev) =>
prev.map((n) => {
if (n.id === a.data.undo.id) {
return a.data.undo;
}
return n;
})
);
}
setRedoStack((prev) => [...prev, a]);
} else if (a.action === Action.PAN) {
console.log(a)
setSettings((prev) => ({
...prev,
pan: a.data.undo
}));
setRedoStack((prev) => [...prev, a]);
} }
}; };
@ -206,6 +225,24 @@ export default function ControlPanel(props) {
deleteArea(a.data.id, false); deleteArea(a.data.id, false);
} }
setUndoStack((prev) => [...prev, a]); setUndoStack((prev) => [...prev, a]);
} else if (a.action === Action.EDIT) {
if (a.element === ObjectType.AREA) {
setAreas((prev) =>
prev.map((n) => {
if (n.id === a.data.redo.id) {
return a.data.redo;
}
return n;
})
);
}
setUndoStack((prev) => [...prev, a]);
} else if (a.action === Action.PAN) {
setSettings((prev) => ({
...prev,
pan: a.data.redo
}));
setUndoStack((prev) => [...prev, a]);
} }
}; };

View File

@ -11,7 +11,7 @@ export default function Note(props) {
const textarea = document.getElementById(`note_${props.data.id}`); const textarea = document.getElementById(`note_${props.data.id}`);
textarea.style.height = "0"; textarea.style.height = "0";
textarea.style.height = textarea.scrollHeight + "px"; textarea.style.height = textarea.scrollHeight + "px";
const newHeight = textarea.scrollHeight + 16 + 20 + 4; const newHeight = textarea.scrollHeight + 41;
setNotes((prev) => setNotes((prev) =>
prev.map((n) => { prev.map((n) => {
if (n.id === props.data.id) { if (n.id === props.data.id) {
@ -25,17 +25,17 @@ export default function Note(props) {
return ( return (
<g> <g>
<path <path
d={`M${props.data.x + fold} ${props.data.y} L${ d={`M${props.data.x + fold} ${props.data.y} L${props.data.x + w - r} ${
props.data.y
} A${r} ${r} 0 0 1 ${props.data.x + w} ${props.data.y + r} L${
props.data.x + w
} ${props.data.y + props.data.height - r} A${r} ${r} 0 0 1 ${
props.data.x + w - r props.data.x + w - r
} ${props.data.y} A${r} ${r} 0 0 1 ${props.data.x + w} ${ } ${props.data.y + props.data.height} L${props.data.x + r} ${
props.data.y + r
} L${props.data.x + w} ${
props.data.y + props.data.height - r
} A${r} ${r} 0 0 1 ${props.data.x + w - r} ${
props.data.y + props.data.height props.data.y + props.data.height
} L${props.data.x + r} ${props.data.y + props.data.height} A${r} ${r} 0 0 1 ${ } A${r} ${r} 0 0 1 ${props.data.x} ${
props.data.x props.data.y + props.data.height - r
} ${props.data.y + props.data.height - r} L${props.data.x} ${props.data.y + fold}`} } L${props.data.x} ${props.data.y + fold}`}
fill={props.data.color} fill={props.data.color}
stroke="#665b25" stroke="#665b25"
strokeLinejoin="round" strokeLinejoin="round"
@ -62,13 +62,15 @@ export default function Note(props) {
onMouseDown={props.onMouseDown} onMouseDown={props.onMouseDown}
> >
<div className="text-gray-900 select-none w-full h-full cursor-move px-3 py-2"> <div className="text-gray-900 select-none w-full h-full cursor-move px-3 py-2">
<label htmlFor={`note_${props.data.id}`} className="ms-5">{props.data.title}</label> <label htmlFor={`note_${props.data.id}`} className="ms-5">
{props.data.title}
</label>
<textarea <textarea
id={`note_${props.data.id}`} id={`note_${props.data.id}`}
value={props.data.content} value={props.data.content}
onInput={handleChange} onChange={handleChange}
className="mt-1 w-full resize-none outline-none overflow-y-hidden border-none select-none" className="w-full resize-none outline-none overflow-y-hidden border-none select-none"
style={{backgroundColor: props.data.color}} style={{ backgroundColor: props.data.color }}
></textarea> ></textarea>
</div> </div>
</foreignObject> </foreignObject>

View File

@ -102,13 +102,7 @@ export default function TableOverview(props) {
/> />
</Col> </Col>
<Col span={8}> <Col span={8}>
<Button <Button icon={<IconPlus />} block onClick={() => addTable(true)}>
icon={<IconPlus />}
block
onClick={() => {
addTable(true);
}}
>
Add table Add table
</Button> </Button>
</Col> </Col>

View File

@ -80,6 +80,8 @@ const Action = {
ADD: 0, ADD: 0,
MOVE: 1, MOVE: 1,
DELETE: 2, DELETE: 2,
EDIT: 3,
PAN: 4,
}; };
export { export {

View File

@ -325,6 +325,20 @@ export default function Editor(props) {
); );
}; };
const editNote = (id, values, addToHistory=true) => {
setNotes((prev) =>
prev.map((t) => {
if (t.id === id) {
return {
...t,
...values,
};
}
return t;
})
);
}
useEffect(() => { useEffect(() => {
document.title = "Editor - drawDB"; document.title = "Editor - drawDB";
}, []); }, []);
@ -348,7 +362,7 @@ export default function Editor(props) {
value={{ areas, setAreas, moveArea, addArea, deleteArea }} value={{ areas, setAreas, moveArea, addArea, deleteArea }}
> >
<NoteContext.Provider <NoteContext.Provider
value={{ notes, setNotes, moveNote, addNote, deleteNote }} value={{ notes, setNotes, moveNote, addNote, deleteNote, editNote }}
> >
<TabContext.Provider value={{ tab, setTab }}> <TabContext.Provider value={{ tab, setTab }}>
<SettingsContext.Provider value={{ settings, setSettings }}> <SettingsContext.Provider value={{ settings, setSettings }}>