mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
restructure files onto electron-vite boilerplate
This commit is contained in:
parent
91ce2cd8a1
commit
1cf587bc8f
457 changed files with 9927 additions and 11705 deletions
|
|
@ -1,7 +1,9 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { Box, Group, Stack } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { closeModal, ContextModalProps } from '@mantine/modals';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { PlaylistListSort, SongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||
|
|
@ -11,7 +13,6 @@ import { useAddToPlaylist } from '/@/renderer/features/playlists/mutations/add-t
|
|||
import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const AddToPlaylistContextModal = ({
|
||||
id,
|
||||
|
|
@ -217,13 +218,13 @@ export const AddToPlaylistContextModal = ({
|
|||
<Stack>
|
||||
<MultiSelect
|
||||
clearable
|
||||
searchable
|
||||
data={playlistSelect}
|
||||
disabled={playlistList.isLoading}
|
||||
label={t('form.addToPlaylist.input', {
|
||||
context: 'playlists',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
searchable
|
||||
size="md"
|
||||
{...form.getInputProps('playlistId')}
|
||||
/>
|
||||
|
|
@ -238,9 +239,9 @@ export const AddToPlaylistContextModal = ({
|
|||
<Group>
|
||||
<Button
|
||||
disabled={addToPlaylistMutation.isLoading}
|
||||
onClick={() => closeModal(id)}
|
||||
size="md"
|
||||
variant="subtle"
|
||||
onClick={() => closeModal(id)}
|
||||
>
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { useRef, useState } from 'react';
|
||||
import { Group, Stack } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { CreatePlaylistBody, ServerType, SongListSort } from '/@/renderer/api/types';
|
||||
import { hasFeature } from '/@/renderer/api/utils';
|
||||
import { Button, Switch, Text, TextInput, toast } from '/@/renderer/components';
|
||||
import {
|
||||
PlaylistQueryBuilder,
|
||||
|
|
@ -10,9 +14,6 @@ import {
|
|||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||
import { convertQueryGroupToNDQuery } from '/@/renderer/features/playlists/utils';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { hasFeature } from '/@/renderer/api/utils';
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
|
||||
interface CreatePlaylistFormProps {
|
||||
onCancel: () => void;
|
||||
|
|
@ -95,11 +96,11 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
|||
<Stack>
|
||||
<TextInput
|
||||
data-autofocus
|
||||
required
|
||||
label={t('form.createPlaylist.input', {
|
||||
context: 'name',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
required
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
{server?.type === ServerType.NAVIDROME && (
|
||||
|
|
@ -135,10 +136,10 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
|||
<Stack pt="1rem">
|
||||
<Text>Query Editor</Text>
|
||||
<PlaylistQueryBuilder
|
||||
ref={queryBuilderRef}
|
||||
isSaving={false}
|
||||
limit={undefined}
|
||||
query={undefined}
|
||||
ref={queryBuilderRef}
|
||||
sortBy={SongListSort.ALBUM}
|
||||
sortOrder="asc"
|
||||
/>
|
||||
|
|
@ -147,8 +148,8 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
|||
|
||||
<Group position="right">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import type {
|
|||
RowDragEvent,
|
||||
} from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
|
|
@ -23,8 +25,9 @@ import {
|
|||
SongListSort,
|
||||
SortOrder,
|
||||
} from '/@/renderer/api/types';
|
||||
import { toast } from '/@/renderer/components';
|
||||
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||
import { TablePagination, VirtualTable, getColumnDefs } from '/@/renderer/components/virtual-table';
|
||||
import { getColumnDefs, TablePagination, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
|
||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||
import {
|
||||
|
|
@ -34,6 +37,7 @@ import {
|
|||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||
import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
|
||||
import { useAppFocus } from '/@/renderer/hooks';
|
||||
import {
|
||||
useCurrentServer,
|
||||
useCurrentSong,
|
||||
|
|
@ -45,8 +49,6 @@ import {
|
|||
} from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { ListDisplayType, ServerType } from '/@/renderer/types';
|
||||
import { useAppFocus } from '/@/renderer/hooks';
|
||||
import { toast } from '/@/renderer/components';
|
||||
|
||||
interface PlaylistDetailContentProps {
|
||||
songs?: Song[];
|
||||
|
|
@ -290,12 +292,7 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
|
|||
<>
|
||||
<VirtualGridAutoSizerContainer>
|
||||
<VirtualTable
|
||||
// https://github.com/ag-grid/ag-grid/issues/5284
|
||||
// Key is used to force remount of table when display, rowHeight, or server changes
|
||||
key={`table-${page.display}-${page.table.rowHeight}-${server?.id}`}
|
||||
ref={tableRef}
|
||||
alwaysShowHorizontalScroll
|
||||
shouldUpdateSong
|
||||
autoFitColumns={page.table.autoFit}
|
||||
columnDefs={columnDefs}
|
||||
context={{
|
||||
|
|
@ -309,14 +306,9 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
|
|||
infiniteInitialRowCount={
|
||||
iSClientSide ? undefined : checkPlaylistList.data?.totalRecordCount || 100
|
||||
}
|
||||
pagination={isPaginationEnabled}
|
||||
paginationAutoPageSize={isPaginationEnabled}
|
||||
paginationPageSize={pagination.itemsPerPage || 100}
|
||||
rowClassRules={rowClassRules}
|
||||
rowData={songs}
|
||||
rowDragEntireRow={canDrag}
|
||||
rowHeight={page.table.rowHeight || 40}
|
||||
rowModelType={iSClientSide ? 'clientSide' : 'infinite'}
|
||||
// https://github.com/ag-grid/ag-grid/issues/5284
|
||||
// Key is used to force remount of table when display, rowHeight, or server changes
|
||||
key={`table-${page.display}-${page.table.rowHeight}-${server?.id}`}
|
||||
onBodyScrollEnd={handleScroll}
|
||||
onCellContextMenu={handleContextMenu}
|
||||
onColumnMoved={handleColumnChange}
|
||||
|
|
@ -326,13 +318,23 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
|
|||
onPaginationChanged={onPaginationChanged}
|
||||
onRowDoubleClicked={handleRowDoubleClick}
|
||||
onRowDragEnd={handleDragEnd}
|
||||
pagination={isPaginationEnabled}
|
||||
paginationAutoPageSize={isPaginationEnabled}
|
||||
paginationPageSize={pagination.itemsPerPage || 100}
|
||||
ref={tableRef}
|
||||
rowClassRules={rowClassRules}
|
||||
rowData={songs}
|
||||
rowDragEntireRow={canDrag}
|
||||
rowHeight={page.table.rowHeight || 40}
|
||||
rowModelType={iSClientSide ? 'clientSide' : 'infinite'}
|
||||
shouldUpdateSong
|
||||
/>
|
||||
</VirtualGridAutoSizerContainer>
|
||||
{isPaginationEnabled && (
|
||||
<AnimatePresence
|
||||
presenceAffectsLayout
|
||||
initial={false}
|
||||
mode="wait"
|
||||
presenceAffectsLayout
|
||||
>
|
||||
{page.display === ListDisplayType.TABLE_PAGINATED && (
|
||||
<TablePagination
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
import { useCallback, ChangeEvent, MutableRefObject, MouseEvent } from 'react';
|
||||
import { IDatasource } from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { IDatasource } from '@ag-grid-community/core';
|
||||
import { Divider, Flex, Group, Stack } from '@mantine/core';
|
||||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
RiMoreFill,
|
||||
RiSettings3Fill,
|
||||
RiPlayFill,
|
||||
RiAddCircleFill,
|
||||
RiAddBoxFill,
|
||||
RiEditFill,
|
||||
RiAddCircleFill,
|
||||
RiDeleteBinFill,
|
||||
RiEditFill,
|
||||
RiMoreFill,
|
||||
RiPlayFill,
|
||||
RiRefreshLine,
|
||||
RiSettings3Fill,
|
||||
} from 'react-icons/ri';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
|
|
@ -25,20 +29,26 @@ import {
|
|||
SortOrder,
|
||||
} from '/@/renderer/api/types';
|
||||
import {
|
||||
DropdownMenu,
|
||||
Button,
|
||||
Slider,
|
||||
ConfirmModal,
|
||||
DropdownMenu,
|
||||
MultiSelect,
|
||||
Slider,
|
||||
Switch,
|
||||
Text,
|
||||
ConfirmModal,
|
||||
toast,
|
||||
} from '/@/renderer/components';
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form';
|
||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import {
|
||||
useCurrentServer,
|
||||
SongListFilter,
|
||||
useCurrentServer,
|
||||
usePlaylistDetailStore,
|
||||
useSetPlaylistDetailFilters,
|
||||
useSetPlaylistDetailTable,
|
||||
|
|
@ -46,14 +56,6 @@ import {
|
|||
useSetPlaylistTablePagination,
|
||||
} from '/@/renderer/store';
|
||||
import { ListDisplayType, Play, TableColumn } from '/@/renderer/types';
|
||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||
import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form';
|
||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
const FILTERS = {
|
||||
jellyfin: [
|
||||
|
|
@ -265,8 +267,8 @@ interface PlaylistDetailSongListHeaderFiltersProps {
|
|||
}
|
||||
|
||||
export const PlaylistDetailSongListHeaderFilters = ({
|
||||
tableRef,
|
||||
handleToggleShowQueryBuilder,
|
||||
tableRef,
|
||||
}: PlaylistDetailSongListHeaderFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
|
|
@ -493,10 +495,10 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
<DropdownMenu.Dropdown>
|
||||
{FILTERS[server?.type as keyof typeof FILTERS].map((filter) => (
|
||||
<DropdownMenu.Item
|
||||
key={`filter-${filter.name}`}
|
||||
$isActive={filter.value === filters.sortBy}
|
||||
value={filter.value}
|
||||
key={`filter-${filter.name}`}
|
||||
onClick={handleSetSortBy}
|
||||
value={filter.value}
|
||||
>
|
||||
{filter.name}
|
||||
</DropdownMenu.Item>
|
||||
|
|
@ -506,8 +508,8 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
sortOrder={filters.sortOrder || SortOrder.ASC}
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filters.sortOrder || SortOrder.ASC}
|
||||
/>
|
||||
<Divider orientation="vertical" />
|
||||
<DropdownMenu position="bottom-start">
|
||||
|
|
@ -601,8 +603,8 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Item
|
||||
$isActive={page.display === ListDisplayType.TABLE}
|
||||
value={ListDisplayType.TABLE}
|
||||
onClick={handleSetViewType}
|
||||
value={ListDisplayType.TABLE}
|
||||
>
|
||||
Table
|
||||
</DropdownMenu.Item>
|
||||
|
|
@ -642,8 +644,8 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
|||
defaultValue={page.table?.columns.map(
|
||||
(column) => column.column,
|
||||
)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
width={300}
|
||||
/>
|
||||
<Group position="apart">
|
||||
<Text>Auto Fit Columns</Text>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { MutableRefObject } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { Stack } from '@mantine/core';
|
||||
import { MutableRefObject } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
import { Badge, PageHeader, Paper, SpinnerIcon } from '/@/renderer/components';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
|
|
@ -20,9 +22,9 @@ interface PlaylistDetailHeaderProps {
|
|||
}
|
||||
|
||||
export const PlaylistDetailSongListHeader = ({
|
||||
tableRef,
|
||||
itemCount,
|
||||
handleToggleShowQueryBuilder,
|
||||
itemCount,
|
||||
tableRef,
|
||||
}: PlaylistDetailHeaderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { playlistId } = useParams() as { playlistId: string };
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { lazy, MutableRefObject, Suspense } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { lazy, MutableRefObject, Suspense } from 'react';
|
||||
|
||||
import { useListContext } from '../../../context/list-context';
|
||||
import { useListStoreByKey } from '../../../store/list.store';
|
||||
|
||||
import { Spinner } from '/@/renderer/components';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { ListDisplayType } from '/@/renderer/types';
|
||||
import { useListContext } from '../../../context/list-context';
|
||||
import { useListStoreByKey } from '../../../store/list.store';
|
||||
|
||||
const PlaylistListTableView = lazy(() =>
|
||||
import('/@/renderer/features/playlists/components/playlist-list-table-view').then((module) => ({
|
||||
|
|
@ -19,12 +22,12 @@ const PlaylistListGridView = lazy(() =>
|
|||
);
|
||||
|
||||
interface PlaylistListContentProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const PlaylistListContent = ({ gridRef, tableRef, itemCount }: PlaylistListContentProps) => {
|
||||
export const PlaylistListContent = ({ gridRef, itemCount, tableRef }: PlaylistListContentProps) => {
|
||||
const { pageKey } = useListContext();
|
||||
const { display } = useListStoreByKey({ key: pageKey });
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import { QueryKey, useQueryClient } from '@tanstack/react-query';
|
|||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import AutoSizer, { Size } from 'react-virtualized-auto-sizer';
|
||||
import { ListOnScrollProps } from 'react-window';
|
||||
|
||||
import { useListContext } from '../../../context/list-context';
|
||||
import { useListStoreActions } from '../../../store/list.store';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
|
|
@ -20,13 +22,13 @@ import {
|
|||
VirtualInfiniteGridRef,
|
||||
} from '/@/renderer/components/virtual-grid';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer, useListStoreByKey } from '/@/renderer/store';
|
||||
import { CardRow, ListDisplayType } from '/@/renderer/types';
|
||||
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
|
||||
|
||||
interface PlaylistListGridViewProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +37,7 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
|||
const queryClient = useQueryClient();
|
||||
const server = useCurrentServer();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const { display, grid, filter } = useListStoreByKey<PlaylistListQuery>({ key: pageKey });
|
||||
const { display, filter, grid } = useListStoreByKey<PlaylistListQuery>({ key: pageKey });
|
||||
const { setGrid } = useListStoreActions();
|
||||
const handleFavorite = useHandleFavorite({ gridRef, server });
|
||||
|
||||
|
|
@ -49,15 +51,15 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
|||
case PlaylistListSort.NAME:
|
||||
rows.push(PLAYLIST_CARD_ROWS.songCount);
|
||||
break;
|
||||
case PlaylistListSort.SONG_COUNT:
|
||||
rows.push(PLAYLIST_CARD_ROWS.songCount);
|
||||
break;
|
||||
case PlaylistListSort.OWNER:
|
||||
rows.push(PLAYLIST_CARD_ROWS.owner);
|
||||
break;
|
||||
case PlaylistListSort.PUBLIC:
|
||||
rows.push(PLAYLIST_CARD_ROWS.public);
|
||||
break;
|
||||
case PlaylistListSort.SONG_COUNT:
|
||||
rows.push(PLAYLIST_CARD_ROWS.songCount);
|
||||
break;
|
||||
case PlaylistListSort.UPDATED_AT:
|
||||
break;
|
||||
}
|
||||
|
|
@ -73,7 +75,7 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
|||
);
|
||||
|
||||
const fetchInitialData = useCallback(() => {
|
||||
const query: Omit<PlaylistListQuery, 'startIndex' | 'limit'> = {
|
||||
const query: Omit<PlaylistListQuery, 'limit' | 'startIndex'> = {
|
||||
...filter,
|
||||
};
|
||||
|
||||
|
|
@ -140,8 +142,6 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
|||
<AutoSizer>
|
||||
{({ height, width }: Size) => (
|
||||
<VirtualInfiniteGrid
|
||||
key={`playlist-list-${server?.id}`}
|
||||
ref={gridRef}
|
||||
cardRows={cardRows}
|
||||
display={display || ListDisplayType.CARD}
|
||||
fetchFn={fetch}
|
||||
|
|
@ -154,14 +154,16 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
|||
itemGap={grid?.itemGap ?? 10}
|
||||
itemSize={grid?.itemSize || 200}
|
||||
itemType={LibraryItem.PLAYLIST}
|
||||
key={`playlist-list-${server?.id}`}
|
||||
loading={itemCount === undefined || itemCount === null}
|
||||
minimumBatchSize={40}
|
||||
onScroll={handleGridScroll}
|
||||
ref={gridRef}
|
||||
route={{
|
||||
route: AppRoute.PLAYLISTS_DETAIL_SONGS,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'playlistId' }],
|
||||
}}
|
||||
width={width}
|
||||
onScroll={handleGridScroll}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||
import { IDatasource } from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { IDatasource } from '@ag-grid-community/core';
|
||||
import { Divider, Flex, Group, Stack } from '@mantine/core';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiMoreFill, RiRefreshLine, RiSettings3Fill } from 'react-icons/ri';
|
||||
|
||||
import { useListContext } from '../../../context/list-context';
|
||||
import { useListStoreByKey } from '../../../store/list.store';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { LibraryItem, PlaylistListQuery, PlaylistListSort, SortOrder } from '/@/renderer/api/types';
|
||||
|
|
@ -17,7 +21,6 @@ import { OrderToggleButton } from '/@/renderer/features/shared';
|
|||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { PlaylistListFilter, useCurrentServer, useListStoreActions } from '/@/renderer/store';
|
||||
import { ListDisplayType, TableColumn } from '/@/renderer/types';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
const FILTERS = {
|
||||
jellyfin: [
|
||||
|
|
@ -104,7 +107,7 @@ const FILTERS = {
|
|||
};
|
||||
|
||||
interface PlaylistListHeaderFiltersProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
|
|
@ -116,9 +119,9 @@ export const PlaylistListHeaderFilters = ({
|
|||
const { pageKey } = useListContext();
|
||||
const queryClient = useQueryClient();
|
||||
const server = useCurrentServer();
|
||||
const { setFilter, setTable, setTablePagination, setGrid, setDisplayType } =
|
||||
const { setDisplayType, setFilter, setGrid, setTable, setTablePagination } =
|
||||
useListStoreActions();
|
||||
const { display, filter, table, grid } = useListStoreByKey<PlaylistListQuery>({ key: pageKey });
|
||||
const { display, filter, grid, table } = useListStoreByKey<PlaylistListQuery>({ key: pageKey });
|
||||
const cq = useContainerQuery();
|
||||
|
||||
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER;
|
||||
|
|
@ -337,10 +340,10 @@ export const PlaylistListHeaderFilters = ({
|
|||
<DropdownMenu.Dropdown>
|
||||
{FILTERS[server?.type as keyof typeof FILTERS].map((f) => (
|
||||
<DropdownMenu.Item
|
||||
key={`filter-${f.name}`}
|
||||
$isActive={f.value === filter.sortBy}
|
||||
value={f.value}
|
||||
key={`filter-${f.name}`}
|
||||
onClick={handleSetSortBy}
|
||||
value={f.value}
|
||||
>
|
||||
{f.name}
|
||||
</DropdownMenu.Item>
|
||||
|
|
@ -349,16 +352,16 @@ export const PlaylistListHeaderFilters = ({
|
|||
</DropdownMenu>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
sortOrder={filter.sortOrder}
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
<Divider orientation="vertical" />
|
||||
<Button
|
||||
compact
|
||||
onClick={handleRefresh}
|
||||
size="md"
|
||||
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
||||
variant="subtle"
|
||||
onClick={handleRefresh}
|
||||
>
|
||||
<RiRefreshLine size="1.3rem" />
|
||||
</Button>
|
||||
|
|
@ -404,22 +407,22 @@ export const PlaylistListHeaderFilters = ({
|
|||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.CARD}
|
||||
value={ListDisplayType.CARD}
|
||||
onClick={handleSetViewType}
|
||||
value={ListDisplayType.CARD}
|
||||
>
|
||||
{t('table.config.view.card', { postProcess: 'titleCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.POSTER}
|
||||
value={ListDisplayType.POSTER}
|
||||
onClick={handleSetViewType}
|
||||
value={ListDisplayType.POSTER}
|
||||
>
|
||||
{t('table.config.view.poster', { postProcess: 'titleCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.TABLE}
|
||||
value={ListDisplayType.TABLE}
|
||||
onClick={handleSetViewType}
|
||||
value={ListDisplayType.TABLE}
|
||||
>
|
||||
{t('table.config.view.table', { postProcess: 'titleCase' })}
|
||||
</DropdownMenu.Item>
|
||||
|
|
@ -478,8 +481,8 @@ export const PlaylistListHeaderFilters = ({
|
|||
defaultValue={table?.columns.map(
|
||||
(column) => column.column,
|
||||
)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
width={300}
|
||||
/>
|
||||
<Group position="apart">
|
||||
<Text>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,29 @@
|
|||
import { ChangeEvent, MutableRefObject } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { Flex, Group, Stack } from '@mantine/core';
|
||||
import { openModal, closeAllModals } from '@mantine/modals';
|
||||
import { PageHeader, SpinnerIcon, Paper, Button, SearchInput } from '/@/renderer/components';
|
||||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, MutableRefObject } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiFileAddFill } from 'react-icons/ri';
|
||||
|
||||
import { LibraryItem, PlaylistListQuery, ServerType } from '/@/renderer/api/types';
|
||||
import { Button, PageHeader, Paper, SearchInput, SpinnerIcon } from '/@/renderer/components';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { CreatePlaylistForm } from '/@/renderer/features/playlists/components/create-playlist-form';
|
||||
import { PlaylistListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-list-header-filters';
|
||||
import { LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { PlaylistListFilter, useCurrentServer } from '/@/renderer/store';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiFileAddFill } from 'react-icons/ri';
|
||||
import { LibraryItem, PlaylistListQuery, ServerType } from '/@/renderer/api/types';
|
||||
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';
|
||||
import { PlaylistListFilter, useCurrentServer } from '/@/renderer/store';
|
||||
|
||||
interface PlaylistListHeaderProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistListHeaderProps) => {
|
||||
export const PlaylistListHeader = ({ gridRef, itemCount, tableRef }: PlaylistListHeaderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const cq = useContainerQuery();
|
||||
const server = useCurrentServer();
|
||||
|
|
@ -78,12 +80,12 @@ export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistLis
|
|||
)}
|
||||
</Paper>
|
||||
<Button
|
||||
onClick={handleCreatePlaylistModal}
|
||||
tooltip={{
|
||||
label: t('action.createPlaylist', { postProcess: 'sentenceCase' }),
|
||||
openDelay: 500,
|
||||
}}
|
||||
variant="filled"
|
||||
onClick={handleCreatePlaylistModal}
|
||||
>
|
||||
<RiFileAddFill />
|
||||
</Button>
|
||||
|
|
@ -91,8 +93,8 @@ export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistLis
|
|||
<Group>
|
||||
<SearchInput
|
||||
defaultValue={filter.searchTerm}
|
||||
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
|
||||
onChange={handleSearch}
|
||||
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
|
||||
/>
|
||||
</Group>
|
||||
</Flex>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { MutableRefObject } from 'react';
|
||||
import { RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||
import { MutableRefObject } from 'react';
|
||||
import { generatePath, useNavigate } from 'react-router';
|
||||
|
||||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||
import { VirtualTable } from '/@/renderer/components/virtual-table';
|
||||
|
|
@ -15,7 +17,7 @@ interface PlaylistListTableViewProps {
|
|||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const PlaylistListTableView = ({ tableRef, itemCount }: PlaylistListTableViewProps) => {
|
||||
export const PlaylistListTableView = ({ itemCount, tableRef }: PlaylistListTableViewProps) => {
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
const pageKey = 'playlist';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { forwardRef, Ref, useImperativeHandle, useMemo, useState } from 'react';
|
||||
import { Group } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { openModal } from '@mantine/modals';
|
||||
|
|
@ -6,6 +5,19 @@ import clone from 'lodash/clone';
|
|||
import get from 'lodash/get';
|
||||
import setWith from 'lodash/setWith';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { forwardRef, Ref, useImperativeHandle, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiMore2Fill, RiSaveLine } from 'react-icons/ri';
|
||||
|
||||
import {
|
||||
NDSongQueryBooleanOperators,
|
||||
NDSongQueryDateOperators,
|
||||
NDSongQueryFields,
|
||||
NDSongQueryNumberOperators,
|
||||
NDSongQueryPlaylistOperators,
|
||||
NDSongQueryStringOperators,
|
||||
} from '/@/renderer/api/navidrome.types';
|
||||
import { PlaylistListSort, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
|
|
@ -15,25 +27,14 @@ import {
|
|||
ScrollArea,
|
||||
Select,
|
||||
} from '/@/renderer/components';
|
||||
import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query';
|
||||
import {
|
||||
convertNDQueryToQueryGroup,
|
||||
convertQueryGroupToNDQuery,
|
||||
} from '/@/renderer/features/playlists/utils';
|
||||
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiMore2Fill, RiSaveLine } from 'react-icons/ri';
|
||||
import { PlaylistListSort, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||
import {
|
||||
NDSongQueryBooleanOperators,
|
||||
NDSongQueryDateOperators,
|
||||
NDSongQueryFields,
|
||||
NDSongQueryNumberOperators,
|
||||
NDSongQueryPlaylistOperators,
|
||||
NDSongQueryStringOperators,
|
||||
} from '/@/renderer/api/navidrome.types';
|
||||
import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { JsonPreview } from '/@/renderer/features/shared/components/json-preview';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
|
||||
|
||||
type AddArgs = {
|
||||
groupIndex: number[];
|
||||
|
|
@ -91,14 +92,14 @@ export type PlaylistQueryBuilderRef = {
|
|||
export const PlaylistQueryBuilder = forwardRef(
|
||||
(
|
||||
{
|
||||
sortOrder,
|
||||
sortBy,
|
||||
limit,
|
||||
isSaving,
|
||||
query,
|
||||
limit,
|
||||
onSave,
|
||||
onSaveAs,
|
||||
playlistId,
|
||||
query,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
}: PlaylistQueryBuilderProps,
|
||||
ref: Ref<PlaylistQueryBuilderRef>,
|
||||
) => {
|
||||
|
|
@ -177,7 +178,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleAddRuleGroup = (args: AddArgs) => {
|
||||
const { level, groupIndex } = args;
|
||||
const { groupIndex, level } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const getPath = (level: number) => {
|
||||
|
|
@ -218,7 +219,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleDeleteRuleGroup = (args: DeleteArgs) => {
|
||||
const { uniqueId, level, groupIndex } = args;
|
||||
const { groupIndex, level, uniqueId } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const getPath = (level: number) => {
|
||||
|
|
@ -264,7 +265,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleAddRule = (args: AddArgs) => {
|
||||
const { level, groupIndex } = args;
|
||||
const { groupIndex, level } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
|
|
@ -287,7 +288,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleDeleteRule = (args: DeleteArgs) => {
|
||||
const { uniqueId, level, groupIndex } = args;
|
||||
const { groupIndex, level, uniqueId } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
|
|
@ -304,7 +305,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleChangeField = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const { groupIndex, level, uniqueId, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
|
|
@ -327,7 +328,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleChangeType = (args: any) => {
|
||||
const { level, groupIndex, value } = args;
|
||||
const { groupIndex, level, value } = args;
|
||||
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
|
|
@ -359,7 +360,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleChangeOperator = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const { groupIndex, level, uniqueId, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
|
|
@ -380,7 +381,7 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
};
|
||||
|
||||
const handleChangeValue = (args: any) => {
|
||||
const { uniqueId, level, groupIndex, value } = args;
|
||||
const { groupIndex, level, uniqueId, value } = args;
|
||||
const filtersCopy = clone(filters);
|
||||
|
||||
const path = getRulePath(level, groupIndex);
|
||||
|
|
@ -424,15 +425,6 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
filters={NDSongQueryFields}
|
||||
groupIndex={[]}
|
||||
level={0}
|
||||
operators={{
|
||||
boolean: NDSongQueryBooleanOperators,
|
||||
date: NDSongQueryDateOperators,
|
||||
number: NDSongQueryNumberOperators,
|
||||
playlist: NDSongQueryPlaylistOperators,
|
||||
string: NDSongQueryStringOperators,
|
||||
}}
|
||||
playlists={playlistData}
|
||||
uniqueId={filters.uniqueId}
|
||||
onAddRule={handleAddRule}
|
||||
onAddRuleGroup={handleAddRuleGroup}
|
||||
onChangeField={handleChangeField}
|
||||
|
|
@ -443,12 +435,21 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
onDeleteRule={handleDeleteRule}
|
||||
onDeleteRuleGroup={handleDeleteRuleGroup}
|
||||
onResetFilters={handleResetFilters}
|
||||
operators={{
|
||||
boolean: NDSongQueryBooleanOperators,
|
||||
date: NDSongQueryDateOperators,
|
||||
number: NDSongQueryNumberOperators,
|
||||
playlist: NDSongQueryPlaylistOperators,
|
||||
string: NDSongQueryStringOperators,
|
||||
}}
|
||||
playlists={playlistData}
|
||||
uniqueId={filters.uniqueId}
|
||||
/>
|
||||
</ScrollArea>
|
||||
<Group
|
||||
noWrap
|
||||
align="flex-end"
|
||||
m="1rem"
|
||||
noWrap
|
||||
position="apart"
|
||||
>
|
||||
<Group
|
||||
|
|
@ -457,10 +458,10 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
w="100%"
|
||||
>
|
||||
<Select
|
||||
searchable
|
||||
data={sortOptions}
|
||||
label="Sort"
|
||||
maxWidth="20%"
|
||||
searchable
|
||||
width={150}
|
||||
{...extraFiltersForm.getInputProps('sortBy')}
|
||||
/>
|
||||
|
|
@ -494,15 +495,15 @@ export const PlaylistQueryBuilder = forwardRef(
|
|||
>
|
||||
<Button
|
||||
loading={isSaving}
|
||||
variant="filled"
|
||||
onClick={handleSaveAs}
|
||||
variant="filled"
|
||||
>
|
||||
{t('common.saveAs', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={openPreviewModal}
|
||||
p="0.5em"
|
||||
variant="default"
|
||||
onClick={openPreviewModal}
|
||||
>
|
||||
{t('common.preview', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { Group, Stack } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { CreatePlaylistBody, CreatePlaylistResponse, ServerType } from '/@/renderer/api/types';
|
||||
import { hasFeature } from '/@/renderer/api/utils';
|
||||
import { Button, Switch, TextInput, toast } from '/@/renderer/components';
|
||||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { hasFeature } from '/@/renderer/api/utils';
|
||||
|
||||
interface SaveAsPlaylistFormProps {
|
||||
body: Partial<CreatePlaylistBody>;
|
||||
|
|
@ -17,9 +18,9 @@ interface SaveAsPlaylistFormProps {
|
|||
|
||||
export const SaveAsPlaylistForm = ({
|
||||
body,
|
||||
serverId,
|
||||
onSuccess,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
serverId,
|
||||
}: SaveAsPlaylistFormProps) => {
|
||||
const { t } = useTranslation();
|
||||
const mutation = useCreatePlaylist({});
|
||||
|
|
@ -68,11 +69,11 @@ export const SaveAsPlaylistForm = ({
|
|||
<Stack>
|
||||
<TextInput
|
||||
data-autofocus
|
||||
required
|
||||
label={t('form.createPlaylist.input', {
|
||||
context: 'name',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
required
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
{server?.type === ServerType.NAVIDROME && (
|
||||
|
|
@ -95,8 +96,8 @@ export const SaveAsPlaylistForm = ({
|
|||
)}
|
||||
<Group position="right">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { Group, Stack } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { openModal, closeAllModals } from '@mantine/modals';
|
||||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
PlaylistDetailResponse,
|
||||
|
|
@ -14,14 +18,11 @@ import {
|
|||
UserListQuery,
|
||||
UserListSort,
|
||||
} from '/@/renderer/api/types';
|
||||
import { hasFeature } from '/@/renderer/api/utils';
|
||||
import { Button, Select, Switch, TextInput, toast } from '/@/renderer/components';
|
||||
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { hasFeature } from '/@/renderer/api/utils';
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
|
||||
interface UpdatePlaylistFormProps {
|
||||
body: Partial<UpdatePlaylistBody>;
|
||||
|
|
@ -30,7 +31,7 @@ interface UpdatePlaylistFormProps {
|
|||
users?: User[];
|
||||
}
|
||||
|
||||
export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlaylistFormProps) => {
|
||||
export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlaylistFormProps) => {
|
||||
const { t } = useTranslation();
|
||||
const mutation = useUpdatePlaylist({});
|
||||
const server = useCurrentServer();
|
||||
|
|
@ -89,11 +90,11 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
|
|||
<Stack>
|
||||
<TextInput
|
||||
data-autofocus
|
||||
required
|
||||
label={t('form.createPlaylist.input', {
|
||||
context: 'name',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
required
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
{server?.type === ServerType.NAVIDROME && (
|
||||
|
|
@ -135,8 +136,8 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
|
|||
)}
|
||||
<Group position="right">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={onCancel}
|
||||
variant="subtle"
|
||||
>
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
|
|
@ -200,9 +201,9 @@ export const openUpdatePlaylistModal = async (args: {
|
|||
name: playlist?.name,
|
||||
public: playlist?.public || false,
|
||||
}}
|
||||
onCancel={closeAllModals}
|
||||
query={{ id: playlist?.id }}
|
||||
users={users?.items}
|
||||
onCancel={closeAllModals}
|
||||
/>
|
||||
),
|
||||
title: i18n.t('form.editPlaylist.title', { postProcess: 'titleCase' }) as string,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export * from './queries/playlist-list-query';
|
||||
export * from './components/add-to-playlist-context-modal';
|
||||
export * from './components/create-playlist-form';
|
||||
export * from './mutations/delete-playlist-mutation';
|
||||
export * from './mutations/create-playlist-mutation';
|
||||
export * from './mutations/update-playlist-mutation';
|
||||
export * from './mutations/add-to-playlist-mutation';
|
||||
export * from './mutations/create-playlist-mutation';
|
||||
export * from './mutations/delete-playlist-mutation';
|
||||
export * from './mutations/remove-from-playlist-mutation';
|
||||
export * from './mutations/update-playlist-mutation';
|
||||
export * from './queries/playlist-list-query';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { AddToPlaylistArgs, AddToPlaylistResponse } from '/@/renderer/api/types';
|
||||
|
|
@ -13,7 +14,7 @@ export const useAddToPlaylist = (args: MutationHookArgs) => {
|
|||
return useMutation<
|
||||
AddToPlaylistResponse,
|
||||
AxiosError,
|
||||
Omit<AddToPlaylistArgs, 'server' | 'apiClientProps'>,
|
||||
Omit<AddToPlaylistArgs, 'apiClientProps' | 'server'>,
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { queryKeys } from '../../../api/query-keys';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { CreatePlaylistArgs, CreatePlaylistResponse } from '/@/renderer/api/types';
|
||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { AxiosError } from 'axios';
|
||||
import { queryKeys } from '../../../api/query-keys';
|
||||
|
||||
export const useCreatePlaylist = (args: MutationHookArgs) => {
|
||||
const { options } = args || {};
|
||||
|
|
@ -13,7 +15,7 @@ export const useCreatePlaylist = (args: MutationHookArgs) => {
|
|||
return useMutation<
|
||||
CreatePlaylistResponse,
|
||||
AxiosError,
|
||||
Omit<CreatePlaylistArgs, 'server' | 'apiClientProps'>,
|
||||
Omit<CreatePlaylistArgs, 'apiClientProps' | 'server'>,
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { DeletePlaylistArgs, DeletePlaylistResponse } from '/@/renderer/api/types';
|
||||
|
|
@ -14,7 +15,7 @@ export const useDeletePlaylist = (args: MutationHookArgs) => {
|
|||
return useMutation<
|
||||
DeletePlaylistResponse,
|
||||
AxiosError,
|
||||
Omit<DeletePlaylistArgs, 'server' | 'apiClientProps'>,
|
||||
Omit<DeletePlaylistArgs, 'apiClientProps' | 'server'>,
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { RemoveFromPlaylistArgs, RemoveFromPlaylistResponse } from '/@/renderer/api/types';
|
||||
|
|
@ -12,7 +13,7 @@ export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
|||
return useMutation<
|
||||
RemoveFromPlaylistResponse,
|
||||
AxiosError,
|
||||
Omit<RemoveFromPlaylistArgs, 'server' | 'apiClientProps'>,
|
||||
Omit<RemoveFromPlaylistArgs, 'apiClientProps' | 'server'>,
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { UpdatePlaylistArgs, UpdatePlaylistResponse } from '/@/renderer/api/types';
|
||||
|
|
@ -13,7 +14,7 @@ export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
|||
return useMutation<
|
||||
UpdatePlaylistResponse,
|
||||
AxiosError,
|
||||
Omit<UpdatePlaylistArgs, 'server' | 'apiClientProps'>,
|
||||
Omit<UpdatePlaylistArgs, 'apiClientProps' | 'server'>,
|
||||
null
|
||||
>({
|
||||
mutationFn: (args) => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { PlaylistDetailQuery } from '/@/renderer/api/types';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const usePlaylistDetail = (args: QueryHookArgs<PlaylistDetailQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { PlaylistListQuery } from '/@/renderer/api/types';
|
||||
import type { QueryOptions } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const usePlaylistList = (args: {
|
||||
options?: QueryOptions;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { PlaylistSongListQuery } from '/@/renderer/api/types';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
import { useRef, useState } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { Box, Group } from '@mantine/core';
|
||||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiArrowDownSLine, RiArrowUpSLine } from 'react-icons/ri';
|
||||
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||
|
||||
import { PlaylistDetailSongListContent } from '../components/playlist-detail-song-list-content';
|
||||
import { PlaylistDetailSongListHeader } from '../components/playlist-detail-song-list-header';
|
||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||
import { Button, Paper, Text, toast } from '/@/renderer/components';
|
||||
import { SaveAsPlaylistForm } from '/@/renderer/features/playlists/components/save-as-playlist-form';
|
||||
import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store';
|
||||
|
||||
import { PlaylistSongListQuery, ServerType, SongListSort, SortOrder } from '/@/renderer/api/types';
|
||||
import { Button, Paper, Text, toast } from '/@/renderer/components';
|
||||
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
||||
import { SaveAsPlaylistForm } from '/@/renderer/features/playlists/components/save-as-playlist-form';
|
||||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
||||
import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
|
||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store';
|
||||
|
||||
const PlaylistDetailSongListRoute = () => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -105,7 +108,6 @@ const PlaylistDetailSongListRoute = () => {
|
|||
name: detailQuery?.data?.name,
|
||||
public: detailQuery?.data?.public || false,
|
||||
}}
|
||||
serverId={detailQuery?.data?.serverId}
|
||||
onCancel={closeAllModals}
|
||||
onSuccess={(data) =>
|
||||
navigate(
|
||||
|
|
@ -114,6 +116,7 @@ const PlaylistDetailSongListRoute = () => {
|
|||
}),
|
||||
)
|
||||
}
|
||||
serverId={detailQuery?.data?.serverId}
|
||||
/>
|
||||
),
|
||||
title: t('common.saveAs', { postProcess: 'sentenceCase' }),
|
||||
|
|
@ -173,8 +176,8 @@ const PlaylistDetailSongListRoute = () => {
|
|||
<Group p="1rem">
|
||||
<Button
|
||||
compact
|
||||
variant="default"
|
||||
onClick={handleToggleExpand}
|
||||
variant="default"
|
||||
>
|
||||
{isQueryBuilderExpanded ? (
|
||||
<RiArrowUpSLine size={20} />
|
||||
|
|
@ -186,15 +189,15 @@ const PlaylistDetailSongListRoute = () => {
|
|||
</Group>
|
||||
{isQueryBuilderExpanded && (
|
||||
<PlaylistQueryBuilder
|
||||
key={JSON.stringify(detailQuery?.data?.rules)}
|
||||
isSaving={createPlaylistMutation?.isLoading}
|
||||
key={JSON.stringify(detailQuery?.data?.rules)}
|
||||
limit={detailQuery?.data?.rules?.limit}
|
||||
onSave={handleSave}
|
||||
onSaveAs={handleSaveAs}
|
||||
playlistId={playlistId}
|
||||
query={detailQuery?.data?.rules}
|
||||
sortBy={detailQuery?.data?.rules?.sort || SongListSort.ALBUM}
|
||||
sortOrder={detailQuery?.data?.rules?.order || 'asc'}
|
||||
onSave={handleSave}
|
||||
onSaveAs={handleSaveAs}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { PlaylistListSort, PlaylistSongListQuery, SortOrder } from '/@/renderer/api/types';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { ListContext } from '/@/renderer/context/list-context';
|
||||
|
|
@ -11,7 +13,7 @@ import { AnimatedPage } from '/@/renderer/features/shared';
|
|||
import { useCurrentServer, useListStoreByKey } from '/@/renderer/store';
|
||||
|
||||
const PlaylistListRoute = () => {
|
||||
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
|
||||
const gridRef = useRef<null | VirtualInfiniteGridRef>(null);
|
||||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
const server = useCurrentServer();
|
||||
const { playlistId } = useParams();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { nanoid } from 'nanoid/non-secure';
|
||||
|
||||
import { NDSongQueryFields } from '/@/renderer/api/navidrome.types';
|
||||
import { QueryBuilderGroup } from '/@/renderer/types';
|
||||
|
||||
|
|
@ -93,7 +94,7 @@ export const convertNDQueryToQueryGroup = (query: Record<string, any>) => {
|
|||
const rootGroup: QueryBuilderGroup = {
|
||||
group: [],
|
||||
rules: [],
|
||||
type: rootType as 'any' | 'all',
|
||||
type: rootType as 'all' | 'any',
|
||||
uniqueId: nanoid(),
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue