From 524d1b6574d663e25de276ec35bedaf9b4c6eb90 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sat, 22 Feb 2025 16:45:48 +0300 Subject: [PATCH 01/10] Migrate tasks edit page to react --- .../scheduledtasks/scheduledtask.html | 84 ------- .../scheduledtasks/scheduledtask.js | 236 ------------------ .../api/useStartTask.ts | 0 .../api/useStopTask.ts | 0 .../dashboard/features/tasks/api/useTask.ts | 35 +++ .../{scheduledtasks => tasks}/api/useTasks.ts | 0 .../features/tasks/api/useUpdateTask.ts | 22 ++ .../tasks/components/NewTriggerForm.tsx | 169 +++++++++++++ .../components/Task.tsx | 0 .../components/TaskLastRan.tsx | 0 .../components/TaskProgress.tsx | 0 .../tasks/components/TaskTriggerCell.tsx | 34 +++ .../components/Tasks.tsx | 0 .../types/taskProps.ts | 0 .../dashboard/features/tasks/utils/edit.ts | 64 +++++ .../{scheduledtasks => tasks}/utils/tasks.ts | 0 src/apps/dashboard/routes/_asyncRoutes.ts | 1 + src/apps/dashboard/routes/_legacyRoutes.ts | 7 - src/apps/dashboard/routes/tasks/edit.tsx | 173 +++++++++++++ src/apps/dashboard/routes/tasks/index.tsx | 6 +- 20 files changed, 501 insertions(+), 330 deletions(-) delete mode 100644 src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html delete mode 100644 src/apps/dashboard/controllers/scheduledtasks/scheduledtask.js rename src/apps/dashboard/features/{scheduledtasks => tasks}/api/useStartTask.ts (100%) rename src/apps/dashboard/features/{scheduledtasks => tasks}/api/useStopTask.ts (100%) create mode 100644 src/apps/dashboard/features/tasks/api/useTask.ts rename src/apps/dashboard/features/{scheduledtasks => tasks}/api/useTasks.ts (100%) create mode 100644 src/apps/dashboard/features/tasks/api/useUpdateTask.ts create mode 100644 src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/Task.tsx (100%) rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/TaskLastRan.tsx (100%) rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/TaskProgress.tsx (100%) create mode 100644 src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/Tasks.tsx (100%) rename src/apps/dashboard/features/{scheduledtasks => tasks}/types/taskProps.ts (100%) create mode 100644 src/apps/dashboard/features/tasks/utils/edit.ts rename src/apps/dashboard/features/{scheduledtasks => tasks}/utils/tasks.ts (100%) create mode 100644 src/apps/dashboard/routes/tasks/edit.tsx diff --git a/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html b/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html deleted file mode 100644 index a2352e6470..0000000000 --- a/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html +++ /dev/null @@ -1,84 +0,0 @@ -
-
-
-
-
-

-
-

-
- -
-
-

${HeaderTaskTriggers}

- -
-
-
-
-
-
-
-
-

${ButtonAddScheduledTaskTrigger}

-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - -
-
-
-
-
diff --git a/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.js b/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.js deleted file mode 100644 index 7d46af171a..0000000000 --- a/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.js +++ /dev/null @@ -1,236 +0,0 @@ -import loading from 'components/loading/loading'; -import datetime from 'scripts/datetime'; -import dom from 'scripts/dom'; -import globalize from 'lib/globalize'; -import 'elements/emby-input/emby-input'; -import 'elements/emby-button/emby-button'; -import 'elements/emby-select/emby-select'; -import confirm from 'components/confirm/confirm'; -import { getParameterByName } from 'utils/url.ts'; - -function fillTimeOfDay(select) { - const options = []; - - for (let i = 0; i < 86400000; i += 900000) { - options.push({ - name: ScheduledTaskPage.getDisplayTime(i * 10000), - value: i * 10000 - }); - } - - select.innerHTML = options.map(function (o) { - return ''; - }).join(''); -} - -const ScheduledTaskPage = { - refreshScheduledTask: function (view) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - ScheduledTaskPage.loadScheduledTask(view, task); - }); - }, - loadScheduledTask: function (view, task) { - view.querySelector('.taskName').innerHTML = task.Name; - view.querySelector('#pTaskDescription').innerHTML = task.Description; - - import('components/listview/listview.scss').then(() => { - ScheduledTaskPage.loadTaskTriggers(view, task); - }); - - loading.hide(); - }, - loadTaskTriggers: function (context, task) { - let html = ''; - html += '
'; - - for (let i = 0, length = task.Triggers.length; i < length; i++) { - const trigger = task.Triggers[i]; - - html += '
'; - html += ''; - if (trigger.MaxRuntimeTicks) { - html += '
'; - } else { - html += '
'; - } - html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
'; - if (trigger.MaxRuntimeTicks) { - html += '
'; - const hours = trigger.MaxRuntimeTicks / 36e9; - if (hours == 1) { - html += globalize.translate('ValueTimeLimitSingleHour'); - } else { - html += globalize.translate('ValueTimeLimitMultiHour', hours); - } - html += '
'; - } - - html += '
'; - html += ''; - html += '
'; - } - - html += '
'; - context.querySelector('.taskTriggers').innerHTML = html; - }, - // TODO: Replace this mess with date-fns and remove datetime completely - getTriggerFriendlyName: function (trigger) { - if (trigger.Type == 'DailyTrigger') { - return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); - } - - if (trigger.Type == 'WeeklyTrigger') { - // TODO: The day of week isn't localised as well - return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); - } - - if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') { - return globalize.translate('OnWakeFromSleep'); - } - - if (trigger.Type == 'IntervalTrigger') { - const hours = trigger.IntervalTicks / 36e9; - - if (hours == 0.25) { - return globalize.translate('EveryXMinutes', '15'); - } - if (hours == 0.5) { - return globalize.translate('EveryXMinutes', '30'); - } - if (hours == 0.75) { - return globalize.translate('EveryXMinutes', '45'); - } - if (hours == 1) { - return globalize.translate('EveryHour'); - } - - return globalize.translate('EveryXHours', hours); - } - - if (trigger.Type == 'StartupTrigger') { - return globalize.translate('OnApplicationStartup'); - } - - return trigger.Type; - }, - getDisplayTime: function (ticks) { - const ms = ticks / 1e4; - const now = new Date(); - now.setHours(0, 0, 0, 0); - now.setTime(now.getTime() + ms); - return datetime.getDisplayTime(now); - }, - showAddTriggerPopup: function (view) { - view.querySelector('#selectTriggerType').value = 'DailyTrigger'; - view.querySelector('#selectTriggerType').dispatchEvent(new CustomEvent('change', {})); - view.querySelector('#popupAddTrigger').classList.remove('hide'); - }, - confirmDeleteTrigger: function (view, index) { - confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { - ScheduledTaskPage.deleteTrigger(view, index); - }); - }, - deleteTrigger: function (view, index) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - task.Triggers.splice(index, 1); - ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { - ScheduledTaskPage.refreshScheduledTask(view); - }); - }); - }, - refreshTriggerFields: function (page, triggerType) { - if (triggerType == 'DailyTrigger') { - page.querySelector('#fldTimeOfDay').classList.remove('hide'); - page.querySelector('#fldDayOfWeek').classList.add('hide'); - page.querySelector('#fldSelectSystemEvent').classList.add('hide'); - page.querySelector('#fldSelectInterval').classList.add('hide'); - page.querySelector('#selectTimeOfDay').setAttribute('required', 'required'); - } else if (triggerType == 'WeeklyTrigger') { - page.querySelector('#fldTimeOfDay').classList.remove('hide'); - page.querySelector('#fldDayOfWeek').classList.remove('hide'); - page.querySelector('#fldSelectSystemEvent').classList.add('hide'); - page.querySelector('#fldSelectInterval').classList.add('hide'); - page.querySelector('#selectTimeOfDay').setAttribute('required', 'required'); - } else if (triggerType == 'SystemEventTrigger') { - page.querySelector('#fldTimeOfDay').classList.add('hide'); - page.querySelector('#fldDayOfWeek').classList.add('hide'); - page.querySelector('#fldSelectSystemEvent').classList.remove('hide'); - page.querySelector('#fldSelectInterval').classList.add('hide'); - page.querySelector('#selectTimeOfDay').removeAttribute('required'); - } else if (triggerType == 'IntervalTrigger') { - page.querySelector('#fldTimeOfDay').classList.add('hide'); - page.querySelector('#fldDayOfWeek').classList.add('hide'); - page.querySelector('#fldSelectSystemEvent').classList.add('hide'); - page.querySelector('#fldSelectInterval').classList.remove('hide'); - page.querySelector('#selectTimeOfDay').removeAttribute('required'); - } else if (triggerType == 'StartupTrigger') { - page.querySelector('#fldTimeOfDay').classList.add('hide'); - page.querySelector('#fldDayOfWeek').classList.add('hide'); - page.querySelector('#fldSelectSystemEvent').classList.add('hide'); - page.querySelector('#fldSelectInterval').classList.add('hide'); - page.querySelector('#selectTimeOfDay').removeAttribute('required'); - } - }, - getTriggerToAdd: function (page) { - const trigger = { - Type: page.querySelector('#selectTriggerType').value - }; - - if (trigger.Type == 'DailyTrigger') { - trigger.TimeOfDayTicks = page.querySelector('#selectTimeOfDay').value; - } else if (trigger.Type == 'WeeklyTrigger') { - trigger.DayOfWeek = page.querySelector('#selectDayOfWeek').value; - trigger.TimeOfDayTicks = page.querySelector('#selectTimeOfDay').value; - } else if (trigger.Type == 'SystemEventTrigger') { - trigger.SystemEvent = page.querySelector('#selectSystemEvent').value; - } else if (trigger.Type == 'IntervalTrigger') { - trigger.IntervalTicks = page.querySelector('#selectInterval').value; - } - - let timeLimit = page.querySelector('#txtTimeLimit').value || '0'; - timeLimit = parseFloat(timeLimit) * 3600000; - - trigger.MaxRuntimeTicks = timeLimit * 1e4 || null; - - return trigger; - } -}; -export default function (view) { - function onSubmit(e) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view)); - ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { - document.querySelector('#popupAddTrigger').classList.add('hide'); - ScheduledTaskPage.refreshScheduledTask(view); - }); - }); - e.preventDefault(); - } - - view.querySelector('.addTriggerForm').addEventListener('submit', onSubmit); - fillTimeOfDay(view.querySelector('#selectTimeOfDay')); - view.querySelector('#popupAddTrigger').parentNode.trigger(new Event('create')); - view.querySelector('.selectTriggerType').addEventListener('change', function () { - ScheduledTaskPage.refreshTriggerFields(view, this.value); - }); - view.querySelector('.btnAddTrigger').addEventListener('click', function () { - ScheduledTaskPage.showAddTriggerPopup(view); - }); - view.addEventListener('click', function (e) { - const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); - - if (btnDeleteTrigger) { - ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10)); - } - }); - view.addEventListener('viewshow', function () { - ScheduledTaskPage.refreshScheduledTask(view); - }); -} - diff --git a/src/apps/dashboard/features/scheduledtasks/api/useStartTask.ts b/src/apps/dashboard/features/tasks/api/useStartTask.ts similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/api/useStartTask.ts rename to src/apps/dashboard/features/tasks/api/useStartTask.ts diff --git a/src/apps/dashboard/features/scheduledtasks/api/useStopTask.ts b/src/apps/dashboard/features/tasks/api/useStopTask.ts similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/api/useStopTask.ts rename to src/apps/dashboard/features/tasks/api/useStopTask.ts diff --git a/src/apps/dashboard/features/tasks/api/useTask.ts b/src/apps/dashboard/features/tasks/api/useTask.ts new file mode 100644 index 0000000000..98800420b4 --- /dev/null +++ b/src/apps/dashboard/features/tasks/api/useTask.ts @@ -0,0 +1,35 @@ +import type { ScheduledTasksApiGetTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api'; +import type { AxiosRequestConfig } from 'axios'; +import type { Api } from '@jellyfin/sdk'; +import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api'; +import { useQuery } from '@tanstack/react-query'; + +import { useApi } from 'hooks/useApi'; + +export const QUERY_KEY = 'Task'; + +const fetchTask = async ( + api: Api, + params: ScheduledTasksApiGetTaskRequest, + options?: AxiosRequestConfig +) => { + if (!api) { + console.warn('[fetchTasks] No API instance available'); + return; + } + + const response = await getScheduledTasksApi(api).getTask(params, options); + + return response.data; +}; + +export const useTask = (params: ScheduledTasksApiGetTaskRequest) => { + const { api } = useApi(); + + return useQuery({ + queryKey: [ QUERY_KEY, params.taskId ], + queryFn: ({ signal }) => + fetchTask(api!, params, { signal }), + enabled: !!api + }); +}; diff --git a/src/apps/dashboard/features/scheduledtasks/api/useTasks.ts b/src/apps/dashboard/features/tasks/api/useTasks.ts similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/api/useTasks.ts rename to src/apps/dashboard/features/tasks/api/useTasks.ts diff --git a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts new file mode 100644 index 0000000000..8f34c0df38 --- /dev/null +++ b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts @@ -0,0 +1,22 @@ +import { ScheduledTasksApiUpdateTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api'; +import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api'; +import { useMutation } from '@tanstack/react-query'; +import { useApi } from 'hooks/useApi'; +import { queryClient } from 'utils/query/queryClient'; +import { QUERY_KEY } from './useTasks'; + +export const useUpdateTask = () => { + const { api } = useApi(); + + return useMutation({ + mutationFn: (params: ScheduledTasksApiUpdateTaskRequest) => ( + getScheduledTasksApi(api!) + .updateTask(params) + ), + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: [ QUERY_KEY ] + }); + } + }); +}; diff --git a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx new file mode 100644 index 0000000000..53404885fc --- /dev/null +++ b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx @@ -0,0 +1,169 @@ +import React, { FunctionComponent, useCallback, useMemo, useState } from 'react'; +import Dialog from '@mui/material/Dialog'; +import Button from '@mui/material/Button'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import MenuItem from '@mui/material/MenuItem'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info'; +import { TaskTriggerInfoType } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info-type'; +import { DayOfWeek } from '@jellyfin/sdk/lib/generated-client/models/day-of-week'; +import globalize from 'lib/globalize'; +import { getTimeOfDayOptions } from '../utils/edit'; +import { useLocale } from 'hooks/useLocale'; + +type IProps = { + open: boolean, + title: string, + onClose?: () => void, + onSubmit?: (trigger: TaskTriggerInfo) => void +}; + +const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onSubmit }: IProps) => { + const { dateFnsLocale } = useLocale(); + const [triggerType, setTriggerType] = useState('DailyTrigger'); + + const timeOfDayOptions = useMemo(() => getTimeOfDayOptions(dateFnsLocale), [dateFnsLocale]); + + const onTriggerTypeChange = useCallback((e: React.ChangeEvent) => { + setTriggerType(e.target.value); + }, []); + + return ( + ) => { + e.preventDefault(); + + const formData = new FormData(e.currentTarget); + const data = Object.fromEntries(formData.entries()); + const trigger: TaskTriggerInfo = { + Type: data.TriggerType.toString() as TaskTriggerInfoType + }; + + if (trigger.Type == TaskTriggerInfoType.WeeklyTrigger) { + trigger.DayOfWeek = data.DayOfWeek.toString() as DayOfWeek; + } + + if (trigger.Type == TaskTriggerInfoType.DailyTrigger || trigger.Type == TaskTriggerInfoType.WeeklyTrigger) { + trigger.TimeOfDayTicks = parseInt(data.TimeOfDay.toString(), 10); + } + + if (trigger.Type == TaskTriggerInfoType.IntervalTrigger) { + trigger.IntervalTicks = parseInt(data.Interval.toString(), 10); + } + + if (data.TimeLimit.toString()) { + trigger.MaxRuntimeTicks = parseFloat(data.TimeLimit.toString()) * 3600000 * 1e4; + } + + if (onSubmit) { + onSubmit(trigger); + } + } + }} + > + {title} + + + + + {globalize.translate('OptionDaily')} + {globalize.translate('OptionWeekly')} + {globalize.translate('OptionOnInterval')} + {globalize.translate('OnApplicationStartup')} + + + {triggerType == 'WeeklyTrigger' && ( + + {globalize.translate('Sunday')} + {globalize.translate('Monday')} + {globalize.translate('Tuesday')} + {globalize.translate('Wednesday')} + {globalize.translate('Thursday')} + {globalize.translate('Friday')} + {globalize.translate('Saturday')} + + )} + + {['DailyTrigger', 'WeeklyTrigger'].includes(triggerType) && ( + + {timeOfDayOptions.map((option) => { + return {option.name}; + })} + + )} + + {triggerType == 'IntervalTrigger' && ( + + 15 minutes + 30 minutes + 45 minutes + 1 hour + 2 hours + 3 hours + 4 hours + 6 hours + 8 hours + 12 hours + 24 hours + + )} + + + + + + + + + + + ); +}; + +export default NewTriggerForm; diff --git a/src/apps/dashboard/features/scheduledtasks/components/Task.tsx b/src/apps/dashboard/features/tasks/components/Task.tsx similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/components/Task.tsx rename to src/apps/dashboard/features/tasks/components/Task.tsx diff --git a/src/apps/dashboard/features/scheduledtasks/components/TaskLastRan.tsx b/src/apps/dashboard/features/tasks/components/TaskLastRan.tsx similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/components/TaskLastRan.tsx rename to src/apps/dashboard/features/tasks/components/TaskLastRan.tsx diff --git a/src/apps/dashboard/features/scheduledtasks/components/TaskProgress.tsx b/src/apps/dashboard/features/tasks/components/TaskProgress.tsx similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/components/TaskProgress.tsx rename to src/apps/dashboard/features/tasks/components/TaskProgress.tsx diff --git a/src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx b/src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx new file mode 100644 index 0000000000..9f4f8502c9 --- /dev/null +++ b/src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx @@ -0,0 +1,34 @@ +import React, { FC } from 'react'; +import type { MRT_Cell, MRT_RowData } from 'material-react-table'; +import { useLocale } from 'hooks/useLocale'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { getTriggerFriendlyName } from '../utils/edit'; +import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info'; +import globalize from 'lib/globalize'; + +interface CellProps { + cell: MRT_Cell +} + +const TaskTriggerCell: FC = ({ cell }) => { + const { dateFnsLocale } = useLocale(); + const trigger = cell.getValue(); + + const timeLimitHours = trigger.MaxRuntimeTicks ? trigger.MaxRuntimeTicks / 36e9 : false; + + return ( + + {getTriggerFriendlyName(trigger, dateFnsLocale)} + {timeLimitHours && ( + + {timeLimitHours == 1 ? + globalize.translate('ValueTimeLimitSingleHour') : + globalize.translate('ValueTimeLimitMultiHour', timeLimitHours)} + + )} + + ); +}; + +export default TaskTriggerCell; diff --git a/src/apps/dashboard/features/scheduledtasks/components/Tasks.tsx b/src/apps/dashboard/features/tasks/components/Tasks.tsx similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/components/Tasks.tsx rename to src/apps/dashboard/features/tasks/components/Tasks.tsx diff --git a/src/apps/dashboard/features/scheduledtasks/types/taskProps.ts b/src/apps/dashboard/features/tasks/types/taskProps.ts similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/types/taskProps.ts rename to src/apps/dashboard/features/tasks/types/taskProps.ts diff --git a/src/apps/dashboard/features/tasks/utils/edit.ts b/src/apps/dashboard/features/tasks/utils/edit.ts new file mode 100644 index 0000000000..2637465710 --- /dev/null +++ b/src/apps/dashboard/features/tasks/utils/edit.ts @@ -0,0 +1,64 @@ +import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info'; +import { format, Locale, parse } from 'date-fns'; +import globalize from 'lib/globalize'; + +function getDisplayTime(ticks: number, locale: Locale) { + const ms = ticks / 1e4; + const now = new Date(); + now.setHours(0, 0, 0, 0); + now.setTime(now.getTime() + ms); + return format(now, 'p', { locale: locale }); +} + +export function getTimeOfDayOptions(locale: Locale) { + const options = []; + + for (let i = 0; i < 86400000; i += 900000) { + options.push({ + name: getDisplayTime(i * 10000, locale), + value: i * 10000 + }); + } + + return options; +} + +function getIntervalTriggerTime(ticks: number) { + const hours = ticks / 36e9; + + switch (hours) { + case 0.25: + return globalize.translate('EveryXMinutes', '15'); + case 0.5: + return globalize.translate('EveryXMinutes', '30'); + case 0.75: + return globalize.translate('EveryXMinutes', '45'); + case 1: + return globalize.translate('EveryHour'); + default: + return globalize.translate('EveryXHours', hours); + } +} + +function localizeDayOfWeek(dayOfWeek: string | null | undefined, locale: Locale) { + if (!dayOfWeek) return ''; + + const parsedDayOfWeek = parse(dayOfWeek, 'cccc', new Date()); + + return format(parsedDayOfWeek, 'cccc', { locale: locale }); +} + +export function getTriggerFriendlyName(trigger: TaskTriggerInfo, locale: Locale) { + switch (trigger.Type) { + case 'DailyTrigger': + return globalize.translate('DailyAt', getDisplayTime(trigger.TimeOfDayTicks || 0, locale)); + case 'WeeklyTrigger': + return globalize.translate('WeeklyAt', localizeDayOfWeek(trigger.DayOfWeek, locale), getDisplayTime(trigger.TimeOfDayTicks || 0, locale)); + case 'IntervalTrigger': + return getIntervalTriggerTime(trigger.IntervalTicks || 0); + case 'StartupTrigger': + return globalize.translate('OnApplicationStartup'); + default: + return trigger.Type; + } +} diff --git a/src/apps/dashboard/features/scheduledtasks/utils/tasks.ts b/src/apps/dashboard/features/tasks/utils/tasks.ts similarity index 100% rename from src/apps/dashboard/features/scheduledtasks/utils/tasks.ts rename to src/apps/dashboard/features/tasks/utils/tasks.ts diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 5a7398da39..d66c4ed13d 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -15,6 +15,7 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'playback/trickplay', type: AppType.Dashboard }, { path: 'plugins/:pluginId', page: 'plugins/plugin', type: AppType.Dashboard }, { path: 'tasks', type: AppType.Dashboard }, + { path: 'tasks/edit', type: AppType.Dashboard }, { path: 'users', type: AppType.Dashboard }, { path: 'users/access', type: AppType.Dashboard }, { path: 'users/add', type: AppType.Dashboard }, diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts index b690b45440..6cd162d88d 100644 --- a/src/apps/dashboard/routes/_legacyRoutes.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -93,12 +93,5 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'plugins/installed/index', view: 'plugins/installed/index.html' } - }, { - path: 'tasks/edit', - pageProps: { - appType: AppType.Dashboard, - controller: 'scheduledtasks/scheduledtask', - view: 'scheduledtasks/scheduledtask.html' - } } ]; diff --git a/src/apps/dashboard/routes/tasks/edit.tsx b/src/apps/dashboard/routes/tasks/edit.tsx new file mode 100644 index 0000000000..5120f80f05 --- /dev/null +++ b/src/apps/dashboard/routes/tasks/edit.tsx @@ -0,0 +1,173 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import Page from 'components/Page'; +import { useSearchParams } from 'react-router-dom'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import AddIcon from '@mui/icons-material/Add'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; +import Loading from 'components/loading/LoadingComponent'; +import { MRT_ColumnDef, MRT_Table, useMaterialReactTable } from 'material-react-table'; +import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info'; +import globalize from '../../../../lib/globalize'; +import { useTask } from 'apps/dashboard/features/tasks/api/useTask'; +import { useUpdateTask } from 'apps/dashboard/features/tasks/api/useUpdateTask'; +import ConfirmDialog from 'components/ConfirmDialog'; +import TaskTriggerCell from 'apps/dashboard/features/tasks/components/TaskTriggerCell'; +import NewTriggerForm from 'apps/dashboard/features/tasks/components/NewTriggerForm'; + +const TaskEdit = () => { + const [ searchParams ] = useSearchParams(); + const updateTask = useUpdateTask(); + const taskId = searchParams.get('id'); + const { data: task, isLoading } = useTask({ taskId: taskId || '' }); + const [ isAddTriggerDialogOpen, setIsAddTriggerDialogOpen ] = useState(false); + const [ isRemoveConfirmOpen, setIsRemoveConfirmOpen ] = useState(false); + const [ pendingDeleteTrigger, setPendingDeleteTrigger ] = useState(null); + + const onCloseRemoveConfirmDialog = useCallback(() => { + setPendingDeleteTrigger(null); + setIsRemoveConfirmOpen(false); + }, []); + + const onDeleteTrigger = useCallback((trigger: TaskTriggerInfo | null | undefined) => { + if (trigger) { + setPendingDeleteTrigger(trigger); + setIsRemoveConfirmOpen(true); + } + }, []); + + const onConfirmDelete = useCallback(() => { + const triggersRemaining = task?.Triggers?.filter(trigger => trigger != pendingDeleteTrigger); + + if (task?.Id && triggersRemaining) { + updateTask.mutate({ + taskId: task.Id, + taskTriggerInfo: triggersRemaining + }); + setIsRemoveConfirmOpen(false); + } + }, [task, pendingDeleteTrigger, updateTask]); + + const showAddTriggerDialog = useCallback(() => { + setIsAddTriggerDialogOpen(true); + }, []); + + const handleNewTriggerDialogClose = useCallback(() => { + setIsAddTriggerDialogOpen(false); + }, []); + + const onNewTriggerSubmit = useCallback((trigger: TaskTriggerInfo) => { + if (task?.Triggers && task?.Id) { + const triggers = [...task.Triggers, trigger]; + + updateTask.mutate({ + taskId: task.Id, + taskTriggerInfo: triggers + }); + setIsAddTriggerDialogOpen(false); + } + }, [task, updateTask]); + + const columns = useMemo[]>(() => [ + { + id: 'TriggerTime', + accessorFn: row => row, + Cell: TaskTriggerCell, + header: globalize.translate('LabelTime') + } + ], []); + + const table = useMaterialReactTable({ + columns, + data: task?.Triggers || [], + + enableSorting: false, + enableFilters: false, + enableColumnActions: false, + enablePagination: false, + + state: { + isLoading + }, + + muiTableContainerProps: { + sx: { + maxHeight: 'calc(100% - 7rem)' // 2 x 3.5rem for header and footer + } + }, + + // Custom actions + enableRowActions: true, + positionActionsColumn: 'last', + displayColumnDefOptions: { + 'mrt-row-actions': { + header: '' + } + }, + renderRowActions: ({ row }) => { + return ( + + + onDeleteTrigger(row.original)} + > + + + + + ); + } + }); + + if (isLoading || !task) { + return ; + } + + return ( + + + + + + + {task.Name} + {task.Description} + + + + + + + ); +}; + +export default TaskEdit; diff --git a/src/apps/dashboard/routes/tasks/index.tsx b/src/apps/dashboard/routes/tasks/index.tsx index f742ef8ede..232fb34950 100644 --- a/src/apps/dashboard/routes/tasks/index.tsx +++ b/src/apps/dashboard/routes/tasks/index.tsx @@ -3,10 +3,10 @@ import Page from 'components/Page'; import globalize from 'lib/globalize'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; -import { QUERY_KEY, useTasks } from '../../features/scheduledtasks/api/useTasks'; -import { getCategories, getTasksByCategory } from '../../features/scheduledtasks/utils/tasks'; +import { QUERY_KEY, useTasks } from '../../features/tasks/api/useTasks'; +import { getCategories, getTasksByCategory } from '../../features/tasks/utils/tasks'; import Loading from 'components/loading/LoadingComponent'; -import Tasks from '../../features/scheduledtasks/components/Tasks'; +import Tasks from '../../features/tasks/components/Tasks'; import type { TaskInfo } from '@jellyfin/sdk/lib/generated-client/models/task-info'; import { SessionMessageType } from '@jellyfin/sdk/lib/generated-client/models/session-message-type'; import serverNotifications from 'scripts/serverNotifications'; From 934a05cffa4bb141126459091e8f1afb69892f64 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sun, 23 Feb 2025 14:39:15 +0300 Subject: [PATCH 02/10] Localize interval --- .../tasks/components/NewTriggerForm.tsx | 20 +++++++------------ .../tasks/constants/intervalDuration.ts | 13 ++++++++++++ .../dashboard/features/tasks/utils/edit.ts | 18 ++++++++++++++++- 3 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 src/apps/dashboard/features/tasks/constants/intervalDuration.ts diff --git a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx index 53404885fc..53d7663f3b 100644 --- a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx +++ b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx @@ -11,7 +11,7 @@ import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/ import { TaskTriggerInfoType } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info-type'; import { DayOfWeek } from '@jellyfin/sdk/lib/generated-client/models/day-of-week'; import globalize from 'lib/globalize'; -import { getTimeOfDayOptions } from '../utils/edit'; +import { getIntervalOptions, getTimeOfDayOptions } from '../utils/edit'; import { useLocale } from 'hooks/useLocale'; type IProps = { @@ -27,6 +27,8 @@ const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onSub const timeOfDayOptions = useMemo(() => getTimeOfDayOptions(dateFnsLocale), [dateFnsLocale]); + const intervalOptions = useMemo(() => getIntervalOptions(dateFnsLocale), [dateFnsLocale]); + const onTriggerTypeChange = useCallback((e: React.ChangeEvent) => { setTriggerType(e.target.value); }, []); @@ -124,20 +126,12 @@ const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onSub name='Interval' select fullWidth - defaultValue={'9000000000'} + defaultValue={intervalOptions[0].value} label={globalize.translate('LabelEveryXMinutes')} > - 15 minutes - 30 minutes - 45 minutes - 1 hour - 2 hours - 3 hours - 4 hours - 6 hours - 8 hours - 12 hours - 24 hours + {intervalOptions.map((option) => { + return {option.name}; + })} )} diff --git a/src/apps/dashboard/features/tasks/constants/intervalDuration.ts b/src/apps/dashboard/features/tasks/constants/intervalDuration.ts new file mode 100644 index 0000000000..01bd6ca7f5 --- /dev/null +++ b/src/apps/dashboard/features/tasks/constants/intervalDuration.ts @@ -0,0 +1,13 @@ +export const INTERVAL_DURATION: number[] = [ + 9000000000, // 15 minutes + 18000000000, // 30 minutes + 27000000000, // 45 minutes + 36000000000, // 1 hour + 72000000000, // 2 hours + 108000000000, // 3 hours + 144000000000, // 4 hours + 216000000000, // 6 hours + 288000000000, // 8 hours + 432000000000, // 12 hours + 864000000000 // 24 hours +]; diff --git a/src/apps/dashboard/features/tasks/utils/edit.ts b/src/apps/dashboard/features/tasks/utils/edit.ts index 2637465710..e71f563a71 100644 --- a/src/apps/dashboard/features/tasks/utils/edit.ts +++ b/src/apps/dashboard/features/tasks/utils/edit.ts @@ -1,6 +1,7 @@ import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info'; -import { format, Locale, parse } from 'date-fns'; +import { format, formatDistanceStrict, Locale, parse } from 'date-fns'; import globalize from 'lib/globalize'; +import { INTERVAL_DURATION } from '../constants/intervalDuration'; function getDisplayTime(ticks: number, locale: Locale) { const ms = ticks / 1e4; @@ -23,6 +24,21 @@ export function getTimeOfDayOptions(locale: Locale) { return options; } +export function getIntervalOptions(locale: Locale) { + const options = []; + + for (const ticksDuration of INTERVAL_DURATION) { + const durationMs = Math.floor(ticksDuration / 1e4); + const unit = durationMs < 36e5 ? 'minute' : 'hour'; + options.push({ + name: formatDistanceStrict(0, durationMs, { locale: locale, unit: unit }), + value: ticksDuration + }); + } + + return options; +} + function getIntervalTriggerTime(ticks: number) { const hours = ticks / 36e9; From 145c0700b13133d57590cc74308a2a08bccfdb67 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sun, 23 Feb 2025 14:54:25 +0300 Subject: [PATCH 03/10] Cleanup --- .../dashboard/features/tasks/api/useTask.ts | 5 -- .../dashboard/features/tasks/api/useTasks.ts | 2 +- .../features/tasks/api/useUpdateTask.ts | 2 +- .../tasks/components/NewTriggerForm.tsx | 75 ++++++++++--------- ...tervalDuration.ts => intervalDurations.ts} | 2 +- .../dashboard/features/tasks/utils/edit.ts | 4 +- src/apps/dashboard/routes/tasks/edit.tsx | 4 +- 7 files changed, 48 insertions(+), 46 deletions(-) rename src/apps/dashboard/features/tasks/constants/{intervalDuration.ts => intervalDurations.ts} (87%) diff --git a/src/apps/dashboard/features/tasks/api/useTask.ts b/src/apps/dashboard/features/tasks/api/useTask.ts index 98800420b4..78a430cb57 100644 --- a/src/apps/dashboard/features/tasks/api/useTask.ts +++ b/src/apps/dashboard/features/tasks/api/useTask.ts @@ -13,11 +13,6 @@ const fetchTask = async ( params: ScheduledTasksApiGetTaskRequest, options?: AxiosRequestConfig ) => { - if (!api) { - console.warn('[fetchTasks] No API instance available'); - return; - } - const response = await getScheduledTasksApi(api).getTask(params, options); return response.data; diff --git a/src/apps/dashboard/features/tasks/api/useTasks.ts b/src/apps/dashboard/features/tasks/api/useTasks.ts index 928ec4d639..7a4b6e1c4d 100644 --- a/src/apps/dashboard/features/tasks/api/useTasks.ts +++ b/src/apps/dashboard/features/tasks/api/useTasks.ts @@ -22,7 +22,7 @@ export const useTasks = (params?: ScheduledTasksApiGetTasksRequest) => { const { api } = useApi(); return useQuery({ - queryKey: [QUERY_KEY], + queryKey: [ QUERY_KEY ], queryFn: ({ signal }) => fetchTasks(api!, params, { signal }), enabled: !!api diff --git a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts index 8f34c0df38..47de050a7b 100644 --- a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts +++ b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts @@ -3,7 +3,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useMutation } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QUERY_KEY } from './useTasks'; +import { QUERY_KEY } from './useTask'; export const useUpdateTask = () => { const { api } = useApi(); diff --git a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx index 53d7663f3b..7819335669 100644 --- a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx +++ b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx @@ -18,21 +18,50 @@ type IProps = { open: boolean, title: string, onClose?: () => void, - onSubmit?: (trigger: TaskTriggerInfo) => void + onAdd?: (trigger: TaskTriggerInfo) => void }; -const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onSubmit }: IProps) => { +const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onAdd }: IProps) => { const { dateFnsLocale } = useLocale(); const [triggerType, setTriggerType] = useState('DailyTrigger'); const timeOfDayOptions = useMemo(() => getTimeOfDayOptions(dateFnsLocale), [dateFnsLocale]); - const intervalOptions = useMemo(() => getIntervalOptions(dateFnsLocale), [dateFnsLocale]); const onTriggerTypeChange = useCallback((e: React.ChangeEvent) => { setTriggerType(e.target.value); }, []); + const onSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault(); + + const formData = new FormData(e.currentTarget); + const data = Object.fromEntries(formData.entries()); + const trigger: TaskTriggerInfo = { + Type: data.TriggerType.toString() as TaskTriggerInfoType + }; + + if (trigger.Type == TaskTriggerInfoType.WeeklyTrigger) { + trigger.DayOfWeek = data.DayOfWeek.toString() as DayOfWeek; + } + + if (trigger.Type == TaskTriggerInfoType.DailyTrigger || trigger.Type == TaskTriggerInfoType.WeeklyTrigger) { + trigger.TimeOfDayTicks = parseInt(data.TimeOfDay.toString(), 10); + } + + if (trigger.Type == TaskTriggerInfoType.IntervalTrigger) { + trigger.IntervalTicks = parseInt(data.Interval.toString(), 10); + } + + if (data.TimeLimit.toString()) { + trigger.MaxRuntimeTicks = parseFloat(data.TimeLimit.toString()) * 3600000 * 1e4; + } + + if (onAdd) { + onAdd(trigger); + } + }, [onAdd]); + return ( = ({ open, title, onClose, onSub fullWidth PaperProps={{ component: 'form', - onSubmit: (e: React.FormEvent) => { - e.preventDefault(); - - const formData = new FormData(e.currentTarget); - const data = Object.fromEntries(formData.entries()); - const trigger: TaskTriggerInfo = { - Type: data.TriggerType.toString() as TaskTriggerInfoType - }; - - if (trigger.Type == TaskTriggerInfoType.WeeklyTrigger) { - trigger.DayOfWeek = data.DayOfWeek.toString() as DayOfWeek; - } - - if (trigger.Type == TaskTriggerInfoType.DailyTrigger || trigger.Type == TaskTriggerInfoType.WeeklyTrigger) { - trigger.TimeOfDayTicks = parseInt(data.TimeOfDay.toString(), 10); - } - - if (trigger.Type == TaskTriggerInfoType.IntervalTrigger) { - trigger.IntervalTicks = parseInt(data.Interval.toString(), 10); - } - - if (data.TimeLimit.toString()) { - trigger.MaxRuntimeTicks = parseFloat(data.TimeLimit.toString()) * 3600000 * 1e4; - } - - if (onSubmit) { - onSubmit(trigger); - } - } + onSubmit: onSubmit }} > {title} @@ -116,7 +117,10 @@ const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onSub label={globalize.translate('LabelTime')} > {timeOfDayOptions.map((option) => { - return {option.name}; + return {option.name}; })} )} @@ -130,7 +134,10 @@ const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onSub label={globalize.translate('LabelEveryXMinutes')} > {intervalOptions.map((option) => { - return {option.name}; + return {option.name}; })} )} diff --git a/src/apps/dashboard/features/tasks/constants/intervalDuration.ts b/src/apps/dashboard/features/tasks/constants/intervalDurations.ts similarity index 87% rename from src/apps/dashboard/features/tasks/constants/intervalDuration.ts rename to src/apps/dashboard/features/tasks/constants/intervalDurations.ts index 01bd6ca7f5..f90d0f0ddd 100644 --- a/src/apps/dashboard/features/tasks/constants/intervalDuration.ts +++ b/src/apps/dashboard/features/tasks/constants/intervalDurations.ts @@ -1,4 +1,4 @@ -export const INTERVAL_DURATION: number[] = [ +export const INTERVAL_DURATIONS: number[] = [ 9000000000, // 15 minutes 18000000000, // 30 minutes 27000000000, // 45 minutes diff --git a/src/apps/dashboard/features/tasks/utils/edit.ts b/src/apps/dashboard/features/tasks/utils/edit.ts index e71f563a71..536146e9c5 100644 --- a/src/apps/dashboard/features/tasks/utils/edit.ts +++ b/src/apps/dashboard/features/tasks/utils/edit.ts @@ -1,7 +1,7 @@ import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info'; import { format, formatDistanceStrict, Locale, parse } from 'date-fns'; import globalize from 'lib/globalize'; -import { INTERVAL_DURATION } from '../constants/intervalDuration'; +import { INTERVAL_DURATIONS } from '../constants/intervalDurations'; function getDisplayTime(ticks: number, locale: Locale) { const ms = ticks / 1e4; @@ -27,7 +27,7 @@ export function getTimeOfDayOptions(locale: Locale) { export function getIntervalOptions(locale: Locale) { const options = []; - for (const ticksDuration of INTERVAL_DURATION) { + for (const ticksDuration of INTERVAL_DURATIONS) { const durationMs = Math.floor(ticksDuration / 1e4); const unit = durationMs < 36e5 ? 'minute' : 'hour'; options.push({ diff --git a/src/apps/dashboard/routes/tasks/edit.tsx b/src/apps/dashboard/routes/tasks/edit.tsx index 5120f80f05..e203dde1f7 100644 --- a/src/apps/dashboard/routes/tasks/edit.tsx +++ b/src/apps/dashboard/routes/tasks/edit.tsx @@ -60,7 +60,7 @@ const TaskEdit = () => { setIsAddTriggerDialogOpen(false); }, []); - const onNewTriggerSubmit = useCallback((trigger: TaskTriggerInfo) => { + const onNewTriggerAdd = useCallback((trigger: TaskTriggerInfo) => { if (task?.Triggers && task?.Id) { const triggers = [...task.Triggers, trigger]; @@ -150,7 +150,7 @@ const TaskEdit = () => { open={isAddTriggerDialogOpen} title={globalize.translate('ButtonAddScheduledTaskTrigger')} onClose={handleNewTriggerDialogClose} - onSubmit={onNewTriggerSubmit} + onAdd={onNewTriggerAdd} /> From 40cb7c6f65e607ba03c302c74122aa8ee79f2c38 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sun, 23 Feb 2025 16:59:46 +0300 Subject: [PATCH 04/10] Invalidate by task id --- src/apps/dashboard/features/tasks/api/useUpdateTask.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts index 47de050a7b..6bc92eacad 100644 --- a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts +++ b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts @@ -13,9 +13,9 @@ export const useUpdateTask = () => { getScheduledTasksApi(api!) .updateTask(params) ), - onSuccess: () => { + onSuccess: (_data, params) => { void queryClient.invalidateQueries({ - queryKey: [ QUERY_KEY ] + queryKey: [ QUERY_KEY, params.taskId ] }); } }); From dd539b89caef9aac702d089ac7c5cdb22aab78f4 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sun, 23 Feb 2025 17:13:56 +0300 Subject: [PATCH 05/10] Refactor into enums --- .../tasks/components/NewTriggerForm.tsx | 38 +++++++++---------- .../features/tasks/components/Task.tsx | 14 ++----- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx index 7819335669..a5eef10723 100644 --- a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx +++ b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx @@ -23,13 +23,13 @@ type IProps = { const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onAdd }: IProps) => { const { dateFnsLocale } = useLocale(); - const [triggerType, setTriggerType] = useState('DailyTrigger'); + const [triggerType, setTriggerType] = useState(TaskTriggerInfoType.DailyTrigger); const timeOfDayOptions = useMemo(() => getTimeOfDayOptions(dateFnsLocale), [dateFnsLocale]); const intervalOptions = useMemo(() => getIntervalOptions(dateFnsLocale), [dateFnsLocale]); const onTriggerTypeChange = useCallback((e: React.ChangeEvent) => { - setTriggerType(e.target.value); + setTriggerType(e.target.value as TaskTriggerInfoType); }, []); const onSubmit = useCallback((e: React.FormEvent) => { @@ -54,13 +54,13 @@ const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onAdd } if (data.TimeLimit.toString()) { - trigger.MaxRuntimeTicks = parseFloat(data.TimeLimit.toString()) * 3600000 * 1e4; + trigger.MaxRuntimeTicks = parseFloat(data.TimeLimit.toString()) * 36e9; } if (onAdd) { onAdd(trigger); } - }, [onAdd]); + }, [ onAdd ]); return ( = ({ open, title, onClose, onAdd onChange={onTriggerTypeChange} label={globalize.translate('LabelTriggerType')} > - {globalize.translate('OptionDaily')} - {globalize.translate('OptionWeekly')} - {globalize.translate('OptionOnInterval')} - {globalize.translate('OnApplicationStartup')} + {globalize.translate('OptionDaily')} + {globalize.translate('OptionWeekly')} + {globalize.translate('OptionOnInterval')} + {globalize.translate('OnApplicationStartup')} - {triggerType == 'WeeklyTrigger' && ( + {triggerType == TaskTriggerInfoType.WeeklyTrigger && ( - {globalize.translate('Sunday')} - {globalize.translate('Monday')} - {globalize.translate('Tuesday')} - {globalize.translate('Wednesday')} - {globalize.translate('Thursday')} - {globalize.translate('Friday')} - {globalize.translate('Saturday')} + {globalize.translate('Sunday')} + {globalize.translate('Monday')} + {globalize.translate('Tuesday')} + {globalize.translate('Wednesday')} + {globalize.translate('Thursday')} + {globalize.translate('Friday')} + {globalize.translate('Saturday')} )} - {['DailyTrigger', 'WeeklyTrigger'].includes(triggerType) && ( + {(triggerType == TaskTriggerInfoType.DailyTrigger || triggerType == TaskTriggerInfoType.WeeklyTrigger) && ( = ({ open, title, onClose, onAdd )} - {triggerType == 'IntervalTrigger' && ( + {triggerType == TaskTriggerInfoType.IntervalTrigger && ( = ({ task }: TaskProps) => { const startTask = useStartTask(); const stopTask = useStopTask(); - const navigateTaskEdit = useCallback(() => { - Dashboard.navigate(`/dashboard/tasks/edit?id=${task.Id}`) - .catch(err => { - console.error('[Task] failed to navigate to task edit page', err); - }); - }, [task]); - const handleStartTask = useCallback(() => { if (task.Id) { startTask.mutate({ taskId: task.Id }); @@ -48,7 +40,7 @@ const Task: FunctionComponent = ({ task }: TaskProps) => { } > - + @@ -59,7 +51,7 @@ const Task: FunctionComponent = ({ task }: TaskProps) => { secondary={task.State == 'Running' ? : } disableTypography /> - + ); }; From a7621d242d20ae1ec0bf33fa4f1324a365cd9fe5 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sun, 23 Feb 2025 17:38:31 +0300 Subject: [PATCH 06/10] Refactor query keys to enum --- src/apps/dashboard/features/tasks/api/queryKey.ts | 4 ++++ src/apps/dashboard/features/tasks/api/useStartTask.ts | 4 ++-- src/apps/dashboard/features/tasks/api/useStopTask.ts | 4 ++-- src/apps/dashboard/features/tasks/api/useTask.ts | 5 ++--- src/apps/dashboard/features/tasks/api/useTasks.ts | 5 ++--- src/apps/dashboard/features/tasks/api/useUpdateTask.ts | 4 ++-- src/apps/dashboard/routes/tasks/index.tsx | 7 ++++--- 7 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 src/apps/dashboard/features/tasks/api/queryKey.ts diff --git a/src/apps/dashboard/features/tasks/api/queryKey.ts b/src/apps/dashboard/features/tasks/api/queryKey.ts new file mode 100644 index 0000000000..83d23b3568 --- /dev/null +++ b/src/apps/dashboard/features/tasks/api/queryKey.ts @@ -0,0 +1,4 @@ +export enum QueryKey { + Task = 'Task', + Tasks = 'Tasks' +}; diff --git a/src/apps/dashboard/features/tasks/api/useStartTask.ts b/src/apps/dashboard/features/tasks/api/useStartTask.ts index ef37b7e6e8..5258904ae3 100644 --- a/src/apps/dashboard/features/tasks/api/useStartTask.ts +++ b/src/apps/dashboard/features/tasks/api/useStartTask.ts @@ -3,7 +3,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useMutation } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QUERY_KEY } from './useTasks'; +import { QueryKey } from './queryKey'; export const useStartTask = () => { const { api } = useApi(); @@ -15,7 +15,7 @@ export const useStartTask = () => { ), onSuccess: () => { void queryClient.invalidateQueries({ - queryKey: [ QUERY_KEY ] + queryKey: [ QueryKey.Tasks ] }); } }); diff --git a/src/apps/dashboard/features/tasks/api/useStopTask.ts b/src/apps/dashboard/features/tasks/api/useStopTask.ts index 30421ee6af..49ce910c7f 100644 --- a/src/apps/dashboard/features/tasks/api/useStopTask.ts +++ b/src/apps/dashboard/features/tasks/api/useStopTask.ts @@ -3,7 +3,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useMutation } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QUERY_KEY } from './useTasks'; +import { QueryKey } from './queryKey'; export const useStopTask = () => { const { api } = useApi(); @@ -15,7 +15,7 @@ export const useStopTask = () => { ), onSuccess: () => { void queryClient.invalidateQueries({ - queryKey: [ QUERY_KEY ] + queryKey: [ QueryKey.Tasks ] }); } }); diff --git a/src/apps/dashboard/features/tasks/api/useTask.ts b/src/apps/dashboard/features/tasks/api/useTask.ts index 78a430cb57..9ced51614b 100644 --- a/src/apps/dashboard/features/tasks/api/useTask.ts +++ b/src/apps/dashboard/features/tasks/api/useTask.ts @@ -5,8 +5,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; - -export const QUERY_KEY = 'Task'; +import { QueryKey } from './queryKey'; const fetchTask = async ( api: Api, @@ -22,7 +21,7 @@ export const useTask = (params: ScheduledTasksApiGetTaskRequest) => { const { api } = useApi(); return useQuery({ - queryKey: [ QUERY_KEY, params.taskId ], + queryKey: [ QueryKey.Task, params.taskId ], queryFn: ({ signal }) => fetchTask(api!, params, { signal }), enabled: !!api diff --git a/src/apps/dashboard/features/tasks/api/useTasks.ts b/src/apps/dashboard/features/tasks/api/useTasks.ts index 7a4b6e1c4d..592d4c4c94 100644 --- a/src/apps/dashboard/features/tasks/api/useTasks.ts +++ b/src/apps/dashboard/features/tasks/api/useTasks.ts @@ -5,8 +5,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; - -export const QUERY_KEY = 'Tasks'; +import { QueryKey } from './queryKey'; const fetchTasks = async ( api: Api, @@ -22,7 +21,7 @@ export const useTasks = (params?: ScheduledTasksApiGetTasksRequest) => { const { api } = useApi(); return useQuery({ - queryKey: [ QUERY_KEY ], + queryKey: [ QueryKey.Tasks ], queryFn: ({ signal }) => fetchTasks(api!, params, { signal }), enabled: !!api diff --git a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts index 6bc92eacad..4d3f7c87c0 100644 --- a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts +++ b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts @@ -3,7 +3,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useMutation } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QUERY_KEY } from './useTask'; +import { QueryKey } from './queryKey'; export const useUpdateTask = () => { const { api } = useApi(); @@ -15,7 +15,7 @@ export const useUpdateTask = () => { ), onSuccess: (_data, params) => { void queryClient.invalidateQueries({ - queryKey: [ QUERY_KEY, params.taskId ] + queryKey: [ QueryKey.Task, params.taskId ] }); } }); diff --git a/src/apps/dashboard/routes/tasks/index.tsx b/src/apps/dashboard/routes/tasks/index.tsx index 232fb34950..7dd66224d4 100644 --- a/src/apps/dashboard/routes/tasks/index.tsx +++ b/src/apps/dashboard/routes/tasks/index.tsx @@ -3,7 +3,7 @@ import Page from 'components/Page'; import globalize from 'lib/globalize'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; -import { QUERY_KEY, useTasks } from '../../features/tasks/api/useTasks'; +import { useTasks } from '../../features/tasks/api/useTasks'; import { getCategories, getTasksByCategory } from '../../features/tasks/utils/tasks'; import Loading from 'components/loading/LoadingComponent'; import Tasks from '../../features/tasks/components/Tasks'; @@ -14,6 +14,7 @@ import Events, { Event } from 'utils/events'; import { ApiClient } from 'jellyfin-apiclient'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; +import { QueryKey } from 'apps/dashboard/features/tasks/api/queryKey'; export const Component = () => { const { __legacyApiClient__ } = useApi(); @@ -22,13 +23,13 @@ export const Component = () => { // TODO: Replace usage of the legacy apiclient when websocket support is added to the TS SDK. useEffect(() => { const onScheduledTasksUpdate = (_e: Event, _apiClient: ApiClient, info: TaskInfo[]) => { - queryClient.setQueryData([ QUERY_KEY ], info); + queryClient.setQueryData([ QueryKey.Tasks ], info); }; const fallbackInterval = setInterval(() => { if (!__legacyApiClient__?.isMessageChannelOpen()) { void queryClient.invalidateQueries({ - queryKey: [ QUERY_KEY ] + queryKey: [ QueryKey.Tasks ] }); } }, 1e4); From 560881bdeaeea2ba0733134a7fe742a655491a02 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sun, 23 Feb 2025 22:34:03 +0300 Subject: [PATCH 07/10] Add onClose --- src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx index a5eef10723..4cec4b0474 100644 --- a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx +++ b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx @@ -67,6 +67,7 @@ const NewTriggerForm: FunctionComponent = ({ open, title, onClose, onAdd open={open} maxWidth={'xs'} fullWidth + onClose={onClose} PaperProps={{ component: 'form', onSubmit: onSubmit From 33ac6d75eac4604b618dc133a64f08e774c67175 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sun, 23 Feb 2025 22:35:25 +0300 Subject: [PATCH 08/10] Add onClose to ConfirmDialog for consistency --- src/components/ConfirmDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx index 08efb3dd2b..bc3c31ced1 100644 --- a/src/components/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog.tsx @@ -27,7 +27,7 @@ const ConfirmDialog: FC = ({ onConfirm, ...dialogProps }) => ( - + {title} From 53a1cb413befed18546d23f67d443bb9ca2e8fb6 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 26 Feb 2025 20:08:21 +0300 Subject: [PATCH 09/10] Use id as path parameter --- src/apps/dashboard/features/tasks/components/Task.tsx | 2 +- src/apps/dashboard/routes/_asyncRoutes.ts | 2 +- src/apps/dashboard/routes/tasks/{edit.tsx => task.tsx} | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) rename src/apps/dashboard/routes/tasks/{edit.tsx => task.tsx} (96%) diff --git a/src/apps/dashboard/features/tasks/components/Task.tsx b/src/apps/dashboard/features/tasks/components/Task.tsx index eca9a665b5..ef140c0cb3 100644 --- a/src/apps/dashboard/features/tasks/components/Task.tsx +++ b/src/apps/dashboard/features/tasks/components/Task.tsx @@ -40,7 +40,7 @@ const Task: FunctionComponent = ({ task }: TaskProps) => { } > - + diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index d66c4ed13d..4ba4d5e5e1 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -15,7 +15,7 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'playback/trickplay', type: AppType.Dashboard }, { path: 'plugins/:pluginId', page: 'plugins/plugin', type: AppType.Dashboard }, { path: 'tasks', type: AppType.Dashboard }, - { path: 'tasks/edit', type: AppType.Dashboard }, + { path: 'tasks/:id', page: 'tasks/task', type: AppType.Dashboard }, { path: 'users', type: AppType.Dashboard }, { path: 'users/access', type: AppType.Dashboard }, { path: 'users/add', type: AppType.Dashboard }, diff --git a/src/apps/dashboard/routes/tasks/edit.tsx b/src/apps/dashboard/routes/tasks/task.tsx similarity index 96% rename from src/apps/dashboard/routes/tasks/edit.tsx rename to src/apps/dashboard/routes/tasks/task.tsx index e203dde1f7..2aa00f886c 100644 --- a/src/apps/dashboard/routes/tasks/edit.tsx +++ b/src/apps/dashboard/routes/tasks/task.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import Page from 'components/Page'; -import { useSearchParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Stack from '@mui/material/Stack'; @@ -19,10 +19,9 @@ import ConfirmDialog from 'components/ConfirmDialog'; import TaskTriggerCell from 'apps/dashboard/features/tasks/components/TaskTriggerCell'; import NewTriggerForm from 'apps/dashboard/features/tasks/components/NewTriggerForm'; -const TaskEdit = () => { - const [ searchParams ] = useSearchParams(); +export const Component = () => { + const { id: taskId } = useParams(); const updateTask = useUpdateTask(); - const taskId = searchParams.get('id'); const { data: task, isLoading } = useTask({ taskId: taskId || '' }); const [ isAddTriggerDialogOpen, setIsAddTriggerDialogOpen ] = useState(false); const [ isRemoveConfirmOpen, setIsRemoveConfirmOpen ] = useState(false); @@ -170,4 +169,4 @@ const TaskEdit = () => { ); }; -export default TaskEdit; +Component.displayName = 'TaskPage'; From 709378f9867272ca30cfc380b9becc04dcd49f2e Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:36:05 +0300 Subject: [PATCH 10/10] Use Tasks as query key --- src/apps/dashboard/features/tasks/api/queryKey.ts | 4 ---- src/apps/dashboard/features/tasks/api/useStartTask.ts | 4 ++-- src/apps/dashboard/features/tasks/api/useStopTask.ts | 4 ++-- src/apps/dashboard/features/tasks/api/useTask.ts | 4 ++-- src/apps/dashboard/features/tasks/api/useTasks.ts | 5 +++-- src/apps/dashboard/features/tasks/api/useUpdateTask.ts | 4 ++-- src/apps/dashboard/routes/tasks/index.tsx | 7 +++---- 7 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 src/apps/dashboard/features/tasks/api/queryKey.ts diff --git a/src/apps/dashboard/features/tasks/api/queryKey.ts b/src/apps/dashboard/features/tasks/api/queryKey.ts deleted file mode 100644 index 83d23b3568..0000000000 --- a/src/apps/dashboard/features/tasks/api/queryKey.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum QueryKey { - Task = 'Task', - Tasks = 'Tasks' -}; diff --git a/src/apps/dashboard/features/tasks/api/useStartTask.ts b/src/apps/dashboard/features/tasks/api/useStartTask.ts index 5258904ae3..ef37b7e6e8 100644 --- a/src/apps/dashboard/features/tasks/api/useStartTask.ts +++ b/src/apps/dashboard/features/tasks/api/useStartTask.ts @@ -3,7 +3,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useMutation } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QueryKey } from './queryKey'; +import { QUERY_KEY } from './useTasks'; export const useStartTask = () => { const { api } = useApi(); @@ -15,7 +15,7 @@ export const useStartTask = () => { ), onSuccess: () => { void queryClient.invalidateQueries({ - queryKey: [ QueryKey.Tasks ] + queryKey: [ QUERY_KEY ] }); } }); diff --git a/src/apps/dashboard/features/tasks/api/useStopTask.ts b/src/apps/dashboard/features/tasks/api/useStopTask.ts index 49ce910c7f..30421ee6af 100644 --- a/src/apps/dashboard/features/tasks/api/useStopTask.ts +++ b/src/apps/dashboard/features/tasks/api/useStopTask.ts @@ -3,7 +3,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useMutation } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QueryKey } from './queryKey'; +import { QUERY_KEY } from './useTasks'; export const useStopTask = () => { const { api } = useApi(); @@ -15,7 +15,7 @@ export const useStopTask = () => { ), onSuccess: () => { void queryClient.invalidateQueries({ - queryKey: [ QueryKey.Tasks ] + queryKey: [ QUERY_KEY ] }); } }); diff --git a/src/apps/dashboard/features/tasks/api/useTask.ts b/src/apps/dashboard/features/tasks/api/useTask.ts index 9ced51614b..9df4adaefa 100644 --- a/src/apps/dashboard/features/tasks/api/useTask.ts +++ b/src/apps/dashboard/features/tasks/api/useTask.ts @@ -5,7 +5,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; -import { QueryKey } from './queryKey'; +import { QUERY_KEY } from './useTasks'; const fetchTask = async ( api: Api, @@ -21,7 +21,7 @@ export const useTask = (params: ScheduledTasksApiGetTaskRequest) => { const { api } = useApi(); return useQuery({ - queryKey: [ QueryKey.Task, params.taskId ], + queryKey: [ QUERY_KEY, params.taskId ], queryFn: ({ signal }) => fetchTask(api!, params, { signal }), enabled: !!api diff --git a/src/apps/dashboard/features/tasks/api/useTasks.ts b/src/apps/dashboard/features/tasks/api/useTasks.ts index 592d4c4c94..7a4b6e1c4d 100644 --- a/src/apps/dashboard/features/tasks/api/useTasks.ts +++ b/src/apps/dashboard/features/tasks/api/useTasks.ts @@ -5,7 +5,8 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useQuery } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; -import { QueryKey } from './queryKey'; + +export const QUERY_KEY = 'Tasks'; const fetchTasks = async ( api: Api, @@ -21,7 +22,7 @@ export const useTasks = (params?: ScheduledTasksApiGetTasksRequest) => { const { api } = useApi(); return useQuery({ - queryKey: [ QueryKey.Tasks ], + queryKey: [ QUERY_KEY ], queryFn: ({ signal }) => fetchTasks(api!, params, { signal }), enabled: !!api diff --git a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts index 4d3f7c87c0..dd0e8ac72b 100644 --- a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts +++ b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts @@ -3,7 +3,7 @@ import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-task import { useMutation } from '@tanstack/react-query'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QueryKey } from './queryKey'; +import { QUERY_KEY } from './useTasks'; export const useUpdateTask = () => { const { api } = useApi(); @@ -15,7 +15,7 @@ export const useUpdateTask = () => { ), onSuccess: (_data, params) => { void queryClient.invalidateQueries({ - queryKey: [ QueryKey.Task, params.taskId ] + queryKey: [ QUERY_KEY, params.taskId ] }); } }); diff --git a/src/apps/dashboard/routes/tasks/index.tsx b/src/apps/dashboard/routes/tasks/index.tsx index 7dd66224d4..232fb34950 100644 --- a/src/apps/dashboard/routes/tasks/index.tsx +++ b/src/apps/dashboard/routes/tasks/index.tsx @@ -3,7 +3,7 @@ import Page from 'components/Page'; import globalize from 'lib/globalize'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; -import { useTasks } from '../../features/tasks/api/useTasks'; +import { QUERY_KEY, useTasks } from '../../features/tasks/api/useTasks'; import { getCategories, getTasksByCategory } from '../../features/tasks/utils/tasks'; import Loading from 'components/loading/LoadingComponent'; import Tasks from '../../features/tasks/components/Tasks'; @@ -14,7 +14,6 @@ import Events, { Event } from 'utils/events'; import { ApiClient } from 'jellyfin-apiclient'; import { useApi } from 'hooks/useApi'; import { queryClient } from 'utils/query/queryClient'; -import { QueryKey } from 'apps/dashboard/features/tasks/api/queryKey'; export const Component = () => { const { __legacyApiClient__ } = useApi(); @@ -23,13 +22,13 @@ export const Component = () => { // TODO: Replace usage of the legacy apiclient when websocket support is added to the TS SDK. useEffect(() => { const onScheduledTasksUpdate = (_e: Event, _apiClient: ApiClient, info: TaskInfo[]) => { - queryClient.setQueryData([ QueryKey.Tasks ], info); + queryClient.setQueryData([ QUERY_KEY ], info); }; const fallbackInterval = setInterval(() => { if (!__legacyApiClient__?.isMessageChannelOpen()) { void queryClient.invalidateQueries({ - queryKey: [ QueryKey.Tasks ] + queryKey: [ QUERY_KEY ] }); } }, 1e4);