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
+ )}
)
})}