diff --git a/lib/rclone/api.ts b/lib/rclone/api.ts index 252ed8b..0b95703 100644 --- a/lib/rclone/api.ts +++ b/lib/rclone/api.ts @@ -605,11 +605,13 @@ export async function startSync({ dstFs, _config, _filter, + remoteOptions, }: { srcFs: string dstFs: string _config?: Record _filter?: Record + remoteOptions?: Record }) { console.log('[startSync]', srcFs, dstFs) @@ -627,6 +629,12 @@ export async function startSync({ params.set('_filter', JSON.stringify(parseRcloneOptions(_filter))) } + if (remoteOptions && Object.keys(remoteOptions).length > 0) { + for (const [key, value] of Object.entries(remoteOptions)) { + params.set(key, value.toString()) + } + } + const r = await fetch(`http://localhost:5572/sync/sync?${params.toString()}`, { method: 'POST', headers: getAuthHeader(), diff --git a/src/pages/Sync.tsx b/src/pages/Sync.tsx index fc26d75..f9b4434 100644 --- a/src/pages/Sync.tsx +++ b/src/pages/Sync.tsx @@ -1,17 +1,20 @@ -import { Accordion, AccordionItem, Avatar, Button } from '@heroui/react' +import { useAutoAnimate } from '@formkit/auto-animate/react' +import { Accordion, AccordionItem, Avatar, Button, Divider, cn } from '@heroui/react' import { message } from '@tauri-apps/plugin-dialog' import { exists } from '@tauri-apps/plugin-fs' import cronstrue from 'cronstrue' import { AlertOctagonIcon, + ChevronDownIcon, ClockIcon, + CopyIcon, FilterIcon, FolderSyncIcon, FoldersIcon, PlayIcon, WrenchIcon, } from 'lucide-react' -import { useEffect, useState } from 'react' +import { startTransition, useEffect, useState } from 'react' import { useSearchParams } from 'react-router-dom' import { getRemoteName } from '../../lib/format' import { isRemotePath } from '../../lib/fs' @@ -28,6 +31,7 @@ import { openWindow } from '../../lib/window' import CronEditor from '../components/CronEditor' import OptionsSection from '../components/OptionsSection' import { PathFinder } from '../components/PathFinder' +import RemoteOptionsSection from '../components/RemoteOptionsSection' export default function Sync() { const [searchParams] = useSearchParams() @@ -39,7 +43,7 @@ export default function Sync() { const [isStarted, setIsStarted] = useState(false) const [isLoading, setIsLoading] = useState(false) - const [jsonError, setJsonError] = useState<'sync' | 'filter' | 'config' | null>(null) + const [jsonError, setJsonError] = useState<'sync' | 'filter' | 'config' | 'remote' | null>(null) const [syncOptionsLocked, setSyncOptionsLocked] = useState(false) const [syncOptions, setSyncOptions] = useState>({}) @@ -53,10 +57,21 @@ export default function Sync() { const [configOptions, setConfigOptions] = useState>({}) const [configOptionsJson, setConfigOptionsJson] = useState('{}') + const [remoteOptionsLocked, setRemoteOptionsLocked] = useState(false) + const [remoteOptions, setRemoteOptions] = useState>({}) + const [remoteOptionsJson, setRemoteOptionsJson] = useState('{}') + const [cronExpression, setCronExpression] = useState(null) const [currentGlobalOptions, setCurrentGlobalOptions] = useState([]) + const [showMore, setShowMore] = useState(false) + const [animationParent] = useAutoAnimate() + + const selectedRemotes = (() => { + return [source, dest].filter(Boolean) as string[] + })() + const buttonText = (() => { if (isLoading) return 'STARTING...' if (!source) return 'Please select a source path' @@ -154,6 +169,7 @@ export default function Sync() { dstFs: dest, _config: mergedConfig, _filter: filterOptions, + remoteOptions, }) // dummy delay to avoid waiting when opening the Jobs page @@ -181,7 +197,9 @@ export default function Sync() { }, []) useEffect(() => { - getCurrentGlobalFlags().then((flags) => setCurrentGlobalOptions(flags)) + getCurrentGlobalFlags().then((flags) => + startTransition(() => setCurrentGlobalOptions(flags)) + ) }, []) // biome-ignore lint/correctness/useExhaustiveDependencies: when unlocking, we don't want to re-run the effect @@ -241,7 +259,7 @@ export default function Sync() { }, [source, dest]) useEffect(() => { - let step: 'sync' | 'filter' | 'config' = 'sync' + let step: 'sync' | 'filter' | 'config' | 'remote' = 'sync' try { setSyncOptions(JSON.parse(syncOptionsJson)) @@ -251,12 +269,15 @@ export default function Sync() { step = 'config' setConfigOptions(JSON.parse(configOptionsJson)) + step = 'remote' + setRemoteOptions(JSON.parse(remoteOptionsJson)) + setJsonError(null) } catch (error) { setJsonError(step) console.error(`Error parsing ${step} options:`, error) } - }, [syncOptionsJson, filterOptionsJson, configOptionsJson]) + }, [syncOptionsJson, filterOptionsJson, configOptionsJson, remoteOptionsJson]) return (
@@ -289,82 +310,147 @@ export default function Sync() { }} /> - - } /> - } - indicator={} - subtitle="Tap to toggle sync options for this operation" - title="Sync" - > - + + } /> } - getAvailableOptions={getSyncFlags} - rows={20} - isLocked={syncOptionsLocked} - setIsLocked={setSyncOptionsLocked} - /> - - } /> - } - indicator={} - subtitle="Tap to toggle filtering options for this operation" - title="Filters" - > - } + subtitle="Tap to toggle sync options for this operation" + title="Sync" + > + + + } /> } - optionsJson={filterOptionsJson} - setOptionsJson={setFilterOptionsJson} - getAvailableOptions={getFilterFlags} - rows={4} - isLocked={filterOptionsLocked} - setIsLocked={setFilterOptionsLocked} - /> - - } /> - } - indicator={} - subtitle="Tap to toggle config options for this operation" - title="Config" - > - } + subtitle="Tap to toggle filtering options for this operation" + title="Filters" + > + + + } /> } - optionsJson={configOptionsJson} - setOptionsJson={setConfigOptionsJson} - getAvailableOptions={getConfigFlags} - rows={10} - isLocked={configOptionsLocked} - setIsLocked={setConfigOptionsLocked} - /> - - } /> - } - indicator={} - subtitle="Tap to toggle cron options for this operation" - title="Cron" - > - - - + indicator={} + subtitle="Tap to toggle cron options for this operation" + title="Cron" + > + + + + + {showMore ? ( + + ) : ( +
{ + startTransition(() => { + setShowMore(true) + }) + requestAnimationFrame(() => { + setTimeout(() => { + scrollTo({ + top: document.body.scrollHeight, + behavior: 'smooth', + }) + }, 400) + }) + }} + > +

+ Show more options +

+ +
+ )} + + {showMore && ( + // @ts-expect-error + + } /> + } + indicator={} + subtitle="Tap to toggle config options for this operation" + title="Config" + > + + + + {selectedRemotes.length > 0 && ( + } + /> + } + indicator={} + subtitle="Tap to toggle remote options for this operation" + title={'Remotes'} + > + + + )} + + )} +