diff --git a/web/src/api.ts b/web/src/api.ts index 562d2c9..31108e8 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -33,6 +33,8 @@ export interface Account { skipped: number errors: number last_error?: string + folder_mapping?: Record + excluded_folders?: string[] } export interface TaskDetail { @@ -104,6 +106,20 @@ export const probeFolders = ( export const setFolderMapping = (taskId: number, mapping: Record) => api(`/api/tasks/${taskId}/folder-mapping`, { ...jsonBody({ mapping }), method: 'PUT' }) +export const probeAccountFolders = (taskId: number, accId: number) => + api(`/api/tasks/${taskId}/accounts/${accId}/probe`, { method: 'POST' }) + +export const setAccountFolderMapping = ( + taskId: number, + accId: number, + mapping: Record, + excluded: string[], +) => + api(`/api/tasks/${taskId}/accounts/${accId}/folder-mapping`, { + ...jsonBody({ mapping, excluded }), + method: 'PUT', + }) + export const listTasks = () => api('/api/tasks') export const getTask = (id: number) => api(`/api/tasks/${id}`) diff --git a/web/src/app.css b/web/src/app.css index 06bf824..25bac4e 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -368,11 +368,47 @@ .map-row { display: grid; - grid-template-columns: 1fr auto 1fr auto; + grid-template-columns: auto 1fr auto 1fr auto; align-items: center; gap: 10px; } +.map-row-off .map-src, +.map-row-off .map-arrow { + color: var(--fg-faint); + text-decoration: line-through; +} + +.map-check { + display: flex; + align-items: center; +} + +.map-check input, +.map-all input { + accent-color: var(--accent); + cursor: pointer; +} + +.map-all { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--fg-dim); + cursor: pointer; +} + +.map-excluded { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--fg-faint); +} + .map-src { font-family: var(--font-mono); font-size: 13px; diff --git a/web/src/components/FolderMappingModal.tsx b/web/src/components/FolderMappingModal.tsx index 317e24f..e8da172 100644 --- a/web/src/components/FolderMappingModal.tsx +++ b/web/src/components/FolderMappingModal.tsx @@ -6,7 +6,8 @@ type Props = { srcFolders: string[] dstFolders: string[] initialMapping: Record - onConfirm: (mapping: Record) => void + initialExcluded: string[] + onConfirm: (mapping: Record, excluded: string[]) => void onCancel: () => void } @@ -18,8 +19,14 @@ function defaultDst(src: string, dstFolders: string[], initial: Record>({}) + const [synced, setSynced] = useState>(() => { + const excl = new Set(initialExcluded) + return Object.fromEntries(srcFolders.map((f) => [f, !excl.has(f)])) + }) // Options per select: all destination folders, plus the source name itself // (marked "create") when it does not already exist on the destination. @@ -36,12 +43,18 @@ export function FolderMappingModal({ open, srcFolders, dstFolders, initialMappin function confirm() { const mapping: Record = { ...initialMapping } + const excluded: string[] = [] for (const src of srcFolders) { + if (synced[src] === false) { + excluded.push(src) + delete mapping[src] + continue + } const dst = valueFor(src) if (dst === src) delete mapping[src] else mapping[src] = dst } - onConfirm(mapping) + onConfirm(mapping, excluded) } return ( @@ -51,12 +64,32 @@ export function FolderMappingModal({ open, srcFolders, dstFolders, initialMappin Route each source folder to an existing destination folder. Leaving a folder mapped to its own name creates it on the destination if missing (e.g. map СпамSpam to avoid duplicates).

+
{srcFolders.map((src) => { + const on = synced[src] !== false const val = valueFor(src) const creates = !dstFolders.includes(val) return ( -
+
+ {src} @@ -65,6 +98,7 @@ export function FolderMappingModal({ open, srcFolders, dstFolders, initialMappin className="map-select" aria-label={`Destination folder for ${src}`} value={val} + disabled={!on} onChange={(e) => setChoice((c) => ({ ...c, [src]: e.target.value }))} > {options(src).map((f) => ( @@ -74,7 +108,11 @@ export function FolderMappingModal({ open, srcFolders, dstFolders, initialMappin ))} - {creates && new} + {on ? ( + creates && new + ) : ( + excluded + )}
) })}