import { useEffect, useMemo, useState } from 'react'; import { invokeCommand as invoke } from '../ipc'; import { CheckCircle2, Clock3, List, Moon, LockKeyhole, Pause, Play, Power, RotateCcw, Save } from 'lucide-react'; import { PostQueueAction, SchedulerSettings, useSettingsStore } from '../store/useSettingsStore'; import { useDownloadStore, MAIN_QUEUE_ID } from '../store/useDownloadStore'; import { WindowDragRegion } from './WindowDragRegion'; const days = [ { value: 0, label: 'Su' }, { value: 1, label: 'Mo' }, { value: 2, label: 'Tu' }, { value: 3, label: 'We' }, { value: 4, label: 'Th' }, { value: 5, label: 'Fr' }, { value: 6, label: 'Sa' }, ]; const postActions: { value: PostQueueAction; label: string; icon: typeof Moon }[] = [ { value: 'none', label: 'Do nothing', icon: CheckCircle2 }, { value: 'sleep', label: 'Sleep', icon: Moon }, { value: 'restart', label: 'Restart', icon: RotateCcw }, { value: 'shutdown', label: 'Shut down', icon: Power }, ]; function nextScheduledRun(settings: SchedulerSettings): string { if (!settings.enabled) return 'Scheduler is disabled'; const [hour, minute] = settings.startTime.split(':').map(Number); const now = new Date(); for (let offset = 0; offset < 8; offset += 1) { const candidate = new Date(now); candidate.setDate(now.getDate() + offset); candidate.setHours(hour, minute, 0, 0); const allowedDay = settings.everyday || settings.selectedDays.includes(candidate.getDay()); if (allowedDay && candidate > now) { return candidate.toLocaleString(undefined, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } } return 'No scheduled day selected'; } export default function SchedulerView() { const savedSettings = useSettingsStore(state => state.scheduler); const schedulerRunning = useSettingsStore(state => state.schedulerRunning); const setScheduler = useSettingsStore(state => state.setScheduler); const [draft, setDraft] = useState(savedSettings); const [toast, setToast] = useState(''); const [permissionMessage, setPermissionMessage] = useState(''); const isMac = navigator.userAgent.includes('Mac'); useEffect(() => { setDraft(savedSettings); }, [savedSettings]); useEffect(() => { if (!toast) return; const timeout = window.setTimeout(() => setToast(''), 2200); return () => window.clearTimeout(timeout); }, [toast]); const nextRun = useMemo(() => nextScheduledRun(draft), [draft]); const updateDraft = (key: K, value: SchedulerSettings[K]) => { setDraft(current => ({ ...current, [key]: value })); }; const toggleDay = (day: number) => { setDraft(current => ({ ...current, selectedDays: current.selectedDays.includes(day) ? current.selectedDays.filter(value => value !== day) : [...current.selectedDays, day].sort() })); }; const save = () => { const normalized = { ...draft, selectedDays: draft.everyday || draft.selectedDays.length > 0 ? draft.selectedDays : savedSettings.selectedDays }; setScheduler(normalized); setDraft(normalized); setToast('Scheduler settings saved'); }; const runNow = async () => { const count = await useDownloadStore.getState().startQueue(MAIN_QUEUE_ID); if (count > 0) { useSettingsStore.getState().setSchedulerRunning(true); setToast(`Started ${count} download${count === 1 ? '' : 's'}`); } else { setToast('No paused or failed downloads to start'); } }; const pauseNow = async () => { const count = await useDownloadStore.getState().pauseQueue(MAIN_QUEUE_ID); useSettingsStore.getState().setSchedulerRunning(false); setToast(count > 0 ? `Paused ${count} active download${count === 1 ? '' : 's'}` : 'No active downloads'); }; const requestPermission = async () => { setPermissionMessage('Requesting permission...'); try { await invoke('request_automation_permission'); setPermissionMessage('Automation permission is available.'); } catch (error) { setPermissionMessage(String(error)); } }; return (
{schedulerRunning ? 'Running' : nextRun}
Timing
updateDraft('stopTime', event.target.value)} disabled={!draft.enabled || !draft.stopTimeEnabled} className="app-control px-3 py-2 text-text-primary disabled:opacity-50" />
{!draft.everyday && (
{days.map(day => { const selected = draft.selectedDays.includes(day.value); return ( ); })}
)}
Queues to Schedule
After Completion

Choose what happens after downloads started by the scheduler finish.

{postActions.map(action => { const Icon = action.icon; return ( ); })}
{draft.postQueueAction !== 'none' && (

This action can interrupt other work on the computer. Firelink invokes it immediately after the scheduled queue finishes.

)}
{isMac && (
System Permissions

Sleep, restart, and shut down require macOS Automation permission for Finder.

{permissionMessage &&

{permissionMessage}

}
)}
{toast && (
{toast}
)}
); }