172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
|
|
import {
|
||
|
|
formatBandFreqDisplay,
|
||
|
|
formatBandGainDisplay,
|
||
|
|
formatBandQDisplay,
|
||
|
|
eqInterp,
|
||
|
|
} from "./utils";
|
||
|
|
import type { PeqEqUi } from "./constants";
|
||
|
|
import type { PeqUndoBand, PeqUndoSnapshot } from "./hooks/usePeqUndoHistory";
|
||
|
|
|
||
|
|
export type PeqUndoDiffItem = {
|
||
|
|
/** Display label already localized where possible */
|
||
|
|
text: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
function formatAuto(on: boolean, eqUi: PeqEqUi): string {
|
||
|
|
return on ? (eqUi.undoAutoOn ?? "ON") : (eqUi.undoAutoOff ?? "OFF");
|
||
|
|
}
|
||
|
|
|
||
|
|
function bandParamLabel(
|
||
|
|
param: "freq" | "gain" | "q" | "type",
|
||
|
|
eqUi: PeqEqUi,
|
||
|
|
): string {
|
||
|
|
switch (param) {
|
||
|
|
case "freq":
|
||
|
|
return eqUi.undoParamFreq ?? "FREQ";
|
||
|
|
case "gain":
|
||
|
|
return eqUi.undoParamGain ?? "GAIN";
|
||
|
|
case "q":
|
||
|
|
return eqUi.undoParamQ ?? "Q";
|
||
|
|
case "type":
|
||
|
|
return eqUi.undoParamType ?? "Filter";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatBandValue(
|
||
|
|
param: "freq" | "gain" | "q" | "type",
|
||
|
|
band: PeqUndoBand,
|
||
|
|
): string {
|
||
|
|
switch (param) {
|
||
|
|
case "freq":
|
||
|
|
return formatBandFreqDisplay(band.freq);
|
||
|
|
case "gain":
|
||
|
|
return formatBandGainDisplay(band.gain);
|
||
|
|
case "q":
|
||
|
|
return formatBandQDisplay(band.q);
|
||
|
|
case "type":
|
||
|
|
return String(band.type);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function diffBands(
|
||
|
|
fromBands: PeqUndoBand[],
|
||
|
|
toBands: PeqUndoBand[],
|
||
|
|
mode: "A" | "B",
|
||
|
|
eqUi: PeqEqUi,
|
||
|
|
): PeqUndoDiffItem[] {
|
||
|
|
const items: PeqUndoDiffItem[] = [];
|
||
|
|
const len = Math.max(fromBands.length, toBands.length);
|
||
|
|
for (let i = 0; i < len; i++) {
|
||
|
|
const from = fromBands[i];
|
||
|
|
const to = toBands[i];
|
||
|
|
if (!from || !to) continue;
|
||
|
|
const params = ["freq", "gain", "q", "type"] as const;
|
||
|
|
for (const param of params) {
|
||
|
|
const fromVal = formatBandValue(param, from);
|
||
|
|
const toVal = formatBandValue(param, to);
|
||
|
|
if (fromVal === toVal) continue;
|
||
|
|
const text =
|
||
|
|
eqInterp(eqUi.undoBandParam, {
|
||
|
|
mode,
|
||
|
|
band: i + 1,
|
||
|
|
param: bandParamLabel(param, eqUi),
|
||
|
|
from: fromVal,
|
||
|
|
to: toVal,
|
||
|
|
}) ||
|
||
|
|
`${mode} Band ${i + 1} ${bandParamLabel(param, eqUi)} ${fromVal} → ${toVal}`;
|
||
|
|
items.push({ text });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return items;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Diff current state (before clicking Undo) vs restored snapshot (after Undo).
|
||
|
|
* `from` is the value user just undid away from; `to` is the restored value.
|
||
|
|
*/
|
||
|
|
export function diffPeqUndoSnapshots(
|
||
|
|
current: PeqUndoSnapshot,
|
||
|
|
restored: PeqUndoSnapshot,
|
||
|
|
eqUi: PeqEqUi,
|
||
|
|
): PeqUndoDiffItem[] {
|
||
|
|
const items: PeqUndoDiffItem[] = [];
|
||
|
|
|
||
|
|
if (current.abMode !== restored.abMode) {
|
||
|
|
items.push({
|
||
|
|
text:
|
||
|
|
eqInterp(eqUi.undoSimpleParam, {
|
||
|
|
param: eqUi.undoParamAbMode ?? "A/B",
|
||
|
|
from: current.abMode,
|
||
|
|
to: restored.abMode,
|
||
|
|
}) ||
|
||
|
|
`A/B ${current.abMode} → ${restored.abMode}`,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (current.preamp !== restored.preamp) {
|
||
|
|
items.push({
|
||
|
|
text:
|
||
|
|
eqInterp(eqUi.undoSimpleParam, {
|
||
|
|
param: eqUi.undoParamPreamp ?? "Preamp",
|
||
|
|
from: formatBandGainDisplay(current.preamp),
|
||
|
|
to: formatBandGainDisplay(restored.preamp),
|
||
|
|
}) ||
|
||
|
|
`Preamp ${formatBandGainDisplay(current.preamp)} → ${formatBandGainDisplay(restored.preamp)}`,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (current.autoPre !== restored.autoPre) {
|
||
|
|
items.push({
|
||
|
|
text:
|
||
|
|
eqInterp(eqUi.undoSimpleParam, {
|
||
|
|
param: eqUi.undoParamAuto ?? "AUTO",
|
||
|
|
from: formatAuto(current.autoPre === 1, eqUi),
|
||
|
|
to: formatAuto(restored.autoPre === 1, eqUi),
|
||
|
|
}) ||
|
||
|
|
`AUTO ${formatAuto(current.autoPre === 1, eqUi)} → ${formatAuto(restored.autoPre === 1, eqUi)}`,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
items.push(
|
||
|
|
...diffBands(current.bandsByMode.A, restored.bandsByMode.A, "A", eqUi),
|
||
|
|
);
|
||
|
|
items.push(
|
||
|
|
...diffBands(current.bandsByMode.B, restored.bandsByMode.B, "B", eqUi),
|
||
|
|
);
|
||
|
|
|
||
|
|
return items;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Build toast body for an undo action. */
|
||
|
|
export function formatPeqUndoToast(
|
||
|
|
current: PeqUndoSnapshot,
|
||
|
|
restored: PeqUndoSnapshot,
|
||
|
|
eqUi: PeqEqUi,
|
||
|
|
): string {
|
||
|
|
const diffs = diffPeqUndoSnapshots(current, restored, eqUi);
|
||
|
|
if (diffs.length === 0) {
|
||
|
|
return eqUi.toastUndoEmpty ?? eqUi.undo ?? "Undo";
|
||
|
|
}
|
||
|
|
if (diffs.length === 1) {
|
||
|
|
return (
|
||
|
|
eqInterp(eqUi.toastUndo, { detail: diffs[0]!.text }) ||
|
||
|
|
`Undone: ${diffs[0]!.text}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
const summary = diffs
|
||
|
|
.slice(0, 3)
|
||
|
|
.map(d => d.text)
|
||
|
|
.join(eqUi.undoDetailSep ?? "; ");
|
||
|
|
const extra =
|
||
|
|
diffs.length > 3
|
||
|
|
? eqInterp(eqUi.undoMoreSuffix, { count: diffs.length - 3 }) ||
|
||
|
|
` (+${diffs.length - 3})`
|
||
|
|
: "";
|
||
|
|
const detail =
|
||
|
|
eqInterp(eqUi.undoMultiple, {
|
||
|
|
count: diffs.length,
|
||
|
|
summary: `${summary}${extra}`,
|
||
|
|
}) || `${diffs.length} changes: ${summary}${extra}`;
|
||
|
|
return eqInterp(eqUi.toastUndo, { detail }) || `Undone: ${detail}`;
|
||
|
|
}
|