From b6bf6cc86b06063ad96fac6f7693e0a1263179e5 Mon Sep 17 00:00:00 2001 From: Manuel Ruwe Date: Thu, 8 Dec 2022 22:17:33 +0100 Subject: [PATCH] feat: remove unused appshelf components --- src/components/AppShelf/AddAppShelfItem.tsx | 460 ------------------ src/components/AppShelf/AppShelfItem.tsx | 4 +- src/components/AppShelf/AppShelfMenu.tsx | 86 ---- src/components/Dashboard/Menu/TileMenu.tsx | 76 +++ .../ChangePosition/ChangePositionModal.tsx | 31 ++ .../AddElementAction/AddElementAction.tsx | 3 - src/components/layout/header/Header.tsx | 3 - src/modules/docker/ContainerActionBar.tsx | 53 +- src/modules/torrents/TorrentsModule.tsx | 2 - src/modules/torrents/TotalDownloadsModule.tsx | 6 - src/modules/usenet/UsenetModule.tsx | 2 - src/pages/_app.tsx | 7 +- 12 files changed, 152 insertions(+), 581 deletions(-) delete mode 100644 src/components/AppShelf/AddAppShelfItem.tsx delete mode 100644 src/components/AppShelf/AppShelfMenu.tsx create mode 100644 src/components/Dashboard/Menu/TileMenu.tsx create mode 100644 src/components/Dashboard/Modals/ChangePosition/ChangePositionModal.tsx diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx deleted file mode 100644 index 3b89d1c85..000000000 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ /dev/null @@ -1,460 +0,0 @@ -import { - ActionIcon, - Anchor, - Button, - Center, - Group, - Image, - LoadingOverlay, - Modal, - PasswordInput, - Select, - Space, - Stack, - Switch, - Tabs, - TextInput, - Title, - Tooltip, -} from '@mantine/core'; -import { useForm } from '@mantine/form'; -import { useDebouncedValue } from '@mantine/hooks'; -import { IconApps } from '@tabler/icons'; -import { useTranslation } from 'next-i18next'; -import { useEffect, useState } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import { useConfig } from '../../tools/state'; -import { tryMatchPort, ServiceTypeList, Config } from '../../tools/types'; -import apiKeyPaths from './apiKeyPaths.json'; -import Tip from '../layout/Tip'; - -export function AddItemShelfButton(props: any) { - const { config, setConfig } = useConfig(); - const [opened, setOpened] = useState(false); - const { t } = useTranslation('layout/add-service-app-shelf'); - return ( - <> - {t('modal.title')}} - opened={props.opened || opened} - onClose={() => setOpened(false)} - > - - - - setOpened(true)} - > - - - - - ); -} - -function MatchIcon(name: string | undefined, form: any) { - if (name === undefined || name === '') return null; - fetch( - `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${name - .replace(/\s+/g, '-') - .toLowerCase() - .replace(/^dash\.$/, 'dashdot')}.png` - ).then((res) => { - if (res.ok) { - form.setFieldValue('icon', res.url); - } - }); - - return false; -} - -function MatchService(name: string, form: any) { - const service = ServiceTypeList.find((s) => s.toLowerCase() === name.toLowerCase()); - if (service) { - form.setFieldValue('type', service); - } -} - -const DEFAULT_ICON = '/imgs/favicon/favicon.png'; - -interface AddAppShelfItemFormProps { - setOpened: (b: boolean) => void; - config: Config; - setConfig: (config: Config) => void; - // Any other props you want to pass to the form - [key: string]: any; -} - -export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) { - const { setOpened, config, setConfig } = props; - // Only get config and setConfig from useCOnfig if they are not present in props - const [isLoading, setLoading] = useState(false); - const { t } = useTranslation('layout/add-service-app-shelf'); - - // Extract all the categories from the services in config - const InitialCategories = config.services.reduce((acc, cur) => { - if (cur.category && !acc.includes(cur.category)) { - acc.push(cur.category); - } - return acc; - }, [] as string[]); - const [categories, setCategories] = useState(InitialCategories); - - const form = useForm({ - initialValues: { - id: props.id ?? uuidv4(), - type: props.type ?? 'Other', - category: props.category ?? null, - name: props.name ?? '', - icon: props.icon ?? DEFAULT_ICON, - url: props.url ?? '', - apiKey: props.apiKey ?? undefined, - username: props.username ?? undefined, - password: props.password ?? undefined, - openedUrl: props.openedUrl ?? undefined, - ping: props.ping ?? true, - newTab: props.newTab ?? true, - }, - validate: { - apiKey: () => null, - // Validate icon with a regex - icon: (value: string) => - // Disable matching to allow any values - null, - // Validate url with a regex http/https - url: (value: string) => { - try { - const _isValid = new URL(value); - } catch (e) { - return t('modal.form.validation.invalidUrl'); - } - return null; - }, - }, - }); - - const [debounced, cancel] = useDebouncedValue(form.values.name, 250); - useEffect(() => { - if ( - form.values.name !== debounced || - form.values.icon !== DEFAULT_ICON || - form.values.type !== 'Other' - ) { - return; - } - MatchIcon(form.values.name, form); - MatchService(form.values.name, form); - tryMatchPort(form.values.name, form); - }, [debounced]); - - // Try to set const hostname to new URL(form.values.url).hostname) - // If it fails, set it to the form.values.url - let hostname = form.values.url; - try { - hostname = new URL(form.values.url).origin; - } catch (e) { - // Do nothing - } - - return ( - <> -
- Placeholder -
-
{ - const newForm = { ...form.values }; - if (newForm.newTab === true) newForm.newTab = undefined; - if (newForm.openedUrl === '') newForm.openedUrl = undefined; - if (newForm.category === null) newForm.category = undefined; - if (newForm.ping === true) newForm.ping = undefined; - // If service already exists, update it. - if (config.services && config.services.find((s) => s.id === newForm.id)) { - setConfig({ - ...config, - // replace the found item by matching ID - services: config.services.map((s) => { - if (s.id === newForm.id) { - return { - ...newForm, - }; - } - return s; - }), - }); - } else { - setConfig({ - ...config, - services: [...config.services, newForm], - }); - } - setOpened(false); - form.reset(); - })} - > - - - {t('modal.tabs.options.title')} - {t('modal.tabs.advancedOptions.title')} - - - - - - - - - { - const item = { value: query, label: query }; - setCategories([...InitialCategories, query]); - return item; - }} - getCreateLabel={(query) => - t('modal.tabs.options.form.category.createLabel', { - query, - }) - } - {...form.getInputProps('category')} - /> - - {(form.values.type === 'Sonarr' || - form.values.type === 'Radarr' || - form.values.type === 'Lidarr' || - form.values.type === 'Overseerr' || - form.values.type === 'Jellyseerr' || - form.values.type === 'Readarr' || - form.values.type === 'Sabnzbd') && ( - <> - { - form.setFieldValue('apiKey', event.currentTarget.value); - }} - error={ - form.errors.apiKey && - t('modal.tabs.options.form.integrations.apiKey.validation.noKey') - } - /> - - {t('modal.tabs.options.form.integrations.apiKey.tip.text')}{' '} - - {t('modal.tabs.options.form.integrations.apiKey.tip.link')} - - - - )} - {form.values.type === 'qBittorrent' && ( - <> - { - form.setFieldValue('username', event.currentTarget.value); - }} - error={ - form.errors.username && - t( - 'modal.tabs.options.form.integrations.qBittorrent.username.validation.invalidUsername' - ) - } - /> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={ - form.errors.password && - t( - 'modal.tabs.options.form.integrations.qBittorrent.password.validation.invalidPassword' - ) - } - /> - - )} - {form.values.type === 'Deluge' && ( - <> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={ - form.errors.password && - t( - 'modal.tabs.options.form.integrations.deluge.password.validation.invalidPassword' - ) - } - /> - - )} - {form.values.type === 'Transmission' && ( - <> - { - form.setFieldValue('username', event.currentTarget.value); - }} - error={ - form.errors.username && - t( - 'modal.tabs.options.form.integrations.transmission.username.validation.invalidUsername' - ) - } - /> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={ - form.errors.password && - t( - 'modal.tabs.options.form.integrations.transmission.password.validation.invalidPassword' - ) - } - /> - - )} - {form.values.type === 'NZBGet' && ( - <> - { - form.setFieldValue('username', event.currentTarget.value); - }} - error={ - form.errors.username && - t( - 'modal.tabs.options.form.integrations.nzbget.username.validation.invalidUsername' - ) - } - /> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={ - form.errors.password && - t( - 'modal.tabs.options.form.integrations.nzbget.password.validation.invalidPassword' - ) - } - /> - - )} - - - - - - - - - - - - - -
- - ); -} diff --git a/src/components/AppShelf/AppShelfItem.tsx b/src/components/AppShelf/AppShelfItem.tsx index bbc51122c..59189ed5b 100644 --- a/src/components/AppShelf/AppShelfItem.tsx +++ b/src/components/AppShelf/AppShelfItem.tsx @@ -15,7 +15,7 @@ import { useState } from 'react'; import PingComponent from '../../modules/ping/PingModule'; import { useConfig } from '../../tools/state'; import { serviceItem } from '../../tools/types'; -import AppShelfMenu from './AppShelfMenu'; +import { TileMenu } from '../Dashboard/Menu/TileMenu'; const useStyles = createStyles((theme) => ({ item: { @@ -104,7 +104,7 @@ export function AppShelfItem(props: any) { opacity: hovering ? 1 : 0, }} > - + diff --git a/src/components/AppShelf/AppShelfMenu.tsx b/src/components/AppShelf/AppShelfMenu.tsx deleted file mode 100644 index 402867ac0..000000000 --- a/src/components/AppShelf/AppShelfMenu.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { ActionIcon, Menu, Modal, Text } from '@mantine/core'; -import { showNotification } from '@mantine/notifications'; -import { useState } from 'react'; -import { IconCheck as Check, IconEdit as Edit, IconMenu, IconTrash as Trash } from '@tabler/icons'; -import { useTranslation } from 'next-i18next'; -import { useConfig } from '../../tools/state'; -import { serviceItem } from '../../tools/types'; -import { AddAppShelfItemForm } from './AddAppShelfItem'; -import { useColorTheme } from '../../tools/color'; - -export default function AppShelfMenu(props: any) { - const { service }: { service: serviceItem } = props; - const { config, setConfig } = useConfig(); - const { secondaryColor } = useColorTheme(); - const { t } = useTranslation('layout/app-shelf-menu'); - const [opened, setOpened] = useState(false); - return ( - <> - setOpened(false)} - title={t('modal.title')} - > - - - - - - - - - - {t('menu.labels.settings')} - } onClick={() => setOpened(true)}> - {t('menu.actions.edit')} - - {t('menu.labels.dangerZone')} - { - setConfig({ - ...config, - services: config.services.filter((s) => s.id !== service.id), - }); - showNotification({ - autoClose: 5000, - title: ( - - Service {service.name} removed successfully! - - ), - color: 'green', - icon: , - message: undefined, - }); - }} - icon={} - > - {t('menu.actions.delete')} - - - - - ); -} diff --git a/src/components/Dashboard/Menu/TileMenu.tsx b/src/components/Dashboard/Menu/TileMenu.tsx new file mode 100644 index 000000000..22c0c1de6 --- /dev/null +++ b/src/components/Dashboard/Menu/TileMenu.tsx @@ -0,0 +1,76 @@ +import { ActionIcon, Menu, Text } from '@mantine/core'; +import { openContextModal } from '@mantine/modals'; +import { showNotification } from '@mantine/notifications'; +import { IconCheck, IconEdit, IconMenu, IconTrash } from '@tabler/icons'; +import { useTranslation } from 'next-i18next'; +import { useConfigContext } from '../../../config/provider'; +import { useConfigStore } from '../../../config/store'; +import { useColorTheme } from '../../../tools/color'; +import { ServiceType } from '../../../types/service'; + +interface TileMenuProps { + service: ServiceType; +} + +export const TileMenu = ({ service }: TileMenuProps) => { + const { secondaryColor } = useColorTheme(); + const { t } = useTranslation('layout/app-shelf-menu'); + const updateConfig = useConfigStore((x) => x.updateConfig); + const { name: configName } = useConfigContext(); + + return ( + + + + + + + + {t('menu.labels.settings')} + } + onClick={() => + openContextModal({ + modal: 'changeTilePosition', + innerProps: { + type: 'service', + tile: service, + }, + }) + } + > + {t('menu.actions.edit')} + + {t('menu.labels.dangerZone')} + { + if (!configName) { + return; + } + updateConfig(configName, (previousConfig) => ({ + ...previousConfig, + services: previousConfig.services.filter((x) => x.id !== service.id), + })).then(() => { + showNotification({ + autoClose: 5000, + title: ( + + Service {service.name} removed successfully! + + ), + color: 'green', + icon: , + message: undefined, + }); + }); + }} + icon={} + > + {t('menu.actions.delete')} + + + + ); +}; diff --git a/src/components/Dashboard/Modals/ChangePosition/ChangePositionModal.tsx b/src/components/Dashboard/Modals/ChangePosition/ChangePositionModal.tsx new file mode 100644 index 000000000..a486ac888 --- /dev/null +++ b/src/components/Dashboard/Modals/ChangePosition/ChangePositionModal.tsx @@ -0,0 +1,31 @@ +import { Grid, NumberInput } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { ContextModalProps } from '@mantine/modals'; +import { TileBaseType } from '../../../../types/tile'; + +export const ChangePositionModal = ({ + context, + id, + innerProps, +}: ContextModalProps<{ type: 'service' | 'type'; tile: TileBaseType }>) => { + const form = useForm({ + initialValues: { + area: innerProps.tile.area, + shape: innerProps.tile.shape, + }, + }); + + return ( + <> + + + + + + + + + + + ); +}; diff --git a/src/components/layout/header/Actions/AddElementAction/AddElementAction.tsx b/src/components/layout/header/Actions/AddElementAction/AddElementAction.tsx index 33349df6b..1c2a46cb3 100644 --- a/src/components/layout/header/Actions/AddElementAction/AddElementAction.tsx +++ b/src/components/layout/header/Actions/AddElementAction/AddElementAction.tsx @@ -2,9 +2,6 @@ import { ActionIcon, Tooltip } from '@mantine/core'; import { openContextModal } from '@mantine/modals'; import { IconApps } from '@tabler/icons'; import { useTranslation } from 'next-i18next'; -import { v4 as uuidv4 } from 'uuid'; -import { openContextModalGeneric } from '../../../../../tools/mantineModalManagerExtensions'; -import { ServiceType } from '../../../../../types/service'; export const AddElementAction = () => { const { t } = useTranslation('layout/add-service-app-shelf'); diff --git a/src/components/layout/header/Header.tsx b/src/components/layout/header/Header.tsx index 3669bc268..e59476743 100644 --- a/src/components/layout/header/Header.tsx +++ b/src/components/layout/header/Header.tsx @@ -1,6 +1,4 @@ import { Box, createStyles, Group, Header as MantineHeader } from '@mantine/core'; -import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem'; - import { Logo } from '../Logo'; import { useCardStyles } from '../useCardStyles'; import { AddElementAction } from './Actions/AddElementAction/AddElementAction'; @@ -22,7 +20,6 @@ export function Header(props: any) { - diff --git a/src/modules/docker/ContainerActionBar.tsx b/src/modules/docker/ContainerActionBar.tsx index 0dd5e9ff5..6805f18c2 100644 --- a/src/modules/docker/ContainerActionBar.tsx +++ b/src/modules/docker/ContainerActionBar.tsx @@ -16,9 +16,11 @@ import Dockerode from 'dockerode'; import { useTranslation } from 'next-i18next'; import { useState } from 'react'; import { TFunction } from 'react-i18next'; -import { AddAppShelfItemForm } from '../../components/AppShelf/AddAppShelfItem'; +import { v4 as uuidv4 } from 'uuid'; import { tryMatchService } from '../../tools/addToHomarr'; +import { openContextModalGeneric } from '../../tools/mantineModalManagerExtensions'; import { useConfig } from '../../tools/state'; +import { ServiceType } from '../../types/service'; let t: TFunction<'modules/docker', undefined>; @@ -160,21 +162,40 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction radius="md" disabled={selected.length === 0 || selected.length > 1} onClick={() => { - openModal({ - size: 'xl', - modalId: selected.at(0)!.Id, - radius: 'md', - title: t('actionBar.addService.title'), - zIndex: 500, - children: ( - closeModal(selected.at(0)!.Id)} - message={t('actionBar.addService.message')} - {...tryMatchService(selected.at(0)!)} - /> - ), + const containerUrl = `http://localhost:${selected[0].Ports[0].PublicPort}`; + openContextModalGeneric<{ service: ServiceType }>({ + modal: 'editService', + innerProps: { + service: { + id: uuidv4(), + name: selected[0].Names[0], + url: containerUrl, + appearance: { + iconUrl: '/imgs/logo/logo.png', // TODO: find icon automatically + }, + network: { + enabledStatusChecker: false, + okStatus: [], + }, + behaviour: { + isOpeningNewTab: true, + onClickUrl: '', + }, + area: { + type: 'wrapper', // TODO: Set the wrapper automatically + }, + shape: { + location: { + x: 0, + y: 0, + }, + size: { + height: 1, + width: 1, + }, + }, + }, + }, }); }} > diff --git a/src/modules/torrents/TorrentsModule.tsx b/src/modules/torrents/TorrentsModule.tsx index cd010992c..714bcb618 100644 --- a/src/modules/torrents/TorrentsModule.tsx +++ b/src/modules/torrents/TorrentsModule.tsx @@ -20,7 +20,6 @@ import { NormalizedTorrent } from '@ctrl/shared-torrent'; import { useTranslation } from 'next-i18next'; import { IModule } from '../ModuleTypes'; import { useConfig } from '../../tools/state'; -import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem'; import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval'; import { humanFileSize } from '../../tools/humanFileSize'; @@ -93,7 +92,6 @@ export default function TorrentsComponent() { {t('card.errors.noDownloadClients.title')} {t('card.errors.noDownloadClients.text')} - ); diff --git a/src/modules/torrents/TotalDownloadsModule.tsx b/src/modules/torrents/TotalDownloadsModule.tsx index 95f64852e..a0fda21cc 100644 --- a/src/modules/torrents/TotalDownloadsModule.tsx +++ b/src/modules/torrents/TotalDownloadsModule.tsx @@ -8,7 +8,6 @@ import { useTranslation } from 'next-i18next'; import { Datum, ResponsiveLine } from '@nivo/line'; import { useListState } from '@mantine/hooks'; import { showNotification } from '@mantine/notifications'; -import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem'; import { useConfig } from '../../tools/state'; import { humanFileSize } from '../../tools/humanFileSize'; import { IModule } from '../ModuleTypes'; @@ -84,11 +83,6 @@ export default function TotalDownloadsComponent() { {t('card.errors.noDownloadClients.title')}
- {t('card.errors.noDownloadClients.text')}
diff --git a/src/modules/usenet/UsenetModule.tsx b/src/modules/usenet/UsenetModule.tsx index 6545889ee..b733ae34b 100644 --- a/src/modules/usenet/UsenetModule.tsx +++ b/src/modules/usenet/UsenetModule.tsx @@ -22,7 +22,6 @@ import { UsenetHistoryList } from './UsenetHistoryList'; import { useGetServiceByType } from '../../tools/hooks/useGetServiceByType'; import { useGetUsenetInfo, usePauseUsenetQueue, useResumeUsenetQueue } from '../../tools/hooks/api'; import { humanFileSize } from '../../tools/humanFileSize'; -import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem'; dayjs.extend(duration); @@ -49,7 +48,6 @@ export const UsenetComponent: FunctionComponent = () => { {t('card.errors.noDownloadClients.title')} {t('card.errors.noDownloadClients.text')} - ); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 18ad03e37..935f48c8f 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -9,6 +9,7 @@ import { appWithTranslation } from 'next-i18next'; import { AppProps } from 'next/app'; import Head from 'next/head'; import { useState } from 'react'; +import { ChangePositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangePositionModal'; import { EditServiceModal } from '../components/Dashboard/Modals/EditService/EditServiceModal'; import { SelectElementModal } from '../components/Dashboard/Modals/SelectElement/SelectElementModal'; import { ConfigProvider } from '../config/provider'; @@ -79,7 +80,11 @@ function App(this: any, props: AppProps & { colorScheme: ColorScheme }) {