Convert play icon from base64 to svg

This commit is contained in:
jeffvli 2023-10-18 19:51:55 -07:00
parent 8a53fab751
commit b28fe4cbc9
8 changed files with 204 additions and 37 deletions

View file

@ -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"

View file

@ -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 {

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,
}} }}

View file

@ -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);
}
} }