mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-07-18 10:11:24 +00:00
Fix ColorPicker undo stack additions (#494)
* Fix ColorPicker undo stack additions * make custom component ColorPicker
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useMemo, useRef, useState } from "react";
|
import { useMemo, useRef, useState } from "react";
|
||||||
import { Button, Popover, Input, ColorPicker } from "@douyinfe/semi-ui";
|
import { Button, Popover, Input } from "@douyinfe/semi-ui";
|
||||||
|
import ColorPicker from "../EditorSidePanel/ColorPicker";
|
||||||
import {
|
import {
|
||||||
IconEdit,
|
IconEdit,
|
||||||
IconDeleteStroked,
|
IconDeleteStroked,
|
||||||
@@ -220,6 +221,42 @@ function EditPopoverContent({ data }) {
|
|||||||
const { updateArea, deleteArea } = useAreas();
|
const { updateArea, deleteArea } = useAreas();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const initialColorRef = useRef(data.color);
|
||||||
|
|
||||||
|
const handleColorPick = (color) => {
|
||||||
|
setUndoStack((prev) => {
|
||||||
|
let undoColor = initialColorRef.current;
|
||||||
|
const lastColorChange = prev.findLast(
|
||||||
|
(e) =>
|
||||||
|
e.element === ObjectType.AREA &&
|
||||||
|
e.aid === data.id &&
|
||||||
|
e.action === Action.EDIT &&
|
||||||
|
e.redo.color,
|
||||||
|
);
|
||||||
|
if (lastColorChange) {
|
||||||
|
undoColor = lastColorChange.redo.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color === undoColor) return prev;
|
||||||
|
|
||||||
|
const newStack = [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.AREA,
|
||||||
|
aid: data.id,
|
||||||
|
undo: { color: undoColor },
|
||||||
|
redo: { color: color },
|
||||||
|
message: t("edit_area", {
|
||||||
|
areaName: data.name,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return newStack;
|
||||||
|
});
|
||||||
|
setRedoStack([]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="popover-theme">
|
<div className="popover-theme">
|
||||||
@@ -251,26 +288,10 @@ function EditPopoverContent({ data }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
onChange={({ hex: color }) => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.AREA,
|
|
||||||
aid: data.id,
|
|
||||||
undo: { color: data.color },
|
|
||||||
redo: { color },
|
|
||||||
message: t("edit_area", {
|
|
||||||
areaName: data.name,
|
|
||||||
extra: "[color]",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
updateArea(data.id, { color });
|
|
||||||
}}
|
|
||||||
usePopover={true}
|
usePopover={true}
|
||||||
value={ColorPicker.colorStringToValue(data.color)}
|
value={data.color}
|
||||||
|
onChange={(color) => updateArea(data.id, { color })}
|
||||||
|
onColorPick={(color) => handleColorPick(color)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState, useRef } from "react";
|
||||||
import { Action, ObjectType, Tab, State } from "../../data/constants";
|
import { Action, ObjectType, Tab, State } from "../../data/constants";
|
||||||
import { Input, Button, Popover, ColorPicker } from "@douyinfe/semi-ui";
|
import { Input, Button, Popover } from "@douyinfe/semi-ui";
|
||||||
|
import ColorPicker from "../EditorSidePanel/ColorPicker";
|
||||||
import {
|
import {
|
||||||
IconEdit,
|
IconEdit,
|
||||||
IconDeleteStroked,
|
IconDeleteStroked,
|
||||||
@@ -27,6 +28,42 @@ export default function Note({ data, onPointerDown }) {
|
|||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const { selectedElement, setSelectedElement, bulkSelectedElements } =
|
const { selectedElement, setSelectedElement, bulkSelectedElements } =
|
||||||
useSelect();
|
useSelect();
|
||||||
|
const initialColorRef = useRef(data.color);
|
||||||
|
|
||||||
|
const handleColorPick = (color) => {
|
||||||
|
setUndoStack((prev) => {
|
||||||
|
let undoColor = initialColorRef.current;
|
||||||
|
const lastColorChange = prev.findLast(
|
||||||
|
(e) =>
|
||||||
|
e.element === ObjectType.NOTE &&
|
||||||
|
e.nid === data.id &&
|
||||||
|
e.action === Action.EDIT &&
|
||||||
|
e.redo.color,
|
||||||
|
);
|
||||||
|
if (lastColorChange) {
|
||||||
|
undoColor = lastColorChange.redo.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color === undoColor) return prev;
|
||||||
|
|
||||||
|
const newStack = [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.NOTE,
|
||||||
|
nid: data.id,
|
||||||
|
undo: { color: undoColor },
|
||||||
|
redo: { color: color },
|
||||||
|
message: t("edit_note", {
|
||||||
|
noteTitle: data.title,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return newStack;
|
||||||
|
});
|
||||||
|
setRedoStack([]);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const textarea = document.getElementById(`note_${data.id}`);
|
const textarea = document.getElementById(`note_${data.id}`);
|
||||||
@@ -225,32 +262,11 @@ export default function Note({ data, onPointerDown }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
onChange={({ hex: color }) => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.NOTE,
|
|
||||||
nid: data.id,
|
|
||||||
undo: { color: data.color },
|
|
||||||
redo: { color },
|
|
||||||
message: t("edit_note", {
|
|
||||||
noteTitle: data.title,
|
|
||||||
extra: "[color]",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
updateNote(data.id, { color });
|
|
||||||
}}
|
|
||||||
usePopover={true}
|
usePopover={true}
|
||||||
value={ColorPicker.colorStringToValue(data.color)}
|
value={data.color}
|
||||||
>
|
onChange={(color) => updateNote(data.id, { color })}
|
||||||
<div
|
onColorPick={(color) => handleColorPick(color)}
|
||||||
className="h-[32px] w-[32px] rounded-sm"
|
/>
|
||||||
style={{ backgroundColor: data.color }}
|
|
||||||
/>
|
|
||||||
</ColorPicker>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Button
|
<Button
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { Button, Input, ColorPicker } from "@douyinfe/semi-ui";
|
import { Button, Input } from "@douyinfe/semi-ui";
|
||||||
|
import ColorPicker from "../ColorPicker";
|
||||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import { useAreas, useUndoRedo } from "../../../hooks";
|
import { useAreas, useUndoRedo } from "../../../hooks";
|
||||||
import { Action, ObjectType } from "../../../data/constants";
|
import { Action, ObjectType } from "../../../data/constants";
|
||||||
@@ -10,6 +11,42 @@ export default function AreaInfo({ data, i }) {
|
|||||||
const { deleteArea, updateArea } = useAreas();
|
const { deleteArea, updateArea } = useAreas();
|
||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
|
const initialColorRef = useRef(data.color);
|
||||||
|
|
||||||
|
const handleColorPick = (color) => {
|
||||||
|
setUndoStack((prev) => {
|
||||||
|
let undoColor = initialColorRef.current;
|
||||||
|
const lastColorChange = prev.findLast(
|
||||||
|
(e) =>
|
||||||
|
e.element === ObjectType.AREA &&
|
||||||
|
e.aid === data.id &&
|
||||||
|
e.action === Action.EDIT &&
|
||||||
|
e.redo.color,
|
||||||
|
);
|
||||||
|
if (lastColorChange) {
|
||||||
|
undoColor = lastColorChange.redo.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color === undoColor) return prev;
|
||||||
|
|
||||||
|
const newStack = [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.AREA,
|
||||||
|
aid: i,
|
||||||
|
undo: { color: undoColor },
|
||||||
|
redo: { color: color },
|
||||||
|
message: t("edit_area", {
|
||||||
|
areaName: data.name,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return newStack;
|
||||||
|
});
|
||||||
|
setRedoStack([]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={`scroll_area_${data.id}`} className="my-3 flex gap-2 items-center">
|
<div id={`scroll_area_${data.id}`} className="my-3 flex gap-2 items-center">
|
||||||
@@ -38,32 +75,11 @@ export default function AreaInfo({ data, i }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
onChange={({ hex: color }) => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.AREA,
|
|
||||||
aid: i,
|
|
||||||
undo: { color: data.color },
|
|
||||||
redo: { color },
|
|
||||||
message: t("edit_area", {
|
|
||||||
areaName: data.name,
|
|
||||||
extra: "[color]",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
updateArea(i, { color });
|
|
||||||
}}
|
|
||||||
usePopover={true}
|
usePopover={true}
|
||||||
value={ColorPicker.colorStringToValue(data.color)}
|
value={data.color}
|
||||||
>
|
onChange={(color) => updateArea(i, { color })}
|
||||||
<div
|
onColorPick={(color) => handleColorPick(color)}
|
||||||
className="h-[32px] w-[32px] rounded-sm shrink-0"
|
/>
|
||||||
style={{ backgroundColor: data.color }}
|
|
||||||
/>
|
|
||||||
</ColorPicker>
|
|
||||||
<Button
|
<Button
|
||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
|
41
src/components/EditorSidePanel/ColorPicker.jsx
Normal file
41
src/components/EditorSidePanel/ColorPicker.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { ColorPicker as SemiColorPicker } from "@douyinfe/semi-ui";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function ColorPicker({
|
||||||
|
children,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
onColorPick,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
const [pickedColor, setPickedColor] = useState(null);
|
||||||
|
|
||||||
|
const handleColorPick = () => {
|
||||||
|
if (pickedColor) onColorPick(pickedColor);
|
||||||
|
setPickedColor(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onPointerUp={handleColorPick}
|
||||||
|
onBlur={handleColorPick}
|
||||||
|
onMouseLeave={handleColorPick}
|
||||||
|
>
|
||||||
|
<SemiColorPicker
|
||||||
|
{...props}
|
||||||
|
value={SemiColorPicker.colorStringToValue(value)}
|
||||||
|
onChange={({ hex: color }) => {
|
||||||
|
setPickedColor(color);
|
||||||
|
onChange(color);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children || (
|
||||||
|
<div
|
||||||
|
className="h-8 w-8 rounded-md"
|
||||||
|
style={{ backgroundColor: value }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SemiColorPicker>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,11 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState, useRef } from "react";
|
||||||
import {
|
import { Button, Collapse, TextArea, Input } from "@douyinfe/semi-ui";
|
||||||
Button,
|
import ColorPicker from "../ColorPicker";
|
||||||
Collapse,
|
|
||||||
TextArea,
|
|
||||||
Input,
|
|
||||||
ColorPicker,
|
|
||||||
} from "@douyinfe/semi-ui";
|
|
||||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import { Action, ObjectType } from "../../../data/constants";
|
import { Action, ObjectType } from "../../../data/constants";
|
||||||
import { useNotes, useUndoRedo } from "../../../hooks";
|
import { useNotes, useUndoRedo } from "../../../hooks";
|
||||||
@@ -16,6 +11,42 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const initialColorRef = useRef(data.color);
|
||||||
|
|
||||||
|
const handleColorPick = (color) => {
|
||||||
|
setUndoStack((prev) => {
|
||||||
|
let undoColor = initialColorRef.current;
|
||||||
|
const lastColorChange = prev.findLast(
|
||||||
|
(e) =>
|
||||||
|
e.element === ObjectType.NOTE &&
|
||||||
|
e.nid === data.id &&
|
||||||
|
e.action === Action.EDIT &&
|
||||||
|
e.redo.color,
|
||||||
|
);
|
||||||
|
if (lastColorChange) {
|
||||||
|
undoColor = lastColorChange.redo.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color === undoColor) return prev;
|
||||||
|
|
||||||
|
const newStack = [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.NOTE,
|
||||||
|
nid: data.id,
|
||||||
|
undo: { color: undoColor },
|
||||||
|
redo: { color: color },
|
||||||
|
message: t("edit_note", {
|
||||||
|
noteTitle: data.title,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return newStack;
|
||||||
|
});
|
||||||
|
setRedoStack([]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse.Panel
|
<Collapse.Panel
|
||||||
@@ -93,34 +124,13 @@ export default function NoteInfo({ data, nid }) {
|
|||||||
}}
|
}}
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
<div className="ms-2">
|
<div className="ms-2 flex flex-col gap-2">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
onChange={({ hex: color }) => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.NOTE,
|
|
||||||
nid: nid,
|
|
||||||
undo: { color: data.color },
|
|
||||||
redo: { color },
|
|
||||||
message: t("edit_note", {
|
|
||||||
noteTitle: data.title,
|
|
||||||
extra: "[color]",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
updateNote(nid, { color });
|
|
||||||
}}
|
|
||||||
usePopover={true}
|
usePopover={true}
|
||||||
value={ColorPicker.colorStringToValue(data.color)}
|
value={data.color}
|
||||||
>
|
onChange={(color) => updateNote(data.id, { color })}
|
||||||
<div
|
onColorPick={(color) => handleColorPick(color)}
|
||||||
className="h-[32px] w-[32px] rounded-sm shrink-0 mb-2"
|
/>
|
||||||
style={{ backgroundColor: data.color }}
|
|
||||||
/>
|
|
||||||
</ColorPicker>
|
|
||||||
<Button
|
<Button
|
||||||
icon={<IconDeleteStroked />}
|
icon={<IconDeleteStroked />}
|
||||||
type="danger"
|
type="danger"
|
||||||
|
@@ -1,12 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState, useRef } from "react";
|
||||||
import {
|
import { Collapse, Input, TextArea, Button, Card } from "@douyinfe/semi-ui";
|
||||||
Collapse,
|
import ColorPicker from "../ColorPicker";
|
||||||
Input,
|
|
||||||
TextArea,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
ColorPicker,
|
|
||||||
} from "@douyinfe/semi-ui";
|
|
||||||
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
import { IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||||
import { useDiagram, useSaveState, useUndoRedo } from "../../../hooks";
|
import { useDiagram, useSaveState, useUndoRedo } from "../../../hooks";
|
||||||
import { Action, ObjectType, State } from "../../../data/constants";
|
import { Action, ObjectType, State } from "../../../data/constants";
|
||||||
@@ -23,6 +17,44 @@ export default function TableInfo({ data }) {
|
|||||||
const { setUndoStack, setRedoStack } = useUndoRedo();
|
const { setUndoStack, setRedoStack } = useUndoRedo();
|
||||||
const { setSaveState } = useSaveState();
|
const { setSaveState } = useSaveState();
|
||||||
const [editField, setEditField] = useState({});
|
const [editField, setEditField] = useState({});
|
||||||
|
const initialColorRef = useRef(data.color);
|
||||||
|
|
||||||
|
const handleColorPick = (color) => {
|
||||||
|
setUndoStack((prev) => {
|
||||||
|
let undoColor = initialColorRef.current;
|
||||||
|
const lastColorChange = prev.findLast(
|
||||||
|
(e) =>
|
||||||
|
e.element === ObjectType.TABLE &&
|
||||||
|
e.tid === data.id &&
|
||||||
|
e.action === Action.EDIT &&
|
||||||
|
e.redo.color,
|
||||||
|
);
|
||||||
|
if (lastColorChange) {
|
||||||
|
undoColor = lastColorChange.redo.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color === undoColor) return prev;
|
||||||
|
|
||||||
|
const newStack = [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
action: Action.EDIT,
|
||||||
|
element: ObjectType.TABLE,
|
||||||
|
component: "self",
|
||||||
|
tid: data.id,
|
||||||
|
undo: { color: undoColor },
|
||||||
|
redo: { color: color },
|
||||||
|
message: t("edit_table", {
|
||||||
|
tableName: data.name,
|
||||||
|
extra: "[color]",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return newStack;
|
||||||
|
});
|
||||||
|
setRedoStack([]);
|
||||||
|
};
|
||||||
|
undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -143,27 +175,10 @@ export default function TableInfo({ data }) {
|
|||||||
</Card>
|
</Card>
|
||||||
<div className="flex justify-between items-center gap-1 mb-2">
|
<div className="flex justify-between items-center gap-1 mb-2">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
onChange={({ hex: color }) => {
|
|
||||||
setUndoStack((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
action: Action.EDIT,
|
|
||||||
element: ObjectType.TABLE,
|
|
||||||
component: "self",
|
|
||||||
tid: data.id,
|
|
||||||
undo: { color: data.color },
|
|
||||||
redo: { color },
|
|
||||||
message: t("edit_table", {
|
|
||||||
tableName: data.name,
|
|
||||||
extra: "[color]",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
setRedoStack([]);
|
|
||||||
updateTable(data.id, { color });
|
|
||||||
}}
|
|
||||||
usePopover={true}
|
usePopover={true}
|
||||||
value={ColorPicker.colorStringToValue(data.color)}
|
value={data.color}
|
||||||
|
onChange={(color) => updateTable(data.id, { color })}
|
||||||
|
onColorPick={(color) => handleColorPick(color)}
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button
|
<Button
|
||||||
|
Reference in New Issue
Block a user