Files
nomina/ui/src/components/pipeline/configs/ExtensionConfig.tsx
lashman 6f5b862234 pipeline cards, context menus, presets, settings overhaul
rewrote pipeline as draggable card strip with per-rule config popovers,
added right-click menus to pipeline cards, sidebar tree, and file list,
preset import/export with BRU format support, new rules (hash, swap,
truncate, sanitize, padding, randomize, text editor, folder name,
transliterate), settings dialog with all sections, overlay collision
containment, tooltips on icon buttons, empty pipeline default
2026-03-14 19:04:35 +02:00

99 lines
3.8 KiB
TypeScript

import { useRuleStore } from "@/stores/ruleStore";
import { Input } from "@/components/ui/input";
import { SegmentedControl } from "@/components/ui/segmented-control";
import { Checkbox } from "@/components/ui/checkbox";
import { IconPlus, IconTrash } from "@tabler/icons-react";
import type { ExtensionConfig as ExtensionConfigType } from "@/types/rules";
const extModes = [
{ value: "Same", label: "Same" },
{ value: "Lower", label: "lower" },
{ value: "Upper", label: "UPPER" },
{ value: "Title", label: "Title" },
{ value: "Extra", label: "Extra" },
{ value: "Remove", label: "Remove" },
{ value: "Fixed", label: "Fixed" },
] as const;
export function ExtensionConfig({ ruleId }: { ruleId: string }) {
const rule = useRuleStore((s) => s.pipeline.find((r) => r.id === ruleId))?.config as ExtensionConfigType | undefined;
const updateRule = useRuleStore((s) => s.updateRule);
if (!rule) return null;
const update = (changes: Partial<ExtensionConfigType>) => updateRule(ruleId, changes);
const mappings = rule.mapping || [];
const addMapping = () => {
update({ mapping: [...mappings, ["", ""]] });
};
const removeMapping = (idx: number) => {
const next = mappings.filter((_, i) => i !== idx);
update({ mapping: next.length > 0 ? next : null });
};
const updateMapping = (idx: number, pos: 0 | 1, val: string) => {
const next = mappings.map((m, i) => {
if (i !== idx) return m;
const copy: [string, string] = [...m];
copy[pos] = val;
return copy;
});
update({ mapping: next });
};
return (
<div className="flex flex-col gap-3 text-sm">
<div className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">Mode</span>
<SegmentedControl value={rule.mode} onChange={(m) => update({ mode: m })} options={extModes} size="sm" />
</div>
{rule.mode === "Fixed" && (
<label className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">New extension</span>
<Input
value={rule.fixed_value}
onChange={(e) => update({ fixed_value: e.target.value })}
placeholder="e.g. txt, jpg, png"
className="h-8 text-xs font-mono"
/>
</label>
)}
{mappings.length > 0 && (
<div className="flex flex-col gap-1.5">
<span className="text-xs text-muted-foreground">Extension mappings</span>
{mappings.map((m, idx) => (
<div key={idx} className="flex gap-2 items-center">
<Input
value={m[0]}
onChange={(e) => updateMapping(idx, 0, e.target.value)}
placeholder="From..."
className="h-7 text-xs font-mono flex-1"
/>
<span className="text-xs text-muted-foreground">{"->"}</span>
<Input
value={m[1]}
onChange={(e) => updateMapping(idx, 1, e.target.value)}
placeholder="To..."
className="h-7 text-xs font-mono flex-1"
/>
<button onClick={() => removeMapping(idx)} className="text-muted-foreground hover:text-destructive p-0.5">
<IconTrash size={14} />
</button>
</div>
))}
</div>
)}
<div className="flex items-center gap-3">
<label className="flex items-center gap-1.5 cursor-pointer text-xs">
<Checkbox checked={rule.multi_extension} onCheckedChange={(c) => update({ multi_extension: !!c })} />
Multi-extension (e.g. .tar.gz)
</label>
<button onClick={addMapping} className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground ml-auto">
<IconPlus size={14} /> Mapping
</button>
</div>
</div>
);
}