Add owner to playlist update query

- Support smart playlist rules
- Add user list query
This commit is contained in:
jeffvli 2023-01-04 18:33:49 -08:00
parent 75ef43dffb
commit d63e5f5784
13 changed files with 309 additions and 59 deletions

View file

@ -35,6 +35,8 @@ import type {
RawArtistListResponse, RawArtistListResponse,
UpdatePlaylistArgs, UpdatePlaylistArgs,
RawUpdatePlaylistResponse, RawUpdatePlaylistResponse,
UserListArgs,
RawUserListResponse,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { subsonicApi } from '/@/renderer/api/subsonic.api'; import { subsonicApi } from '/@/renderer/api/subsonic.api';
import { jellyfinApi } from '/@/renderer/api/jellyfin.api'; import { jellyfinApi } from '/@/renderer/api/jellyfin.api';
@ -62,6 +64,7 @@ export type ControllerEndpoint = Partial<{
getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<RawSongListResponse>; getPlaylistSongList: (args: PlaylistSongListArgs) => Promise<RawSongListResponse>;
getSongDetail: (args: SongDetailArgs) => Promise<RawSongDetailResponse>; getSongDetail: (args: SongDetailArgs) => Promise<RawSongDetailResponse>;
getSongList: (args: SongListArgs) => Promise<RawSongListResponse>; getSongList: (args: SongListArgs) => Promise<RawSongListResponse>;
getUserList: (args: UserListArgs) => Promise<RawUserListResponse>;
updatePlaylist: (args: UpdatePlaylistArgs) => Promise<RawUpdatePlaylistResponse>; updatePlaylist: (args: UpdatePlaylistArgs) => Promise<RawUpdatePlaylistResponse>;
updateRating: (args: RatingArgs) => Promise<RawRatingResponse>; updateRating: (args: RatingArgs) => Promise<RawRatingResponse>;
}>; }>;
@ -96,6 +99,7 @@ const endpoints: ApiController = {
getPlaylistSongList: jellyfinApi.getPlaylistSongList, getPlaylistSongList: jellyfinApi.getPlaylistSongList,
getSongDetail: undefined, getSongDetail: undefined,
getSongList: jellyfinApi.getSongList, getSongList: jellyfinApi.getSongList,
getUserList: undefined,
updatePlaylist: jellyfinApi.updatePlaylist, updatePlaylist: jellyfinApi.updatePlaylist,
updateRating: undefined, updateRating: undefined,
}, },
@ -122,6 +126,7 @@ const endpoints: ApiController = {
getPlaylistSongList: navidromeApi.getPlaylistSongList, getPlaylistSongList: navidromeApi.getPlaylistSongList,
getSongDetail: navidromeApi.getSongDetail, getSongDetail: navidromeApi.getSongDetail,
getSongList: navidromeApi.getSongList, getSongList: navidromeApi.getSongList,
getUserList: navidromeApi.getUserList,
updatePlaylist: navidromeApi.updatePlaylist, updatePlaylist: navidromeApi.updatePlaylist,
updateRating: subsonicApi.updateRating, updateRating: subsonicApi.updateRating,
}, },
@ -147,6 +152,7 @@ const endpoints: ApiController = {
getPlaylistList: undefined, getPlaylistList: undefined,
getSongDetail: undefined, getSongDetail: undefined,
getSongList: undefined, getSongList: undefined,
getUserList: undefined,
updatePlaylist: undefined, updatePlaylist: undefined,
updateRating: undefined, updateRating: undefined,
}, },
@ -227,6 +233,10 @@ const getPlaylistSongList = async (args: PlaylistSongListArgs) => {
); );
}; };
const getUserList = async (args: UserListArgs) => {
return (apiController('getUserList') as ControllerEndpoint['getUserList'])?.(args);
};
export const controller = { export const controller = {
createPlaylist, createPlaylist,
deletePlaylist, deletePlaylist,
@ -240,5 +250,6 @@ export const controller = {
getPlaylistList, getPlaylistList,
getPlaylistSongList, getPlaylistSongList,
getSongList, getSongList,
getUserList,
updatePlaylist, updatePlaylist,
}; };

View file

@ -433,26 +433,26 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<JFPlaylistList>
}; };
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => { const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
const { query, server } = args; const { body, server } = args;
const body = { const json = {
MediaType: 'Audio', MediaType: 'Audio',
Name: query.name, Name: body.name,
Overview: query.comment || '', Overview: body.comment || '',
UserId: server?.userId, UserId: server?.userId,
}; };
const data = await api const data = await api
.post('playlists', { .post('playlists', {
headers: { 'X-MediaBrowser-Token': server?.credential }, headers: { 'X-MediaBrowser-Token': server?.credential },
json: body, json,
prefixUrl: server?.url, prefixUrl: server?.url,
}) })
.json<JFCreatePlaylistResponse>(); .json<JFCreatePlaylistResponse>();
return { return {
id: data.Id, id: data.Id,
name: query.name, name: body.name,
}; };
}; };
@ -760,12 +760,13 @@ const normalizePlaylist = (
imagePlaceholderUrl, imagePlaceholderUrl,
imageUrl: imageUrl || null, imageUrl: imageUrl || null,
name: item.Name, name: item.Name,
owner: null,
ownerId: null,
public: null, public: null,
rules: null, rules: null,
size: null, size: null,
songCount: item?.ChildCount || null, songCount: item?.ChildCount || null,
userId: null, sync: null,
username: null,
}; };
}; };

View file

@ -37,9 +37,13 @@ import type {
NDPlaylistSongListResponse, NDPlaylistSongListResponse,
NDPlaylistSongList, NDPlaylistSongList,
NDPlaylistSong, NDPlaylistSong,
NDUserList,
NDUserListResponse,
NDUserListParams,
NDUser,
} from '/@/renderer/api/navidrome.types'; } from '/@/renderer/api/navidrome.types';
import { NDPlaylistListSort, NDSongListSort, NDSortOrder } from '/@/renderer/api/navidrome.types'; import { NDSongListSort, NDSortOrder } from '/@/renderer/api/navidrome.types';
import type { import {
Album, Album,
Song, Song,
AuthenticationResponse, AuthenticationResponse,
@ -60,13 +64,14 @@ import type {
Playlist, Playlist,
UpdatePlaylistResponse, UpdatePlaylistResponse,
UpdatePlaylistArgs, UpdatePlaylistArgs,
} from '/@/renderer/api/types'; UserListArgs,
import { userListSortMap,
playlistListSortMap, playlistListSortMap,
albumArtistListSortMap, albumArtistListSortMap,
songListSortMap, songListSortMap,
albumListSortMap, albumListSortMap,
sortOrderMap, sortOrderMap,
User,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { toast } from '/@/renderer/components/toast'; import { toast } from '/@/renderer/components/toast';
import { useAuthStore } from '/@/renderer/store'; import { useAuthStore } from '/@/renderer/store';
@ -132,6 +137,34 @@ const authenticate = async (
}; };
}; };
const getUserList = async (args: UserListArgs): Promise<NDUserList> => {
const { query, server, signal } = args;
const searchParams: NDUserListParams = {
_end: query.startIndex + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder],
_sort: userListSortMap.navidrome[query.sortBy],
_start: query.startIndex,
...query.ndParams,
};
const res = await api.get('api/user', {
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
prefixUrl: server?.url,
searchParams: parseSearchParams(searchParams),
signal,
});
const data = await res.json<NDUserListResponse>();
const itemCount = res.headers.get('x-total-count');
return {
items: data,
startIndex: query?.startIndex || 0,
totalRecordCount: Number(itemCount),
};
};
const getGenreList = async (args: GenreListArgs): Promise<NDGenreList> => { const getGenreList = async (args: GenreListArgs): Promise<NDGenreList> => {
const { server, signal } = args; const { server, signal } = args;
@ -293,12 +326,14 @@ const getSongDetail = async (args: SongDetailArgs): Promise<NDSongDetail> => {
}; };
const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => { const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistResponse> => {
const { query, server } = args; const { body, server } = args;
const json: NDCreatePlaylistParams = { const json: NDCreatePlaylistParams = {
comment: query.comment, comment: body.comment,
name: query.name, name: body.name,
public: query.public || false, ...body.ndParams,
public: body.ndParams?.public || false,
rules: body.ndParams?.rules ? body.ndParams.rules : undefined,
}; };
const data = await api const data = await api
@ -311,7 +346,7 @@ const createPlaylist = async (args: CreatePlaylistArgs): Promise<CreatePlaylistR
return { return {
id: data.id, id: data.id,
name: query.name, name: body.name,
}; };
}; };
@ -321,7 +356,11 @@ const updatePlaylist = async (args: UpdatePlaylistArgs): Promise<UpdatePlaylistR
const json: NDUpdatePlaylistParams = { const json: NDUpdatePlaylistParams = {
comment: body.comment || '', comment: body.comment || '',
name: body.name, name: body.name,
public: body.public || false, ownerId: body.ndParams?.ownerId || undefined,
ownerName: body.ndParams?.owner || undefined,
public: body.ndParams?.public || false,
rules: body.ndParams?.rules ? body.ndParams?.rules : undefined,
sync: body.ndParams?.sync || undefined,
}; };
const data = await api const data = await api
@ -357,8 +396,8 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<NDPlaylistList>
const searchParams: NDPlaylistListParams = { const searchParams: NDPlaylistListParams = {
_end: query.startIndex + (query.limit || 0), _end: query.startIndex + (query.limit || 0),
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : NDSortOrder.ASC, _order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : undefined,
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : NDPlaylistListSort.NAME, _sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : undefined,
_start: query.startIndex, _start: query.startIndex,
...query.ndParams, ...query.ndParams,
}; };
@ -583,12 +622,25 @@ const normalizePlaylist = (
imagePlaceholderUrl, imagePlaceholderUrl,
imageUrl, imageUrl,
name: item.name, name: item.name,
owner: item.ownerName,
ownerId: item.ownerId,
public: item.public, public: item.public,
rules: item?.rules || null, rules: item?.rules || null,
size: item.size, size: item.size,
songCount: item.songCount, songCount: item.songCount,
userId: item.ownerId, sync: item.sync,
username: item.ownerName, };
};
const normalizeUser = (item: NDUser): User => {
return {
createdAt: item.createdAt,
email: item.email,
id: item.id,
isAdmin: item.isAdmin,
lastLoginAt: item.lastLoginAt,
name: item.userName,
updatedAt: item.updatedAt,
}; };
}; };
@ -606,6 +658,7 @@ export const navidromeApi = {
getPlaylistSongList, getPlaylistSongList,
getSongDetail, getSongDetail,
getSongList, getSongList,
getUserList,
updatePlaylist, updatePlaylist,
}; };
@ -614,4 +667,5 @@ export const ndNormalize = {
albumArtist: normalizeAlbumArtist, albumArtist: normalizeAlbumArtist,
playlist: normalizePlaylist, playlist: normalizePlaylist,
song: normalizeSong, song: normalizeSong,
user: normalizeUser,
}; };

View file

@ -8,6 +8,18 @@ export type NDAuthenticate = {
username: string; username: string;
}; };
export type NDUser = {
createdAt: string;
email: string;
id: string;
isAdmin: boolean;
lastAccessAt: string;
lastLoginAt: string;
name: string;
updatedAt: string;
userName: string;
};
export type NDGenre = { export type NDGenre = {
id: string; id: string;
name: string; name: string;
@ -376,3 +388,20 @@ export const NDSongQueryFields = [
{ label: 'Play count', value: 'playcount' }, { label: 'Play count', value: 'playcount' },
{ label: 'Rating', value: 'rating' }, { label: 'Rating', value: 'rating' },
]; ];
export type NDUserListParams = {
_sort?: NDUserListSort;
} & NDPagination &
NDOrder;
export type NDUserListResponse = NDUser[];
export type NDUserList = {
items: NDUser[];
startIndex: number;
totalRecordCount: number;
};
export enum NDUserListSort {
NAME = 'name',
}

View file

@ -14,6 +14,7 @@ import type {
NDGenreList, NDGenreList,
NDPlaylist, NDPlaylist,
NDSong, NDSong,
NDUser,
} from '/@/renderer/api/navidrome.types'; } from '/@/renderer/api/navidrome.types';
import { SSGenreList, SSMusicFolderList } from '/@/renderer/api/subsonic.types'; import { SSGenreList, SSMusicFolderList } from '/@/renderer/api/subsonic.types';
import type { import type {
@ -26,6 +27,7 @@ import type {
RawPlaylistDetailResponse, RawPlaylistDetailResponse,
RawPlaylistListResponse, RawPlaylistListResponse,
RawSongListResponse, RawSongListResponse,
RawUserListResponse,
} from '/@/renderer/api/types'; } from '/@/renderer/api/types';
import { ServerListItem } from '/@/renderer/types'; import { ServerListItem } from '/@/renderer/types';
@ -211,6 +213,25 @@ const playlistDetail = (
return playlist; return playlist;
}; };
const userList = (data: RawUserListResponse | undefined, server: ServerListItem | null) => {
let users;
switch (server?.type) {
case 'jellyfin':
break;
case 'navidrome':
users = data?.items.map((item) => ndNormalize.user(item as NDUser));
break;
case 'subsonic':
break;
}
return {
items: users,
startIndex: data?.startIndex,
totalRecordCount: data?.totalRecordCount,
};
};
export const normalize = { export const normalize = {
albumArtistList, albumArtistList,
albumDetail, albumDetail,
@ -220,4 +241,5 @@ export const normalize = {
playlistDetail, playlistDetail,
playlistList, playlistList,
songList, songList,
userList,
}; };

View file

@ -7,6 +7,7 @@ import type {
PlaylistListQuery, PlaylistListQuery,
PlaylistDetailQuery, PlaylistDetailQuery,
PlaylistSongListQuery, PlaylistSongListQuery,
UserListQuery,
} from './types'; } from './types';
export const queryKeys = { export const queryKeys = {
@ -79,4 +80,11 @@ export const queryKeys = {
}, },
root: (serverId: string) => [serverId, 'songs'] as const, root: (serverId: string) => [serverId, 'songs'] as const,
}, },
users: {
list: (serverId: string, query?: UserListQuery) => {
if (query) return [serverId, 'users', 'list', query] as const;
return [serverId, 'users', 'list'] as const;
},
root: (serverId: string) => [serverId, 'users'] as const,
},
}; };

View file

@ -33,6 +33,8 @@ import {
NDPlaylistListSort, NDPlaylistListSort,
NDPlaylistDetail, NDPlaylistDetail,
NDSongListSort, NDSongListSort,
NDUserList,
NDUserListSort,
} from '/@/renderer/api/navidrome.types'; } from '/@/renderer/api/navidrome.types';
import { import {
SSAlbumList, SSAlbumList,
@ -48,6 +50,16 @@ export enum SortOrder {
DESC = 'DESC', DESC = 'DESC',
} }
export type User = {
createdAt: string | null;
email: string | null;
id: string;
isAdmin: boolean | null;
lastLoginAt: string | null;
name: string;
updatedAt: string | null;
};
export type ServerListItem = { export type ServerListItem = {
credential: string; credential: string;
id: string; id: string;
@ -242,12 +254,13 @@ export type Playlist = {
imagePlaceholderUrl: string | null; imagePlaceholderUrl: string | null;
imageUrl: string | null; imageUrl: string | null;
name: string; name: string;
owner: string | null;
ownerId: string | null;
public: boolean | null; public: boolean | null;
rules?: Record<string, any> | null; rules?: Record<string, any> | null;
size: number | null; size: number | null;
songCount: number | null; songCount: number | null;
userId: string | null; sync?: boolean | null;
username: string | null;
}; };
export type GenresResponse = Genre[]; export type GenresResponse = Genre[];
@ -739,14 +752,19 @@ export type RawCreatePlaylistResponse = CreatePlaylistResponse | undefined;
export type CreatePlaylistResponse = { id: string; name: string }; export type CreatePlaylistResponse = { id: string; name: string };
export type CreatePlaylistQuery = { export type CreatePlaylistBody = {
comment?: string; comment?: string;
name: string; name: string;
public?: boolean; ndParams?: {
rules?: Record<string, any>; owner?: string;
ownerId?: string;
public?: boolean;
rules?: Record<string, any>;
sync?: boolean;
};
}; };
export type CreatePlaylistArgs = { query: CreatePlaylistQuery } & BaseEndpointArgs; export type CreatePlaylistArgs = { body: CreatePlaylistBody } & BaseEndpointArgs;
// Update Playlist // Update Playlist
export type RawUpdatePlaylistResponse = UpdatePlaylistResponse | undefined; export type RawUpdatePlaylistResponse = UpdatePlaylistResponse | undefined;
@ -761,8 +779,13 @@ export type UpdatePlaylistBody = {
comment?: string; comment?: string;
genres?: Genre[]; genres?: Genre[];
name: string; name: string;
public?: boolean; ndParams?: {
rules?: Record<string, any>; owner?: string;
ownerId?: string;
public?: boolean;
rules?: Record<string, any>;
sync?: boolean;
};
}; };
export type UpdatePlaylistArgs = { export type UpdatePlaylistArgs = {
@ -880,3 +903,44 @@ export type CreateFavoriteResponse = { id: string };
export type CreateFavoriteQuery = { comment?: string; name: string; public?: boolean }; export type CreateFavoriteQuery = { comment?: string; name: string; public?: boolean };
export type CreateFavoriteArgs = { query: CreateFavoriteQuery } & BaseEndpointArgs; export type CreateFavoriteArgs = { query: CreateFavoriteQuery } & BaseEndpointArgs;
// User list
// Playlist List
export type RawUserListResponse = NDUserList | undefined;
export type UserListResponse = BasePaginatedResponse<User[]>;
export enum UserListSort {
NAME = 'name',
}
export type UserListQuery = {
limit?: number;
ndParams?: {
owner_id?: string;
};
searchTerm?: string;
sortBy: UserListSort;
sortOrder: SortOrder;
startIndex: number;
};
export type UserListArgs = { query: UserListQuery } & BaseEndpointArgs;
type UserListSortMap = {
jellyfin: Record<UserListSort, undefined>;
navidrome: Record<UserListSort, NDUserListSort | undefined>;
subsonic: Record<UserListSort, undefined>;
};
export const userListSortMap: UserListSortMap = {
jellyfin: {
name: undefined,
},
navidrome: {
name: NDUserListSort.NAME,
},
subsonic: {
name: undefined,
},
};

View file

@ -1,6 +1,6 @@
import { Group, Stack } from '@mantine/core'; import { Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { CreatePlaylistQuery, ServerType } from '/@/renderer/api/types'; import { CreatePlaylistBody, ServerType } from '/@/renderer/api/types';
import { Button, Switch, TextInput, toast } from '/@/renderer/components'; import { Button, Switch, TextInput, toast } from '/@/renderer/components';
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation'; import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
@ -13,18 +13,20 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
const mutation = useCreatePlaylist(); const mutation = useCreatePlaylist();
const server = useCurrentServer(); const server = useCurrentServer();
const form = useForm<CreatePlaylistQuery>({ const form = useForm<CreatePlaylistBody>({
initialValues: { initialValues: {
comment: '', comment: '',
name: '', name: '',
public: false, ndParams: {
rules: undefined, public: false,
rules: undefined,
},
}, },
}); });
const handleSubmit = form.onSubmit((values) => { const handleSubmit = form.onSubmit((values) => {
mutation.mutate( mutation.mutate(
{ query: values }, { body: values },
{ {
onError: (err) => { onError: (err) => {
toast.error({ message: err.message, title: 'Error creating playlist' }); toast.error({ message: err.message, title: 'Error creating playlist' });
@ -56,7 +58,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
{isPublicDisplayed && ( {isPublicDisplayed && (
<Switch <Switch
label="Is Public?" label="Is Public?"
{...form.getInputProps('public')} {...form.getInputProps('ndParams.public')}
/> />
)} )}
<Group position="right"> <Group position="right">

View file

@ -1,6 +1,7 @@
import { forwardRef, Fragment, Ref } from 'react';
import { Group, Stack } from '@mantine/core'; import { Group, Stack } from '@mantine/core';
import { closeAllModals, openModal } from '@mantine/modals'; import { closeAllModals, openModal } from '@mantine/modals';
import { forwardRef, Fragment, Ref } from 'react'; import { useQueryClient } from '@tanstack/react-query';
import { RiMoreFill } from 'react-icons/ri'; import { RiMoreFill } from 'react-icons/ri';
import { generatePath, useNavigate, useParams } from 'react-router'; import { generatePath, useNavigate, useParams } from 'react-router';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -14,6 +15,10 @@ import { AppRoute } from '/@/renderer/router/routes';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem, Play } from '/@/renderer/types'; import { LibraryItem, Play } from '/@/renderer/types';
import { formatDurationString } from '/@/renderer/utils'; import { formatDurationString } from '/@/renderer/utils';
import { UserListSort, SortOrder, UserListQuery } from '/@/renderer/api/types';
import { useCurrentServer } from '/@/renderer/store';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
interface PlaylistDetailHeaderProps { interface PlaylistDetailHeaderProps {
background: string; background: string;
@ -27,10 +32,12 @@ export const PlaylistDetailHeader = forwardRef(
ref: Ref<HTMLDivElement>, ref: Ref<HTMLDivElement>,
) => { ) => {
const navigate = useNavigate(); const navigate = useNavigate();
const queryClient = useQueryClient();
const { playlistId } = useParams() as { playlistId: string }; const { playlistId } = useParams() as { playlistId: string };
const detailQuery = usePlaylistDetail({ id: playlistId }); const detailQuery = usePlaylistDetail({ id: playlistId });
const handlePlayQueueAdd = usePlayQueueAdd(); const handlePlayQueueAdd = usePlayQueueAdd();
const playButtonBehavior = usePlayButtonBehavior(); const playButtonBehavior = usePlayButtonBehavior();
const server = useCurrentServer();
const handlePlay = (playType?: Play) => { const handlePlay = (playType?: Play) => {
handlePlayQueueAdd?.({ handlePlayQueueAdd?.({
@ -42,7 +49,20 @@ export const PlaylistDetailHeader = forwardRef(
}); });
}; };
const openUpdatePlaylistModal = () => { const openUpdatePlaylistModal = async () => {
const query: UserListQuery = {
sortBy: UserListSort.NAME,
sortOrder: SortOrder.ASC,
startIndex: 0,
};
const users = await queryClient.fetchQuery({
queryFn: ({ signal }) => api.controller.getUserList({ query, server, signal }),
queryKey: queryKeys.users.list(server?.id || '', query),
});
const normalizedUsers = api.normalize.userList(users, server);
openModal({ openModal({
children: ( children: (
<UpdatePlaylistForm <UpdatePlaylistForm
@ -50,10 +70,16 @@ export const PlaylistDetailHeader = forwardRef(
comment: detailQuery?.data?.description || undefined, comment: detailQuery?.data?.description || undefined,
genres: detailQuery?.data?.genres, genres: detailQuery?.data?.genres,
name: detailQuery?.data?.name, name: detailQuery?.data?.name,
public: detailQuery?.data?.public || false, ndParams: {
rules: detailQuery?.data?.rules || undefined, owner: detailQuery?.data?.owner || undefined,
ownerId: detailQuery?.data?.ownerId || undefined,
public: detailQuery?.data?.public || false,
rules: detailQuery?.data?.rules || undefined,
sync: detailQuery?.data?.sync || undefined,
},
}} }}
query={{ id: playlistId }} query={{ id: playlistId }}
users={normalizedUsers.items}
onCancel={closeAllModals} onCancel={closeAllModals}
/> />
), ),

View file

@ -1,27 +1,37 @@
import { Group, Stack } from '@mantine/core'; import { Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { ServerType, UpdatePlaylistBody, UpdatePlaylistQuery } from '/@/renderer/api/types'; import { ServerType, UpdatePlaylistBody, UpdatePlaylistQuery, User } from '/@/renderer/api/types';
import { Button, Switch, TextInput, toast } from '/@/renderer/components'; import { Button, Select, Switch, TextInput, toast } from '/@/renderer/components';
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation'; import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
interface CreatePlaylistFormProps { interface UpdatePlaylistFormProps {
body: Partial<UpdatePlaylistBody>; body: Partial<UpdatePlaylistBody>;
onCancel: () => void; onCancel: () => void;
query: UpdatePlaylistQuery; query: UpdatePlaylistQuery;
users?: User[];
} }
export const UpdatePlaylistForm = ({ query, body, onCancel }: CreatePlaylistFormProps) => { export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlaylistFormProps) => {
const mutation = useUpdatePlaylist(); const mutation = useUpdatePlaylist();
const server = useCurrentServer(); const server = useCurrentServer();
const userList = users?.map((user) => ({
label: user.name,
value: user.id,
}));
const form = useForm<UpdatePlaylistBody>({ const form = useForm<UpdatePlaylistBody>({
initialValues: { initialValues: {
comment: '', comment: body?.comment || '',
name: '', name: body?.name || '',
public: false, ndParams: {
rules: undefined, owner: body?.ndParams?.owner || '',
...body, ownerId: body?.ndParams?.ownerId || '',
public: body?.ndParams?.public || false,
rules: undefined,
sync: body?.ndParams?.sync || false,
},
}, },
}); });
@ -56,6 +66,11 @@ export const UpdatePlaylistForm = ({ query, body, onCancel }: CreatePlaylistForm
label="Description" label="Description"
{...form.getInputProps('comment')} {...form.getInputProps('comment')}
/> />
<Select
data={userList || []}
{...form.getInputProps('ndParams.ownerId')}
label="Owner"
/>
{isPublicDisplayed && ( {isPublicDisplayed && (
<Switch <Switch
label="Is Public?" label="Is Public?"

View file

@ -0,0 +1 @@
export * from './queries/user-list-query';

View file

@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react';
import { queryKeys } from '/@/renderer/api/query-keys';
import type { RawUserListResponse, UserListQuery } from '/@/renderer/api/types';
import { useCurrentServer } from '/@/renderer/store';
import { api } from '/@/renderer/api';
import type { QueryOptions } from '/@/renderer/lib/react-query';
export const useUserList = (query: UserListQuery, options?: QueryOptions) => {
const server = useCurrentServer();
return useQuery({
enabled: !!server?.id,
queryFn: ({ signal }) => api.controller.getUserList({ query, server, signal }),
queryKey: queryKeys.users.list(server?.id || '', query),
select: useCallback(
(data: RawUserListResponse | undefined) => api.normalize.userList(data, server),
[server],
),
...options,
});
};

View file

@ -111,22 +111,17 @@ export interface UniqueId {
uniqueId: string; uniqueId: string;
} }
export enum FilterGroupType { export type QueryBuilderRule = {
AND = 'AND',
OR = 'OR',
}
export type AdvancedFilterRule = {
field?: string | null; field?: string | null;
operator?: string | null; operator?: string | null;
uniqueId: string; uniqueId: string;
value?: string | number | Date | undefined | null | any; value?: string | number | Date | undefined | null | any;
}; };
export type AdvancedFilterGroup = { export type QueryBuilderGroup = {
group: AdvancedFilterGroup[]; group: QueryBuilderGroup[];
rules: AdvancedFilterRule[]; rules: QueryBuilderRule[];
type: FilterGroupType; type: 'any' | 'all';
uniqueId: string; uniqueId: string;
}; };