From 9c355ce5bd8ffaca33631a85a446a332f6237725 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 17 Nov 2023 17:42:03 -0800 Subject: [PATCH] Add initial support for static server --- .erb/configs/webpack.config.base.ts | 5 ++ src/i18n/locales/en.json | 2 + src/renderer/app.tsx | 68 +++++++++++++++++- .../components/server-credential-required.tsx | 69 ++++++++++++++++--- .../components/server-required.tsx | 5 +- .../routes/action-required-route.tsx | 28 ++++++-- .../servers/components/edit-server-form.tsx | 7 +- .../servers/components/server-list-item.tsx | 24 +++++-- .../features/titlebar/components/app-menu.tsx | 4 +- src/renderer/router/app-outlet.tsx | 5 +- src/renderer/store/auth.store.ts | 8 +++ src/renderer/types.ts | 7 +- 12 files changed, 203 insertions(+), 29 deletions(-) diff --git a/.erb/configs/webpack.config.base.ts b/.erb/configs/webpack.config.base.ts index c998500c..9aab26f9 100644 --- a/.erb/configs/webpack.config.base.ts +++ b/.erb/configs/webpack.config.base.ts @@ -43,6 +43,11 @@ const configuration: webpack.Configuration = { plugins: [ new webpack.EnvironmentPlugin({ NODE_ENV: 'production', + FS_SERVER_NAME: process.env.SERVER_URL ?? null, + FS_SERVER_URL: process.env.SERVER_URL ?? null, + FS_SERVER_TYPE: process.env.SERVER_URL ?? null, + FS_SERVER_USERNAME: process.env.SERVER_URL ?? null, + FS_SERVER_PASSWORD: process.env.SERVER_URL ?? null, }), ], diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 3997bd64..aa439238 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -7,6 +7,7 @@ "deletePlaylist": "delete $t(entity.playlist_one)", "deselectAll": "deselect all", "editPlaylist": "edit $t(entity.playlist_one)", + "signIn": "sign in", "goToPage": "go to page", "moveToBottom": "move to bottom", "moveToTop": "move to top", @@ -14,6 +15,7 @@ "removeFromFavorites": "remove from $t(entity.favorite_other)", "removeFromPlaylist": "remove from $t(entity.playlist_one)", "removeFromQueue": "remove from queue", + "removeServer": "remove server", "setRating": "set rating", "toggleSmartPlaylistEditor": "toggle $t(entity.smartPlaylist) editor", "viewPlaylists": "view $t(entity.playlist_other)" diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 1b6b82b0..683e5a19 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model'; import { ModuleRegistry } from '@ag-grid-community/core'; import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model'; @@ -22,8 +22,19 @@ import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-han import { PlayQueueHandlerContext } from '/@/renderer/features/player'; import { AddToPlaylistContextModal } from '/@/renderer/features/playlists'; import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings'; -import { PlayerState, usePlayerStore, useQueueControls } from '/@/renderer/store'; -import { FontType, PlaybackType, PlayerStatus } from '/@/renderer/types'; +import { + PlayerState, + useAuthStoreActions, + usePlayerStore, + useQueueControls, +} from '/@/renderer/store'; +import { + FontType, + PlaybackType, + PlayerStatus, + ServerListItem, + ServerType, +} from '/@/renderer/types'; import '@ag-grid-community/styles/ag-grid.css'; import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc'; import i18n from '/@/i18n/i18n'; @@ -48,6 +59,57 @@ export const App = () => { const { clearQueue, restoreQueue } = useQueueControls(); const remoteSettings = useRemoteSettings(); const textStyleRef = useRef(); + const { addServer } = useAuthStoreActions(); + + const setStaticServer = useCallback(() => { + console.log('process.env.FS_SERVER_URL :>> ', process.env.FS_SERVER_URL); + console.log('process.env.FS_SERVER_NAME', process.env.FS_SERVER_NAME); + console.log('process.env.FS_SERVER_TYPE :>> ', process.env.FS_SERVER_TYPE); + const url = process.env.FS_SERVER_URL; + let serverType: ServerType | null = null; + const name = + process.env.FS_SERVER_NAME || serverType === ServerType.NAVIDROME + ? 'Navidrome' + : 'Jellyfin'; + + switch (process.env.FS_SERVER_TYPE?.toLocaleLowerCase()) { + case 'jellyfin': + serverType = ServerType.JELLYFIN; + break; + case 'navidrome': + serverType = ServerType.NAVIDROME; + break; + case 'subsonic': + serverType = ServerType.SUBSONIC; + break; + default: + serverType = null; + break; + } + + if (!url || !serverType) { + return; + } + + const serverItem: ServerListItem = { + credential: undefined, + id: 'static-server', + name, + ndCredential: undefined, + static: true, + type: serverType, + url: url.replace(/\/$/, ''), + userId: undefined, + username: undefined, + }; + + addServer(serverItem); + }, [addServer]); + + useEffect(() => { + setStaticServer(); + }, [setStaticServer]); + useDiscordRpc(); useEffect(() => { diff --git a/src/renderer/features/action-required/components/server-credential-required.tsx b/src/renderer/features/action-required/components/server-credential-required.tsx index 2b7101f5..9031ada1 100644 --- a/src/renderer/features/action-required/components/server-credential-required.tsx +++ b/src/renderer/features/action-required/components/server-credential-required.tsx @@ -1,19 +1,70 @@ -import { Text } from '/@/renderer/components'; +import { openModal, closeAllModals } from '@mantine/modals'; +import isElectron from 'is-electron'; +import { useTranslation } from 'react-i18next'; +import { Button, DropdownMenu } from '/@/renderer/components'; import { useCurrentServer } from '/@/renderer/store'; +import { RiKeyFill, RiMenuFill } from 'react-icons/ri'; +import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu'; +import { EditServerForm } from '/@/renderer/features/servers/components/edit-server-form'; + +const localSettings = isElectron() ? window.electron.localSettings : null; export const ServerCredentialRequired = () => { + const { t } = useTranslation(); const currentServer = useCurrentServer(); + const handleCredentialsModal = async () => { + if (!currentServer) { + return; + } + + const server = currentServer; + let password: string | null = null; + + try { + if (localSettings && server.savePassword) { + password = await localSettings.passwordGet(server.id); + } + } catch (error) { + console.error(error); + } + + openModal({ + children: server && ( + + ), + size: 'sm', + title: server.name, + }); + }; + return ( <> - - The selected server '{currentServer?.name}' requires an additional login - to access. - - - Add your credentials in the 'manage servers' menu or switch to a different - server. - + + + + + + + + + ); }; diff --git a/src/renderer/features/action-required/components/server-required.tsx b/src/renderer/features/action-required/components/server-required.tsx index 4e07353b..90308f7a 100644 --- a/src/renderer/features/action-required/components/server-required.tsx +++ b/src/renderer/features/action-required/components/server-required.tsx @@ -1,8 +1,11 @@ +import { useTranslation } from 'react-i18next'; import { RiMenuFill } from 'react-icons/ri'; import { Button, DropdownMenu, Text } from '/@/renderer/components'; import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu'; export const ServerRequired = () => { + const { t } = useTranslation(); + return ( <> No server selected. @@ -12,7 +15,7 @@ export const ServerRequired = () => { leftIcon={} variant="filled" > - Open menu + {t('common.menu', { postProcess: 'titleCase' })} diff --git a/src/renderer/features/action-required/routes/action-required-route.tsx b/src/renderer/features/action-required/routes/action-required-route.tsx index b5f79e5e..65bf1dfd 100644 --- a/src/renderer/features/action-required/routes/action-required-route.tsx +++ b/src/renderer/features/action-required/routes/action-required-route.tsx @@ -4,7 +4,7 @@ import isElectron from 'is-electron'; import { useTranslation } from 'react-i18next'; import { RiCheckFill } from 'react-icons/ri'; import { Link, Navigate } from 'react-router-dom'; -import { Button, PageHeader, Text } from '/@/renderer/components'; +import { Button, PageHeader } from '/@/renderer/components'; import { ActionRequiredContainer } from '/@/renderer/features/action-required/components/action-required-container'; import { MpvRequired } from '/@/renderer/features/action-required/components/mpv-required'; import { ServerCredentialRequired } from '/@/renderer/features/action-required/components/server-credential-required'; @@ -12,15 +12,36 @@ import { ServerRequired } from '/@/renderer/features/action-required/components/ import { AnimatedPage } from '/@/renderer/features/shared'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer } from '/@/renderer/store'; +import { ServerListItem, ServerType } from '/@/renderer/types'; const localSettings = isElectron() ? window.electron.localSettings : null; +export const getIsCredentialRequired = (currentServer: ServerListItem | null) => { + if (currentServer === null) { + return false; + } + + if (currentServer.type === ServerType.NAVIDROME) { + return !currentServer.ndCredential || !currentServer.credential || !currentServer.username; + } + + if (currentServer.type === ServerType.JELLYFIN) { + return !currentServer.credential || !currentServer.username; + } + + if (currentServer.type === ServerType.SUBSONIC) { + return !currentServer.credential || !currentServer.username; + } + + return false; +}; + const ActionRequiredRoute = () => { const { t } = useTranslation(); const currentServer = useCurrentServer(); const [isMpvRequired, setIsMpvRequired] = useState(false); const isServerRequired = !currentServer; - const isCredentialRequired = false; + const isCredentialRequired = getIsCredentialRequired(currentServer); useEffect(() => { const getMpvPath = async () => { @@ -85,7 +106,6 @@ const ActionRequiredRoute = () => { color="var(--success-color)" size={30} /> - No issues found )} diff --git a/src/renderer/features/servers/components/edit-server-form.tsx b/src/renderer/features/servers/components/edit-server-form.tsx index 02c9d9bf..b4726a15 100644 --- a/src/renderer/features/servers/components/edit-server-form.tsx +++ b/src/renderer/features/servers/components/edit-server-form.tsx @@ -46,7 +46,7 @@ export const EditServerForm = ({ isUpdate, password, server, onCancel }: EditSer savePassword: server.savePassword || false, type: server?.type, url: server?.url, - username: server?.username, + username: server?.username || '', }, }); @@ -125,6 +125,7 @@ export const EditServerForm = ({ isUpdate, password, server, onCancel }: EditSer { + const { t } = useTranslation(); const [edit, editHandlers] = useDisclosure(false); const [savedPassword, setSavedPassword] = useState(''); const { deleteServer } = useAuthStoreActions(); @@ -68,8 +70,18 @@ export const ServerListItem = ({ server }: ServerListItemProps) => { - URL - Username + + {t('form.addServer.input', { + context: 'url', + postProcess: 'sentenceCase', + })} + + + {t('form.addServer.input', { + context: 'username', + postProcess: 'sentenceCase', + })} + {server.url} @@ -83,7 +95,9 @@ export const ServerListItem = ({ server }: ServerListItemProps) => { variant="subtle" onClick={() => handleEdit()} > - Edit + {t('common.edit', { + postProcess: 'sentenceCase', + })} @@ -95,7 +109,9 @@ export const ServerListItem = ({ server }: ServerListItemProps) => { timeoutProps={{ callback: handleDeleteServer, duration: 1000 }} variant="subtle" > - Remove server + {t('action.removeServer', { + postProcess: 'sentenceCase', + })} ); diff --git a/src/renderer/features/titlebar/components/app-menu.tsx b/src/renderer/features/titlebar/components/app-menu.tsx index b5da6025..673df215 100644 --- a/src/renderer/features/titlebar/components/app-menu.tsx +++ b/src/renderer/features/titlebar/components/app-menu.tsx @@ -69,14 +69,14 @@ export const AppMenu = () => { /> ), size: 'sm', - title: `Update session for "${server.name}"`, + title: server.name, }); }; const handleManageServersModal = () => { openModal({ children: , - title: 'Manage Servers', + title: t('page.appMenu.manageServers', { postProcess: 'sentenceCase' }), }); }; diff --git a/src/renderer/router/app-outlet.tsx b/src/renderer/router/app-outlet.tsx index 6bd6daf4..1f004648 100644 --- a/src/renderer/router/app-outlet.tsx +++ b/src/renderer/router/app-outlet.tsx @@ -3,6 +3,7 @@ import isElectron from 'is-electron'; import { Navigate, Outlet } from 'react-router-dom'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer } from '/@/renderer/store'; +import { getIsCredentialRequired } from '/@/renderer/features/action-required/routes/action-required-route'; const localSettings = isElectron() ? window.electron.localSettings : null; @@ -17,9 +18,11 @@ export const AppOutlet = () => { return true; }; + const isCredentialRequired = getIsCredentialRequired(currentServer); + const isServerRequired = !currentServer; - const actions = [isServerRequired, isMpvRequired()]; + const actions = [isServerRequired, isCredentialRequired, isMpvRequired()]; const isActionRequired = actions.some((c) => c); return isActionRequired; diff --git a/src/renderer/store/auth.store.ts b/src/renderer/store/auth.store.ts index 7fa958cd..0cae22bb 100644 --- a/src/renderer/store/auth.store.ts +++ b/src/renderer/store/auth.store.ts @@ -31,7 +31,15 @@ export const useAuthStore = create()( actions: { addServer: (args) => { set((state) => { + if (state.serverList[args.id]) { + return; + } + state.serverList[args.id] = args; + + if (!state.currentServer) { + state.currentServer = args; + } }); }, deleteServer: (id) => { diff --git a/src/renderer/types.ts b/src/renderer/types.ts index a40018b7..6780ab4b 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -61,15 +61,16 @@ export enum ServerType { } export type ServerListItem = { - credential: string; + credential?: string; id: string; name: string; ndCredential?: string; savePassword?: boolean; + static?: boolean; type: ServerType; url: string; - userId: string | null; - username: string; + userId?: string | null; + username?: string; }; export enum PlayerStatus {