Implemented undoRedo on Zustand
This commit is contained in:
parent
882f365ff6
commit
b4f7285b33
15 changed files with 100 additions and 234 deletions
|
|
@ -26,7 +26,6 @@ import {
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
LANGFLOW_SUPPORTED_TYPES,
|
||||||
TOOLTIP_EMPTY,
|
TOOLTIP_EMPTY,
|
||||||
} from "../../../../constants/constants";
|
} from "../../../../constants/constants";
|
||||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
|
||||||
import { postCustomComponentUpdate } from "../../../../controllers/API";
|
import { postCustomComponentUpdate } from "../../../../controllers/API";
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowStore from "../../../../stores/flowStore";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
|
|
@ -88,7 +87,7 @@ export default function ParameterComponent({
|
||||||
|
|
||||||
const myData = useTypesStore((state) => state.data);
|
const myData = useTypesStore((state) => state.data);
|
||||||
|
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
|
||||||
const handleUpdateValues = async (name: string, data: NodeDataType) => {
|
const handleUpdateValues = async (name: string, data: NodeDataType) => {
|
||||||
const code = data.node?.template["code"]?.value;
|
const code = data.node?.template["code"]?.value;
|
||||||
|
|
@ -528,7 +527,6 @@ export default function ParameterComponent({
|
||||||
duplicateKey={errorDuplicateKey}
|
duplicateKey={errorDuplicateKey}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
const valueToNumbers = convertValuesToNumbers(newValue);
|
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||||
data.node!.template[name].value = valueToNumbers;
|
|
||||||
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
||||||
handleOnNewValue(valueToNumbers);
|
handleOnNewValue(valueToNumbers);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import InputComponent from "../../components/inputComponent";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import { priorityFields } from "../../constants/constants";
|
import { priorityFields } from "../../constants/constants";
|
||||||
import { useSSE } from "../../contexts/SSEContext";
|
import { useSSE } from "../../contexts/SSEContext";
|
||||||
import { undoRedoContext } from "../../contexts/undoRedoContext";
|
|
||||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import { validationStatusType } from "../../types/components";
|
import { validationStatusType } from "../../types/components";
|
||||||
|
|
@ -17,6 +16,7 @@ import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
import { classNames, cn, getFieldTitle } from "../../utils/utils";
|
import { classNames, cn, getFieldTitle } from "../../utils/utils";
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
import { useTypesStore } from "../../stores/typesStore";
|
import { useTypesStore } from "../../stores/typesStore";
|
||||||
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
|
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
|
|
@ -44,7 +44,7 @@ export default function GenericNode({
|
||||||
const [handles, setHandles] = useState<boolean[] | []>([]);
|
const [handles, setHandles] = useState<boolean[] | []>([]);
|
||||||
let numberOfInputs: boolean[] = [];
|
let numberOfInputs: boolean[] = [];
|
||||||
|
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
|
||||||
function countHandles(): void {
|
function countHandles(): void {
|
||||||
numberOfInputs = Object.keys(data.node!.template)
|
numberOfInputs = Object.keys(data.node!.template)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
} from "../../../ui/dropdown-menu";
|
} from "../../../ui/dropdown-menu";
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
|
||||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
|
|
@ -19,7 +18,8 @@ export const MenuBar = (): JSX.Element => {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const { undo, redo } = useContext(undoRedoContext);
|
const undo = useFlowsManagerStore((state) => state.undo);
|
||||||
|
const redo = useFlowsManagerStore((state) => state.redo);
|
||||||
const [openSettings, setOpenSettings] = useState(false);
|
const [openSettings, setOpenSettings] = useState(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,6 @@ export default function InputComponent({
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.ctrlKey && e.key === "c") {
|
|
||||||
// Perform any actions you need when Ctrl+C is detected
|
|
||||||
}
|
|
||||||
handleKeyDown(e, value, "");
|
handleKeyDown(e, value, "");
|
||||||
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export default function InputFileComponent({
|
||||||
|
|
||||||
// Clear component state
|
// Clear component state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
onFileChange("");
|
onFileChange("");
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,6 @@ export default function InputListComponent({
|
||||||
newInputList[idx] = event.target.value;
|
newInputList[idx] = event.target.value;
|
||||||
onChange(newInputList);
|
onChange(newInputList);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.ctrlKey && e.key === "Backspace") {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{idx === value.length - 1 ? (
|
{idx === value.length - 1 ? (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function IntComponent({
|
||||||
|
|
||||||
// Clear component state
|
// Clear component state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,6 @@ export default function KeypairListComponent({
|
||||||
)}
|
)}
|
||||||
placeholder="Type key..."
|
placeholder="Type key..."
|
||||||
onChange={(event) => handleChangeKey(event, index)}
|
onChange={(event) => handleChangeKey(event, index)}
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.ctrlKey && e.key === "Backspace") {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default function PromptAreaComponent({
|
||||||
readonly = false,
|
readonly = false,
|
||||||
}: PromptAreaComponentType): JSX.Element {
|
}: PromptAreaComponentType): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled && value !== "") {
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import { SSEProvider } from "./SSEContext";
|
||||||
import { AuthProvider } from "./authContext";
|
import { AuthProvider } from "./authContext";
|
||||||
import { LocationProvider } from "./locationContext";
|
import { LocationProvider } from "./locationContext";
|
||||||
|
|
||||||
import { UndoRedoProvider } from "./undoRedoContext";
|
|
||||||
|
|
||||||
export default function ContextWrapper({ children }: { children: ReactNode }) {
|
export default function ContextWrapper({ children }: { children: ReactNode }) {
|
||||||
//element to wrap all context
|
//element to wrap all context
|
||||||
return (
|
return (
|
||||||
|
|
@ -20,7 +18,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
|
||||||
<LocationProvider>
|
<LocationProvider>
|
||||||
<ApiInterceptor />
|
<ApiInterceptor />
|
||||||
<SSEProvider>
|
<SSEProvider>
|
||||||
<UndoRedoProvider>{children}</UndoRedoProvider>
|
{children}
|
||||||
</SSEProvider>
|
</SSEProvider>
|
||||||
</LocationProvider>
|
</LocationProvider>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
|
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
import { cloneDeep } from "lodash";
|
|
||||||
import {
|
|
||||||
createContext,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import useFlowStore from "../stores/flowStore";
|
|
||||||
import {
|
|
||||||
HistoryItem,
|
|
||||||
UseUndoRedoOptions,
|
|
||||||
undoRedoContextType,
|
|
||||||
} from "../types/typesContext";
|
|
||||||
import { isWrappedWithClass } from "../utils/utils";
|
|
||||||
import useFlowsManagerStore from "../stores/flowsManagerStore";
|
|
||||||
|
|
||||||
const initialValue = {
|
|
||||||
undo: () => {},
|
|
||||||
redo: () => {},
|
|
||||||
takeSnapshot: () => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultOptions: UseUndoRedoOptions = {
|
|
||||||
maxHistorySize: 100,
|
|
||||||
enableShortcuts: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const undoRedoContext = createContext<undoRedoContextType>(initialValue);
|
|
||||||
|
|
||||||
export function UndoRedoProvider({ children }) {
|
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
|
||||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
|
||||||
|
|
||||||
const setNodes = useFlowStore((state) => state.setNodes);
|
|
||||||
const setEdges = useFlowStore((state) => state.setEdges);
|
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
|
||||||
const edges = useFlowStore((state) => state.edges);
|
|
||||||
|
|
||||||
const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
|
|
||||||
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
|
|
||||||
const [tabIndex, setTabIndex] = useState(
|
|
||||||
flows.findIndex((flow) => flow.id === currentFlowId)
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// whenever the flows variable changes, we need to add one array to the past and future states
|
|
||||||
setPast((old) =>
|
|
||||||
flows.map((flow, index) => (old[index] ? old[index] : []))
|
|
||||||
);
|
|
||||||
setFuture((old) =>
|
|
||||||
flows.map((flow, index) => (old[index] ? old[index] : []))
|
|
||||||
);
|
|
||||||
setTabIndex(flows.findIndex((flow) => flow.id === currentFlowId));
|
|
||||||
}, [flows, currentFlowId]);
|
|
||||||
|
|
||||||
const takeSnapshot = useCallback(() => {
|
|
||||||
// push the current graph to the past state
|
|
||||||
let newPast = cloneDeep(past);
|
|
||||||
let newState = {
|
|
||||||
nodes: cloneDeep(nodes),
|
|
||||||
edges: cloneDeep(edges),
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
past[tabIndex] &&
|
|
||||||
JSON.stringify(past[tabIndex][past[tabIndex].length - 1]) !==
|
|
||||||
JSON.stringify(newState)
|
|
||||||
) {
|
|
||||||
newPast[tabIndex] = past[tabIndex].slice(
|
|
||||||
past[tabIndex].length - defaultOptions.maxHistorySize + 1,
|
|
||||||
past[tabIndex].length
|
|
||||||
);
|
|
||||||
newPast[tabIndex].push(newState);
|
|
||||||
}
|
|
||||||
setPast(newPast);
|
|
||||||
|
|
||||||
// whenever we take a new snapshot, the redo operations need to be cleared to avoid state mismatches
|
|
||||||
setFuture((old) => {
|
|
||||||
let newFuture = cloneDeep(old);
|
|
||||||
newFuture[tabIndex] = [];
|
|
||||||
return newFuture;
|
|
||||||
});
|
|
||||||
}, [nodes, edges, past, future, flows, currentFlowId, setPast, setFuture]);
|
|
||||||
|
|
||||||
const undo = useCallback(() => {
|
|
||||||
// get the last state that we want to go back to
|
|
||||||
const pastState = past[tabIndex][past[tabIndex].length - 1];
|
|
||||||
|
|
||||||
if (pastState) {
|
|
||||||
// first we remove the state from the history
|
|
||||||
setPast((old) => {
|
|
||||||
let newPast = cloneDeep(old);
|
|
||||||
newPast[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
|
|
||||||
return newPast;
|
|
||||||
});
|
|
||||||
// we store the current graph for the redo operation
|
|
||||||
setFuture((old) => {
|
|
||||||
let newFuture = cloneDeep(old);
|
|
||||||
newFuture[tabIndex] = old[tabIndex];
|
|
||||||
newFuture[tabIndex].push({ nodes: nodes, edges: edges });
|
|
||||||
return newFuture;
|
|
||||||
});
|
|
||||||
// now we can set the graph to the past state
|
|
||||||
setNodes(pastState.nodes);
|
|
||||||
setEdges(pastState.edges);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
setNodes,
|
|
||||||
setEdges,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
future,
|
|
||||||
past,
|
|
||||||
setFuture,
|
|
||||||
setPast,
|
|
||||||
tabIndex,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const redo = useCallback(() => {
|
|
||||||
const futureState = future[tabIndex][future[tabIndex].length - 1];
|
|
||||||
|
|
||||||
if (futureState) {
|
|
||||||
setFuture((old) => {
|
|
||||||
let newFuture = cloneDeep(old);
|
|
||||||
newFuture[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
|
|
||||||
return newFuture;
|
|
||||||
});
|
|
||||||
setPast((old) => {
|
|
||||||
let newPast = cloneDeep(old);
|
|
||||||
newPast[tabIndex] = old[tabIndex];
|
|
||||||
newPast[tabIndex].push({ nodes: nodes, edges: edges });
|
|
||||||
return newPast;
|
|
||||||
});
|
|
||||||
setNodes(futureState.nodes);
|
|
||||||
setEdges(futureState.edges);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
future,
|
|
||||||
past,
|
|
||||||
setFuture,
|
|
||||||
setPast,
|
|
||||||
setNodes,
|
|
||||||
setEdges,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
future,
|
|
||||||
tabIndex,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// this effect is used to attach the global event handlers
|
|
||||||
if (!defaultOptions.enableShortcuts) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyDownHandler = (event: KeyboardEvent) => {
|
|
||||||
if (!isWrappedWithClass(event, "noundo")) {
|
|
||||||
if (
|
|
||||||
event.key === "z" &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
event.shiftKey
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
redo();
|
|
||||||
} else if (event.key === "y" && (event.ctrlKey || event.metaKey)) {
|
|
||||||
event.preventDefault(); // prevent the default action
|
|
||||||
redo();
|
|
||||||
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
|
|
||||||
event.preventDefault();
|
|
||||||
undo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("keydown", keyDownHandler);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", keyDownHandler);
|
|
||||||
};
|
|
||||||
}, [undo, redo]);
|
|
||||||
return (
|
|
||||||
<undoRedoContext.Provider
|
|
||||||
value={{
|
|
||||||
undo,
|
|
||||||
redo,
|
|
||||||
takeSnapshot,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</undoRedoContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -22,7 +22,6 @@ import GenericNode from "../../../../CustomNodes/GenericNode";
|
||||||
import Chat from "../../../../components/chatComponent";
|
import Chat from "../../../../components/chatComponent";
|
||||||
import Loading from "../../../../components/ui/loading";
|
import Loading from "../../../../components/ui/loading";
|
||||||
import { locationContext } from "../../../../contexts/locationContext";
|
import { locationContext } from "../../../../contexts/locationContext";
|
||||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowStore from "../../../../stores/flowStore";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
|
|
@ -66,8 +65,6 @@ export default function Page({
|
||||||
edges: any;
|
edges: any;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
|
||||||
|
|
||||||
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
|
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
|
||||||
const setReactFlowInstance = useFlowStore(
|
const setReactFlowInstance = useFlowStore(
|
||||||
(state) => state.setReactFlowInstance
|
(state) => state.setReactFlowInstance
|
||||||
|
|
@ -83,6 +80,9 @@ export default function Page({
|
||||||
const deleteNode = useFlowStore((state) => state.deleteNode);
|
const deleteNode = useFlowStore((state) => state.deleteNode);
|
||||||
const deleteEdge = useFlowStore((state) => state.deleteEdge);
|
const deleteEdge = useFlowStore((state) => state.deleteEdge);
|
||||||
const setPending = useFlowStore((state) => state.setPending);
|
const setPending = useFlowStore((state) => state.setPending);
|
||||||
|
const undo = useFlowsManagerStore((state) => state.undo);
|
||||||
|
const redo = useFlowsManagerStore((state) => state.redo);
|
||||||
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
const isPending = useFlowStore((state) => state.isPending);
|
const isPending = useFlowStore((state) => state.isPending);
|
||||||
const paste = useFlowStore((state) => state.paste);
|
const paste = useFlowStore((state) => state.paste);
|
||||||
|
|
||||||
|
|
@ -92,6 +92,19 @@ export default function Page({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (!isWrappedWithClass(event, "noundo")) {
|
||||||
|
if (
|
||||||
|
(event.key === "y" || (event.key === "z" && event.shiftKey)) &&
|
||||||
|
(event.ctrlKey || event.metaKey)
|
||||||
|
) {
|
||||||
|
event.preventDefault(); // prevent the default action
|
||||||
|
redo();
|
||||||
|
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
|
||||||
|
console.log("nao era pra tar aqui");
|
||||||
|
event.preventDefault();
|
||||||
|
undo();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!isWrappedWithClass(event, "nocopy") &&
|
!isWrappedWithClass(event, "nocopy") &&
|
||||||
window.getSelection()?.toString().length === 0
|
window.getSelection()?.toString().length === 0
|
||||||
|
|
@ -103,8 +116,7 @@ export default function Page({
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
||||||
}
|
} else if (
|
||||||
if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
event.key === "v" &&
|
event.key === "v" &&
|
||||||
lastCopiedSelection
|
lastCopiedSelection
|
||||||
|
|
@ -115,8 +127,7 @@ export default function Page({
|
||||||
x: position.current.x,
|
x: position.current.x,
|
||||||
y: position.current.y,
|
y: position.current.y,
|
||||||
});
|
});
|
||||||
}
|
} else if (
|
||||||
if (
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
event.key === "g" &&
|
event.key === "g" &&
|
||||||
lastSelection
|
lastSelection
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "../../../../components/ui/select-custom";
|
} from "../../../../components/ui/select-custom";
|
||||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
|
||||||
import ConfirmationModal from "../../../../modals/ConfirmationModal";
|
import ConfirmationModal from "../../../../modals/ConfirmationModal";
|
||||||
import EditNodeModal from "../../../../modals/EditNodeModal";
|
import EditNodeModal from "../../../../modals/EditNodeModal";
|
||||||
import ShareModal from "../../../../modals/shareModal";
|
import ShareModal from "../../../../modals/shareModal";
|
||||||
|
|
@ -34,7 +33,7 @@ export default function NodeToolbarComponent({
|
||||||
numberOfHandles,
|
numberOfHandles,
|
||||||
showNode,
|
showNode,
|
||||||
}: nodeToolbarPropsType): JSX.Element {
|
}: nodeToolbarPropsType): JSX.Element {
|
||||||
const [nodeLength, setNodeLength] = useState(
|
const nodeLength =
|
||||||
Object.keys(data.node!.template).filter(
|
Object.keys(data.node!.template).filter(
|
||||||
(templateField) =>
|
(templateField) =>
|
||||||
templateField.charAt(0) !== "_" &&
|
templateField.charAt(0) !== "_" &&
|
||||||
|
|
@ -49,8 +48,7 @@ export default function NodeToolbarComponent({
|
||||||
data.node.template[templateField].type === "int" ||
|
data.node.template[templateField].type === "int" ||
|
||||||
data.node.template[templateField].type === "dict" ||
|
data.node.template[templateField].type === "dict" ||
|
||||||
data.node.template[templateField].type === "NestedDict")
|
data.node.template[templateField].type === "NestedDict")
|
||||||
).length
|
).length;
|
||||||
);
|
|
||||||
|
|
||||||
const hasStore = useStoreStore((state) => state.hasStore);
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||||
|
|
@ -76,7 +74,7 @@ export default function NodeToolbarComponent({
|
||||||
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
const version = useDarkStore((state) => state.version);
|
const version = useDarkStore((state) => state.version);
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
||||||
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
||||||
const [selectedValue, setSelectedValue] = useState("");
|
const [selectedValue, setSelectedValue] = useState("");
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from "../controllers/API";
|
} from "../controllers/API";
|
||||||
import { FlowType, NodeDataType } from "../types/flow";
|
import { FlowType, NodeDataType } from "../types/flow";
|
||||||
import { FlowState } from "../types/tabs";
|
import { FlowState } from "../types/tabs";
|
||||||
|
import { UseUndoRedoOptions } from "../types/typesContext";
|
||||||
import { FlowsManagerStoreType } from "../types/zustand/flowsManager";
|
import { FlowsManagerStoreType } from "../types/zustand/flowsManager";
|
||||||
import {
|
import {
|
||||||
addVersionToDuplicates,
|
addVersionToDuplicates,
|
||||||
|
|
@ -19,12 +20,20 @@ import {
|
||||||
processFlows,
|
processFlows,
|
||||||
} from "../utils/reactflowUtils";
|
} from "../utils/reactflowUtils";
|
||||||
import useAlertStore from "./alertStore";
|
import useAlertStore from "./alertStore";
|
||||||
|
import { useDarkStore } from "./darkStore";
|
||||||
import useFlowStore from "./flowStore";
|
import useFlowStore from "./flowStore";
|
||||||
import { useTypesStore } from "./typesStore";
|
import { useTypesStore } from "./typesStore";
|
||||||
import { useDarkStore } from "./darkStore";
|
|
||||||
|
|
||||||
let saveTimeoutId: NodeJS.Timeout | null = null;
|
let saveTimeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
const defaultOptions: UseUndoRedoOptions = {
|
||||||
|
maxHistorySize: 100,
|
||||||
|
enableShortcuts: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const past = {};
|
||||||
|
const future = {};
|
||||||
|
|
||||||
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
currentFlowId: "",
|
currentFlowId: "",
|
||||||
setCurrentFlowId: (currentFlowId: string) => {
|
setCurrentFlowId: (currentFlowId: string) => {
|
||||||
|
|
@ -328,7 +337,63 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
},
|
},
|
||||||
saveComponent: (component: NodeDataType, override: boolean) => {
|
saveComponent: (component: NodeDataType, override: boolean) => {
|
||||||
component.node!.official = false;
|
component.node!.official = false;
|
||||||
return get().addFlow(true, createFlowComponent(component, useDarkStore.getState().version), override);
|
return get().addFlow(
|
||||||
|
true,
|
||||||
|
createFlowComponent(component, useDarkStore.getState().version),
|
||||||
|
override
|
||||||
|
);
|
||||||
|
},
|
||||||
|
takeSnapshot: () => {
|
||||||
|
const currentFlowId = get().currentFlowId;
|
||||||
|
// push the current graph to the past state
|
||||||
|
const newState = useFlowStore.getState();
|
||||||
|
const pastLength = past[currentFlowId]?.length ?? 0;
|
||||||
|
if (
|
||||||
|
pastLength > 0 &&
|
||||||
|
JSON.stringify(past[currentFlowId][pastLength - 1]) !==
|
||||||
|
JSON.stringify(newState)
|
||||||
|
) {
|
||||||
|
past[currentFlowId] = past[currentFlowId]
|
||||||
|
.slice(pastLength - defaultOptions.maxHistorySize + 1, pastLength)
|
||||||
|
|
||||||
|
past[currentFlowId].push(newState);
|
||||||
|
} else {
|
||||||
|
past[currentFlowId] = [newState];
|
||||||
|
}
|
||||||
|
|
||||||
|
future[currentFlowId] = [];
|
||||||
|
},
|
||||||
|
undo: () => {
|
||||||
|
const newState = useFlowStore.getState();
|
||||||
|
const currentFlowId = get().currentFlowId;
|
||||||
|
const pastLength = past[currentFlowId]?.length ?? 0;
|
||||||
|
const pastState = past[currentFlowId][pastLength - 1] ?? null;
|
||||||
|
|
||||||
|
if (pastState) {
|
||||||
|
past[currentFlowId] = past[currentFlowId].slice(0, pastLength - 1);
|
||||||
|
|
||||||
|
if(!future[currentFlowId]) future[currentFlowId] = [];
|
||||||
|
future[currentFlowId].push({ nodes: newState.nodes, edges: newState.edges });
|
||||||
|
|
||||||
|
newState.setNodes(pastState.nodes);
|
||||||
|
newState.setEdges(pastState.edges);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
redo: () => {
|
||||||
|
const newState = useFlowStore.getState();
|
||||||
|
const currentFlowId = get().currentFlowId;
|
||||||
|
const futureLength = future[currentFlowId]?.length ?? 0;
|
||||||
|
const futureState = future[currentFlowId][futureLength - 1] ?? null;
|
||||||
|
|
||||||
|
if (futureState) {
|
||||||
|
future[currentFlowId] = future[currentFlowId].slice(0, futureLength - 1);
|
||||||
|
|
||||||
|
if(!past[currentFlowId]) past[currentFlowId] = [];
|
||||||
|
past[currentFlowId].push({ nodes: newState.nodes, edges: newState.edges });
|
||||||
|
|
||||||
|
newState.setNodes(futureState.nodes);
|
||||||
|
newState.setEdges(futureState.edges);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,7 @@ export type FlowsManagerStoreType = {
|
||||||
deleteComponent: (key: string) => Promise<void>;
|
deleteComponent: (key: string) => Promise<void>;
|
||||||
removeFlow: (id: string) => Promise<void>;
|
removeFlow: (id: string) => Promise<void>;
|
||||||
saveComponent: (component: any, override: boolean) => Promise<string | undefined>;
|
saveComponent: (component: any, override: boolean) => Promise<string | undefined>;
|
||||||
|
undo: () => void;
|
||||||
|
redo: () => void;
|
||||||
|
takeSnapshot: () => void;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue