import { useMemo, useState } from "react"; import { createPortal } from "react-dom"; import Button from "../../../components/Button"; import ToggleSwitch from "../../../components/ToggleSwitch"; import { getBool, getText } from "../utils/draftEditor"; import { createAttachmentRule, mockAttachmentFiles, summarizeAttachmentRules, type AttachmentBasePath, type AttachmentRule } from "../utils/attachments"; export type { AttachmentBasePath, AttachmentRule } from "../utils/attachments"; type AttachmentRulesOverlayProps = { title: string; rules: AttachmentRule[]; disabled?: boolean; buttonLabel?: string; emptyText?: string; basePaths?: AttachmentBasePath[]; onChange: (rules: AttachmentRule[]) => void; }; type AttachmentRulesTableProps = { rules: AttachmentRule[]; disabled?: boolean; emptyText?: string; basePaths?: AttachmentBasePath[]; showAddButton?: boolean; activeChooserRuleIndex?: number | null; onOpenFileChooser?: (ruleIndex: number) => void; onChange: (rules: AttachmentRule[]) => void; }; type FileChooserState = { ruleIndex: number; basePath: string; }; export default function AttachmentRulesOverlay({ title, rules, disabled = false, buttonLabel, emptyText = "No attachment files or matching rules configured yet.", basePaths = [], onChange }: AttachmentRulesOverlayProps) { const [open, setOpen] = useState(false); const [fileChooser, setFileChooser] = useState(null); const summary = useMemo(() => summarizeAttachmentRules(rules), [rules]); const label = buttonLabel ?? `direct: ${summary.direct} / rules: ${summary.rules}`; function patchRule(index: number, patch: Partial) { onChange(rules.map((rule, currentIndex) => currentIndex === index ? { ...rule, ...patch } : rule)); } function openFileChooser(ruleIndex: number) { const rule = rules[ruleIndex] ?? {}; setFileChooser({ ruleIndex, basePath: getText(rule, "base_dir", basePaths[0]?.path ?? "") }); } function selectFileFilter(fileFilter: string) { if (!fileChooser) return; patchRule(fileChooser.ruleIndex, { file_filter: fileFilter }); setFileChooser(null); } function closeOverlay() { setFileChooser(null); setOpen(false); } const activeRule = fileChooser ? rules[fileChooser.ruleIndex] : undefined; const activeBasePath = fileChooser ? getText(activeRule ?? {}, "base_dir", fileChooser.basePath || basePaths[0]?.path || "") : ""; const dialog = open ? createPortal(

{fileChooser ? "Choose file or pattern" : title}

{fileChooser ? ( setFileChooser(null)} /> ) : ( <>

Use direct files for fixed attachments and rules/patterns for files resolved during build.

)}
{fileChooser ? ( ) : ( )}
, document.body ) : null; return ( <> {dialog} ); } export function AttachmentRulesTable({ rules, disabled = false, emptyText = "No attachment files or matching rules configured yet.", basePaths = [], showAddButton = true, activeChooserRuleIndex = null, onOpenFileChooser, onChange }: AttachmentRulesTableProps) { const [fileChooser, setFileChooser] = useState(null); function patchRule(index: number, patch: Partial) { onChange(rules.map((rule, currentIndex) => currentIndex === index ? { ...rule, ...patch } : rule)); } function addRule() { onChange([...rules, createAttachmentRule(basePaths[0]?.path ?? "")]); } function removeRule(index: number) { setFileChooser(null); onChange(rules.filter((_, currentIndex) => currentIndex !== index)); } function openFileChooser(ruleIndex: number) { if (onOpenFileChooser) { onOpenFileChooser(ruleIndex); return; } const rule = rules[ruleIndex] ?? {}; setFileChooser({ ruleIndex, basePath: getText(rule, "base_dir", basePaths[0]?.path ?? "") }); } function selectFileFilter(fileFilter: string) { if (!fileChooser) return; patchRule(fileChooser.ruleIndex, { file_filter: fileFilter }); setFileChooser(null); } const activeRule = fileChooser ? rules[fileChooser.ruleIndex] : undefined; const activeBasePath = fileChooser ? getText(activeRule ?? {}, "base_dir", fileChooser.basePath || basePaths[0]?.path || "") : ""; return (
{rules.length === 0 ? (

{emptyText}

) : (
{rules.map((rule, index) => { const currentBasePath = getText(rule, "base_dir", basePaths[0]?.path ?? ""); const isChoosingFile = (activeChooserRuleIndex ?? fileChooser?.ruleIndex) === index; return ( ); })}
Label Base path File / pattern Required Subdirs
patchRule(index, { label: event.target.value })} /> {basePaths.length > 0 ? ( ) : ( )}
!disabled && openFileChooser(index)} onKeyDown={(event) => { if (!disabled && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); openFileChooser(index); } }} />
patchRule(index, { required: checked })} /> patchRule(index, { include_subdirs: checked })} />
)} {showAddButton && (
)}
{fileChooser && ( setFileChooser(null)} /> )}
); } function MockFileChooserOverlay({ basePath, onSelect, onClose }: { basePath: string; onSelect: (fileFilter: string) => void; onClose: () => void }) { return createPortal(

Choose file or pattern

, document.body ); } function MockFileChooserContent({ basePath, onSelect, onClose }: { basePath: string; onSelect: (fileFilter: string) => void; onClose: () => void }) { return (

Mock browser below {basePath || "."}. Later this will browse uploaded files and directories.

{mockAttachmentFiles.map((file) => ( ))}
); }