chore: add prettier with config and format all files

This commit is contained in:
Sebastian Seedorf
2026-06-04 11:44:20 +02:00
parent d212ae3f30
commit cf9bb33ecb
50 changed files with 1290 additions and 714 deletions

View File

@@ -9,7 +9,7 @@ type DraftChart = Omit<ChartConfig, 'id'>;
interface Props {
initial?: ChartConfig;
onSave: (draft: DraftChart) => void;
onSave: (draft: DraftChart) => void;
onClose: () => void;
}
@@ -21,32 +21,42 @@ const inputCls =
* to raw item_keys. Unknown tokens are kept as-is.
*/
function normalizeList(raw: string, reverseMap: Map<string, string>): string[] | null {
const arr = raw.split(',').map(x => x.trim()).filter(Boolean);
const arr = raw
.split(',')
.map((x) => x.trim())
.filter(Boolean);
if (arr.length === 0) return null;
return arr.map(t => resolveKey(t, reverseMap));
return arr.map((t) => resolveKey(t, reverseMap));
}
export default function ChartEditor({ initial, onSave, onClose }: Props) {
const { reverseMap } = useApp();
const [title, setTitle] = useState(initial?.title ?? '');
const [chartType, setChartType] = useState<ChartConfig['chart_type']>(initial?.chart_type ?? 'signals');
const [vizType, setVizType] = useState<ChartConfig['viz_type']>(initial?.viz_type ?? 'line');
const [signalType, setSignalType] = useState<ChartConfig['signal_type']>(initial?.signal_type ?? 'both');
const [title, setTitle] = useState(initial?.title ?? '');
const [chartType, setChartType] = useState<ChartConfig['chart_type']>(
initial?.chart_type ?? 'signals',
);
const [vizType, setVizType] = useState<ChartConfig['viz_type']>(initial?.viz_type ?? 'line');
const [signalType, setSignalType] = useState<ChartConfig['signal_type']>(
initial?.signal_type ?? 'both',
);
const [combinators, setCombinators] = useState((initial?.filter_combinators ?? []).join(', '));
const [whitelist, setWhitelist] = useState((initial?.filter_items ?? []).join(', '));
const [blacklist, setBlacklist] = useState((initial?.filter_items_exclude ?? []).join(', '));
const [useRegex, setUseRegex] = useState(initial?.filter_items_regex ?? false);
const [orderBy, setOrderBy] = useState<ChartConfig['order_by']>(initial?.order_by ?? 'value_asc');
const [whitelist, setWhitelist] = useState((initial?.filter_items ?? []).join(', '));
const [blacklist, setBlacklist] = useState((initial?.filter_items_exclude ?? []).join(', '));
const [useRegex, setUseRegex] = useState(initial?.filter_items_regex ?? false);
const [orderBy, setOrderBy] = useState<ChartConfig['order_by']>(initial?.order_by ?? 'value_asc');
const [seriesLimit, setSeriesLimit] = useState(initial?.series_limit ?? 20);
const [yMin, setYMin] = useState(initial?.y_min?.toString() ?? '');
const [yMax, setYMax] = useState(initial?.y_max?.toString() ?? '');
const [yScale, setYScale] = useState<ChartConfig['y_scale']>(initial?.y_scale ?? 'linear');
const [width, setWidth] = useState(initial?.width ?? 2);
const [height, setHeight] = useState(initial?.height ?? 4);
const [yMin, setYMin] = useState(initial?.y_min?.toString() ?? '');
const [yMax, setYMax] = useState(initial?.y_max?.toString() ?? '');
const [yScale, setYScale] = useState<ChartConfig['y_scale']>(initial?.y_scale ?? 'linear');
const [width, setWidth] = useState(initial?.width ?? 2);
const [height, setHeight] = useState(initial?.height ?? 4);
function splitCombinators(): string[] | null {
const arr = combinators.split(',').map(x => x.trim()).filter(Boolean);
const arr = combinators
.split(',')
.map((x) => x.trim())
.filter(Boolean);
return arr.length > 0 ? arr : null;
}
@@ -55,30 +65,35 @@ export default function ChartEditor({ initial, onSave, onClose }: Props) {
// Regex mode: store pattern exactly as typed — server expands via matchKeys at query time.
// Non-regex mode: resolve each comma-token (localized name or raw key) to raw key.
const filter_items = useRegex
? (whitelist.trim() ? [whitelist.trim()] : null)
const filter_items = useRegex
? whitelist.trim()
? [whitelist.trim()]
: null
: normalizeList(whitelist, reverseMap);
const filter_items_exclude = useRegex
? (blacklist.trim() ? [blacklist.trim()] : null)
? blacklist.trim()
? [blacklist.trim()]
: null
: normalizeList(blacklist, reverseMap);
onSave({
title: title.trim(),
chart_type: chartType,
viz_type: chartType === 'divider' ? 'line' : vizType,
signal_type: signalType,
pos_x: initial?.pos_x ?? 0,
pos_y: initial?.pos_y ?? 0,
width, height,
filter_combinators: chartType === 'divider' ? null : splitCombinators(),
filter_items: chartType === 'divider' ? null : filter_items,
title: title.trim(),
chart_type: chartType,
viz_type: chartType === 'divider' ? 'line' : vizType,
signal_type: signalType,
pos_x: initial?.pos_x ?? 0,
pos_y: initial?.pos_y ?? 0,
width,
height,
filter_combinators: chartType === 'divider' ? null : splitCombinators(),
filter_items: chartType === 'divider' ? null : filter_items,
filter_items_exclude: chartType === 'divider' ? null : filter_items_exclude,
filter_items_regex: useRegex,
order_by: orderBy,
series_limit: seriesLimit,
y_min: yMin !== '' ? parseFloat(yMin) : null,
y_max: yMax !== '' ? parseFloat(yMax) : null,
y_scale: yScale,
filter_items_regex: useRegex,
order_by: orderBy,
series_limit: seriesLimit,
y_min: yMin !== '' ? parseFloat(yMin) : null,
y_max: yMax !== '' ? parseFloat(yMax) : null,
y_scale: yScale,
});
}
@@ -86,115 +101,205 @@ export default function ChartEditor({ initial, onSave, onClose }: Props) {
const isDivider = chartType === 'divider';
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60" onClick={onClose}>
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
onClick={onClose}
>
<div
className="bg-gray-900 border border-gray-700 rounded-lg p-6 w-full max-w-md shadow-xl overflow-y-auto max-h-[90vh]"
onClick={e => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
<h2 className="text-lg font-semibold text-white mb-4">
{initial ? 'Edit Chart' : 'New Chart'}
</h2>
<label className="block text-sm text-gray-400 mb-1">Title</label>
<input value={title} onChange={e => setTitle(e.target.value)} className={`${inputCls} mb-3`} />
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
className={`${inputCls} mb-3`}
/>
<label className="block text-sm text-gray-400 mb-1">Chart Type</label>
<select value={chartType} onChange={e => setChartType(e.target.value as ChartConfig['chart_type'])} className={`${inputCls} mb-3`}>
<select
value={chartType}
onChange={(e) => setChartType(e.target.value as ChartConfig['chart_type'])}
className={`${inputCls} mb-3`}
>
<option value="signals">Signals</option>
<option value="ups">UPS / Game Tick Rate</option>
<option value="divider">Divider / Section Label</option>
</select>
{!isDivider && <>
<label className="block text-sm text-gray-400 mb-1">Visualization</label>
<select value={vizType} onChange={e => setVizType(e.target.value as ChartConfig['viz_type'])} className={`${inputCls} mb-3`}>
<option value="line">Line Chart</option>
<option value="table">Table</option>
</select>
</>}
{!isDivider && (
<>
<label className="block text-sm text-gray-400 mb-1">Visualization</label>
<select
value={vizType}
onChange={(e) => setVizType(e.target.value as ChartConfig['viz_type'])}
className={`${inputCls} mb-3`}
>
<option value="line">Line Chart</option>
<option value="table">Table</option>
</select>
</>
)}
{isSignals && <>
<label className="block text-sm text-gray-400 mb-1">Signal</label>
<select value={signalType} onChange={e => setSignalType(e.target.value as ChartConfig['signal_type'])} className={`${inputCls} mb-3`}>
<option value="both">Both (green + red)</option>
<option value="green">Green only</option>
<option value="red">Red only</option>
</select>
{isSignals && (
<>
<label className="block text-sm text-gray-400 mb-1">Signal</label>
<select
value={signalType}
onChange={(e) => setSignalType(e.target.value as ChartConfig['signal_type'])}
className={`${inputCls} mb-3`}
>
<option value="both">Both (green + red)</option>
<option value="green">Green only</option>
<option value="red">Red only</option>
</select>
<label className="block text-sm text-gray-400 mb-1">Sort series by</label>
<select value={orderBy} onChange={e => setOrderBy(e.target.value as ChartConfig['order_by'])} className={`${inputCls} mb-3`}>
<option value="value_asc">Latest lowest values</option>
<option value="value_desc">Latest highest values</option>
<option value="delta_asc">Biggest decrease (last 10 min)</option>
<option value="delta_desc">Biggest increase (last 10 min)</option>
</select>
<label className="block text-sm text-gray-400 mb-1">Sort series by</label>
<select
value={orderBy}
onChange={(e) => setOrderBy(e.target.value as ChartConfig['order_by'])}
className={`${inputCls} mb-3`}
>
<option value="value_asc">Latest lowest values</option>
<option value="value_desc">Latest highest values</option>
<option value="delta_asc">Biggest decrease (last 10 min)</option>
<option value="delta_desc">Biggest increase (last 10 min)</option>
</select>
<label className="block text-sm text-gray-400 mb-1">Max series (lines)</label>
<input type="number" min={1} max={200} value={seriesLimit}
onChange={e => setSeriesLimit(Number(e.target.value))}
className={`${inputCls} mb-3`} />
<label className="block text-sm text-gray-400 mb-1">Max series (lines)</label>
<input
type="number"
min={1}
max={200}
value={seriesLimit}
onChange={(e) => setSeriesLimit(Number(e.target.value))}
className={`${inputCls} mb-3`}
/>
<label className="block text-sm text-gray-400 mb-1">Combinators (comma-separated, empty = all)</label>
<input value={combinators} onChange={e => setCombinators(e.target.value)}
placeholder="nauvis, nauvis-orbit" className={`${inputCls} mb-3`} />
<div className="flex items-center gap-2 mb-2">
<label className="text-sm text-gray-400 flex-1">Item filters</label>
<label className="flex items-center gap-1.5 text-xs text-gray-400 cursor-pointer">
<input type="checkbox" checked={useRegex} onChange={e => setUseRegex(e.target.checked)}
className="accent-indigo-500" />
Use regex
<label className="block text-sm text-gray-400 mb-1">
Combinators (comma-separated, empty = all)
</label>
</div>
<input value={whitelist} onChange={e => setWhitelist(e.target.value)}
placeholder={useRegex ? 'wissen.*|Iron Plate' : 'Iron Plate, copper-plate'}
className={`${inputCls} mb-1`} />
<p className="text-xs text-gray-500 mb-2">Whitelist localized names or item keys accepted (empty = all)</p>
<input value={blacklist} onChange={e => setBlacklist(e.target.value)}
placeholder={useRegex ? 'Holz|stone' : 'Wood, stone'}
className={`${inputCls} mb-1`} />
<p className="text-xs text-gray-500 mb-3">Blacklist localized names or item keys accepted</p>
</>}
<input
value={combinators}
onChange={(e) => setCombinators(e.target.value)}
placeholder="nauvis, nauvis-orbit"
className={`${inputCls} mb-3`}
/>
{!isDivider && <>
<div className="flex gap-3 mb-3">
<div className="flex-1">
<label className="block text-sm text-gray-400 mb-1">Y Min (empty = auto)</label>
<input type="number" value={yMin} onChange={e => setYMin(e.target.value)}
placeholder="auto" className={inputCls} />
<div className="flex items-center gap-2 mb-2">
<label className="text-sm text-gray-400 flex-1">Item filters</label>
<label className="flex items-center gap-1.5 text-xs text-gray-400 cursor-pointer">
<input
type="checkbox"
checked={useRegex}
onChange={(e) => setUseRegex(e.target.checked)}
className="accent-indigo-500"
/>
Use regex
</label>
</div>
<div className="flex-1">
<label className="block text-sm text-gray-400 mb-1">Y Max (empty = auto)</label>
<input type="number" value={yMax} onChange={e => setYMax(e.target.value)}
placeholder="auto" className={inputCls} />
</div>
</div>
<input
value={whitelist}
onChange={(e) => setWhitelist(e.target.value)}
placeholder={useRegex ? 'wissen.*|Iron Plate' : 'Iron Plate, copper-plate'}
className={`${inputCls} mb-1`}
/>
<p className="text-xs text-gray-500 mb-2">
Whitelist localized names or item keys accepted (empty = all)
</p>
<input
value={blacklist}
onChange={(e) => setBlacklist(e.target.value)}
placeholder={useRegex ? 'Holz|stone' : 'Wood, stone'}
className={`${inputCls} mb-1`}
/>
<p className="text-xs text-gray-500 mb-3">
Blacklist localized names or item keys accepted
</p>
</>
)}
<label className="block text-sm text-gray-400 mb-1">Y Scale</label>
<select value={yScale} onChange={e => setYScale(e.target.value as ChartConfig['y_scale'])} className={`${inputCls} mb-3`}>
<option value="linear">Linear</option>
<option value="log">Symmetric Log (arcsinh)</option>
</select>
</>}
{!isDivider && (
<>
<div className="flex gap-3 mb-3">
<div className="flex-1">
<label className="block text-sm text-gray-400 mb-1">Y Min (empty = auto)</label>
<input
type="number"
value={yMin}
onChange={(e) => setYMin(e.target.value)}
placeholder="auto"
className={inputCls}
/>
</div>
<div className="flex-1">
<label className="block text-sm text-gray-400 mb-1">Y Max (empty = auto)</label>
<input
type="number"
value={yMax}
onChange={(e) => setYMax(e.target.value)}
placeholder="auto"
className={inputCls}
/>
</div>
</div>
<label className="block text-sm text-gray-400 mb-1">Y Scale</label>
<select
value={yScale}
onChange={(e) => setYScale(e.target.value as ChartConfig['y_scale'])}
className={`${inputCls} mb-3`}
>
<option value="linear">Linear</option>
<option value="log">Symmetric Log (arcsinh)</option>
</select>
</>
)}
<div className="flex gap-3 mb-4">
<div className="flex-1">
<label className="block text-sm text-gray-400 mb-1">Width (16 cols)</label>
<input type="number" min={1} max={6} value={width}
onChange={e => setWidth(Number(e.target.value))} className={inputCls} />
<input
type="number"
min={1}
max={6}
value={width}
onChange={(e) => setWidth(Number(e.target.value))}
className={inputCls}
/>
</div>
<div className="flex-1">
<label className="block text-sm text-gray-400 mb-1">Height (rows)</label>
<input type="number" min={2} max={20} value={height}
onChange={e => setHeight(Number(e.target.value))} className={inputCls} />
<input
type="number"
min={2}
max={20}
value={height}
onChange={(e) => setHeight(Number(e.target.value))}
className={inputCls}
/>
</div>
</div>
<div className="flex justify-end gap-2">
<button onClick={onClose} className="px-4 py-1.5 text-sm text-gray-400 hover:text-white rounded hover:bg-gray-700">Cancel</button>
<button onClick={handleSave} className="px-4 py-1.5 text-sm bg-indigo-600 hover:bg-indigo-500 text-white rounded">Save</button>
<button
onClick={onClose}
className="px-4 py-1.5 text-sm text-gray-400 hover:text-white rounded hover:bg-gray-700"
>
Cancel
</button>
<button
onClick={handleSave}
className="px-4 py-1.5 text-sm bg-indigo-600 hover:bg-indigo-500 text-white rounded"
>
Save
</button>
</div>
</div>
</div>
);
}
}