From c4a442953a699b23e9e46634cb2758d0f766ac76 Mon Sep 17 00:00:00 2001 From: FTCHD <144691102+FTCHD@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:06:47 +0700 Subject: [PATCH] editable schedule name, #101 Signed-off-by: FTCHD <144691102+FTCHD@users.noreply.github.com> --- src/pages/Bisync.tsx | 12 ++++++++ src/pages/Copy.tsx | 12 ++++++++ src/pages/Delete.tsx | 12 ++++++++ src/pages/Move.tsx | 12 ++++++++ src/pages/Purge.tsx | 12 ++++++++ src/pages/Schedules.tsx | 65 ++++++++++++++++++++++++++++++++++++----- src/pages/Sync.tsx | 12 ++++++++ types/schedules.d.ts | 1 + 8 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/pages/Bisync.tsx b/src/pages/Bisync.tsx index 12723ba..fa68e81 100644 --- a/src/pages/Bisync.tsx +++ b/src/pages/Bisync.tsx @@ -12,6 +12,7 @@ import { Tooltip, } from '@heroui/react' import { useMutation } from '@tanstack/react-query' +import { invoke } from '@tauri-apps/api/core' import { message } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' import cronstrue from 'cronstrue' @@ -134,7 +135,18 @@ export default function Bisync() { throw new Error('Invalid cron expression') } + const name = await invoke('prompt', { + title: 'Schedule Name', + message: 'Enter a name for this schedule', + default: `New Schedule ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: '2-digit' })}`, + }) + + if (!name) { + throw new Error('Schedule name is required') + } + useHostStore.getState().addScheduledTask({ + name, operation: 'bisync', cron: cronExpression, args: { diff --git a/src/pages/Copy.tsx b/src/pages/Copy.tsx index 02daf28..7197150 100644 --- a/src/pages/Copy.tsx +++ b/src/pages/Copy.tsx @@ -11,6 +11,7 @@ import { Tooltip, } from '@heroui/react' import { useMutation } from '@tanstack/react-query' +import { invoke } from '@tauri-apps/api/core' import { message } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' import cronstrue from 'cronstrue' @@ -136,7 +137,18 @@ export default function Copy() { throw new Error('Invalid cron expression') } + const name = await invoke('prompt', { + title: 'Schedule Name', + message: 'Enter a name for this schedule', + default: `New Schedule ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: '2-digit' })}`, + }) + + if (!name) { + throw new Error('Schedule name is required') + } + useHostStore.getState().addScheduledTask({ + name, operation: 'copy', cron: cronExpression, args: { diff --git a/src/pages/Delete.tsx b/src/pages/Delete.tsx index 86b8e7f..1566e9d 100644 --- a/src/pages/Delete.tsx +++ b/src/pages/Delete.tsx @@ -13,6 +13,7 @@ import { } from '@heroui/react' import * as Sentry from '@sentry/browser' import { useMutation, useQuery } from '@tanstack/react-query' +import { invoke } from '@tauri-apps/api/core' import { message } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' import cronstrue from 'cronstrue' @@ -159,7 +160,18 @@ export default function Delete() { throw new Error('Invalid cron expression') } + const name = await invoke('prompt', { + title: 'Schedule Name', + message: 'Enter a name for this schedule', + default: `New Schedule ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: '2-digit' })}`, + }) + + if (!name) { + throw new Error('Schedule name is required') + } + useHostStore.getState().addScheduledTask({ + name, operation: 'delete', cron: cronExpression, args: { diff --git a/src/pages/Move.tsx b/src/pages/Move.tsx index c82370e..672edc5 100644 --- a/src/pages/Move.tsx +++ b/src/pages/Move.tsx @@ -11,6 +11,7 @@ import { Tooltip, } from '@heroui/react' import { useMutation } from '@tanstack/react-query' +import { invoke } from '@tauri-apps/api/core' import { message } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' import cronstrue from 'cronstrue' @@ -163,7 +164,18 @@ export default function Move() { throw new Error('Invalid cron expression') } + const name = await invoke('prompt', { + title: 'Schedule Name', + message: 'Enter a name for this schedule', + default: `New Schedule ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: '2-digit' })}`, + }) + + if (!name) { + throw new Error('Schedule name is required') + } + useHostStore.getState().addScheduledTask({ + name, operation: 'move', cron: cronExpression, args: { diff --git a/src/pages/Purge.tsx b/src/pages/Purge.tsx index ebe00ef..85f8c1a 100644 --- a/src/pages/Purge.tsx +++ b/src/pages/Purge.tsx @@ -12,6 +12,7 @@ import { } from '@heroui/react' import * as Sentry from '@sentry/browser' import { useMutation } from '@tanstack/react-query' +import { invoke } from '@tauri-apps/api/core' import { message } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' import cronstrue from 'cronstrue' @@ -95,7 +96,18 @@ export default function Purge() { throw new Error('Invalid cron expression') } + const name = await invoke('prompt', { + title: 'Schedule Name', + message: 'Enter a name for this schedule', + default: `New Schedule ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: '2-digit' })}`, + }) + + if (!name) { + throw new Error('Schedule name is required') + } + useHostStore.getState().addScheduledTask({ + name, operation: 'purge', cron: cronExpression, args: { diff --git a/src/pages/Schedules.tsx b/src/pages/Schedules.tsx index 16681ef..5aee862 100644 --- a/src/pages/Schedules.tsx +++ b/src/pages/Schedules.tsx @@ -1,4 +1,4 @@ -import { Card, CardBody, CardHeader, Tooltip } from '@heroui/react' +import { Card, CardBody, CardHeader, Input, Tooltip } from '@heroui/react' import { Button, Chip } from '@heroui/react' import { ask } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' @@ -6,7 +6,7 @@ import CronExpressionParser from 'cron-parser' import cronstrue from 'cronstrue' import { formatDistance } from 'date-fns' import { AlertCircleIcon, Clock7Icon, PauseIcon, PlayIcon, Trash2Icon } from 'lucide-react' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { buildReadablePath } from '../../lib/format' import { useHostStore } from '../../store/host' import type { ScheduledTask } from '../../types/schedules' @@ -19,8 +19,7 @@ export default function Schedules() { return (

- You can schedule tasks to run later. Schedule one below and keep the UI running - in the background. + You can schedule tasks to run later, when the UI is in the background.

@@ -41,10 +40,18 @@ export default function Schedules() { function TaskCard({ task }: { task: ScheduledTask }) { const [isBusy, setIsBusy] = useState(false) + const [isEditingName, setIsEditingName] = useState(false) + const [editingName, setEditingName] = useState(task.name) const removeScheduledTask = useHostStore((state) => state.removeScheduledTask) const updateScheduledTask = useHostStore((state) => state.updateScheduledTask) + useEffect(() => { + if (!isEditingName) { + setEditingName(task.name) + } + }, [task.name, isEditingName]) + const parsed = useMemo(() => CronExpressionParser.parse(task.cron), [task.cron]) const nextRun = useMemo(() => (parsed.hasNext() ? parsed.next().toDate() : null), [parsed]) @@ -107,10 +114,54 @@ function TaskCard({ task }: { task: ScheduledTask }) { {task.operation.toUpperCase()}
-
- {buildReadablePath(source, 'short')} -
+ + { + setEditingName(task.name || 'Untitled Schedule') + setIsEditingName(true) + e.currentTarget.select() + }} + onBlur={() => { + updateScheduledTask(task.id, { name: editingName }) + setIsEditingName(false) + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + updateScheduledTask(task.id, { name: editingName }) + setIsEditingName(false) + e.currentTarget.blur() + } else if (e.key === 'Escape') { + setEditingName(task.name) + setIsEditingName(false) + e.currentTarget.blur() + } + }} + onValueChange={(newName) => setEditingName(newName)} + /> +
+ {buildReadablePath(source, 'short')} {'→'}{' '} {'destination' in task.args ? buildReadablePath(task.args.destination, 'short') : 'N/A'} diff --git a/src/pages/Sync.tsx b/src/pages/Sync.tsx index 1613c24..aef7fc3 100644 --- a/src/pages/Sync.tsx +++ b/src/pages/Sync.tsx @@ -12,6 +12,7 @@ import { } from '@heroui/react' import * as Sentry from '@sentry/browser' import { useMutation } from '@tanstack/react-query' +import { invoke } from '@tauri-apps/api/core' import { message } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' import cronstrue from 'cronstrue' @@ -130,7 +131,18 @@ export default function Sync() { throw new Error('Invalid cron expression') } + const name = await invoke('prompt', { + title: 'Schedule Name', + message: 'Enter a name for this schedule', + default: `New Schedule ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: '2-digit' })}`, + }) + + if (!name) { + throw new Error('Schedule name is required') + } + useHostStore.getState().addScheduledTask({ + name, operation: 'sync', cron: cronExpression, args: { diff --git a/types/schedules.d.ts b/types/schedules.d.ts index a11a1b0..7d87985 100644 --- a/types/schedules.d.ts +++ b/types/schedules.d.ts @@ -9,6 +9,7 @@ import type { export type ScheduledTask = { id: string + name?: string cron: string isRunning: boolean isEnabled: boolean