diff --git a/lib/flags.ts b/lib/flags.ts index 6e40166..b5aa3b5 100644 --- a/lib/flags.ts +++ b/lib/flags.ts @@ -110,7 +110,7 @@ export function groupByCategory( const category = getFlagCategory(k, allFlags) if (!category) continue if (category.category.startsWith('serve.')) { - const serveType = category.category.slice(5) as (typeof SERVE_TYPES)[number] + const serveType = category.category.slice(6) as (typeof SERVE_TYPES)[number] collectedFlags.serve[serveType] = { ...collectedFlags.serve[serveType], [k]: v, diff --git a/lib/format.ts b/lib/format.ts index f45df0a..83605ba 100644 --- a/lib/format.ts +++ b/lib/format.ts @@ -6,6 +6,8 @@ const RE_LOCAL_WINDOWS_PATH = /^:local:([a-zA-Z]:\/?.*)$/ const RE_PATH_SEPARATOR = /[/\\]/ export function formatBytes(bytes: number) { + if (!Number.isFinite(bytes)) return '0 B' + if (bytes < 1024) { return `${Math.round(bytes)} B` } diff --git a/lib/hooks.ts b/lib/hooks.ts index 7fb3097..694cdac 100644 --- a/lib/hooks.ts +++ b/lib/hooks.ts @@ -50,18 +50,16 @@ export function useFlags() { const serveFlags = SERVE_TYPES.reduce( (acc, type) => { - acc[type] = - allFlags?.[type] || - [] - .map((flag: any) => ({ - ...flag, - FieldName: flag.Name, - DefaultStr: - flag.Name === 'addr' - ? flag.DefaultStr.replace('[', '').replace(']', '') - : flag.DefaultStr, - })) - .sort((a: any, b: any) => a.Name.localeCompare(b.Name)) + acc[type] = (allFlags?.[type] || []) + .map((flag: any) => ({ + ...flag, + FieldName: flag.Name, + DefaultStr: + flag.Name === 'addr' + ? flag.DefaultStr.replace('[', '').replace(']', '') + : flag.DefaultStr, + })) + .sort((a: any, b: any) => a.Name.localeCompare(b.Name)) return acc }, {} as Record<(typeof SERVE_TYPES)[number], any[]> diff --git a/lib/rclone/api.ts b/lib/rclone/api.ts index 782497c..609bfd4 100644 --- a/lib/rclone/api.ts +++ b/lib/rclone/api.ts @@ -892,7 +892,7 @@ export async function startBisync({ { retries: 3, } - ).catch(null) + ).catch(() => null) console.log('jobStatus', JSON.stringify(jobStatus, null, 2)) @@ -988,7 +988,7 @@ export async function startSync({ { retries: 3, } - ).catch(null) + ).catch(() => null) console.log('jobStatus', JSON.stringify(jobStatus, null, 2)) @@ -1229,7 +1229,7 @@ export async function startBatch(inputs: ({ _path: string } & Record null) console.log('[startBatch] jobStatus', { jobid: r.jobid, diff --git a/lib/rclone/common.ts b/lib/rclone/common.ts index 6f1e97d..b8d4e56 100644 --- a/lib/rclone/common.ts +++ b/lib/rclone/common.ts @@ -231,15 +231,13 @@ export function parseRcloneVersion(output: string) { } export function shouldUpdateRclone(versionData: { yours: string; latest: string } | null) { - if (!versionData?.yours) return false - if (!versionData) { console.warn('[shouldUpdateRclone] received no version data:', versionData) return false } - const currentVersion = versionData.yours - const latestVersion = versionData.latest + const currentVersion = versionData?.yours + const latestVersion = versionData?.latest if (!currentVersion || !latestVersion) { console.warn('[shouldUpdateRclone] could not parse version output:', versionData) @@ -249,8 +247,8 @@ export function shouldUpdateRclone(versionData: { yours: string; latest: string console.log('[shouldUpdateRclone] current version:', currentVersion) console.log('[shouldUpdateRclone] latest version:', latestVersion) - if (useHostStore.getState().lastSkippedVersion === currentVersion) { - console.log('[shouldUpdateRclone] current version is in the lastSkippedVersion') + if (useHostStore.getState().lastSkippedVersion === latestVersion) { + console.log('[shouldUpdateRclone] latest version is in the lastSkippedVersion') return false } diff --git a/main.ts b/main.ts index 3870b4d..dcdec30 100644 --- a/main.ts +++ b/main.ts @@ -636,6 +636,11 @@ async function resumeTasks() { isEnabled: task.isEnabled, }) + if (!task.isEnabled) { + console.log('[resumeTasks] task', task.id, 'is disabled, skipping') + continue + } + if (task.isRunning) { console.log('[resumeTasks] task', task.id, 'was marked as running, resetting state') useHostStore.getState().updateScheduledTask(task.id, { @@ -866,8 +871,6 @@ async function handleTask(task: ScheduledTask) { console.error('[handleTask] task', task.id, 'failed with error:', err) console.error('[handleTask] task args were:', JSON.stringify(task.args, null, 2)) useHostStore.getState().updateScheduledTask(task.id, { - isRunning: false, - currentRunId: undefined, lastRunError: err instanceof Error ? err.message : 'Unknown error', }) } finally { diff --git a/src/components/CronEditor.tsx b/src/components/CronEditor.tsx index 627c67f..4797426 100644 --- a/src/components/CronEditor.tsx +++ b/src/components/CronEditor.tsx @@ -2,7 +2,7 @@ import { Button, Input, Select, SelectItem } from '@heroui/react' import cronstrue from 'cronstrue' import { ClockIcon, XIcon } from 'lucide-react' import type React from 'react' -import { startTransition, useCallback, useEffect, useMemo, useState } from 'react' +import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react' interface CronEditorProps { expression: string | null @@ -51,7 +51,12 @@ export default function CronEditor({ expression, onChange }: CronEditorProps) { [cronExpression] ) + const hasMounted = useRef(false) useEffect(() => { + if (!hasMounted.current) { + hasMounted.current = true + return + } onChange(cronExpression) }, [cronExpression, onChange]) diff --git a/src/components/PathFinder.tsx b/src/components/PathFinder.tsx index 0f1f22d..b1d628a 100644 --- a/src/components/PathFinder.tsx +++ b/src/components/PathFinder.tsx @@ -264,10 +264,11 @@ export function PathField({ if (!item) { return } - item.type === 'folder' && !item.path.endsWith('/') && !item.path.endsWith('\\') - ? `${item.path}${sep()}` - : item.path - setPath(item.path) + setPath( + item.type === 'folder' && !item.path.endsWith('/') && !item.path.endsWith('\\') + ? `${item.path}${sep()}` + : item.path + ) }} isOpen={isOpen} initialPaths={path ? [path] : []} diff --git a/src/components/RemoteCreateDrawer.tsx b/src/components/RemoteCreateDrawer.tsx index 4907e25..cc48f1a 100644 --- a/src/components/RemoteCreateDrawer.tsx +++ b/src/components/RemoteCreateDrawer.tsx @@ -271,10 +271,11 @@ export default function RemoteCreateDrawer({ data-focus-visible="false" onPress={() => { setTimeout(() => { + const { name, type, ...parameters } = config createRemoteMutation.mutate({ - name: config.name, - type: config.type, - parameters: config, + name, + type, + parameters, }) }, 10) }} diff --git a/src/components/TemplateAddDrawer.tsx b/src/components/TemplateAddDrawer.tsx index a7ba4eb..9b2ef50 100644 --- a/src/components/TemplateAddDrawer.tsx +++ b/src/components/TemplateAddDrawer.tsx @@ -186,7 +186,7 @@ export default function TemplateAddDrawer({ const parsedNumber = Number(v) if (!isNaN(parsedNumber)) v = parsedNumber - if (value.includes(',')) v = value.split(',') + if (value?.includes(',')) v = value.split(',') flagGroups[flag.trim()] = v } diff --git a/src/components/navigator/PreviewDrawer.tsx b/src/components/navigator/PreviewDrawer.tsx index 37372ae..0056ffe 100644 --- a/src/components/navigator/PreviewDrawer.tsx +++ b/src/components/navigator/PreviewDrawer.tsx @@ -78,11 +78,14 @@ export default function PreviewDrawer({ return } + const abortController = new AbortController() + setIsLoadingText(true) setTextError(null) fetch(previewUrl, { headers: auth ? { Authorization: `Basic ${auth}` } : undefined, + signal: abortController.signal, }) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) @@ -93,9 +96,12 @@ export default function PreviewDrawer({ setIsLoadingText(false) }) .catch((err: Error) => { + if (abortController.signal.aborted) return setTextError(err.message) setIsLoadingText(false) }) + + return () => abortController.abort() }, [item, fileType, previewUrl, auth, isTooLarge]) const handleDownload = useCallback(() => { diff --git a/src/pages/Bisync.tsx b/src/pages/Bisync.tsx index 01cc07a..5693d4c 100644 --- a/src/pages/Bisync.tsx +++ b/src/pages/Bisync.tsx @@ -468,15 +468,15 @@ export default function Bisync() { startTransition(() => { if (shouldMerge) { if (groupedOptions.copy) - setBisyncOptions({ ...bisyncOptions, ...groupedOptions.copy }) + setBisyncOptionsJsonString(JSON.stringify({ ...bisyncOptions, ...groupedOptions.copy }, null, 2)) if (groupedOptions.filter) - setFilterOptions({ ...filterOptions, ...groupedOptions.filter }) + setFilterOptionsJsonString(JSON.stringify({ ...filterOptions, ...groupedOptions.filter }, null, 2)) if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else { - if (groupedOptions.copy) setBisyncOptions(groupedOptions.copy) - if (groupedOptions.filter) setFilterOptions(groupedOptions.filter) - if (groupedOptions.config) setConfigOptions(groupedOptions.config) + if (groupedOptions.copy) setBisyncOptionsJsonString(JSON.stringify(groupedOptions.copy, null, 2)) + if (groupedOptions.filter) setFilterOptionsJsonString(JSON.stringify(groupedOptions.filter, null, 2)) + if (groupedOptions.config) setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) } }) }} diff --git a/src/pages/Commander.tsx b/src/pages/Commander.tsx index 1dc032f..7dec765 100644 --- a/src/pages/Commander.tsx +++ b/src/pages/Commander.tsx @@ -67,20 +67,6 @@ export default function Browser() { const [trackedJobIds, setTrackedJobIds] = useState>(new Set()) const handleJobStarted = useCallback((jobId: number) => { - // #region agent log - fetch('http://127.0.0.1:7250/ingest/2834749a-e80b-4040-ad02-08316d12b97e', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - location: 'Commander.tsx:handleJobStarted', - message: 'handleJobStarted called', - data: { jobId }, - timestamp: Date.now(), - sessionId: 'debug-session', - hypothesisId: 'G', - }), - }).catch(() => {}) - // #endregion setTrackedJobIds((prev) => new Set([...prev, jobId])) }, []) @@ -206,91 +192,85 @@ export default function Browser() { setContextMenu(null) }, []) - const handleDelete = useCallback( - async (entry: Entry) => { - const confirmed = await ask( - `Are you sure you want to delete "${entry.name}"?`, - { title: 'Confirm Delete', kind: 'warning' } - ) - if (!confirmed) return + const handleDelete = useCallback(async (entry: Entry) => { + const confirmed = await ask(`Are you sure you want to delete "${entry.name}"?`, { + title: 'Confirm Delete', + kind: 'warning', + }) + if (!confirmed) return - try { - const source = entry.fullPath + (entry.isDir ? '/' : '') - const info = getFsInfo(source) - const endpoint = entry.isDir ? '/operations/purge' : '/operations/deletefile' + try { + const source = entry.fullPath + (entry.isDir ? '/' : '') + const info = getFsInfo(source) + const endpoint = entry.isDir ? '/operations/purge' : '/operations/deletefile' - await rclone(endpoint as any, { + await rclone(endpoint as any, { + params: { + query: { + fs: info.root, + remote: info.filePath, + }, + }, + }) + + leftPanelRef.current?.refresh() + rightPanelRef.current?.refresh() + } catch (error) { + await message(error instanceof Error ? error.message : 'Delete failed', { + title: 'Error', + kind: 'error', + }) + } + }, []) + + const handleRename = useCallback(async (entry: Entry) => { + const newName = await invoke('prompt', { + title: 'Rename', + message: `Enter a new name for "${entry.name}"`, + default: entry.name, + sensitive: false, + }) + if (!newName || newName === entry.name) return + + try { + const info = getFsInfo(entry.fullPath) + const parentDir = info.filePath.includes('/') + ? info.filePath.slice(0, info.filePath.lastIndexOf('/') + 1) + : '' + const dstRemote = `${parentDir}${newName}` + + if (entry.isDir) { + await rclone('/sync/move' as any, { params: { query: { - fs: info.root, - remote: info.filePath, + srcFs: `${info.root}${info.filePath}/`, + dstFs: `${info.root}${dstRemote}/`, + deleteEmptySrcDirs: true, }, }, }) - - leftPanelRef.current?.refresh() - rightPanelRef.current?.refresh() - } catch (error) { - await message(error instanceof Error ? error.message : 'Delete failed', { - title: 'Error', - kind: 'error', + } else { + await rclone('/operations/movefile' as any, { + params: { + query: { + srcFs: info.root, + srcRemote: info.filePath, + dstFs: info.root, + dstRemote, + }, + }, }) } - }, - [] - ) - const handleRename = useCallback( - async (entry: Entry) => { - const newName = await invoke('prompt', { - title: 'Rename', - message: `Enter a new name for "${entry.name}"`, - default: entry.name, - sensitive: false, + leftPanelRef.current?.refresh() + rightPanelRef.current?.refresh() + } catch (error) { + await message(error instanceof Error ? error.message : 'Rename failed', { + title: 'Error', + kind: 'error', }) - if (!newName || newName === entry.name) return - - try { - const info = getFsInfo(entry.fullPath) - const parentDir = info.filePath.includes('/') - ? info.filePath.slice(0, info.filePath.lastIndexOf('/') + 1) - : '' - const dstRemote = `${parentDir}${newName}` - - if (entry.isDir) { - await rclone('/sync/move' as any, { - params: { - query: { - srcFs: `${info.root}${info.filePath}/`, - dstFs: `${info.root}${dstRemote}/`, - deleteEmptySrcDirs: true, - }, - }, - }) - } else { - await rclone('/operations/movefile' as any, { - params: { - query: { - srcFs: info.root, - srcRemote: info.filePath, - dstFs: info.root, - dstRemote, - }, - }, - }) - } - - leftPanelRef.current?.refresh() - rightPanelRef.current?.refresh() - } catch (error) { - await message(error instanceof Error ? error.message : 'Rename failed', { - title: 'Error', - kind: 'error', - }) - } - }, - [] - ) + } + }, []) const handleCreateFolder = useCallback( async (panelSide: 'left' | 'right') => { @@ -519,21 +499,6 @@ function TransfersBar({ trackedJobIds }: { trackedJobIds: Set }) { const [isExpanded, setIsExpanded] = useState(false) const jobIds = Array.from(trackedJobIds) - // #region agent log - fetch('http://127.0.0.1:7250/ingest/2834749a-e80b-4040-ad02-08316d12b97e', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - location: 'Commander.tsx:TransfersBar:render', - message: 'TransfersBar rendering', - data: { jobIdsCount: jobIds.length, jobIds }, - timestamp: Date.now(), - sessionId: 'debug-session', - hypothesisId: 'G,H', - }), - }).catch(() => {}) - // #endregion - const itemsQuery = useQuery({ queryKey: ['transfers', 'items', jobIds], queryFn: async () => { diff --git a/src/pages/Copy.tsx b/src/pages/Copy.tsx index 1ff369b..7a69ab1 100644 --- a/src/pages/Copy.tsx +++ b/src/pages/Copy.tsx @@ -266,7 +266,7 @@ export default function Copy() { if (startCopyMutation.isPending) return 'STARTING...' if (!sources || sources.length === 0) return 'Please select a source path' if (!dest) return 'Please select a destination path' - if (sources[0] === dest) return 'Source and destination cannot be the same' + if (sources.some((s) => s === dest)) return 'Source and destination cannot be the same' if (jsonError) return 'Invalid JSON for ' + jsonError.toUpperCase() + ' options' if (cronExpression) return 'START AND SCHEDULE COPY' return 'START COPY' @@ -274,7 +274,7 @@ export default function Copy() { const buttonIcon = useMemo(() => { if (startCopyMutation.isPending) return - if (!sources || sources.length === 0 || !dest || sources[0] === dest) + if (!sources || sources.length === 0 || !dest || sources.some((s) => s === dest)) return if (jsonError) return return @@ -411,15 +411,15 @@ export default function Copy() { startTransition(() => { if (shouldMerge) { if (groupedOptions.copy) - setCopyOptions({ ...copyOptions, ...groupedOptions.copy }) + setCopyOptionsJsonString(JSON.stringify({ ...copyOptions, ...groupedOptions.copy }, null, 2)) if (groupedOptions.filter) - setFilterOptions({ ...filterOptions, ...groupedOptions.filter }) + setFilterOptionsJsonString(JSON.stringify({ ...filterOptions, ...groupedOptions.filter }, null, 2)) if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else { - if (groupedOptions.copy) setCopyOptions(groupedOptions.copy) - if (groupedOptions.filter) setFilterOptions(groupedOptions.filter) - if (groupedOptions.config) setConfigOptions(groupedOptions.config) + if (groupedOptions.copy) setCopyOptionsJsonString(JSON.stringify(groupedOptions.copy, null, 2)) + if (groupedOptions.filter) setFilterOptionsJsonString(JSON.stringify(groupedOptions.filter, null, 2)) + if (groupedOptions.config) setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) } }) }} @@ -565,7 +565,7 @@ export default function Copy() { !sources || sources.length === 0 || !dest || - sources[0] === dest + sources.some((s) => s === dest) } isLoading={startCopyMutation.isPending} endContent={buttonIcon} @@ -597,7 +597,7 @@ export default function Copy() { !sources || sources.length === 0 || !dest || - sources[0] === dest + sources.some((s) => s === dest) ) { return } diff --git a/src/pages/Delete.tsx b/src/pages/Delete.tsx index 7d0fb80..c9689f8 100644 --- a/src/pages/Delete.tsx +++ b/src/pages/Delete.tsx @@ -343,12 +343,12 @@ export default function Delete() { startTransition(() => { if (shouldMerge) { if (groupedOptions.filter) - setFilterOptions({ ...filterOptions, ...groupedOptions.filter }) + setFilterOptionsJsonString(JSON.stringify({ ...filterOptions, ...groupedOptions.filter }, null, 2)) if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else { - if (groupedOptions.filter) setFilterOptions(groupedOptions.filter) - if (groupedOptions.config) setConfigOptions(groupedOptions.config) + if (groupedOptions.filter) setFilterOptionsJsonString(JSON.stringify(groupedOptions.filter, null, 2)) + if (groupedOptions.config) setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) } }) }} diff --git a/src/pages/Download.tsx b/src/pages/Download.tsx index bcc0a62..058ef3e 100644 --- a/src/pages/Download.tsx +++ b/src/pages/Download.tsx @@ -207,7 +207,11 @@ export default function Download() { setIsFetchingDownloadData(false) }) }) - .catch() + .catch(() => { + startTransition(() => { + setIsFetchingDownloadData(false) + }) + }) return () => { abortController.abort() diff --git a/src/pages/Mount.tsx b/src/pages/Mount.tsx index a988c4b..2c9c2e5 100644 --- a/src/pages/Mount.tsx +++ b/src/pages/Mount.tsx @@ -308,18 +308,18 @@ export default function Mount() { startTransition(() => { if (shouldMerge) { if (groupedOptions.mount) - setMountOptions({ ...mountOptions, ...groupedOptions.mount }) + setMountOptionsJsonString(JSON.stringify({ ...mountOptions, ...groupedOptions.mount }, null, 2)) if (groupedOptions.vfs) - setVfsOptions({ ...vfsOptions, ...groupedOptions.vfs }) + setVfsOptionsJsonString(JSON.stringify({ ...vfsOptions, ...groupedOptions.vfs }, null, 2)) if (groupedOptions.filter) - setFilterOptions({ ...filterOptions, ...groupedOptions.filter }) + setFilterOptionsJsonString(JSON.stringify({ ...filterOptions, ...groupedOptions.filter }, null, 2)) if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else { - if (groupedOptions.mount) setMountOptions(groupedOptions.mount) - if (groupedOptions.vfs) setVfsOptions(groupedOptions.vfs) - if (groupedOptions.filter) setFilterOptions(groupedOptions.filter) - if (groupedOptions.config) setConfigOptions(groupedOptions.config) + if (groupedOptions.mount) setMountOptionsJsonString(JSON.stringify(groupedOptions.mount, null, 2)) + if (groupedOptions.vfs) setVfsOptionsJsonString(JSON.stringify(groupedOptions.vfs, null, 2)) + if (groupedOptions.filter) setFilterOptionsJsonString(JSON.stringify(groupedOptions.filter, null, 2)) + if (groupedOptions.config) setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) } }) }} diff --git a/src/pages/Move.tsx b/src/pages/Move.tsx index bea043b..2f87a1a 100644 --- a/src/pages/Move.tsx +++ b/src/pages/Move.tsx @@ -293,7 +293,7 @@ export default function Move() { if (startMoveMutation.isPending) return 'STARTING...' if (!sources || sources.length === 0) return 'Please select a source path' if (!dest) return 'Please select a destination path' - if (sources[0] === dest) return 'Source and destination cannot be the same' + if (sources.some((s) => s === dest)) return 'Source and destination cannot be the same' if (jsonError) return 'Invalid JSON for ' + jsonError.toUpperCase() + ' options' if (cronExpression) return 'START AND SCHEDULE MOVE' return 'START MOVE' @@ -301,7 +301,7 @@ export default function Move() { const buttonIcon = useMemo(() => { if (startMoveMutation.isPending) return - if (!sources || sources.length === 0 || !dest || sources[0] === dest) + if (!sources || sources.length === 0 || !dest || sources.some((s) => s === dest)) return if (jsonError) return return @@ -433,15 +433,15 @@ export default function Move() { startTransition(() => { if (shouldMerge) { if (groupedOptions.copy) - setMoveOptions({ ...moveOptions, ...groupedOptions.copy }) + setMoveOptionsJsonString(JSON.stringify({ ...moveOptions, ...groupedOptions.copy }, null, 2)) if (groupedOptions.filter) - setFilterOptions({ ...filterOptions, ...groupedOptions.filter }) + setFilterOptionsJsonString(JSON.stringify({ ...filterOptions, ...groupedOptions.filter }, null, 2)) if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else { - if (groupedOptions.copy) setMoveOptions(groupedOptions.copy) - if (groupedOptions.filter) setFilterOptions(groupedOptions.filter) - if (groupedOptions.config) setConfigOptions(groupedOptions.config) + if (groupedOptions.copy) setMoveOptionsJsonString(JSON.stringify(groupedOptions.copy, null, 2)) + if (groupedOptions.filter) setFilterOptionsJsonString(JSON.stringify(groupedOptions.filter, null, 2)) + if (groupedOptions.config) setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) } }) }} @@ -587,7 +587,7 @@ export default function Move() { !sources || sources.length === 0 || !dest || - sources[0] === dest + sources.some((s) => s === dest) } isLoading={startMoveMutation.isPending} endContent={buttonIcon} @@ -619,7 +619,7 @@ export default function Move() { !sources || sources.length === 0 || !dest || - sources[0] === dest + sources.some((s) => s === dest) ) { return } diff --git a/src/pages/Purge.tsx b/src/pages/Purge.tsx index 5bd2450..9b57dda 100644 --- a/src/pages/Purge.tsx +++ b/src/pages/Purge.tsx @@ -230,9 +230,9 @@ export default function Purge() { startTransition(() => { if (shouldMerge) { if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else if (groupedOptions.config) - setConfigOptions(groupedOptions.config) + setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) }) }} getOptions={() => ({ diff --git a/src/pages/Schedules.tsx b/src/pages/Schedules.tsx index c29b38a..78dfc58 100644 --- a/src/pages/Schedules.tsx +++ b/src/pages/Schedules.tsx @@ -69,8 +69,10 @@ function TaskCard({ } }, [task.name, isEditingName]) - const parsed = useMemo(() => CronExpressionParser.parse(task.cron), [task.cron]) - const nextRun = useMemo(() => (parsed.hasNext() ? parsed.next().toDate() : null), [parsed]) + const nextRun = useMemo(() => { + const parsed = CronExpressionParser.parse(task.cron) + return parsed.hasNext() ? parsed.next().toDate() : null + }, [task.cron]) const source = useMemo( () => ('source' in task.args ? task.args.source : task.args.sources[0]), diff --git a/src/pages/Serve.tsx b/src/pages/Serve.tsx index 744dde3..1778d97 100644 --- a/src/pages/Serve.tsx +++ b/src/pages/Serve.tsx @@ -290,22 +290,19 @@ export default function Serve() { startTransition(() => { if (shouldMerge) { if (groupedOptions.serve && type) - setServeOptions({ - ...serveOptions, - ...groupedOptions.serve[type], - }) + setServeOptionsJsonString(JSON.stringify({ ...serveOptions, ...groupedOptions.serve[type] }, null, 2)) if (groupedOptions.vfs) - setVfsOptions({ ...vfsOptions, ...groupedOptions.vfs }) + setVfsOptionsJsonString(JSON.stringify({ ...vfsOptions, ...groupedOptions.vfs }, null, 2)) if (groupedOptions.filter) - setFilterOptions({ ...filterOptions, ...groupedOptions.filter }) + setFilterOptionsJsonString(JSON.stringify({ ...filterOptions, ...groupedOptions.filter }, null, 2)) if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else { if (groupedOptions.serve && type) - setServeOptions(groupedOptions.serve[type]) - if (groupedOptions.vfs) setVfsOptions(groupedOptions.vfs) - if (groupedOptions.filter) setFilterOptions(groupedOptions.filter) - if (groupedOptions.config) setConfigOptions(groupedOptions.config) + setServeOptionsJsonString(JSON.stringify(groupedOptions.serve[type], null, 2)) + if (groupedOptions.vfs) setVfsOptionsJsonString(JSON.stringify(groupedOptions.vfs, null, 2)) + if (groupedOptions.filter) setFilterOptionsJsonString(JSON.stringify(groupedOptions.filter, null, 2)) + if (groupedOptions.config) setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) } }) }} diff --git a/src/pages/Settings/LicenseSection.tsx b/src/pages/Settings/LicenseSection.tsx index 24dfcd7..46e853b 100644 --- a/src/pages/Settings/LicenseSection.tsx +++ b/src/pages/Settings/LicenseSection.tsx @@ -109,22 +109,18 @@ export default function LicenseSection() { try { await validateLicense(licenseKeyInput) } catch (e) { - if (e instanceof Error) { - await message(e.message, { + await message( + e instanceof Error ? e.message : 'An error occurred. Please try again.', + { title: 'Error', kind: 'error', okLabel: 'Ok', - }) - return - } - - await message('An error occurred. Please try again.', { - title: 'Error', - kind: 'error', - okLabel: 'Ok', - }) + } + ) + return + } finally { + setIsActivating(false) } - setIsActivating(false) await message('Your license has been successfully activated.', { title: 'Congrats!', @@ -165,22 +161,18 @@ export default function LicenseSection() { try { await revokeMachineLicense(licenseKeyInput) } catch (e) { - if (e instanceof Error) { - await message(e.message, { + await message( + e instanceof Error ? e.message : 'An error occurred. Please try again.', + { title: 'Error', kind: 'error', okLabel: 'Ok', - }) - return - } - - await message('An error occurred. Please try again.', { - title: 'Error', - kind: 'error', - okLabel: 'Ok', - }) + } + ) + return + } finally { + setIsRevoking(false) } - setIsRevoking(false) await message('Your license has been successfully deactivated.', { title: 'License deactivated', diff --git a/src/pages/Settings/RemotesSection.tsx b/src/pages/Settings/RemotesSection.tsx index 47480cb..9c0e9f4 100644 --- a/src/pages/Settings/RemotesSection.tsx +++ b/src/pages/Settings/RemotesSection.tsx @@ -365,8 +365,6 @@ function RemoteCard({ enabled: supportsAbout, }) - console.log('about', remote, type, JSON.stringify(remoteAboutData, null, 2)) - const imageUrl = useMemo( () => provider && !type ? `/icons/providers/${provider}.png` : `/icons/backends/${type}.png`, diff --git a/src/pages/Sync.tsx b/src/pages/Sync.tsx index 73eae60..65235d9 100644 --- a/src/pages/Sync.tsx +++ b/src/pages/Sync.tsx @@ -416,15 +416,15 @@ export default function Sync() { startTransition(() => { if (shouldMerge) { if (groupedOptions.sync) - setSyncOptions({ ...syncOptions, ...groupedOptions.sync }) + setSyncOptionsJsonString(JSON.stringify({ ...syncOptions, ...groupedOptions.sync }, null, 2)) if (groupedOptions.filter) - setFilterOptions({ ...filterOptions, ...groupedOptions.filter }) + setFilterOptionsJsonString(JSON.stringify({ ...filterOptions, ...groupedOptions.filter }, null, 2)) if (groupedOptions.config) - setConfigOptions({ ...configOptions, ...groupedOptions.config }) + setConfigOptionsJsonString(JSON.stringify({ ...configOptions, ...groupedOptions.config }, null, 2)) } else { - if (groupedOptions.sync) setSyncOptions(groupedOptions.sync) - if (groupedOptions.filter) setFilterOptions(groupedOptions.filter) - if (groupedOptions.config) setConfigOptions(groupedOptions.config) + if (groupedOptions.sync) setSyncOptionsJsonString(JSON.stringify(groupedOptions.sync, null, 2)) + if (groupedOptions.filter) setFilterOptionsJsonString(JSON.stringify(groupedOptions.filter, null, 2)) + if (groupedOptions.config) setConfigOptionsJsonString(JSON.stringify(groupedOptions.config, null, 2)) } }) }} diff --git a/src/pages/Transfers.tsx b/src/pages/Transfers.tsx index 5885aea..7a1a4f4 100644 --- a/src/pages/Transfers.tsx +++ b/src/pages/Transfers.tsx @@ -59,17 +59,6 @@ export default function Transfers() { [transfersQuery.data] ) - console.log('[Transfers] query state', { - isLoading: transfersQuery.isLoading, - isRefetching: transfersQuery.isRefetching, - isError: transfersQuery.isError, - error: transfersQuery.error, - activeCount: transfers.active.length, - inactiveCount: transfers.inactive.length, - // checkingJobs: transfers.active.filter((j) => j.isChecking).length, - }) - console.log('[Transfers] data', JSON.stringify(transfers, null, 2)) - if (transfersQuery.isLoading) { return (
diff --git a/store/host.ts b/store/host.ts index 5b1a7ce..58aa8da 100644 --- a/store/host.ts +++ b/store/host.ts @@ -34,9 +34,9 @@ export async function initHostStore(hostId: string) { } try { - disposeKeyChange = activeStore.onKeyChange('host-store', async () => { + disposeKeyChange = await activeStore.onKeyChange('host-store', async () => { await useHostStore.persist.rehydrate() - }) as unknown as () => void + }) } catch (err) { console.error('[HostStore] failed to register onKeyChange listener', err) } @@ -49,7 +49,7 @@ const getStorage = (): StateStorage => ({ getItem: async (name: string): Promise => { if (!activeStore) return null // console.log('[HostStore] getItem', { name, host: activeHostId }) - return (await activeStore.get(name)) || null + return (await activeStore.get(name)) ?? null }, setItem: async (name: string, value: string): Promise => { if (!activeStore) return diff --git a/store/persisted.ts b/store/persisted.ts index a4ddcd5..326d1db 100644 --- a/store/persisted.ts +++ b/store/persisted.ts @@ -142,7 +142,7 @@ interface PersistedStateV2 { const getStorage = (store: LazyStore): StateStorage => ({ getItem: async (name: string): Promise => { console.log('getItem', { name }) - return (await store.get(name)) || null + return (await store.get(name)) ?? null }, setItem: async (name: string, value: string): Promise => { console.log('setItem', { name, value }) diff --git a/tsconfig.json b/tsconfig.json index a668e2e..596fde7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "lib", "reset.d.ts", "toolbar"] + "include": ["src", "lib", "store", "types", "toolbar", "main.ts", "reset.d.ts"] }