mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
Add files
This commit is contained in:
commit
e87c814068
266 changed files with 63938 additions and 0 deletions
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||
import { generatePath } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { AlbumArtist, Artist } from '/@/renderer/api/types';
|
||||
import { Text } from '/@/renderer/components/text';
|
||||
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
|
||||
export const AlbumArtistCell = ({ value, data }: ICellRendererParams) => {
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
$secondary
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
>
|
||||
{value?.map((item: Artist | AlbumArtist, index: number) => (
|
||||
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
|
||||
{index > 0 && (
|
||||
<Text
|
||||
$link
|
||||
$secondary
|
||||
size="sm"
|
||||
style={{ display: 'inline-block' }}
|
||||
>
|
||||
,
|
||||
</Text>
|
||||
)}{' '}
|
||||
<Text
|
||||
$link
|
||||
$secondary
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUMARTISTS_DETAIL, {
|
||||
albumArtistId: item.id,
|
||||
})}
|
||||
>
|
||||
{item.name || '—'}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Text>
|
||||
</CellContainer>
|
||||
);
|
||||
};
|
||||
47
src/renderer/components/virtual-table/cells/artist-cell.tsx
Normal file
47
src/renderer/components/virtual-table/cells/artist-cell.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||
import { generatePath } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { AlbumArtist, Artist } from '/@/renderer/api/types';
|
||||
import { Text } from '/@/renderer/components/text';
|
||||
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
|
||||
export const ArtistCell = ({ value, data }: ICellRendererParams) => {
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
$secondary
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
>
|
||||
{value?.map((item: Artist | AlbumArtist, index: number) => (
|
||||
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
|
||||
{index > 0 && (
|
||||
<Text
|
||||
$link
|
||||
$secondary
|
||||
size="sm"
|
||||
style={{ display: 'inline-block' }}
|
||||
>
|
||||
,
|
||||
</Text>
|
||||
)}{' '}
|
||||
<Text
|
||||
$link
|
||||
$secondary
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
to={generatePath(AppRoute.LIBRARY_ARTISTS_DETAIL, {
|
||||
artistId: item.id,
|
||||
})}
|
||||
>
|
||||
{item.name || '—'}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Text>
|
||||
</CellContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||
import { motion } from 'framer-motion';
|
||||
import { generatePath } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import type { AlbumArtist, Artist } from '/@/renderer/api/types';
|
||||
import { Text } from '/@/renderer/components/text';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { ServerType } from '/@/renderer/types';
|
||||
|
||||
const CellContainer = styled(motion.div)<{ height: number }>`
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas: 'image info';
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: ${(props) => props.height}px minmax(0, 1fr);
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
display: flex;
|
||||
grid-area: image;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const MetadataWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-area: info;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledImage = styled.img`
|
||||
object-fit: cover;
|
||||
`;
|
||||
|
||||
export const CombinedTitleCell = ({ value, rowIndex, node }: ICellRendererParams) => {
|
||||
const artists = useMemo(() => {
|
||||
return value.type === ServerType.JELLYFIN ? value.artists : value.albumArtists;
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<CellContainer height={node.rowHeight || 40}>
|
||||
<ImageWrapper>
|
||||
<StyledImage
|
||||
alt="song-cover"
|
||||
height={(node.rowHeight || 40) - 10}
|
||||
loading="lazy"
|
||||
src={value.imageUrl}
|
||||
style={{}}
|
||||
width={(node.rowHeight || 40) - 10}
|
||||
/>
|
||||
</ImageWrapper>
|
||||
<MetadataWrapper>
|
||||
<Text
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
>
|
||||
{value.name}
|
||||
</Text>
|
||||
<Text
|
||||
$secondary
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
>
|
||||
{artists?.length ? (
|
||||
artists.map((artist: Artist | AlbumArtist, index: number) => (
|
||||
<React.Fragment key={`queue-${rowIndex}-artist-${artist.id}`}>
|
||||
{index > 0 ? ', ' : null}
|
||||
<Text
|
||||
$link
|
||||
$secondary
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
sx={{ width: 'fit-content' }}
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUMARTISTS_DETAIL, {
|
||||
albumArtistId: artist.id,
|
||||
})}
|
||||
>
|
||||
{artist.name}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
))
|
||||
) : (
|
||||
<Text $secondary>—</Text>
|
||||
)}
|
||||
</Text>
|
||||
</MetadataWrapper>
|
||||
</CellContainer>
|
||||
);
|
||||
};
|
||||
63
src/renderer/components/virtual-table/cells/generic-cell.tsx
Normal file
63
src/renderer/components/virtual-table/cells/generic-cell.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Text } from '/@/renderer/components/text';
|
||||
|
||||
export const CellContainer = styled.div<{
|
||||
position?: 'left' | 'center' | 'right';
|
||||
}>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: ${(props) =>
|
||||
props.position === 'right'
|
||||
? 'flex-end'
|
||||
: props.position === 'center'
|
||||
? 'center'
|
||||
: 'flex-start'};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
type Options = {
|
||||
array?: boolean;
|
||||
isArray?: boolean;
|
||||
isLink?: boolean;
|
||||
position?: 'left' | 'center' | 'right';
|
||||
primary?: boolean;
|
||||
};
|
||||
|
||||
export const GenericCell = (
|
||||
{ value, valueFormatted }: ICellRendererParams,
|
||||
{ position, primary, isLink }: Options,
|
||||
) => {
|
||||
const displayedValue = valueFormatted || value;
|
||||
|
||||
return (
|
||||
<CellContainer position={position || 'left'}>
|
||||
{isLink ? (
|
||||
<Text
|
||||
$link={isLink}
|
||||
$secondary={!primary}
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
to={displayedValue.link}
|
||||
>
|
||||
{isLink ? displayedValue.value : displayedValue}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
$secondary={!primary}
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
>
|
||||
{displayedValue}
|
||||
</Text>
|
||||
)}
|
||||
</CellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
GenericCell.defaultProps = {
|
||||
position: undefined,
|
||||
};
|
||||
43
src/renderer/components/virtual-table/cells/genre-cell.tsx
Normal file
43
src/renderer/components/virtual-table/cells/genre-cell.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { AlbumArtist, Artist } from '/@/renderer/api/types';
|
||||
import { Text } from '/@/renderer/components/text';
|
||||
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||
|
||||
export const GenreCell = ({ value, data }: ICellRendererParams) => {
|
||||
return (
|
||||
<CellContainer position="left">
|
||||
<Text
|
||||
$secondary
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
>
|
||||
{value?.map((item: Artist | AlbumArtist, index: number) => (
|
||||
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
|
||||
{index > 0 && (
|
||||
<Text
|
||||
$link
|
||||
$secondary
|
||||
size="sm"
|
||||
style={{ display: 'inline-block' }}
|
||||
>
|
||||
,
|
||||
</Text>
|
||||
)}{' '}
|
||||
<Text
|
||||
$link
|
||||
$secondary
|
||||
component={Link}
|
||||
overflow="hidden"
|
||||
size="sm"
|
||||
to="/"
|
||||
>
|
||||
{item.name || '—'}
|
||||
</Text>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Text>
|
||||
</CellContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import type { IHeaderParams } from '@ag-grid-community/core';
|
||||
import { FiClock } from 'react-icons/fi';
|
||||
|
||||
export interface ICustomHeaderParams extends IHeaderParams {
|
||||
menuIcon: string;
|
||||
}
|
||||
|
||||
export const DurationHeader = () => {
|
||||
return <FiClock size={15} />;
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import type { IHeaderParams } from '@ag-grid-community/core';
|
||||
import { AiOutlineNumber } from 'react-icons/ai';
|
||||
import { FiClock } from 'react-icons/fi';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type Presets = 'duration' | 'rowIndex';
|
||||
|
||||
type Options = {
|
||||
children?: ReactNode;
|
||||
position?: 'left' | 'center' | 'right';
|
||||
preset?: Presets;
|
||||
};
|
||||
|
||||
const HeaderWrapper = styled.div<{ position: 'left' | 'center' | 'right' }>`
|
||||
display: flex;
|
||||
justify-content: ${(props) =>
|
||||
props.position === 'right'
|
||||
? 'flex-end'
|
||||
: props.position === 'center'
|
||||
? 'center'
|
||||
: 'flex-start'};
|
||||
width: 100%;
|
||||
font-family: var(--header-font-family);
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
const headerPresets = { duration: <FiClock size={15} />, rowIndex: <AiOutlineNumber size={15} /> };
|
||||
|
||||
export const GenericTableHeader = (
|
||||
{ displayName }: IHeaderParams,
|
||||
{ preset, children, position }: Options,
|
||||
) => {
|
||||
if (preset) {
|
||||
return <HeaderWrapper position={position || 'left'}>{headerPresets[preset]}</HeaderWrapper>;
|
||||
}
|
||||
|
||||
return <HeaderWrapper position={position || 'left'}>{children || displayName}</HeaderWrapper>;
|
||||
};
|
||||
|
||||
GenericTableHeader.defaultProps = {
|
||||
position: 'left',
|
||||
preset: undefined,
|
||||
};
|
||||
190
src/renderer/components/virtual-table/index.tsx
Normal file
190
src/renderer/components/virtual-table/index.tsx
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
import type { Ref } from 'react';
|
||||
import { forwardRef, useRef } from 'react';
|
||||
import type {
|
||||
ICellRendererParams,
|
||||
ValueGetterParams,
|
||||
IHeaderParams,
|
||||
ValueFormatterParams,
|
||||
ColDef,
|
||||
} from '@ag-grid-community/core';
|
||||
import type { AgGridReactProps } from '@ag-grid-community/react';
|
||||
import { AgGridReact } from '@ag-grid-community/react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import { useMergedRef } from '@mantine/hooks';
|
||||
import formatDuration from 'format-duration';
|
||||
import { generatePath } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import { AlbumArtistCell } from '/@/renderer/components/virtual-table/cells/album-artist-cell';
|
||||
import { ArtistCell } from '/@/renderer/components/virtual-table/cells/artist-cell';
|
||||
import { CombinedTitleCell } from '/@/renderer/components/virtual-table/cells/combined-title-cell';
|
||||
import { GenericCell } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||
import { GenreCell } from '/@/renderer/components/virtual-table/cells/genre-cell';
|
||||
import { GenericTableHeader } from '/@/renderer/components/virtual-table/headers/generic-table-header';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { PersistedTableColumn } from '/@/renderer/store/settings.store';
|
||||
import { TableColumn } from '/@/renderer/types';
|
||||
|
||||
export * from './table-config-dropdown';
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const tableColumns: { [key: string]: ColDef } = {
|
||||
album: {
|
||||
cellRenderer: (params: ICellRendererParams) =>
|
||||
GenericCell(params, { isLink: true, position: 'left' }),
|
||||
colId: TableColumn.ALBUM,
|
||||
headerName: 'Album',
|
||||
valueGetter: (params: ValueGetterParams) => ({
|
||||
link: generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||
albumId: params.data?.albumId || '',
|
||||
}),
|
||||
value: params.data?.album,
|
||||
}),
|
||||
},
|
||||
albumArtist: {
|
||||
cellRenderer: AlbumArtistCell,
|
||||
colId: TableColumn.ALBUM_ARTIST,
|
||||
headerName: 'Album Artist',
|
||||
valueGetter: (params: ValueGetterParams) => params.data?.albumArtists,
|
||||
},
|
||||
artist: {
|
||||
cellRenderer: ArtistCell,
|
||||
colId: TableColumn.ARTIST,
|
||||
headerName: 'Artist',
|
||||
valueGetter: (params: ValueGetterParams) => params.data?.artists,
|
||||
},
|
||||
bitRate: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }),
|
||||
colId: TableColumn.BIT_RATE,
|
||||
field: 'bitRate',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }),
|
||||
valueFormatter: (params: ValueFormatterParams) => `${params.value} kbps`,
|
||||
},
|
||||
dateAdded: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }),
|
||||
colId: TableColumn.DATE_ADDED,
|
||||
field: 'createdAt',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'left' }),
|
||||
headerName: 'Date Added',
|
||||
valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0],
|
||||
},
|
||||
discNumber: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.DISC_NUMBER,
|
||||
field: 'discNumber',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Disc',
|
||||
initialWidth: 75,
|
||||
suppressSizeToFit: true,
|
||||
},
|
||||
duration: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.DURATION,
|
||||
field: 'duration',
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'center', preset: 'duration' }),
|
||||
initialWidth: 100,
|
||||
valueFormatter: (params: ValueFormatterParams) => formatDuration(params.value * 1000),
|
||||
},
|
||||
genre: {
|
||||
cellRenderer: GenreCell,
|
||||
colId: TableColumn.GENRE,
|
||||
headerName: 'Genre',
|
||||
valueGetter: (params: ValueGetterParams) => params.data.genres,
|
||||
},
|
||||
releaseDate: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.RELEASE_DATE,
|
||||
field: 'releaseDate',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Release Date',
|
||||
valueFormatter: (params: ValueFormatterParams) => params.value?.split('T')[0],
|
||||
},
|
||||
releaseYear: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.YEAR,
|
||||
field: 'releaseYear',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Year',
|
||||
},
|
||||
rowIndex: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'left' }),
|
||||
colId: TableColumn.ROW_INDEX,
|
||||
headerComponent: (params: IHeaderParams) =>
|
||||
GenericTableHeader(params, { position: 'left', preset: 'rowIndex' }),
|
||||
initialWidth: 50,
|
||||
suppressSizeToFit: true,
|
||||
valueGetter: (params) => {
|
||||
return (params.node?.rowIndex || 0) + 1;
|
||||
},
|
||||
},
|
||||
title: {
|
||||
cellRenderer: (params: ICellRendererParams) =>
|
||||
GenericCell(params, { position: 'left', primary: true }),
|
||||
colId: TableColumn.TITLE,
|
||||
field: 'name',
|
||||
headerName: 'Title',
|
||||
},
|
||||
titleCombined: {
|
||||
cellRenderer: CombinedTitleCell,
|
||||
colId: TableColumn.TITLE_COMBINED,
|
||||
headerName: 'Title',
|
||||
initialWidth: 500,
|
||||
valueGetter: (params: ValueGetterParams) => ({
|
||||
albumArtists: params.data?.albumArtists,
|
||||
artists: params.data?.artists,
|
||||
imageUrl: params.data?.imageUrl,
|
||||
name: params.data?.name,
|
||||
rowHeight: params.node?.rowHeight,
|
||||
type: params.data?.type,
|
||||
}),
|
||||
},
|
||||
trackNumber: {
|
||||
cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }),
|
||||
colId: TableColumn.TRACK_NUMBER,
|
||||
field: 'trackNumber',
|
||||
headerComponent: (params: IHeaderParams) => GenericTableHeader(params, { position: 'center' }),
|
||||
headerName: 'Track',
|
||||
initialWidth: 75,
|
||||
suppressSizeToFit: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const getColumnDef = (column: TableColumn) => {
|
||||
return tableColumns[column as keyof typeof tableColumns];
|
||||
};
|
||||
|
||||
export const getColumnDefs = (columns: PersistedTableColumn[]) => {
|
||||
const columnDefs: any[] = [];
|
||||
for (const column of columns) {
|
||||
const columnExists = tableColumns[column.column as keyof typeof tableColumns];
|
||||
if (columnExists) columnDefs.push(columnExists);
|
||||
}
|
||||
|
||||
return columnDefs;
|
||||
};
|
||||
|
||||
export const VirtualTable = forwardRef(
|
||||
({ ...rest }: AgGridReactProps, ref: Ref<AgGridReactType | null>) => {
|
||||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
|
||||
const mergedRef = useMergedRef(ref, tableRef);
|
||||
|
||||
return (
|
||||
<TableWrapper className="ag-theme-alpine-dark">
|
||||
<AgGridReact
|
||||
ref={mergedRef}
|
||||
suppressMoveWhenRowDragging
|
||||
suppressScrollOnNewData
|
||||
rowBuffer={30}
|
||||
{...rest}
|
||||
/>
|
||||
</TableWrapper>
|
||||
);
|
||||
},
|
||||
);
|
||||
163
src/renderer/components/virtual-table/table-config-dropdown.tsx
Normal file
163
src/renderer/components/virtual-table/table-config-dropdown.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import type { ChangeEvent } from 'react';
|
||||
import { Stack } from '@mantine/core';
|
||||
import { MultiSelect, Slider, Switch, Text } from '/@/renderer/components';
|
||||
import { useSettingsStoreActions, useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
import { TableColumn, TableType } from '/@/renderer/types';
|
||||
|
||||
export const tableColumns = [
|
||||
{ label: 'Row Index', value: TableColumn.ROW_INDEX },
|
||||
{ label: 'Title', value: TableColumn.TITLE },
|
||||
{ label: 'Title (Combined)', value: TableColumn.TITLE_COMBINED },
|
||||
{ label: 'Duration', value: TableColumn.DURATION },
|
||||
{ label: 'Album', value: TableColumn.ALBUM },
|
||||
{ label: 'Album Artist', value: TableColumn.ALBUM_ARTIST },
|
||||
{ label: 'Artist', value: TableColumn.ARTIST },
|
||||
{ label: 'Genre', value: TableColumn.GENRE },
|
||||
{ label: 'Year', value: TableColumn.YEAR },
|
||||
{ label: 'Release Date', value: TableColumn.RELEASE_DATE },
|
||||
{ label: 'Disc Number', value: TableColumn.DISC_NUMBER },
|
||||
{ label: 'Track Number', value: TableColumn.TRACK_NUMBER },
|
||||
{ label: 'Bitrate', value: TableColumn.BIT_RATE },
|
||||
// { label: 'Size', value: TableColumn.SIZE },
|
||||
// { label: 'Skip', value: TableColumn.SKIP },
|
||||
// { label: 'Path', value: TableColumn.PATH },
|
||||
// { label: 'Play Count', value: TableColumn.PLAY_COUNT },
|
||||
// { label: 'Favorite', value: TableColumn.FAVORITE },
|
||||
// { label: 'Rating', value: TableColumn.RATING },
|
||||
{ label: 'Date Added', value: TableColumn.DATE_ADDED },
|
||||
];
|
||||
|
||||
interface TableConfigDropdownProps {
|
||||
type: TableType;
|
||||
}
|
||||
|
||||
export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => {
|
||||
const { setSettings } = useSettingsStoreActions();
|
||||
const tableConfig = useSettingsStore((state) => state.tables);
|
||||
|
||||
const handleAddOrRemoveColumns = (values: TableColumn[]) => {
|
||||
const existingColumns = tableConfig[type].columns;
|
||||
|
||||
if (values.length === 0) {
|
||||
setSettings({
|
||||
tables: {
|
||||
...useSettingsStore.getState().tables,
|
||||
[type]: {
|
||||
...useSettingsStore.getState().tables[type],
|
||||
columns: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If adding a column
|
||||
if (values.length > existingColumns.length) {
|
||||
const newColumn = { column: values[values.length - 1] };
|
||||
setSettings({
|
||||
tables: {
|
||||
...useSettingsStore.getState().tables,
|
||||
[type]: {
|
||||
...useSettingsStore.getState().tables[type],
|
||||
columns: [...existingColumns, newColumn],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
// If removing a column
|
||||
else {
|
||||
const removed = existingColumns.filter((column) => !values.includes(column.column));
|
||||
|
||||
const newColumns = existingColumns.filter((column) => !removed.includes(column));
|
||||
|
||||
setSettings({
|
||||
tables: {
|
||||
...useSettingsStore.getState().tables,
|
||||
[type]: {
|
||||
...useSettingsStore.getState().tables[type],
|
||||
columns: newColumns,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateRowHeight = (value: number) => {
|
||||
setSettings({
|
||||
tables: {
|
||||
...useSettingsStore.getState().tables,
|
||||
[type]: {
|
||||
...useSettingsStore.getState().tables[type],
|
||||
rowHeight: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateAutoFit = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSettings({
|
||||
tables: {
|
||||
...useSettingsStore.getState().tables,
|
||||
[type]: {
|
||||
...useSettingsStore.getState().tables[type],
|
||||
autoFit: e.currentTarget.checked,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateFollow = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSettings({
|
||||
tables: {
|
||||
...useSettingsStore.getState().tables,
|
||||
[type]: {
|
||||
...useSettingsStore.getState().tables[type],
|
||||
followCurrentSong: e.currentTarget.checked,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
p="1rem"
|
||||
spacing="xl"
|
||||
>
|
||||
<Stack spacing="xs">
|
||||
<Text>Table Columns</Text>
|
||||
<MultiSelect
|
||||
clearable
|
||||
data={tableColumns}
|
||||
defaultValue={tableConfig[type]?.columns.map((column) => column.column)}
|
||||
dropdownPosition="top"
|
||||
width={300}
|
||||
onChange={handleAddOrRemoveColumns}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Row Height</Text>
|
||||
<Slider
|
||||
defaultValue={tableConfig[type]?.rowHeight}
|
||||
max={100}
|
||||
min={25}
|
||||
sx={{ width: 150 }}
|
||||
onChangeEnd={handleUpdateRowHeight}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Auto Fit Columns</Text>
|
||||
<Switch
|
||||
defaultChecked={tableConfig[type]?.autoFit}
|
||||
onChange={handleUpdateAutoFit}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing="xs">
|
||||
<Text>Follow Current Song</Text>
|
||||
<Switch
|
||||
defaultChecked={tableConfig[type]?.followCurrentSong}
|
||||
onChange={handleUpdateFollow}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue