import { closeAllModals } from '@mantine/modals'; import isElectron from 'is-electron'; import { nanoid } from 'nanoid/non-secure'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { api } from '/@/renderer/api'; import JellyfinIcon from '/@/renderer/features/servers/assets/jellyfin.png'; import NavidromeIcon from '/@/renderer/features/servers/assets/navidrome.png'; import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png'; import { useAuthStoreActions } from '/@/renderer/store'; import { Checkbox } from '/@/shared/components/checkbox/checkbox'; import { Group } from '/@/shared/components/group/group'; import { ModalButton } from '/@/shared/components/modal/model-shared'; import { Paper } from '/@/shared/components/paper/paper'; import { PasswordInput } from '/@/shared/components/password-input/password-input'; import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control'; import { Stack } from '/@/shared/components/stack/stack'; import { TextInput } from '/@/shared/components/text-input/text-input'; import { Text } from '/@/shared/components/text/text'; import { toast } from '/@/shared/components/toast/toast'; import { useFocusTrap } from '/@/shared/hooks/use-focus-trap'; import { useForm } from '/@/shared/hooks/use-form'; import { AuthenticationResponse, ServerListItemWithCredential } from '/@/shared/types/domain-types'; import { DiscoveredServerItem, ServerType, toServerType } from '/@/shared/types/types'; const autodiscover = isElectron() ? window.api.autodiscover : null; const localSettings = isElectron() ? window.api.localSettings : null; interface AddServerFormProps { onCancel: (() => void) | null; } interface ServerDetails { icon: string; name: string; } function ServerIconWithLabel({ icon, label }: { icon: string; label: string }) { return ( {label} ); } function useAutodiscovery() { const [isDone, setDone] = useState(false); const [servers, setServers] = useState([]); useEffect(() => { setServers([]); autodiscover ?.discover((newServer) => { setServers((tail) => [...tail, newServer]); }) .then(() => { setDone(true); }); }, []); return { isDone, servers }; } const SERVER_TYPES: Record = { [ServerType.JELLYFIN]: { icon: JellyfinIcon, name: 'Jellyfin', }, [ServerType.NAVIDROME]: { icon: NavidromeIcon, name: 'Navidrome', }, [ServerType.SUBSONIC]: { icon: SubsonicIcon, name: 'OpenSubsonic', }, }; const ALL_SERVERS = Object.keys(SERVER_TYPES).map((serverType) => { const info = SERVER_TYPES[serverType]; return { label: , value: serverType, }; }); export const AddServerForm = ({ onCancel }: AddServerFormProps) => { const { t } = useTranslation(); const focusTrapRef = useFocusTrap(true); const [isLoading, setIsLoading] = useState(false); const { addServer, setCurrentServer } = useAuthStoreActions(); const { servers: discovered } = useAutodiscovery(); const form = useForm({ initialValues: { legacyAuth: false, name: (localSettings ? localSettings.env.SERVER_NAME : window.SERVER_NAME) || 'My Server', password: '', preferInstantMix: undefined, savePassword: undefined, type: (localSettings ? localSettings.env.SERVER_TYPE : toServerType(window.SERVER_TYPE)) ?? ServerType.NAVIDROME, url: (localSettings ? localSettings.env.SERVER_URL : window.SERVER_URL) ?? 'https://', username: '', }, }); // server lock for web is only true if lock is true *and* all other properties are set const serverLock = (localSettings ? !!localSettings.env.SERVER_LOCK : !!window.SERVER_LOCK && window.SERVER_TYPE && window.SERVER_NAME && window.SERVER_URL) || false; const isSubmitDisabled = !form.values.name || !form.values.url || !form.values.username; const fillServerDetails = (server: DiscoveredServerItem) => { form.setValues({ ...server }); }; const handleSubmit = form.onSubmit(async (values) => { const authFunction = api.controller.authenticate; if (!authFunction) { return toast.error({ message: t('error.invalidServer', { postProcess: 'sentenceCase' }), }); } try { setIsLoading(true); const data: AuthenticationResponse | undefined = await authFunction( values.url, { legacy: values.legacyAuth, password: values.password, username: values.username, }, values.type as ServerType, ); if (!data) { return toast.error({ message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }), }); } const serverItem: ServerListItemWithCredential = { credential: data.credential, id: nanoid(), name: values.name, type: values.type as ServerType, url: values.url.replace(/\/$/, ''), userId: data.userId, username: data.username, }; if (values.preferInstantMix !== undefined) { serverItem.preferInstantMix = values.preferInstantMix; } if (values.savePassword !== undefined) { serverItem.savePassword = values.savePassword; } if (data.ndCredential !== undefined) { serverItem.ndCredential = data.ndCredential; } addServer(serverItem); setCurrentServer(serverItem); closeAllModals(); toast.success({ message: t('form.addServer.success', { postProcess: 'sentenceCase' }), }); if (localSettings && values.savePassword) { const saved = await localSettings.passwordSet(values.password, serverItem.id); if (!saved) { toast.error({ message: t('form.addServer.error', { context: 'savePassword', postProcess: 'sentenceCase', }), }); } } } catch (err: any) { setIsLoading(false); return toast.error({ message: err?.message }); } return setIsLoading(false); }); return ( <> {discovered.map((server) => ( fillServerDetails(server)} style={{ cursor: 'pointer' }} > {server.name} {SERVER_TYPES[server.type].name} server at {server.url} ))} {localSettings && form.values.type === ServerType.NAVIDROME && ( )} {form.values.type === ServerType.SUBSONIC && ( )} {form.values.type === ServerType.JELLYFIN && ( )} {onCancel && ( {t('common.cancel')} )} {t('common.add')} > ); };