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 <jeffvictorli@gmail.com> Co-authored-by: Jeff <42182408+jeffvli@users.noreply.github.com>
This commit is contained in:
parent
9964f95d5d
commit
8a53fab751
13 changed files with 248 additions and 16 deletions
|
|
@ -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 (
|
||||
<CellContainer $position="right">
|
||||
<Text
|
||||
$secondary
|
||||
align="right"
|
||||
className="current-song-child current-song-index"
|
||||
overflow="hidden"
|
||||
size="md"
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
</CellContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -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<boolean>(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<any>[];
|
||||
|
||||
if (rowNodes) {
|
||||
api.redrawRows({ rowNodes });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [currentSong?.id, isFocused, tableRef]);
|
||||
|
||||
const rowClassRules = useMemo<RowClassRules<Song> | 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<any>[];
|
||||
|
||||
api.redrawRows({ rowNodes });
|
||||
}
|
||||
},
|
||||
{ equalityFn: (a, b) => a?.id === b?.id },
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubSongChange();
|
||||
unsubStatusChange();
|
||||
};
|
||||
}, [tableRef]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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<any>) => {
|
|||
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<any>) => {
|
|||
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<any>) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
}, [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<any>) => {
|
|||
autoFitColumns={tableConfig.autoFit}
|
||||
columnDefs={columnDefs}
|
||||
context={{
|
||||
isFocused,
|
||||
onCellContextMenu,
|
||||
status,
|
||||
}}
|
||||
deselectOnClickOutside={type === 'fullScreen'}
|
||||
getRowId={(data) => data.data.uniqueId}
|
||||
|
|
|
|||
|
|
@ -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<AgGridReactType | null>;
|
||||
|
|
@ -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<PlaylistSongListQuery> = 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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
22
src/renderer/hooks/use-app-focus.ts
Normal file
22
src/renderer/hooks/use-app-focus.ts
Normal file
|
|
@ -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;
|
||||
};
|
||||
7
src/renderer/media/play-static.svg
Normal file
7
src/renderer/media/play-static.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="100 130 57 80" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="100" y="130" width="12" height="20" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 106px 140px;"/>
|
||||
<rect x="115" y="130" width="12" height="60" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 121px 160px;"/>
|
||||
<rect x="130" y="130" width="12" height="80" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 136px 170px;"/>
|
||||
<rect x="145" y="130" width="12" height="45" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 151px 152.5px;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 656 B |
15
src/renderer/media/play.svg
Normal file
15
src/renderer/media/play.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="100 130 57 80" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="100" y="130" width="12" height="80" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 106px 170px;">
|
||||
<animate attributeName="height" values="80;15;80" begin="0.1s" dur="0.95s" keyTimes="0; 0.47368; 1" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</rect>
|
||||
<rect x="115" y="130" width="12" height="80" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 121px 170px;">
|
||||
<animate attributeName="height" values="25;80;25" begin="0.1s" dur="0.95s" keyTimes="0; 0.44444; 1" repeatCount="indefinite" calcMode="spline" keySplines="0.45 0 0.55 1; 0.45 0 0.55 1"/>
|
||||
</rect>
|
||||
<rect x="130" y="130" width="12" height="80" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 136px 170px;">
|
||||
<animate attributeName="height" values="80;10;80" begin="0.1s" dur="0.85s" keyTimes="0; 0.42105; 1" repeatCount="indefinite" calcMode="spline" keySplines="0.65 0 0.35 1; 0.65 0 0.35 1"/>
|
||||
</rect>
|
||||
<rect x="145" y="130" width="12" height="80" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 151px 170px;">
|
||||
<animate attributeName="height" values="30;80;30" begin="0.1s" dur="1.05s" keyTimes="0; 0.31579; 1" repeatCount="indefinite" calcMode="spline" keySplines="0.42 0 0.58 1; 0.42 0 0.58 1"/>
|
||||
</rect>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue