From 8a53fab751e6de26109f743058647a733fe0f718 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Thu, 19 Oct 2023 01:32:11 +0000 Subject: [PATCH] add more emphasis to current song (#283) * add more emphasis to current song * add css indicator (rivolumelineup) * don't use absolute position, support album track number * Respect order of set-queue function (fix race condition) * Fix table row actions button on album detail and play queue * Fix album detail table customizations * Bump to v0.4.1 * Fix opacity mask for unsynced lyrics container * Separate sidebar icons to new component - Fixes react render issue * Add app focus hook * Remove css play image * Add player status as cell refresh condition for queue * Add current song images * Add current song styles for all song tables * Revert row index cell width * Remove animated svg on browser --------- Co-authored-by: jeffvli Co-authored-by: Jeff <42182408+jeffvli@users.noreply.github.com> --- .../virtual-table/cells/row-index-cell.tsx | 19 ++++++ .../hooks/use-current-song-row-styles.ts | 62 +++++++++++++++++-- .../components/virtual-table/index.tsx | 56 +++++++++++++++-- .../components/album-detail-content.tsx | 13 +++- .../now-playing/components/play-queue.tsx | 8 ++- .../playlist-detail-song-list-content.tsx | 11 +++- .../songs/components/song-list-table-view.tsx | 10 ++- src/renderer/hooks/index.ts | 1 + src/renderer/hooks/use-app-focus.ts | 22 +++++++ src/renderer/media/play-static.svg | 7 +++ src/renderer/media/play.svg | 15 +++++ src/renderer/store/settings.store.ts | 6 +- src/renderer/themes/default.scss | 34 ++++++++++ 13 files changed, 248 insertions(+), 16 deletions(-) create mode 100644 src/renderer/components/virtual-table/cells/row-index-cell.tsx create mode 100644 src/renderer/hooks/use-app-focus.ts create mode 100644 src/renderer/media/play-static.svg create mode 100644 src/renderer/media/play.svg diff --git a/src/renderer/components/virtual-table/cells/row-index-cell.tsx b/src/renderer/components/virtual-table/cells/row-index-cell.tsx new file mode 100644 index 00000000..32721b0a --- /dev/null +++ b/src/renderer/components/virtual-table/cells/row-index-cell.tsx @@ -0,0 +1,19 @@ +import type { ICellRendererParams } from '@ag-grid-community/core'; +import { Text } from '/@/renderer/components/text'; +import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell'; + +export const RowIndexCell = ({ value }: ICellRendererParams) => { + return ( + + + {value} + + + ); +}; diff --git a/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts b/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts index 1ad467f2..04631427 100644 --- a/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts +++ b/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts @@ -1,7 +1,8 @@ import { RowClassRules, RowNode } from '@ag-grid-community/core'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import { MutableRefObject, useEffect, useMemo } from 'react'; +import { MutableRefObject, useEffect, useMemo, useRef } from 'react'; import { Song } from '/@/renderer/api/types'; +import { useAppFocus } from '/@/renderer/hooks'; import { useCurrentSong, usePlayerStore } from '/@/renderer/store'; interface UseCurrentSongRowStylesProps { @@ -10,17 +11,43 @@ interface UseCurrentSongRowStylesProps { export const useCurrentSongRowStyles = ({ tableRef }: UseCurrentSongRowStylesProps) => { const currentSong = useCurrentSong(); + const isFocused = useAppFocus(); + const isFocusedRef = useRef(isFocused); + + useEffect(() => { + // Redraw rows if the app focus changes + if (isFocusedRef.current !== isFocused) { + isFocusedRef.current = isFocused; + if (tableRef?.current) { + const { api, columnApi } = tableRef?.current || {}; + if (api == null || columnApi == null) { + return; + } + + const currentNode = currentSong?.id ? api.getRowNode(currentSong.id) : undefined; + + const rowNodes = [currentNode].filter((e) => e !== undefined) as RowNode[]; + + if (rowNodes) { + api.redrawRows({ rowNodes }); + } + } + } + }, [currentSong?.id, isFocused, tableRef]); const rowClassRules = useMemo | undefined>(() => { return { 'current-song': (params) => { - return params?.data?.id === currentSong?.id; + return ( + params?.data?.id === currentSong?.id && + params?.data?.albumId === currentSong?.albumId + ); }, }; - }, [currentSong?.id]); + }, [currentSong?.albumId, currentSong?.id]); - // Redraw song rows when current song changes useEffect(() => { + // Redraw song rows when current song changes const unsubSongChange = usePlayerStore.subscribe( (state) => state.current.song, (song, previousSong) => { @@ -46,8 +73,35 @@ export const useCurrentSongRowStyles = ({ tableRef }: UseCurrentSongRowStylesPro { equalityFn: (a, b) => a?.id === b?.id }, ); + // Redraw song rows when the status changes + const unsubStatusChange = usePlayerStore.subscribe( + (state) => state.current.song, + (song, previousSong) => { + if (tableRef?.current) { + const { api, columnApi } = tableRef?.current || {}; + if (api == null || columnApi == null) { + return; + } + + const currentNode = song?.id ? api.getRowNode(song.id) : undefined; + + const previousNode = previousSong?.id + ? api.getRowNode(previousSong?.id) + : undefined; + + const rowNodes = [currentNode, previousNode].filter( + (e) => e !== undefined, + ) as RowNode[]; + + api.redrawRows({ rowNodes }); + } + }, + { equalityFn: (a, b) => a?.id === b?.id }, + ); + return () => { unsubSongChange(); + unsubStatusChange(); }; }, [tableRef]); diff --git a/src/renderer/components/virtual-table/index.tsx b/src/renderer/components/virtual-table/index.tsx index f59ec520..9ff51992 100644 --- a/src/renderer/components/virtual-table/index.tsx +++ b/src/renderer/components/virtual-table/index.tsx @@ -19,6 +19,7 @@ import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import formatDuration from 'format-duration'; import { AnimatePresence } from 'framer-motion'; +import isElectron from 'is-electron'; import { generatePath } from 'react-router'; import styled from 'styled-components'; import { AlbumArtistCell } from '/@/renderer/components/virtual-table/cells/album-artist-cell'; @@ -29,7 +30,11 @@ 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, TablePagination as TablePaginationType } from '/@/renderer/types'; +import { + PlayerStatus, + TableColumn, + TablePagination as TablePaginationType, +} from '/@/renderer/types'; import { FavoriteCell } from '/@/renderer/components/virtual-table/cells/favorite-cell'; import { RatingCell } from '/@/renderer/components/virtual-table/cells/rating-cell'; import { TablePagination } from '/@/renderer/components/virtual-table/table-pagination'; @@ -37,6 +42,7 @@ import { ActionsCell } from '/@/renderer/components/virtual-table/cells/actions- import { TitleCell } from '/@/renderer/components/virtual-table/cells/title-cell'; import { useFixedTableHeader } from '/@/renderer/components/virtual-table/hooks/use-fixed-table-header'; import { NoteCell } from '/@/renderer/components/virtual-table/cells/note-cell'; +import { RowIndexCell } from '/@/renderer/components/virtual-table/cells/row-index-cell'; export * from './table-config-dropdown'; export * from './table-pagination'; @@ -260,7 +266,16 @@ const tableColumns: { [key: string]: ColDef } = { width: 80, }, rowIndex: { - cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'right' }), + cellClass: 'row-index', + cellClassRules: { + focused: (params) => { + return isElectron() && 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' }), @@ -311,7 +326,29 @@ const tableColumns: { [key: string]: ColDef } = { width: 250, }, trackNumber: { - cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'center' }), + cellClass: 'track-number', + cellRenderer: (params: ICellRendererParams) => GenericCell(params, { position: 'right' }), + colId: TableColumn.TRACK_NUMBER, + field: 'trackNumber', + headerComponent: (params: IHeaderParams) => + GenericTableHeader(params, { position: 'center' }), + headerName: 'Track', + suppressSizeToFit: true, + valueGetter: (params: ValueGetterParams) => + params.data ? params.data.trackNumber : undefined, + width: 80, + }, + trackNumberDetail: { + cellClass: 'row-index', + cellClassRules: { + focused: (params) => { + return isElectron() && params.context?.isFocused; + }, + playing: (params) => { + return params.context?.status === PlayerStatus.PLAYING; + }, + }, + cellRenderer: RowIndexCell, colId: TableColumn.TRACK_NUMBER, field: 'trackNumber', headerComponent: (params: IHeaderParams) => @@ -354,10 +391,19 @@ export const getColumnDef = (column: TableColumn) => { return tableColumns[column as keyof typeof tableColumns]; }; -export const getColumnDefs = (columns: PersistedTableColumn[], useWidth?: boolean) => { +export const getColumnDefs = ( + columns: PersistedTableColumn[], + useWidth?: boolean, + type?: 'albumDetail', +) => { const columnDefs: ColDef[] = []; for (const column of columns) { - const 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) { + presetColumn = tableColumns['trackNumberDetail' as keyof typeof tableColumns]; + } + if (presetColumn) { columnDefs.push({ ...presetColumn, diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index f232298b..2d363032 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -31,9 +31,9 @@ import { import { usePlayQueueAdd } from '/@/renderer/features/player'; import { PlayButton, useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared'; import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay'; -import { useContainerQuery } from '/@/renderer/hooks'; +import { useAppFocus, useContainerQuery } from '/@/renderer/hooks'; import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer } from '/@/renderer/store'; +import { useCurrentServer, useCurrentStatus } from '/@/renderer/store'; import { usePlayButtonBehavior, useSettingsStoreActions, @@ -70,8 +70,13 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP const handlePlayQueueAdd = usePlayQueueAdd(); const tableConfig = useTableSettings('albumDetail'); const { setTable } = useSettingsStoreActions(); + const status = useCurrentStatus(); + const isFocused = useAppFocus(); - const columnDefs = useMemo(() => getColumnDefs(tableConfig.columns), [tableConfig.columns]); + const columnDefs = useMemo( + () => getColumnDefs(tableConfig.columns, false, 'albumDetail'), + [tableConfig.columns], + ); const getRowHeight = useCallback( (params: RowHeightParams) => { @@ -396,7 +401,9 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP autoFitColumns={tableConfig.autoFit} columnDefs={columnDefs} context={{ + isFocused, onCellContextMenu, + status, }} enableCellChangeFlash={false} fullWidthCellRenderer={FullWidthDiscCell} diff --git a/src/renderer/features/now-playing/components/play-queue.tsx b/src/renderer/features/now-playing/components/play-queue.tsx index 62253305..d1df1b5d 100644 --- a/src/renderer/features/now-playing/components/play-queue.tsx +++ b/src/renderer/features/now-playing/components/play-queue.tsx @@ -11,6 +11,7 @@ import '@ag-grid-community/styles/ag-theme-alpine.css'; import { useAppStoreActions, useCurrentSong, + useCurrentStatus, useDefaultQueue, usePlayerControls, usePreviousSong, @@ -34,6 +35,7 @@ import { LibraryItem, QueueSong } from '/@/renderer/api/types'; import { useHandleTableContextMenu } from '/@/renderer/features/context-menu'; import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid'; +import { useAppFocus } from '/@/renderer/hooks'; const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null; const remote = isElectron() ? window.electron.remote : null; @@ -49,6 +51,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { const { reorderQueue, setCurrentTrack } = useQueueControls(); const currentSong = useCurrentSong(); const previousSong = usePreviousSong(); + const status = useCurrentStatus(); const { setSettings } = useSettingsStoreActions(); const { setAppStore } = useAppStoreActions(); const tableConfig = useTableSettings(type); @@ -56,6 +59,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { const playerType = usePlayerType(); const { play } = usePlayerControls(); const volume = useVolume(); + const isFocused = useAppFocus(); useEffect(() => { if (tableRef.current) { @@ -204,7 +208,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { } } } - }, [currentSong, previousSong, tableConfig.followCurrentSong]); + }, [currentSong, previousSong, tableConfig.followCurrentSong, status, isFocused]); const onCellContextMenu = useHandleTableContextMenu(LibraryItem.SONG, QUEUE_CONTEXT_MENU_ITEMS); @@ -219,7 +223,9 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { autoFitColumns={tableConfig.autoFit} columnDefs={columnDefs} context={{ + isFocused, onCellContextMenu, + status, }} deselectOnClickOutside={type === 'fullScreen'} getRowId={(data) => data.data.uniqueId} diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx index f1020241..7365d886 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx @@ -22,7 +22,7 @@ import { SortOrder, } from '/@/renderer/api/types'; import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid'; -import { getColumnDefs, TablePagination, VirtualTable } from '/@/renderer/components/virtual-table'; +import { TablePagination, VirtualTable, getColumnDefs } 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 +34,7 @@ import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playli import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query'; import { useCurrentServer, + useCurrentStatus, usePlaylistDetailStore, usePlaylistDetailTablePagination, useSetPlaylistDetailTable, @@ -41,6 +42,7 @@ import { } from '/@/renderer/store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { ListDisplayType } from '/@/renderer/types'; +import { useAppFocus } from '/@/renderer/hooks'; interface PlaylistDetailContentProps { tableRef: MutableRefObject; @@ -49,6 +51,8 @@ interface PlaylistDetailContentProps { export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailContentProps) => { const { playlistId } = useParams() as { playlistId: string }; const queryClient = useQueryClient(); + const status = useCurrentStatus(); + const isFocused = useAppFocus(); const server = useCurrentServer(); const page = usePlaylistDetailStore(); const filters: Partial = useMemo(() => { @@ -236,6 +240,11 @@ export const PlaylistDetailSongListContent = ({ tableRef }: PlaylistDetailConten alwaysShowHorizontalScroll autoFitColumns={page.table.autoFit} columnDefs={columnDefs} + context={{ + isFocused, + onCellContextMenu: handleContextMenu, + status, + }} getRowId={(data) => data.data.uniqueId} infiniteInitialRowCount={checkPlaylistList.data?.totalRecordCount || 100} pagination={isPaginationEnabled} diff --git a/src/renderer/features/songs/components/song-list-table-view.tsx b/src/renderer/features/songs/components/song-list-table-view.tsx index 36ad27b4..06de099d 100644 --- a/src/renderer/features/songs/components/song-list-table-view.tsx +++ b/src/renderer/features/songs/components/song-list-table-view.tsx @@ -8,7 +8,8 @@ import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/ho import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table'; import { useListContext } from '/@/renderer/context/list-context'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import { useCurrentServer, usePlayButtonBehavior } from '/@/renderer/store'; +import { useAppFocus } from '/@/renderer/hooks'; +import { useCurrentServer, useCurrentStatus, usePlayButtonBehavior } from '/@/renderer/store'; interface SongListTableViewProps { itemCount?: number; @@ -18,6 +19,8 @@ interface SongListTableViewProps { export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProps) => { const server = useCurrentServer(); const { pageKey, id, handlePlay, customFilters } = useListContext(); + const isFocused = useAppFocus(); + const status = useCurrentStatus(); const { rowClassRules } = useCurrentSongRowStyles({ tableRef }); @@ -46,6 +49,11 @@ export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProp key={`table-${tableProps.rowHeight}-${server?.id}`} ref={tableRef} {...tableProps} + context={{ + ...tableProps.context, + isFocused, + status, + }} rowClassRules={rowClassRules} onRowDoubleClicked={handleRowDoubleClick} /> diff --git a/src/renderer/hooks/index.ts b/src/renderer/hooks/index.ts index e0f836c1..82dd033a 100644 --- a/src/renderer/hooks/index.ts +++ b/src/renderer/hooks/index.ts @@ -4,3 +4,4 @@ export * from './use-should-pad-titlebar'; export * from './use-container-query'; export * from './use-fast-average-color'; export * from './use-hide-scrollbar'; +export * from './use-app-focus'; diff --git a/src/renderer/hooks/use-app-focus.ts b/src/renderer/hooks/use-app-focus.ts new file mode 100644 index 00000000..72e163f8 --- /dev/null +++ b/src/renderer/hooks/use-app-focus.ts @@ -0,0 +1,22 @@ +// From https://learnersbucket.com/examples/interview/usehasfocus-hook-in-react/ + +import { useState, useEffect } from 'react'; + +export const useAppFocus = () => { + const [focus, setFocus] = useState(document.hasFocus()); + + useEffect(() => { + const onFocus = () => setFocus(true); + const onBlur = () => setFocus(false); + + window.addEventListener('focus', onFocus); + window.addEventListener('blur', onBlur); + + return () => { + window.removeEventListener('focus', onFocus); + window.removeEventListener('blur', onBlur); + }; + }, []); + + return focus; +}; diff --git a/src/renderer/media/play-static.svg b/src/renderer/media/play-static.svg new file mode 100644 index 00000000..acee2363 --- /dev/null +++ b/src/renderer/media/play-static.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/renderer/media/play.svg b/src/renderer/media/play.svg new file mode 100644 index 00000000..f1808b5d --- /dev/null +++ b/src/renderer/media/play.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 60ebda84..b1650afa 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -344,6 +344,10 @@ const initialState: SettingsState = { fullScreen: { autoFit: true, columns: [ + { + column: TableColumn.ROW_INDEX, + width: 80, + }, { column: TableColumn.TITLE_COMBINED, width: 500, @@ -365,7 +369,7 @@ const initialState: SettingsState = { columns: [ { column: TableColumn.ROW_INDEX, - width: 50, + width: 80, }, { column: TableColumn.TITLE, diff --git a/src/renderer/themes/default.scss b/src/renderer/themes/default.scss index c8c64b5f..709e0606 100644 --- a/src/renderer/themes/default.scss +++ b/src/renderer/themes/default.scss @@ -101,6 +101,8 @@ --card-poster-bg-hover: transparent; --card-poster-radius: 3px; --background-noise: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDAiIGhlaWdodD0iMzAwIj48ZmlsdGVyIGlkPSJhIiB4PSIwIiB5PSIwIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iLjc1IiBzdGl0Y2hUaWxlcz0ic3RpdGNoIi8+PGZlQ29sb3JNYXRyaXggdHlwZT0ic2F0dXJhdGUiIHZhbHVlcz0iMCIvPjwvZmlsdGVyPjxwYXRoIGZpbHRlcj0idXJsKCNhKSIgb3BhY2l0eT0iLjA1IiBkPSJNMCAwaDMwMHYzMDBIMHoiLz48L3N2Zz4='); + --current-song-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2aWV3Qm94PSIxMDAgMTMwIDU3IDgwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHg9IjEwMCIgeT0iMTMwIiB3aWR0aD0iMTIiIGhlaWdodD0iMjAiIHN0eWxlPSJmaWxsOiByZ2IoMjE2LCAyMTYsIDIxNik7IHN0cm9rZTogcmdiKDAsIDAsIDApOyB0cmFuc2Zvcm0tb3JpZ2luOiAxMDZweCAxNDBweDsiLz4KICA8cmVjdCB4PSIxMTUiIHk9IjEzMCIgd2lkdGg9IjEyIiBoZWlnaHQ9IjYwIiBzdHlsZT0iZmlsbDogcmdiKDIxNiwgMjE2LCAyMTYpOyBzdHJva2U6IHJnYigwLCAwLCAwKTsgdHJhbnNmb3JtLW9yaWdpbjogMTIxcHggMTYwcHg7Ii8+CiAgPHJlY3QgeD0iMTMwIiB5PSIxMzAiIHdpZHRoPSIxMiIgaGVpZ2h0PSI4MCIgc3R5bGU9ImZpbGw6IHJnYigyMTYsIDIxNiwgMjE2KTsgc3Ryb2tlOiByZ2IoMCwgMCwgMCk7IHRyYW5zZm9ybS1vcmlnaW46IDEzNnB4IDE3MHB4OyIvPgogIDxyZWN0IHg9IjE0NSIgeT0iMTMwIiB3aWR0aD0iMTIiIGhlaWdodD0iNDUiIHN0eWxlPSJmaWxsOiByZ2IoMjE2LCAyMTYsIDIxNik7IHN0cm9rZTogcmdiKDAsIDAsIDApOyB0cmFuc2Zvcm0tb3JpZ2luOiAxNTFweCAxNTIuNXB4OyIvPgo8L3N2Zz4='); + --current-song-image-animated: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2aWV3Qm94PSIxMDAgMTMwIDU3IDgwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHg9IjEwMCIgeT0iMTMwIiB3aWR0aD0iMTIiIGhlaWdodD0iODAiIHN0eWxlPSJmaWxsOiByZ2IoMjE2LCAyMTYsIDIxNik7IHN0cm9rZTogcmdiKDAsIDAsIDApOyB0cmFuc2Zvcm0tb3JpZ2luOiAxMDZweCAxNzBweDsiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iaGVpZ2h0IiB2YWx1ZXM9IjgwOzE1OzgwIiBiZWdpbj0iMC4xcyIgZHVyPSIwLjk1cyIga2V5VGltZXM9IjA7IDAuNDczNjg7IDEiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlTcGxpbmVzPSIwLjQyIDAgMC41OCAxOyAwLjQyIDAgMC41OCAxIi8+CiAgPC9yZWN0PgogIDxyZWN0IHg9IjExNSIgeT0iMTMwIiB3aWR0aD0iMTIiIGhlaWdodD0iODAiIHN0eWxlPSJmaWxsOiByZ2IoMjE2LCAyMTYsIDIxNik7IHN0cm9rZTogcmdiKDAsIDAsIDApOyB0cmFuc2Zvcm0tb3JpZ2luOiAxMjFweCAxNzBweDsiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iaGVpZ2h0IiB2YWx1ZXM9IjI1OzgwOzI1IiBiZWdpbj0iMC4xcyIgZHVyPSIwLjk1cyIga2V5VGltZXM9IjA7IDAuNDQ0NDQ7IDEiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlTcGxpbmVzPSIwLjQ1IDAgMC41NSAxOyAwLjQ1IDAgMC41NSAxIi8+CiAgPC9yZWN0PgogIDxyZWN0IHg9IjEzMCIgeT0iMTMwIiB3aWR0aD0iMTIiIGhlaWdodD0iODAiIHN0eWxlPSJmaWxsOiByZ2IoMjE2LCAyMTYsIDIxNik7IHN0cm9rZTogcmdiKDAsIDAsIDApOyB0cmFuc2Zvcm0tb3JpZ2luOiAxMzZweCAxNzBweDsiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iaGVpZ2h0IiB2YWx1ZXM9IjgwOzEwOzgwIiBiZWdpbj0iMC4xcyIgZHVyPSIwLjg1cyIga2V5VGltZXM9IjA7IDAuNDIxMDU7IDEiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlTcGxpbmVzPSIwLjY1IDAgMC4zNSAxOyAwLjY1IDAgMC4zNSAxIi8+CiAgPC9yZWN0PgogIDxyZWN0IHg9IjE0NSIgeT0iMTMwIiB3aWR0aD0iMTIiIGhlaWdodD0iODAiIHN0eWxlPSJmaWxsOiByZ2IoMjE2LCAyMTYsIDIxNik7IHN0cm9rZTogcmdiKDAsIDAsIDApOyB0cmFuc2Zvcm0tb3JpZ2luOiAxNTFweCAxNzBweDsiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iaGVpZ2h0IiB2YWx1ZXM9IjMwOzgwOzMwIiBiZWdpbj0iMC4xcyIgZHVyPSIxLjA1cyIga2V5VGltZXM9IjA7IDAuMzE1Nzk7IDEiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBjYWxjTW9kZT0ic3BsaW5lIiBrZXlTcGxpbmVzPSIwLjQyIDAgMC41OCAxOyAwLjQyIDAgMC41OCAxIi8+CiAgPC9yZWN0Pgo8L3N2Zz4='); --bg-header-overlay: linear-gradient(transparent 0%, rgba(0, 0, 0, 50%) 100%), var(--background-noise); --bg-subheader-overlay: linear-gradient(180deg, rgba(0, 0, 0, 5%) 0%, var(--main-bg) 100%), @@ -184,8 +186,40 @@ } .current-song { + background: var(--table-row-hover-bg); + .current-song-child { color: var(--primary-color) !important; } } + + .current-song > .row-index.playing .current-song-index { + 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); + } }