diff --git a/.eslintrc.js b/.eslintrc.js index 16b883880..2490d3526 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,9 +3,9 @@ module.exports = { 'mantine', 'plugin:@next/next/recommended', 'plugin:jest/recommended', - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', ], plugins: ['testing-library', 'jest', 'react-hooks', 'react', 'unused-imports'], overrides: [ @@ -20,12 +20,13 @@ module.exports = { rules: { 'react/react-in-jsx-scope': 'off', 'react/no-children-prop': 'off', - "unused-imports/no-unused-imports": "warn", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-unused-imports": "off", - "@typescript-eslint/no-unused-expressions": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-shadow": "off", - "@typescript-eslint/no-use-before-define": "off", + 'unused-imports/no-unused-imports': 'warn', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-unused-imports': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-shadow': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', }, }; diff --git a/@types/react-i18next.d.ts b/@types/react-i18next.d.ts new file mode 100644 index 000000000..30da9af59 --- /dev/null +++ b/@types/react-i18next.d.ts @@ -0,0 +1,68 @@ +import 'react-i18next'; + +import common from '../public/locales/en/common.json'; +import appShelf from '../public/locales/en/layout/app-shelf.json'; +import addServiceAppShelf from '../public/locales/en/layout/add-service-app-shelf.json'; +import appShelfMenu from '../public/locales/en/layout/app-shelf-menu.json'; +import commonSettings from '../public/locales/en/settings/common.json'; +import themeSelector from '../public/locales/en/settings/general/theme-selector.json'; +import configChanger from '../public/locales/en/settings/general/config-changer.json'; +import i18n from '../public/locales/en/settings/general/internationalization.json'; +import moduleEnabler from '../public/locales/en/settings/general/module-enabler.json'; +import searchEngine from '../public/locales/en/settings/general/search-engine.json'; +import widgetPositions from '../public/locales/en/settings/general/widget-positions.json'; +import colorSelector from '../public/locales/en/settings/customization/color-selector.json'; +import pageAppearance from '../public/locales/en/settings/customization/page-appearance.json'; +import shadeSelector from '../public/locales/en/settings/customization/shade-selector.json'; +import appWidth from '../public/locales/en/settings/customization/app-width.json'; +import opacitySelector from '../public/locales/en/settings/customization/opacity-selector.json'; +import commonModule from '../public/locales/en/modules/common.json'; +import dateModule from '../public/locales/en/modules/date.json'; +import calendarModule from '../public/locales/en/modules/calendar.json'; +import dlSpeedModule from '../public/locales/en/modules/dlspeed.json'; +import usenetModule from '../public/locales/en/modules/usenet.json'; +import searchModule from '../public/locales/en/modules/search.json'; +import torrentsModule from '../public/locales/en/modules/torrents-status.json'; +import weatherModule from '../public/locales/en/modules/weather.json'; +import pingModule from '../public/locales/en/modules/ping.json'; +import dockerModule from '../public/locales/en/modules/docker.json'; +import dashDotModule from '../public/locales/en/modules/dashdot.json'; +import overseerrModule from '../public/locales/en/modules/overseerr.json'; +import mediaCardsModule from '../public/locales/en/modules/common-media-cards.json'; + +declare module 'react-i18next' { + interface CustomTypeOptions { + defaultNS: 'common'; + resources: { + common: typeof common; + 'layout/app-shelf': typeof appShelf; + 'layout/add-service-app-shelf': typeof addServiceAppShelf; + 'layout/app-shelf-menu': typeof appShelfMenu; + 'settings/common': typeof commonSettings; + 'settings/general/theme-selector': typeof themeSelector; + 'settings/general/config-changer': typeof configChanger; + 'settings/general/internationalization': typeof i18n; + 'settings/general/module-enabler': typeof moduleEnabler; + 'settings/general/search-engine': typeof searchEngine; + 'settings/general/widget-positions': typeof widgetPositions; + 'settings/customization/color-selector': typeof colorSelector; + 'settings/customization/page-appearance': typeof pageAppearance; + 'settings/customization/shade-selector': typeof shadeSelector; + 'settings/customization/app-width': typeof appWidth; + 'settings/customization/opacity-selector': typeof opacitySelector; + 'modules/common': typeof commonModule; + 'modules/date': typeof dateModule; + 'modules/calendar': typeof calendarModule; + 'modules/dlspeed': typeof dlSpeedModule; + 'modules/usenet': typeof usenetModule; + 'modules/search': typeof searchModule; + 'modules/torrents-status': typeof torrentsModule; + 'modules/weather': typeof weatherModule; + 'modules/ping': typeof pingModule; + 'modules/docker': typeof dockerModule; + 'modules/dashdot': typeof dashDotModule; + 'modules/overseerr': typeof overseerrModule; + 'modules/common-media-cards': typeof mediaCardsModule; + }; + } +} diff --git a/data/configs/default.json b/data/configs/default.json index 3f2acea0d..415c095ab 100644 --- a/data/configs/default.json +++ b/data/configs/default.json @@ -7,14 +7,42 @@ "type": "Other", "icon": "https://c.tenor.com/o656qFKDzeUAAAAC/rick-astley-never-gonna-give-you-up.gif", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + }, + { + "id": "4cb1c2af-9f23-4f52-86e1-0eac25673b56", + "type": "Sabnzbd", + "name": "Sabnzbd", + "icon": "/favicon.png", + "url": "https://sabnzbd.jannesv.be", + "apiKey": "2982e4afbc6d42d6bb5863751a354d20", + "openedUrl": "https://sabnzbd.jannesv.be" } ], "settings": { - "searchUrl": "https://google.com/search?q=" + "searchUrl": "https://google.com/search?q=", + "primaryColor": "grape" }, "modules": { "Search Bar": { "enabled": true + }, + "Download Speed": { + "enabled": false + }, + "Torrent": { + "enabled": false + }, + "Dash.": { + "enabled": true + }, + "Usenet": { + "enabled": true + }, + "usenet": { + "enabled": true + }, + "docker": { + "enabled": true } } } \ No newline at end of file diff --git a/public/locales/en/modules/usenet.json b/public/locales/en/modules/usenet.json new file mode 100644 index 000000000..59f298103 --- /dev/null +++ b/public/locales/en/modules/usenet.json @@ -0,0 +1,35 @@ +{ + "descriptor": { + "name": "Usenet", + "description": "Show the queue and history of supported services" + }, + "tabs": { + "queue": "Queue", + "history": "History" + }, + "info": { + "sizeLeft": "Size left", + "paused": "Paused" + }, + "queue": { + "header": { + "name": "Name", + "size": "Size", + "eta": "ETA", + "progress": "Progress" + }, + "empty": "Queue is empty.", + "error": "Some error has occured while fetching data:", + "paused": "Paused" + }, + "history": { + "header": { + "name": "Name", + "size": "Size", + "duration": "Download Duration" + }, + "empty": "Queue is empty.", + "error": "Some error has occured while fetching data:", + "paused": "Paused" + } +} diff --git a/src/modules/usenet/UsenetHistoryList.tsx b/src/modules/usenet/UsenetHistoryList.tsx index b464f7653..ba5a29601 100644 --- a/src/modules/usenet/UsenetHistoryList.tsx +++ b/src/modules/usenet/UsenetHistoryList.tsx @@ -14,6 +14,7 @@ import { IconAlertCircle } from '@tabler/icons'; import { AxiosError } from 'axios'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; +import { useTranslation } from 'next-i18next'; import { FunctionComponent, useState } from 'react'; import { useGetUsenetHistory } from '../../tools/hooks/api'; import { humanFileSize } from '../../tools/humanFileSize'; @@ -28,6 +29,7 @@ const PAGE_SIZE = 10; export const UsenetHistoryList: FunctionComponent = ({ serviceId }) => { const [page, setPage] = useState(1); + const { t } = useTranslation('modules/usenet'); const { data, isLoading, isError, error } = useGetUsenetHistory({ limit: PAGE_SIZE, @@ -50,7 +52,7 @@ export const UsenetHistoryList: FunctionComponent = ({ s return ( } my="lg" title="Error!" color="red" radius="md"> - Some error has occured while fetching data: + {t('history.error')} {(error as AxiosError)?.response?.data as string} @@ -62,13 +64,13 @@ export const UsenetHistoryList: FunctionComponent = ({ s if (!data || data.items.length <= 0) { return (
- History is empty + {t('history.empty')}
); } return ( -
+ <> @@ -77,9 +79,9 @@ export const UsenetHistoryList: FunctionComponent = ({ s - - - + + + @@ -113,14 +115,16 @@ export const UsenetHistoryList: FunctionComponent = ({ s ))}
NameSizeDownload Duration{t('history.header.name')}{t('history.header.size')}{t('history.header.duration')}
- -
+ {totalPages > 1 && ( + + )} + ); }; diff --git a/src/modules/usenet/UsenetModule.tsx b/src/modules/usenet/UsenetModule.tsx index da9837767..155734d59 100644 --- a/src/modules/usenet/UsenetModule.tsx +++ b/src/modules/usenet/UsenetModule.tsx @@ -1,16 +1,27 @@ -import { Group, Select, Tabs } from '@mantine/core'; -import { IconDownload } from '@tabler/icons'; +import { Badge, Button, Group, Select, Tabs } from '@mantine/core'; +import { IconDownload, IconPlayerPause, IconPlayerPlay } from '@tabler/icons'; import { FunctionComponent, useState } from 'react'; +import { useTranslation } from 'next-i18next'; +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; import { IModule } from '../ModuleTypes'; import { UsenetQueueList } from './UsenetQueueList'; import { UsenetHistoryList } from './UsenetHistoryList'; import { useGetServiceByType } from '../../tools/hooks/useGetServiceByType'; +import { useGetUsenetInfo, usePauseUsenetQueue, useResumeUsenetQueue } from '../../tools/hooks/api'; +import { humanFileSize } from '../../tools/humanFileSize'; + +dayjs.extend(duration); export const UsenetComponent: FunctionComponent = () => { const downloadServices = useGetServiceByType('Sabnzbd'); + const { t } = useTranslation('modules/usenet'); const [selectedServiceId, setSelectedService] = useState(downloadServices[0]?.id); + const { data } = useGetUsenetInfo({ serviceId: selectedServiceId! }); + const { mutate: pause } = usePauseUsenetQueue({ serviceId: selectedServiceId! }); + const { mutate: resume } = useResumeUsenetQueue({ serviceId: selectedServiceId! }); if (!selectedServiceId) { return null; @@ -20,8 +31,26 @@ export const UsenetComponent: FunctionComponent = () => { - Queue - History + {t('tabs.queue')} + {t('tabs.history')} + {data && ( + + {humanFileSize(data?.speed)}/s + + {t('info.sizeLeft')}: {humanFileSize(data?.sizeLeft)} + + {data.paused ? ( + + ) : ( + + )} + + )} {downloadServices.length > 1 && (