From 752b191ad74eec39e154b4cf1bef9a1ba76ba58b Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 14 Dec 2025 04:43:48 -0800 Subject: [PATCH] optimize playlist edit modal (#1234) - remove user list fetch if not admin - move to context modal to allow dynamic content --- .../actions/edit-playlist-action.tsx | 37 +---- .../components/update-playlist-form.tsx | 144 ++++++++---------- src/renderer/router/app-router.tsx | 2 + src/renderer/store/auth.store.ts | 1 + 4 files changed, 73 insertions(+), 111 deletions(-) diff --git a/src/renderer/features/context-menu/actions/edit-playlist-action.tsx b/src/renderer/features/context-menu/actions/edit-playlist-action.tsx index f693f9c59..a4b653191 100644 --- a/src/renderer/features/context-menu/actions/edit-playlist-action.tsx +++ b/src/renderer/features/context-menu/actions/edit-playlist-action.tsx @@ -1,13 +1,8 @@ import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form'; -import { queryClient } from '/@/renderer/lib/react-query'; -import { useCurrentServer } from '/@/renderer/store'; import { ContextMenu } from '/@/shared/components/context-menu/context-menu'; -import { toast } from '/@/shared/components/toast/toast'; import { Playlist } from '/@/shared/types/domain-types'; interface EditPlaylistActionProps { @@ -17,38 +12,16 @@ interface EditPlaylistActionProps { export const EditPlaylistAction = ({ disabled, items }: EditPlaylistActionProps) => { const { t } = useTranslation(); - const server = useCurrentServer(); const handleEditPlaylist = useCallback(async () => { - if (items.length === 0 || !server) return; + if (items.length === 0) return; - // Only allow editing a single playlist at a time const playlist = items[0]; - try { - // Fetch the full playlist detail - const playlistDetail = await queryClient.fetchQuery({ - queryFn: ({ signal }) => - api.controller.getPlaylistDetail({ - apiClientProps: { serverId: server.id, signal }, - query: { id: playlist.id }, - }), - queryKey: queryKeys.playlists.detail(server.id, playlist.id, { id: playlist.id }), - }); - - if (playlistDetail) { - await openUpdatePlaylistModal({ - playlist: playlistDetail, - server, - }); - } - } catch (err: any) { - toast.error({ - message: err.message, - title: t('error.genericError', { postProcess: 'sentenceCase' }), - }); - } - }, [items, server, t]); + openUpdatePlaylistModal({ + playlist, + }); + }, [items]); if (items.length === 0 || items.length > 1) return null; diff --git a/src/renderer/features/playlists/components/update-playlist-form.tsx b/src/renderer/features/playlists/components/update-playlist-form.tsx index 9b795fcb3..0640f0c0f 100644 --- a/src/renderer/features/playlists/components/update-playlist-form.tsx +++ b/src/renderer/features/playlists/components/update-playlist-form.tsx @@ -1,12 +1,12 @@ -import { closeAllModals, openModal } from '@mantine/modals'; +import { closeModal, ContextModalProps, openContextModal } from '@mantine/modals'; +import { useQuery } from '@tanstack/react-query'; +import { t } from 'i18next'; import { useTranslation } from 'react-i18next'; import i18n from '/@/i18n/i18n'; -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation'; -import { queryClient } from '/@/renderer/lib/react-query'; -import { useCurrentServer } from '/@/renderer/store'; +import { sharedQueries } from '/@/renderer/features/shared/api/shared-api'; +import { useCurrentServer, useCurrentServerId, usePermissions } from '/@/renderer/store'; import { hasFeature } from '/@/shared/api/utils'; import { Group } from '/@/shared/components/group/group'; import { ModalButton } from '/@/shared/components/modal/model-shared'; @@ -17,34 +17,26 @@ import { TextInput } from '/@/shared/components/text-input/text-input'; import { toast } from '/@/shared/components/toast/toast'; import { useForm } from '/@/shared/hooks/use-form'; import { - PlaylistDetailResponse, - ServerListItem, + Playlist, ServerType, SortOrder, UpdatePlaylistBody, UpdatePlaylistQuery, - User, - UserListQuery, UserListSort, } from '/@/shared/types/domain-types'; import { ServerFeature } from '/@/shared/types/features-types'; -interface UpdatePlaylistFormProps { +export const UpdatePlaylistContextModal = ({ + id, + innerProps, +}: ContextModalProps<{ body: Partial; - onCancel: () => void; query: UpdatePlaylistQuery; - users?: User[]; -} - -export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlaylistFormProps) => { +}>) => { const { t } = useTranslation(); const mutation = useUpdatePlaylist({}); const server = useCurrentServer(); - - const userList = users?.map((user) => ({ - label: user.name, - value: user.id, - })); + const { body, query } = innerProps; const form = useForm({ initialValues: { @@ -75,14 +67,15 @@ export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlayl toast.success({ message: t('form.editPlaylist.success', { postProcess: 'sentenceCase' }), }); - onCancel(); + closeModal(id); }, }, ); }); const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST); - const isOwnerDisplayed = server?.type === ServerType.NAVIDROME && userList; + const isOwnerDisplayed = server?.type === ServerType.NAVIDROME; + const isCommentDisplayed = server?.type === ServerType.NAVIDROME; const isSubmitDisabled = !form.values.name || mutation.isPending; return ( @@ -97,7 +90,7 @@ export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlayl required {...form.getInputProps('name')} /> - {server?.type === ServerType.NAVIDROME && ( + {isCommentDisplayed && ( )} - {isOwnerDisplayed && ( - + ); +}; + +export const openUpdatePlaylistModal = async (args: { playlist: Playlist }) => { + const { playlist } = args; + + openContextModal({ + innerProps: { + body: { + comment: playlist?.description || undefined, + genres: playlist?.genres, + name: playlist?.name, + ownerId: playlist?.ownerId || undefined, + public: playlist?.public || false, + queryBuilderRules: playlist?.rules || undefined, + sync: playlist?.sync || undefined, + }, + query: { id: playlist?.id }, + }, + modalKey: 'updatePlaylist', title: i18n.t('form.editPlaylist.title', { postProcess: 'titleCase' }) as string, }); }; diff --git a/src/renderer/router/app-router.tsx b/src/renderer/router/app-router.tsx index d1a1c743e..c23c52f36 100644 --- a/src/renderer/router/app-router.tsx +++ b/src/renderer/router/app-router.tsx @@ -4,6 +4,7 @@ import { HashRouter, Route, Routes } from 'react-router'; import { ShuffleAllContextModal } from '/@/renderer/features/player/components/shuffle-all-modal'; import { AddToPlaylistContextModal } from '/@/renderer/features/playlists/components/add-to-playlist-context-modal'; import { SaveAndReplaceContextModal } from '/@/renderer/features/playlists/components/save-and-replace-context-modal'; +import { UpdatePlaylistContextModal } from '/@/renderer/features/playlists/components/update-playlist-form'; import { SettingsContextModal } from '/@/renderer/features/settings/components/settings-modal'; import { RouterErrorBoundary } from '/@/renderer/features/shared/components/router-error-boundary'; import { ShareItemContextModal } from '/@/renderer/features/sharing/components/share-item-context-modal'; @@ -90,6 +91,7 @@ export const AppRouter = () => { settings: SettingsContextModal, shareItem: ShareItemContextModal, shuffleAll: ShuffleAllContextModal, + updatePlaylist: UpdatePlaylistContextModal, }} > diff --git a/src/renderer/store/auth.store.ts b/src/renderer/store/auth.store.ts index 1dfdf8699..d3ee0c7b7 100644 --- a/src/renderer/store/auth.store.ts +++ b/src/renderer/store/auth.store.ts @@ -161,6 +161,7 @@ export const usePermissions = () => { return { playlists: { + editOwner: isAdmin, editPublic: isAdmin, }, radio: {