web: add redo

This commit is contained in:
2025-11-13 08:54:38 -06:00
parent 8b170cdbbf
commit 61179bb7c7
4 changed files with 44 additions and 5 deletions

View File

@@ -44,11 +44,13 @@ const CaseBuilder = () => {
return processedScenario!!;
});
const [undoStack, setUndoStack] = useState<UnitCommitmentScenario[]>([]);
const [redoStack, setRedoStack] = useState<UnitCommitmentScenario[]>([]);
const [toastMessage, setToastMessage] = useState<string>("");
const setAndSaveScenario = (
newScenario: UnitCommitmentScenario,
updateUndoStack = true,
clearRedoStack = true,
) => {
if (updateUndoStack) {
const newUndoStack = [...undoStack, scenario];
@@ -57,6 +59,9 @@ const CaseBuilder = () => {
}
setUndoStack(newUndoStack);
}
if (clearRedoStack) {
setRedoStack([]);
}
setScenario(newScenario);
localStorage.setItem("scenario", JSON.stringify(newScenario));
};
@@ -90,8 +95,19 @@ const CaseBuilder = () => {
const onUndo = () => {
if (undoStack.length === 0) return;
const newRedoStack = [...redoStack, scenario];
if (newRedoStack.length > 25) {
newRedoStack.splice(0, newRedoStack.length - 25);
}
setRedoStack(newRedoStack);
setUndoStack(undoStack.slice(0, -1));
setAndSaveScenario(undoStack[undoStack.length - 1]!, false);
setAndSaveScenario(undoStack[undoStack.length - 1]!, false, false);
};
const onRedo = () => {
if (redoStack.length === 0) return;
setRedoStack(redoStack.slice(0, -1));
setAndSaveScenario(redoStack[redoStack.length - 1]!, true, false);
};
const onSolve = async () => {
@@ -128,7 +144,10 @@ const CaseBuilder = () => {
onSave={onSave}
onLoad={onLoad}
onUndo={onUndo}
onRedo={onRedo}
onSolve={onSolve}
canUndo={undoStack.length > 0}
canRedo={redoStack.length > 0}
/>
<div className="content">
<Parameters

View File

@@ -9,14 +9,17 @@ import SiteHeaderButton from "../Common/Buttons/SiteHeaderButton";
import { useRef } from "react";
import FileUploadElement from "../Common/Buttons/FileUploadElement";
import { UnitCommitmentScenario } from "../../core/Data/types";
import { faDownload, faGear, faRotateLeft, faTrash, faUpload } from "@fortawesome/free-solid-svg-icons";
import { faDownload, faGear, faRotateLeft, faRotateRight, faTrash, faUpload } from "@fortawesome/free-solid-svg-icons";
interface HeaderProps {
onClear: () => void;
onSave: () => void;
onUndo: () => void;
onRedo: () => void;
onLoad: (data: UnitCommitmentScenario) => void;
onSolve: () => void;
canUndo: boolean;
canRedo: boolean;
}
function Header(props: HeaderProps) {
@@ -44,6 +47,13 @@ function Header(props: HeaderProps) {
title="Undo"
icon={faRotateLeft}
onClick={props.onUndo}
disabled={!props.canUndo}
/>
<SiteHeaderButton
title="Redo"
icon={faRotateRight}
onClick={props.onRedo}
disabled={!props.canRedo}
/>
<SiteHeaderButton
title="Clear"

View File

@@ -5,8 +5,8 @@
*/
.SiteHeaderButton {
padding: 6px 20px;
margin: 0 0 0 0px;
padding: 6px 16px;
margin: 0 0 0 0;
line-height: 24px;
border: var(--box-border);
box-shadow: var(--box-shadow);
@@ -59,3 +59,9 @@
.primary:active {
background: color-mix(in hsl, #000, var(--primary) 90%);
}
.disabled {
color: var(--contrast-20);
cursor: not-allowed;
pointer-events: none;
}

View File

@@ -13,19 +13,23 @@ function SiteHeaderButton({
icon,
onClick,
variant = "light",
disabled = false,
}: {
title: string;
icon: IconDefinition;
onClick?: () => void;
variant?: "light" | "primary";
disabled?: boolean;
}) {
const variantClass = variant === "primary" ? styles.primary : styles.light;
const disabledClass = disabled ? styles.disabled : "";
return (
<button
className={`${styles.SiteHeaderButton} ${variantClass}`}
className={`${styles.SiteHeaderButton} ${variantClass} ${disabledClass}`}
title={title}
onClick={onClick}
disabled={disabled}
>
<FontAwesomeIcon icon={icon} />
</button>