Fix ColorPicker undo stack additions (#494)

* Fix ColorPicker undo stack additions

* make custom component ColorPicker
This commit is contained in:
Karen Mkrtumyan
2025-06-20 18:18:06 +04:00
committed by GitHub
parent 3ea88314eb
commit e56f6409ff
6 changed files with 256 additions and 137 deletions

View File

@@ -1,5 +1,6 @@
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 {
IconEdit,
IconDeleteStroked,
@@ -220,6 +221,42 @@ function EditPopoverContent({ data }) {
const { updateArea, deleteArea } = useAreas();
const { setUndoStack, setRedoStack } = useUndoRedo();
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 (
<div className="popover-theme">
@@ -251,26 +288,10 @@ function EditPopoverContent({ data }) {
}}
/>
<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}
value={ColorPicker.colorStringToValue(data.color)}
value={data.color}
onChange={(color) => updateArea(data.id, { color })}
onColorPick={(color) => handleColorPick(color)}
/>
</div>
<div className="flex">

View File

@@ -1,6 +1,7 @@
import { useMemo, useState } from "react";
import { useMemo, useState, useRef } from "react";
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 {
IconEdit,
IconDeleteStroked,
@@ -27,6 +28,42 @@ export default function Note({ data, onPointerDown }) {
const { setUndoStack, setRedoStack } = useUndoRedo();
const { selectedElement, setSelectedElement, bulkSelectedElements } =
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 textarea = document.getElementById(`note_${data.id}`);
@@ -225,32 +262,11 @@ export default function Note({ data, onPointerDown }) {
}}
/>
<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}
value={ColorPicker.colorStringToValue(data.color)}
>
<div
className="h-[32px] w-[32px] rounded-sm"
style={{ backgroundColor: data.color }}
/>
</ColorPicker>
value={data.color}
onChange={(color) => updateNote(data.id, { color })}
onColorPick={(color) => handleColorPick(color)}
/>
</div>
<div className="flex">
<Button

View File

@@ -1,5 +1,6 @@
import { useState } from "react";
import { Button, Input, ColorPicker } from "@douyinfe/semi-ui";
import { useState, useRef } from "react";
import { Button, Input } from "@douyinfe/semi-ui";
import ColorPicker from "../ColorPicker";
import { IconDeleteStroked } from "@douyinfe/semi-icons";
import { useAreas, useUndoRedo } from "../../../hooks";
import { Action, ObjectType } from "../../../data/constants";
@@ -10,6 +11,42 @@ export default function AreaInfo({ data, i }) {
const { deleteArea, updateArea } = useAreas();
const { setUndoStack, setRedoStack } = useUndoRedo();
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 (
<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
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}
value={ColorPicker.colorStringToValue(data.color)}
>
<div
className="h-[32px] w-[32px] rounded-sm shrink-0"
style={{ backgroundColor: data.color }}
/>
</ColorPicker>
value={data.color}
onChange={(color) => updateArea(i, { color })}
onColorPick={(color) => handleColorPick(color)}
/>
<Button
icon={<IconDeleteStroked />}
type="danger"

View 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>
);
}

View File

@@ -1,11 +1,6 @@
import { useState } from "react";
import {
Button,
Collapse,
TextArea,
Input,
ColorPicker,
} from "@douyinfe/semi-ui";
import { useState, useRef } from "react";
import { Button, Collapse, TextArea, Input } from "@douyinfe/semi-ui";
import ColorPicker from "../ColorPicker";
import { IconDeleteStroked } from "@douyinfe/semi-icons";
import { Action, ObjectType } from "../../../data/constants";
import { useNotes, useUndoRedo } from "../../../hooks";
@@ -16,6 +11,42 @@ export default function NoteInfo({ data, nid }) {
const { setUndoStack, setRedoStack } = useUndoRedo();
const [editField, setEditField] = useState({});
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 (
<Collapse.Panel
@@ -93,34 +124,13 @@ export default function NoteInfo({ data, nid }) {
}}
rows={3}
/>
<div className="ms-2">
<div className="ms-2 flex flex-col gap-2">
<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}
value={ColorPicker.colorStringToValue(data.color)}
>
<div
className="h-[32px] w-[32px] rounded-sm shrink-0 mb-2"
style={{ backgroundColor: data.color }}
/>
</ColorPicker>
value={data.color}
onChange={(color) => updateNote(data.id, { color })}
onColorPick={(color) => handleColorPick(color)}
/>
<Button
icon={<IconDeleteStroked />}
type="danger"

View File

@@ -1,12 +1,6 @@
import { useState } from "react";
import {
Collapse,
Input,
TextArea,
Button,
Card,
ColorPicker,
} from "@douyinfe/semi-ui";
import { useState, useRef } from "react";
import { Collapse, Input, TextArea, Button, Card } from "@douyinfe/semi-ui";
import ColorPicker from "../ColorPicker";
import { IconDeleteStroked } from "@douyinfe/semi-icons";
import { useDiagram, useSaveState, useUndoRedo } from "../../../hooks";
import { Action, ObjectType, State } from "../../../data/constants";
@@ -23,6 +17,44 @@ export default function TableInfo({ data }) {
const { setUndoStack, setRedoStack } = useUndoRedo();
const { setSaveState } = useSaveState();
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 (
<div>
@@ -143,27 +175,10 @@ export default function TableInfo({ data }) {
</Card>
<div className="flex justify-between items-center gap-1 mb-2">
<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}
value={ColorPicker.colorStringToValue(data.color)}
value={data.color}
onChange={(color) => updateTable(data.id, { color })}
onColorPick={(color) => handleColorPick(color)}
/>
<div className="flex gap-1">
<Button