Convert play icon from base64 to svg
This commit is contained in:
parent
8a53fab751
commit
b28fe4cbc9
8 changed files with 204 additions and 37 deletions
|
@ -2,9 +2,145 @@ import type { ICellRendererParams } from '@ag-grid-community/core';
|
||||||
import { Text } from '/@/renderer/components/text';
|
import { Text } from '/@/renderer/components/text';
|
||||||
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
|
||||||
|
|
||||||
export const RowIndexCell = ({ value }: ICellRendererParams) => {
|
const AnimatedSvg = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ height: '1rem', transform: 'rotate(180deg)', width: '1rem' }}>
|
||||||
|
<svg
|
||||||
|
viewBox="100 130 57 80"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="80"
|
||||||
|
id="bar-1"
|
||||||
|
width="12"
|
||||||
|
x="100"
|
||||||
|
y="130"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="height"
|
||||||
|
begin="0.1s"
|
||||||
|
calcMode="spline"
|
||||||
|
dur="0.95s"
|
||||||
|
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"
|
||||||
|
keyTimes="0; 0.47368; 1"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
values="80;15;80"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="80"
|
||||||
|
id="bar-2"
|
||||||
|
width="12"
|
||||||
|
x="115"
|
||||||
|
y="130"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="height"
|
||||||
|
begin="0.1s"
|
||||||
|
calcMode="spline"
|
||||||
|
dur="0.95s"
|
||||||
|
keySplines="0.45 0 0.55 1; 0.45 0 0.55 1"
|
||||||
|
keyTimes="0; 0.44444; 1"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
values="25;80;25"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="80"
|
||||||
|
id="bar-3"
|
||||||
|
width="12"
|
||||||
|
x="130"
|
||||||
|
y="130"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="height"
|
||||||
|
begin="0.1s"
|
||||||
|
calcMode="spline"
|
||||||
|
dur="0.85s"
|
||||||
|
keySplines="0.65 0 0.35 1; 0.65 0 0.35 1"
|
||||||
|
keyTimes="0; 0.42105; 1"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
values="80;10;80"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="80"
|
||||||
|
id="bar-4"
|
||||||
|
width="12"
|
||||||
|
x="145"
|
||||||
|
y="130"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="height"
|
||||||
|
begin="0.1s"
|
||||||
|
calcMode="spline"
|
||||||
|
dur="1.05s"
|
||||||
|
keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"
|
||||||
|
keyTimes="0; 0.31579; 1"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
values="30;80;30"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StaticSvg = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ height: '1rem', transform: 'rotate(180deg)', width: '1rem' }}>
|
||||||
|
<svg
|
||||||
|
viewBox="100 130 57 80"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="20"
|
||||||
|
width="12"
|
||||||
|
x="100"
|
||||||
|
y="130"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="60"
|
||||||
|
width="12"
|
||||||
|
x="115"
|
||||||
|
y="130"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="80"
|
||||||
|
width="12"
|
||||||
|
x="130"
|
||||||
|
y="130"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
height="45"
|
||||||
|
width="12"
|
||||||
|
x="145"
|
||||||
|
y="130"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RowIndexCell = ({ value, eGridCell }: ICellRendererParams) => {
|
||||||
|
const isFocused = eGridCell.classList.contains('focused');
|
||||||
|
const isCurrentSong =
|
||||||
|
eGridCell.classList.contains('current-song') ||
|
||||||
|
eGridCell.classList.contains('current-playlist-song');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellContainer $position="right">
|
<CellContainer $position="right">
|
||||||
|
{isFocused && isCurrentSong ? <AnimatedSvg /> : isCurrentSong ? <StaticSvg /> : null}
|
||||||
<Text
|
<Text
|
||||||
$secondary
|
$secondary
|
||||||
align="right"
|
align="right"
|
||||||
|
|
|
@ -31,6 +31,7 @@ export type AgGridFetchFn<TResponse, TFilter> = (
|
||||||
) => Promise<TResponse>;
|
) => Promise<TResponse>;
|
||||||
|
|
||||||
interface UseAgGridProps<TFilter> {
|
interface UseAgGridProps<TFilter> {
|
||||||
|
columnType?: 'albumDetail' | 'generic';
|
||||||
contextMenu: SetContextMenuItems;
|
contextMenu: SetContextMenuItems;
|
||||||
customFilters?: Partial<TFilter>;
|
customFilters?: Partial<TFilter>;
|
||||||
isClientSideSort?: boolean;
|
isClientSideSort?: boolean;
|
||||||
|
@ -52,6 +53,7 @@ export const useVirtualTable = <TFilter>({
|
||||||
customFilters,
|
customFilters,
|
||||||
isSearchParams,
|
isSearchParams,
|
||||||
isClientSideSort,
|
isClientSideSort,
|
||||||
|
columnType,
|
||||||
}: UseAgGridProps<TFilter>) => {
|
}: UseAgGridProps<TFilter>) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -75,8 +77,8 @@ export const useVirtualTable = <TFilter>({
|
||||||
const isPaginationEnabled = properties.display === ListDisplayType.TABLE_PAGINATED;
|
const isPaginationEnabled = properties.display === ListDisplayType.TABLE_PAGINATED;
|
||||||
|
|
||||||
const columnDefs: ColDef[] = useMemo(() => {
|
const columnDefs: ColDef[] = useMemo(() => {
|
||||||
return getColumnDefs(properties.table.columns, true);
|
return getColumnDefs(properties.table.columns, true, columnType);
|
||||||
}, [properties.table.columns]);
|
}, [columnType, properties.table.columns]);
|
||||||
|
|
||||||
const defaultColumnDefs: ColDef = useMemo(() => {
|
const defaultColumnDefs: ColDef = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -19,7 +19,6 @@ import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import formatDuration from 'format-duration';
|
import formatDuration from 'format-duration';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import isElectron from 'is-electron';
|
|
||||||
import { generatePath } from 'react-router';
|
import { generatePath } from 'react-router';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { AlbumArtistCell } from '/@/renderer/components/virtual-table/cells/album-artist-cell';
|
import { AlbumArtistCell } from '/@/renderer/components/virtual-table/cells/album-artist-cell';
|
||||||
|
@ -268,8 +267,40 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||||
rowIndex: {
|
rowIndex: {
|
||||||
cellClass: 'row-index',
|
cellClass: 'row-index',
|
||||||
cellClassRules: {
|
cellClassRules: {
|
||||||
|
'current-playlist-song': (params) => {
|
||||||
|
return params.data?.uniqueId === params.context?.currentSong?.uniqueId;
|
||||||
|
},
|
||||||
|
'current-song': (params) => {
|
||||||
|
return params.data?.uniqueId === params.context?.currentSong?.uniqueId;
|
||||||
|
},
|
||||||
focused: (params) => {
|
focused: (params) => {
|
||||||
return isElectron() && params.context?.isFocused;
|
return params.context?.isFocused;
|
||||||
|
},
|
||||||
|
playing: (params) => {
|
||||||
|
return params.context?.status === PlayerStatus.PLAYING;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cellRenderer: RowIndexCell,
|
||||||
|
colId: TableColumn.ROW_INDEX,
|
||||||
|
headerComponent: (params: IHeaderParams) =>
|
||||||
|
GenericTableHeader(params, { position: 'right', preset: 'rowIndex' }),
|
||||||
|
suppressSizeToFit: true,
|
||||||
|
valueGetter: (params) => {
|
||||||
|
return (params.node?.rowIndex || 0) + 1;
|
||||||
|
},
|
||||||
|
width: 65,
|
||||||
|
},
|
||||||
|
rowIndexGeneric: {
|
||||||
|
cellClass: 'row-index',
|
||||||
|
cellClassRules: {
|
||||||
|
'current-song': (params) => {
|
||||||
|
return (
|
||||||
|
params.data?.id === params.context?.currentSong?.id &&
|
||||||
|
params.data?.albumId === params.context?.currentSong?.albumId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
focused: (params) => {
|
||||||
|
return params.context?.isFocused;
|
||||||
},
|
},
|
||||||
playing: (params) => {
|
playing: (params) => {
|
||||||
return params.context?.status === PlayerStatus.PLAYING;
|
return params.context?.status === PlayerStatus.PLAYING;
|
||||||
|
@ -341,8 +372,14 @@ const tableColumns: { [key: string]: ColDef } = {
|
||||||
trackNumberDetail: {
|
trackNumberDetail: {
|
||||||
cellClass: 'row-index',
|
cellClass: 'row-index',
|
||||||
cellClassRules: {
|
cellClassRules: {
|
||||||
|
'current-song': (params) => {
|
||||||
|
return (
|
||||||
|
params.data?.id === params.context?.currentSong?.id &&
|
||||||
|
params.data?.albumId === params.context?.currentSong?.albumId
|
||||||
|
);
|
||||||
|
},
|
||||||
focused: (params) => {
|
focused: (params) => {
|
||||||
return isElectron() && params.context?.isFocused;
|
return params.context?.isFocused;
|
||||||
},
|
},
|
||||||
playing: (params) => {
|
playing: (params) => {
|
||||||
return params.context?.status === PlayerStatus.PLAYING;
|
return params.context?.status === PlayerStatus.PLAYING;
|
||||||
|
@ -394,16 +431,20 @@ export const getColumnDef = (column: TableColumn) => {
|
||||||
export const getColumnDefs = (
|
export const getColumnDefs = (
|
||||||
columns: PersistedTableColumn[],
|
columns: PersistedTableColumn[],
|
||||||
useWidth?: boolean,
|
useWidth?: boolean,
|
||||||
type?: 'albumDetail',
|
type?: 'albumDetail' | 'generic',
|
||||||
) => {
|
) => {
|
||||||
const columnDefs: ColDef[] = [];
|
const columnDefs: ColDef[] = [];
|
||||||
for (const column of columns) {
|
for (const column of columns) {
|
||||||
let presetColumn = tableColumns[column.column as keyof typeof tableColumns];
|
let presetColumn = tableColumns[column.column as keyof typeof tableColumns];
|
||||||
|
|
||||||
if (type === 'albumDetail' && column.column === TableColumn.TRACK_NUMBER) {
|
if (column.column === TableColumn.TRACK_NUMBER && type === 'albumDetail') {
|
||||||
presetColumn = tableColumns['trackNumberDetail' as keyof typeof tableColumns];
|
presetColumn = tableColumns['trackNumberDetail' as keyof typeof tableColumns];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (column.column === TableColumn.ROW_INDEX && type === 'generic') {
|
||||||
|
presetColumn = tableColumns['rowIndexGeneric' as keyof typeof tableColumns];
|
||||||
|
}
|
||||||
|
|
||||||
if (presetColumn) {
|
if (presetColumn) {
|
||||||
columnDefs.push({
|
columnDefs.push({
|
||||||
...presetColumn,
|
...presetColumn,
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { PlayButton, useCreateFavorite, useDeleteFavorite } from '/@/renderer/fe
|
||||||
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
|
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
|
||||||
import { useAppFocus, useContainerQuery } from '/@/renderer/hooks';
|
import { useAppFocus, useContainerQuery } from '/@/renderer/hooks';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer, useCurrentStatus } from '/@/renderer/store';
|
import { useCurrentServer, useCurrentSong, useCurrentStatus } from '/@/renderer/store';
|
||||||
import {
|
import {
|
||||||
usePlayButtonBehavior,
|
usePlayButtonBehavior,
|
||||||
useSettingsStoreActions,
|
useSettingsStoreActions,
|
||||||
|
@ -72,6 +72,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||||
const { setTable } = useSettingsStoreActions();
|
const { setTable } = useSettingsStoreActions();
|
||||||
const status = useCurrentStatus();
|
const status = useCurrentStatus();
|
||||||
const isFocused = useAppFocus();
|
const isFocused = useAppFocus();
|
||||||
|
const currentSong = useCurrentSong();
|
||||||
|
|
||||||
const columnDefs = useMemo(
|
const columnDefs = useMemo(
|
||||||
() => getColumnDefs(tableConfig.columns, false, 'albumDetail'),
|
() => getColumnDefs(tableConfig.columns, false, 'albumDetail'),
|
||||||
|
@ -401,6 +402,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||||
autoFitColumns={tableConfig.autoFit}
|
autoFitColumns={tableConfig.autoFit}
|
||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
context={{
|
context={{
|
||||||
|
currentSong,
|
||||||
isFocused,
|
isFocused,
|
||||||
onCellContextMenu,
|
onCellContextMenu,
|
||||||
status,
|
status,
|
||||||
|
|
|
@ -223,6 +223,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
||||||
autoFitColumns={tableConfig.autoFit}
|
autoFitColumns={tableConfig.autoFit}
|
||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
context={{
|
context={{
|
||||||
|
currentSong,
|
||||||
isFocused,
|
isFocused,
|
||||||
onCellContextMenu,
|
onCellContextMenu,
|
||||||
status,
|
status,
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playli
|
||||||
import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
|
import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
|
||||||
import {
|
import {
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
|
useCurrentSong,
|
||||||
useCurrentStatus,
|
useCurrentStatus,
|
||||||
usePlaylistDetailStore,
|
usePlaylistDetailStore,
|
||||||
usePlaylistDetailTablePagination,
|
usePlaylistDetailTablePagination,
|
||||||
|
@ -53,6 +54,7 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const status = useCurrentStatus();
|
const status = useCurrentStatus();
|
||||||
const isFocused = useAppFocus();
|
const isFocused = useAppFocus();
|
||||||
|
const currentSong = useCurrentSong();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const page = usePlaylistDetailStore();
|
const page = usePlaylistDetailStore();
|
||||||
const filters: Partial<PlaylistSongListQuery> = useMemo(() => {
|
const filters: Partial<PlaylistSongListQuery> = useMemo(() => {
|
||||||
|
@ -90,7 +92,7 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
|
||||||
});
|
});
|
||||||
|
|
||||||
const columnDefs: ColDef[] = useMemo(
|
const columnDefs: ColDef[] = useMemo(
|
||||||
() => getColumnDefs(page.table.columns),
|
() => getColumnDefs(page.table.columns, false, 'generic'),
|
||||||
[page.table.columns],
|
[page.table.columns],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -241,6 +243,7 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten
|
||||||
autoFitColumns={page.table.autoFit}
|
autoFitColumns={page.table.autoFit}
|
||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
context={{
|
context={{
|
||||||
|
currentSong,
|
||||||
isFocused,
|
isFocused,
|
||||||
onCellContextMenu: handleContextMenu,
|
onCellContextMenu: handleContextMenu,
|
||||||
status,
|
status,
|
||||||
|
|
|
@ -9,7 +9,12 @@ import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
import { useAppFocus } from '/@/renderer/hooks';
|
import { useAppFocus } from '/@/renderer/hooks';
|
||||||
import { useCurrentServer, useCurrentStatus, usePlayButtonBehavior } from '/@/renderer/store';
|
import {
|
||||||
|
useCurrentServer,
|
||||||
|
useCurrentSong,
|
||||||
|
useCurrentStatus,
|
||||||
|
usePlayButtonBehavior,
|
||||||
|
} from '/@/renderer/store';
|
||||||
|
|
||||||
interface SongListTableViewProps {
|
interface SongListTableViewProps {
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
|
@ -20,11 +25,13 @@ export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProp
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const { pageKey, id, handlePlay, customFilters } = useListContext();
|
const { pageKey, id, handlePlay, customFilters } = useListContext();
|
||||||
const isFocused = useAppFocus();
|
const isFocused = useAppFocus();
|
||||||
|
const currentSong = useCurrentSong();
|
||||||
const status = useCurrentStatus();
|
const status = useCurrentStatus();
|
||||||
|
|
||||||
const { rowClassRules } = useCurrentSongRowStyles({ tableRef });
|
const { rowClassRules } = useCurrentSongRowStyles({ tableRef });
|
||||||
|
|
||||||
const tableProps = useVirtualTable<SongListQuery>({
|
const tableProps = useVirtualTable<SongListQuery>({
|
||||||
|
columnType: 'generic',
|
||||||
contextMenu: SONG_CONTEXT_MENU_ITEMS,
|
contextMenu: SONG_CONTEXT_MENU_ITEMS,
|
||||||
customFilters,
|
customFilters,
|
||||||
isSearchParams: Boolean(id),
|
isSearchParams: Boolean(id),
|
||||||
|
@ -51,6 +58,7 @@ export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProp
|
||||||
{...tableProps}
|
{...tableProps}
|
||||||
context={{
|
context={{
|
||||||
...tableProps.context,
|
...tableProps.context,
|
||||||
|
currentSong,
|
||||||
isFocused,
|
isFocused,
|
||||||
status,
|
status,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -196,30 +196,4 @@
|
||||||
.current-song > .row-index.playing .current-song-index {
|
.current-song > .row-index.playing .current-song-index {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-song > .row-index.playing.focused ::before {
|
|
||||||
content: ' ';
|
|
||||||
display: block;
|
|
||||||
height: 1rem;
|
|
||||||
width: 1rem;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
-webkit-mask-image: var(--current-song-image-animated);
|
|
||||||
-webkit-mask-repeat: no-repeat;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
transform: rotate(180deg);
|
|
||||||
mask-image: var(--current-song-image-animated);
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-song > .row-index.playing ::before {
|
|
||||||
content: ' ';
|
|
||||||
display: block;
|
|
||||||
height: 1rem;
|
|
||||||
width: 1rem;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
-webkit-mask-image: var(--current-song-image);
|
|
||||||
-webkit-mask-repeat: no-repeat;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
transform: rotate(180deg);
|
|
||||||
mask-image: var(--current-song-image);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue