Compare commits

...

2 Commits

Author SHA1 Message Date
61179bb7c7 web: add redo 2025-11-13 08:55:03 -06:00
8b170cdbbf web: update buttons 2025-11-13 08:47:27 -06:00
10 changed files with 101 additions and 35 deletions

View File

@@ -6,11 +6,7 @@
import SectionHeader from "../Common/SectionHeader/SectionHeader"; import SectionHeader from "../Common/SectionHeader/SectionHeader";
import SectionButton from "../Common/Buttons/SectionButton"; import SectionButton from "../Common/Buttons/SectionButton";
import { import { faDownload, faPlus, faUpload } from "@fortawesome/free-solid-svg-icons";
faDownload,
faPlus,
faUpload,
} from "@fortawesome/free-solid-svg-icons";
import { offerDownload } from "../Common/io"; import { offerDownload } from "../Common/io";
import FileUploadElement from "../Common/Buttons/FileUploadElement"; import FileUploadElement from "../Common/Buttons/FileUploadElement";
import { useRef } from "react"; import { useRef } from "react";
@@ -20,16 +16,11 @@ import DataTable, {
generateCsv, generateCsv,
generateTableColumns, generateTableColumns,
generateTableData, generateTableData,
parseCsv, parseCsv
} from "../Common/Forms/DataTable"; } from "../Common/Forms/DataTable";
import { ColumnDefinition } from "tabulator-tables"; import { ColumnDefinition } from "tabulator-tables";
import { import { changeBusData, createBus, deleteBus, renameBus } from "../../core/Operations/busOps";
changeBusData,
createBus,
deleteBus,
renameBus,
} from "../../core/Operations/busOps";
import { CaseBuilderSectionProps } from "./CaseBuilder"; import { CaseBuilderSectionProps } from "./CaseBuilder";
import { UnitCommitmentScenario } from "../../core/Data/types"; import { UnitCommitmentScenario } from "../../core/Data/types";
@@ -128,13 +119,13 @@ function BusesComponent(props: CaseBuilderSectionProps) {
return ( return (
<div> <div>
<SectionHeader title="Buses"> <SectionHeader title="Buses">
<SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} /> <SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} />
<SectionButton <SectionButton
icon={faDownload} icon={faDownload}
tooltip="Download" tooltip="Download"
onClick={onDownload} onClick={onDownload}
/> />
<SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} /> <SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} />
</SectionHeader> </SectionHeader>
<DataTable <DataTable
onRowDeleted={onDelete} onRowDeleted={onDelete}

View File

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

View File

@@ -9,13 +9,17 @@ import SiteHeaderButton from "../Common/Buttons/SiteHeaderButton";
import { useRef } from "react"; import { useRef } from "react";
import FileUploadElement from "../Common/Buttons/FileUploadElement"; import FileUploadElement from "../Common/Buttons/FileUploadElement";
import { UnitCommitmentScenario } from "../../core/Data/types"; import { UnitCommitmentScenario } from "../../core/Data/types";
import { faDownload, faGear, faRotateLeft, faRotateRight, faTrash, faUpload } from "@fortawesome/free-solid-svg-icons";
interface HeaderProps { interface HeaderProps {
onClear: () => void; onClear: () => void;
onSave: () => void; onSave: () => void;
onUndo: () => void; onUndo: () => void;
onRedo: () => void;
onLoad: (data: UnitCommitmentScenario) => void; onLoad: (data: UnitCommitmentScenario) => void;
onSolve: () => void; onSolve: () => void;
canUndo: boolean;
canRedo: boolean;
} }
function Header(props: HeaderProps) { function Header(props: HeaderProps) {
@@ -33,13 +37,33 @@ function Header(props: HeaderProps) {
<h1>UnitCommitment.jl</h1> <h1>UnitCommitment.jl</h1>
<h2>Case Builder</h2> <h2>Case Builder</h2>
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
<SiteHeaderButton title="Undo" onClick={props.onUndo} /> <SiteHeaderButton title="Load" icon={faUpload} onClick={onLoad} />
<SiteHeaderButton title="Clear" onClick={props.onClear} /> <SiteHeaderButton
<SiteHeaderButton title="Load" onClick={onLoad} /> title="Save"
<SiteHeaderButton title="Save" onClick={props.onSave} /> icon={faDownload}
onClick={props.onSave}
/>
<SiteHeaderButton
title="Undo"
icon={faRotateLeft}
onClick={props.onUndo}
disabled={!props.canUndo}
/>
<SiteHeaderButton
title="Redo"
icon={faRotateRight}
onClick={props.onRedo}
disabled={!props.canRedo}
/>
<SiteHeaderButton
title="Clear"
icon={faTrash}
onClick={props.onClear}
/>
<SiteHeaderButton <SiteHeaderButton
title="Solve" title="Solve"
variant="primary" variant="primary"
icon={faGear}
onClick={props.onSolve} onClick={props.onSolve}
/> />
</div> </div>

View File

@@ -175,13 +175,13 @@ const ProfiledUnitsComponent = (props: CaseBuilderSectionProps) => {
return ( return (
<div> <div>
<SectionHeader title="Profiled units"> <SectionHeader title="Profiled units">
<SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} /> <SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} />
<SectionButton <SectionButton
icon={faDownload} icon={faDownload}
tooltip="Download" tooltip="Download"
onClick={onDownload} onClick={onDownload}
/> />
<SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} /> <SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} />
</SectionHeader> </SectionHeader>
<DataTable <DataTable
onRowDeleted={onDelete} onRowDeleted={onDelete}

View File

@@ -153,13 +153,13 @@ const PriceSensitiveLoadsComponent = (props: CaseBuilderSectionProps) => {
return ( return (
<div> <div>
<SectionHeader title="Price-sensitive loads"> <SectionHeader title="Price-sensitive loads">
<SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} /> <SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} />
<SectionButton <SectionButton
icon={faDownload} icon={faDownload}
tooltip="Download" tooltip="Download"
onClick={onDownload} onClick={onDownload}
/> />
<SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} /> <SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} />
</SectionHeader> </SectionHeader>
<DataTable <DataTable
onRowDeleted={onDelete} onRowDeleted={onDelete}

View File

@@ -213,13 +213,13 @@ const StorageUnitsComponent = (props: CaseBuilderSectionProps) => {
return ( return (
<div> <div>
<SectionHeader title="Storage units"> <SectionHeader title="Storage units">
<SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} /> <SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} />
<SectionButton <SectionButton
icon={faDownload} icon={faDownload}
tooltip="Download" tooltip="Download"
onClick={onDownload} onClick={onDownload}
/> />
<SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} /> <SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} />
</SectionHeader> </SectionHeader>
<DataTable <DataTable
onRowDeleted={onDelete} onRowDeleted={onDelete}

View File

@@ -229,13 +229,13 @@ const ThermalUnitsComponent = (props: CaseBuilderSectionProps) => {
return ( return (
<div> <div>
<SectionHeader title="Thermal units"> <SectionHeader title="Thermal units">
<SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} /> <SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} />
<SectionButton <SectionButton
icon={faDownload} icon={faDownload}
tooltip="Download" tooltip="Download"
onClick={onDownload} onClick={onDownload}
/> />
<SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} /> <SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} />
</SectionHeader> </SectionHeader>
<DataTable <DataTable
onRowDeleted={onDelete} onRowDeleted={onDelete}

View File

@@ -181,13 +181,13 @@ const TransmissionLinesComponent = (props: CaseBuilderSectionProps) => {
return ( return (
<div> <div>
<SectionHeader title="Transmission lines"> <SectionHeader title="Transmission lines">
<SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} /> <SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} />
<SectionButton <SectionButton
icon={faDownload} icon={faDownload}
tooltip="Download" tooltip="Download"
onClick={onDownload} onClick={onDownload}
/> />
<SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} /> <SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} />
</SectionHeader> </SectionHeader>
<DataTable <DataTable
onRowDeleted={onDelete} onRowDeleted={onDelete}

View File

@@ -5,16 +5,33 @@
*/ */
.SiteHeaderButton { .SiteHeaderButton {
padding: 6px 24px; padding: 6px 16px;
margin: 0 0 0 8px; margin: 0 0 0 0;
line-height: 24px; line-height: 24px;
border: var(--box-border); border: var(--box-border);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
border-radius: var(--border-radius); border-radius: 0;
cursor: pointer; cursor: pointer;
text-transform: uppercase; text-transform: uppercase;
font-weight: bold; font-weight: bold;
font-size: 12px; font-size: 14px;
}
.SiteHeaderButton:not(:last-child) {
border-right: none;
}
.SiteHeaderButton:first-child {
border-radius: var(--border-radius) 0 0 var(--border-radius);
}
.SiteHeaderButton:last-child {
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.SiteHeaderButton:only-child {
border-radius: var(--border-radius);
border-right: var(--box-border);
} }
.light { .light {
@@ -42,3 +59,9 @@
.primary:active { .primary:active {
background: color-mix(in hsl, #000, var(--primary) 90%); background: color-mix(in hsl, #000, var(--primary) 90%);
} }
.disabled {
color: var(--contrast-20);
cursor: not-allowed;
pointer-events: none;
}

View File

@@ -5,24 +5,33 @@
*/ */
import styles from "./SiteHeaderButton.module.css"; import styles from "./SiteHeaderButton.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
function SiteHeaderButton({ function SiteHeaderButton({
title, title,
icon,
onClick, onClick,
variant = "light", variant = "light",
disabled = false,
}: { }: {
title: string; title: string;
icon: IconDefinition;
onClick?: () => void; onClick?: () => void;
variant?: "light" | "primary"; variant?: "light" | "primary";
disabled?: boolean;
}) { }) {
const variantClass = variant === "primary" ? styles.primary : styles.light; const variantClass = variant === "primary" ? styles.primary : styles.light;
const disabledClass = disabled ? styles.disabled : "";
return ( return (
<button <button
className={`${styles.SiteHeaderButton} ${variantClass}`} className={`${styles.SiteHeaderButton} ${variantClass} ${disabledClass}`}
title={title}
onClick={onClick} onClick={onClick}
disabled={disabled}
> >
{title} <FontAwesomeIcon icon={icon} />
</button> </button>
); );
} }