A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

webui: add PlayQueue Component

+490 -2
docs/logo/rockbox-icon.png

This is a binary file and will not be displayed.

docs/rockbox-ui.png

This is a binary file and will not be displayed.

webui/rockbox/src/Assets/rockbox-icon.png

This is a binary file and will not be displayed.

+113
webui/rockbox/src/Assets/rockbox-icon.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Creator: CorelDRAW --> 3 + 4 + <svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 11 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 + xml:space="preserve" 13 + width="195.40291" 14 + height="193.33" 15 + shape-rendering="geometricPrecision" 16 + text-rendering="geometricPrecision" 17 + image-rendering="optimizeQuality" 18 + viewBox="0 0 5514.7044 5456.2021" 19 + id="svg2918" 20 + version="1.1" 21 + inkscape:version="0.47 r22583" 22 + sodipodi:docname="rockboxlogo.svg" 23 + style="fill-rule:evenodd"><metadata 24 + id="metadata2956"><rdf:RDF><cc:Work 25 + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type 26 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs 27 + id="defs2954"><inkscape:perspective 28 + sodipodi:type="inkscape:persp3d" 29 + inkscape:vp_x="0 : 109.84252 : 1" 30 + inkscape:vp_y="0 : 1000 : 0" 31 + inkscape:vp_z="708.66144 : 109.84252 : 1" 32 + inkscape:persp3d-origin="354.33072 : 73.228348 : 1" 33 + id="perspective2958" /></defs><sodipodi:namedview 34 + pagecolor="#ffffff" 35 + bordercolor="#666666" 36 + borderopacity="1" 37 + objecttolerance="10" 38 + gridtolerance="10" 39 + guidetolerance="10" 40 + inkscape:pageopacity="0" 41 + inkscape:pageshadow="2" 42 + inkscape:window-width="1571" 43 + inkscape:window-height="859" 44 + id="namedview2952" 45 + showgrid="false" 46 + showguides="true" 47 + inkscape:showpageshadow="true" 48 + showborder="true" 49 + inkscape:zoom="2.49387" 50 + inkscape:cx="157.54221" 51 + inkscape:cy="88.164195" 52 + inkscape:window-x="158" 53 + inkscape:window-y="36" 54 + inkscape:window-maximized="0" 55 + inkscape:current-layer="Ebene_x0020_1"><inkscape:grid 56 + type="xygrid" 57 + id="grid2986" 58 + empspacing="5" 59 + visible="true" 60 + enabled="true" 61 + snapvisiblegridlinesonly="true" /></sodipodi:namedview><g 62 + id="Ebene_x0020_1" 63 + transform="translate(-169.74957,-49.383066)"><defs 64 + id="defs2921"><linearGradient 65 + id="id0" 66 + gradientUnits="userSpaceOnUse" 67 + x1="17608" 68 + y1="4190.54" 69 + x2="17715.699" 70 + y2="4801.2798"><stop 71 + offset="0" 72 + stop-color="#FFC100" 73 + id="stop2924" /><stop 74 + offset="0.6" 75 + stop-color="#997200" 76 + id="stop2926" /><stop 77 + offset="1" 78 + stop-color="#332201" 79 + id="stop2928" /></linearGradient></defs><path 80 + d="m 169.74957,5505.5851 5514.70423,0 0,-5456.21855 -5514.70423,0 0,5456.21855 z" 81 + id="path2930" 82 + style="fill:#000000" /><path 83 + d="m 265.51629,148.81 5314.78741,0 0,5259.14 -5314.78741,0 0,-5259.14" 84 + id="path2936" 85 + style="fill:#ffc000" /><path 86 + d="m 2092.78,651 -1041.74,0 0,2579 c 6.74,18.03 13.42,36.06 20.05,54.13 46.78,-12.8 95.31,-17.99 143.72,-15.41 94.74,5.08 180.1,58.83 225.6,142.07 41.13,75.24 60.7,160.36 56.57,245.99 -1.73,35.79 -11.15,70.79 -27.63,102.61 -40.47,78.17 -102.93,142.77 -179.67,185.85 25.96,88.13 50.77,176.58 74.44,265.35 45.27,169.84 66.54,345.17 63.14,520.92 -0.88,45.32 -10.71,90.04 -28.94,131.55 -18.69,42.53 -51.42,77.36 -92.74,98.65 -29.98,15.44 -64.95,17.98 -96.84,7.06 -31.9,-10.92 -57.97,-34.37 -72.2,-64.94 -16.55,-35.6 -24.66,-74.53 -23.67,-113.79 0.55,-22.08 11.22,-42.69 28.94,-55.9 22.34,-16.66 49.78,-25.02 77.61,-23.69 18.27,0.9 34.64,11.56 42.83,27.93 8.18,16.35 6.88,35.86 -3.37,51.01 -12.63,18.66 -31.16,32.56 -52.62,39.46 l 7.35,16.01 c 3.46,9.08 11.14,15.91 20.59,18.24 9.44,2.34 19.43,-0.09 26.73,-6.51 35.94,-30.62 58.33,-74.2 62.27,-121.25 5.2,-81.09 3.49,-162.46 -5.13,-243.25 -25.27,-172.56 -62.87,-343.11 -112.53,-510.3 -12.75,4.05 -25.68,7.52 -38.74,10.44 -38.23,8.5 -76.87,15.12 -115.76,19.84 l 0,1079.87 697.19,0 0,144.42 69.17,0 0,-144.42 148.54,0 0,-69.17 -148.54,0 0,-1557.18 66.8,0 608.45,1626.35 668.19,0 60.39,154.11 64.4,-25.24 -60.04,-153.21 208.48,0 0,-69.17 -235.58,0 -719.55,-1836.41 C 2743.8,2888.85 2895.75,2477.67 2895.75,1955.48 2895.75,1255.6 2706.28,651 2092.78,651 z m -917.66,3233.37 c -39.71,16.35 -81.45,27.32 -124.08,32.59 l 0,-393.54 c 42.92,119.28 84.14,239.17 123.65,359.62 0.15,0.45 0.29,0.88 0.43,1.33 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 m -65.29,-493.05 c 13.58,-3.73 27.34,-6.89 41.19,-9.47 43.45,-8.08 88.31,2.42 123.65,28.94 32.18,24.15 55.33,58.38 65.82,97.23 23.35,86.59 18.26,178.4 -14.52,261.89 -6.87,17.52 -18.21,32.94 -32.88,44.73 -11.26,9.05 -22.92,17.58 -34.94,25.58 -46.11,-150.72 -95.57,-300.4 -148.32,-448.9 m -58.79,525.64 c -10.92,1.34 -21.89,2.32 -32.88,2.91 -119.79,6.51 -231.2,-61.49 -280.19,-171 -86.95,-194.38 -99.58,-413.88 -35.52,-616.95 22.5,-71.28 45.73,-142.32 69.72,-213.11 6.63,-19.58 13.34,-39.15 20.12,-58.71 64.3,152.74 125.78,306.66 184.38,461.68 -83.5,44.73 -150.88,114.51 -192.66,199.51 -6.9,14.05 -4.2,30.93 6.76,42.11 10.95,11.18 27.77,14.23 41.96,7.62 12.33,-8.89 23.76,-18.93 34.16,-30 42.21,-46.84 92.8,-85.4 149.16,-113.72 11.79,32 23.44,64.03 34.99,96.12 l 0,-293.42 c -55.46,-148.43 -114.22,-295.6 -176.26,-441.4 -10.28,-24.15 -20.77,-48.21 -31.47,-72.15 12.91,-35.44 26.03,-70.82 39.35,-106.1 19.03,-50.37 33.55,-102.3 43.42,-155.23 11.14,-59.85 6.15,-121.6 -14.48,-178.89 -47,-130.55 -137.6,-240.9 -256.51,-312.42 -52.31,-31.47 -112.63,-47 -173.63,-44.73 -51.11,1.91 -92.39,42.35 -95.37,93.4 -2.71,46.33 8.04,92.47 30.91,132.86 21.18,37.38 44,73.81 68.4,109.17 75.28,109.11 140.33,224.93 194.34,345.97 10.61,23.82 21.17,47.67 31.66,71.53 -16.48,45.32 -32.62,90.75 -48.42,136.32 -73.28,211.39 -120.56,430.94 -140.75,653.77 -9.53,105.17 -0.18,211.19 27.63,313.08 38.14,139.76 162.4,238.75 307.16,244.67 64.78,2.65 129.66,0.05 194.02,-7.78 l 0,-95.11 m 947.33,-3450.88 -1077,0 0,-167.8 -69.17,0 0,167.8 -161.09,0 0,69.17 161.09,0 0,770.49 69.17,0 0,-770.49 1077,0 0,-69.17" 87 + id="path2940" 88 + sodipodi:nodetypes="ccccsssscsssssssssssccsscccsccccccccccccccccccccsccccscccccssssscccssssccsscccccscssssssssscssssccccccccccccccc" 89 + style="fill:#000000" /><path 90 + d="m 1622.93,5022.77 125.3,0 0,-1557.18 -125.3,0 0,1557.18 m 258.74,-2538.71 c 211.32,0 244.43,-267.93 244.23,-451.8 -0.21,-183.87 -26.91,-383.4 -211.32,-383.4 l -291.65,0 0,1016.08 125.3,0 0,-180.88 133.44,0 z m 0,69.16 -64.27,0 0,111.72 4.44,0 c 470.96,0 470.96,-421.72 470.96,-568.25 l 0,-94.59 c 0,-142.05 -0.96,-534.11 -397.53,-534.11 l -272.34,0 0,111.7 258.74,0 c 134.59,0 315.12,19.14 315.12,470.44 0,451.3 -181.46,499.33 -315.12,503.09 z M 760.15,2540.89 C 705.94,2432.7 647.29,2326.77 584.34,2223.43 c -20.02,-32.86 -38.12,-66.83 -54.21,-101.78 -10.51,-22.78 -16.74,-47.3 -18.42,-72.33 -0.35,-5.4 1.58,-10.7 5.35,-14.58 3.74,-3.89 8.98,-6 14.38,-5.81 26.01,0.88 51.34,8.34 73.67,21.7 95.97,57.41 164.37,151.47 189.42,260.45 7.3,31.79 7.53,64.8 0.66,96.68 -9.68,44.89 -21.37,89.3 -35.04,133.13 l 0,0" 91 + id="path2946" 92 + sodipodi:nodetypes="ccccccsccccccccccccccccsccsssssssscc" 93 + style="fill:#ffc000" /><path 94 + sodipodi:nodetypes="ccccccccc" 95 + id="path2988" 96 + d="m 2753.6812,1681.0201 531.7824,-10.5701 320.38,0 0,855.77 c 599.6612,-295.14 1317.1164,-78.6 1678.7288,499.17 278.7411,549.2036 214.2735,1092.4325 1.25,1497.56 -361.1224,579.18 -1079.5176,791.1 -1679.9788,495.58 l -221.9725,73.41 C 2463.4517,4766.9855 2744.9669,2612.0821 2753.6812,1681.0201 z" 97 + style="fill:#000000" /><path 98 + d="m 4509.5968,2549.61 c -341.2024,-101.6 -707.9132,-43.84 -1005.0632,158.37 l 0,-952.03 -674.17,0 0,3218.89 674.17,0 0,-129.83 c 297.68,202.55 665.1108,260.16 1006.7632,157.86 1204.1258,-492.1126 1074.3015,-2083.1205 -1.7,-2453.26 z" 99 + id="path2942" 100 + sodipodi:nodetypes="ccccccccc" 101 + style="fill:#b4c3d3" /><path 102 + d="m 4815.7872,3773.21 c 0,-330.93 -268.262,-599.2 -599.1844,-599.2 -330.9104,0 -599.1804,268.27 -599.1804,599.2 0,330.92 268.27,599.18 599.1804,599.18 330.9224,0 599.1844,-268.26 599.1844,-599.18 z" 103 + id="path2944" 104 + sodipodi:nodetypes="csssc" 105 + style="fill:#000000" /><path 106 + style="fill:#ffc000" 107 + sodipodi:nodetypes="ccsssc" 108 + id="path3001" 109 + d="m 4200.2428,4270.81 32.67,0 c 271.642,-8.92 485.9436,-234.03 481.4736,-505.78 -4.45,-271.75 -226.0216,-489.71 -497.8136,-489.71 -271.7904,0 -493.3504,217.96 -497.8004,489.71 -4.47,271.75 209.82,496.86 481.4704,505.78 z" /><path 110 + d="m 4216.6228,4146.9 c -9.03,0 -16.33,-7.32 -16.33,-16.33 0,-9.04 7.3,-16.36 16.33,-16.36 188.3412,0 341.0224,-152.67 341.0224,-341 0,-188.35 -152.6812,-341.02 -341.0224,-341.02 -9.03,0 -16.33,-7.32 -16.33,-16.34 0,-9.03 7.3,-16.35 16.33,-16.35 206.3916,0 373.6928,167.31 373.6928,373.71 0,206.38 -167.3012,373.69 -373.6928,373.69 z m -16.33,123.91 c 10.87,0.53 21.76,0.4 32.67,0 l 0,-45.77 c 246.422,-8.92 440.2332,-213.64 435.7132,-460.16 -4.53,-246.53 -205.7916,-443.98 -452.3732,-443.8 -5.91,-0.12 -10.79,2.99 -13.77,8.07 -2.98,5.11 -3,11.43 -0.02,16.52 2.98,5.1 7.9,8.19 13.81,8.09 151.8412,-0.11 291.892,81.84 366.1528,214.28 74.2704,132.43 71.1504,294.69 -8.1104,424.16 -79.2504,129.49 -222.3616,206.05 -374.0724,200.14 l 0,78.47 z" 111 + id="path2948" 112 + sodipodi:nodetypes="csssssssccccscsscsscc" 113 + style="fill:#000000" /></g></svg>
+4 -1
webui/rockbox/src/Components/ControlBar/CurrentTrack/CurrentTrack.tsx
··· 16 16 import Track from "../../Icons/Track"; 17 17 import { useTimeFormat } from "../../../Hooks/useFormat"; 18 18 import { CurrentTrack as NowPlaying } from "../../../Types/track"; 19 + import _ from "lodash"; 19 20 20 21 export type CurrentTrackProps = { 21 22 nowPlaying?: NowPlaying; ··· 53 54 > 54 55 <Time>{formatTime(nowPlaying.progress)}</Time> 55 56 <ArtistAlbum> 56 - {nowPlaying.artist} 57 + {_.get(nowPlaying, "artist.length", 0) > 75 58 + ? `${nowPlaying.artist?.substring(0, 54)}...` 59 + : nowPlaying.artist} 57 60 <Separator>-</Separator> 58 61 <Album to={`/albums/${nowPlaying.albumId}`}> 59 62 {album.length > 75
+28
webui/rockbox/src/Components/ControlBar/PlayQueue/PlayQueue.stories.tsx
··· 1 + import type { Meta, StoryObj } from "@storybook/react"; 2 + 3 + import PlayQueue from "./PlayQueue"; 4 + import { fn } from "@storybook/test"; 5 + import { nextTracks, previousTracks } from "./mocks"; 6 + 7 + // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export 8 + const meta = { 9 + title: "Components/PlayQueue", 10 + component: PlayQueue, 11 + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 12 + tags: ["autodocs"], 13 + // More on argTypes: https://storybook.js.org/docs/api/argtypes 14 + argTypes: {}, 15 + } satisfies Meta<typeof PlayQueue>; 16 + 17 + export default meta; 18 + type Story = StoryObj<typeof meta>; 19 + 20 + // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args 21 + export const Default: Story = { 22 + args: { 23 + previousTracks, 24 + nextTracks, 25 + onPlayTrackAt: fn(), 26 + onRemoveTrackAt: fn(), 27 + }, 28 + };
+29
webui/rockbox/src/Components/ControlBar/PlayQueue/PlayQueue.test.tsx
··· 1 + import { render } from "@testing-library/react"; 2 + import { vi } from "vitest"; 3 + import PlayQueue from "./PlayQueue"; 4 + import Providers from "../../../Providers"; 5 + import { MockedProvider } from "@apollo/client/testing"; 6 + import { mocks } from "../../../mocks"; 7 + import { MemoryRouter } from "react-router-dom"; 8 + import { nextTracks, previousTracks } from "./mocks"; 9 + 10 + describe("PlayQueue", () => { 11 + it("should render", () => { 12 + const { container } = render( 13 + <Providers> 14 + <MemoryRouter initialEntries={["/"]}> 15 + <MockedProvider mocks={mocks}> 16 + <PlayQueue 17 + previousTracks={previousTracks} 18 + nextTracks={nextTracks} 19 + currentTrack={undefined} 20 + onPlayTrackAt={vi.fn()} 21 + onRemoveTrackAt={vi.fn()} 22 + /> 23 + </MockedProvider> 24 + </MemoryRouter> 25 + </Providers> 26 + ); 27 + expect(container).toMatchSnapshot(); 28 + }); 29 + });
+130
webui/rockbox/src/Components/ControlBar/PlayQueue/PlayQueue.tsx
··· 1 + import { FC, useState } from "react"; 2 + import { Track } from "../../../Types/track"; 3 + import { Link } from "react-router-dom"; 4 + import { useTheme } from "@emotion/react"; 5 + import TrackIcon from "../../Icons/Track"; 6 + import { CloseOutline } from "@styled-icons/evaicons-outline"; 7 + import { Play } from "@styled-icons/ionicons-sharp"; 8 + import { 9 + AlbumCover, 10 + AlbumCoverAlt, 11 + Artist, 12 + Container, 13 + Header, 14 + List, 15 + ListItem, 16 + Placeholder, 17 + Remove, 18 + Switch, 19 + Title, 20 + TrackDetails, 21 + TrackTitle, 22 + } from "./styles"; 23 + 24 + export type PlayQueueProps = { 25 + previousTracks?: Track[]; 26 + nextTracks?: Track[]; 27 + currentTrack?: Track; 28 + onPlayTrackAt: (index: number) => void; 29 + onRemoveTrackAt: (index: number) => void; 30 + }; 31 + 32 + const PlayQueue: FC<PlayQueueProps> = ({ 33 + previousTracks = [], 34 + nextTracks = [], 35 + onPlayTrackAt, 36 + onRemoveTrackAt, 37 + }) => { 38 + const theme = useTheme(); 39 + const [active, setActive] = useState("playqueue"); 40 + 41 + const onSwitch = () => { 42 + if (active === "playqueue") { 43 + setActive("history"); 44 + return; 45 + } 46 + setActive("playqueue"); 47 + }; 48 + 49 + const _onPlayTrackAt = (index: number) => { 50 + if (active === "playqueue") { 51 + onPlayTrackAt((previousTracks?.length || 0) + index); 52 + return; 53 + } 54 + onPlayTrackAt(index); 55 + }; 56 + 57 + const _onRemoveTrack = (index: number) => { 58 + if (active === "playqueue") { 59 + onRemoveTrackAt((previousTracks?.length || 0) + index); 60 + return; 61 + } 62 + onRemoveTrackAt(index); 63 + }; 64 + 65 + const tracks = active === "playqueue" ? nextTracks! : previousTracks!; 66 + return ( 67 + <Container> 68 + <Header> 69 + <Title>{active === "playqueue" ? "Play Queue" : "History"}</Title> 70 + <Switch onClick={onSwitch}> 71 + {active === "playqueue" ? "History" : "Play Queue"} 72 + </Switch> 73 + </Header> 74 + <List> 75 + {tracks.map((track, index) => ( 76 + <ListItem key={track.id}> 77 + {track.cover && ( 78 + <div className="album-cover-container"> 79 + <AlbumCover src={track.cover} /> 80 + <div 81 + onClick={() => _onPlayTrackAt(index)} 82 + className="floating-play" 83 + > 84 + <Play size={16} color={track.cover ? "#fff" : "#000"} /> 85 + </div> 86 + </div> 87 + )} 88 + {!track.cover && ( 89 + <div className="album-cover-container"> 90 + <AlbumCoverAlt> 91 + <TrackIcon width={28} height={28} color="#a4a3a3" /> 92 + </AlbumCoverAlt> 93 + <div 94 + onClick={() => _onPlayTrackAt(index)} 95 + className="floating-play" 96 + > 97 + <Play size={16} color={track.cover ? "#fff" : "#000"} /> 98 + </div> 99 + </div> 100 + )} 101 + <TrackDetails> 102 + <TrackTitle>{track.title}</TrackTitle> 103 + <Link 104 + to={`/artists/${track.artistId}`} 105 + style={{ textDecoration: "none" }} 106 + > 107 + <Artist>{track.artist}</Artist> 108 + </Link> 109 + </TrackDetails> 110 + <Remove onClick={() => _onRemoveTrack(index)}> 111 + <CloseOutline size={24} color={theme.colors.text} /> 112 + </Remove> 113 + </ListItem> 114 + ))} 115 + {tracks.length === 0 && active === "playqueue" && ( 116 + <Placeholder> 117 + No upcoming tracks. Add some to your play queue. 118 + </Placeholder> 119 + )} 120 + {tracks.length === 0 && active === "history" && ( 121 + <Placeholder> 122 + No history. Play some tracks to see them here. 123 + </Placeholder> 124 + )} 125 + </List> 126 + </Container> 127 + ); 128 + }; 129 + 130 + export default PlayQueue;
+16
webui/rockbox/src/Components/ControlBar/PlayQueue/PlayQueueWithData.tsx
··· 1 + import { FC } from "react"; 2 + import PlayQueue from "./PlayQueue"; 3 + 4 + const PlayQueueWithData: FC = () => { 5 + return ( 6 + <PlayQueue 7 + previousTracks={[]} 8 + nextTracks={[]} 9 + currentTrack={undefined} 10 + onPlayTrackAt={() => {}} 11 + onRemoveTrackAt={() => {}} 12 + /> 13 + ); 14 + }; 15 + 16 + export default PlayQueueWithData;
+3
webui/rockbox/src/Components/ControlBar/PlayQueue/index.tsx
··· 1 + import PlayQueueWithData from "./PlayQueueWithData"; 2 + 3 + export default PlayQueueWithData;
+50
webui/rockbox/src/Components/ControlBar/PlayQueue/mocks.tsx
··· 1 + /* eslint-disable no-loss-of-precision */ 2 + 3 + export const nextTracks = [ 4 + { 5 + id: "5ce25a3dadabc01f94130d806294a296", 6 + title: "Can I", 7 + artist: "Kodak Black", 8 + album: "Lil Big Pac", 9 + cover: 10 + "https://resources.tidal.com/images/f36fa0ef/fe50/4c54/8b97/3c9f4f72f8e8/320x320.jpg", 11 + duration: 216083.99963378906, 12 + artistId: "c38e31502394622bbdfd3d82f6911eb3", 13 + albumId: "229251493", 14 + }, 15 + { 16 + id: "c2754b1edfcb8fb4dd34d94f4e42b622", 17 + title: "Too Many Years", 18 + artist: "Kodak Black, PnB Rock", 19 + album: "Lil Big Pac", 20 + cover: 21 + "https://resources.tidal.com/images/f36fa0ef/fe50/4c54/8b97/3c9f4f72f8e8/320x320.jpg", 22 + duration: 196486.99951171875, 23 + artistId: "a01003e6ac321328ad1e208bf41206f0", 24 + albumId: "229251493", 25 + }, 26 + { 27 + id: "89ab06746b363d3295a2bc297339c5be", 28 + title: "ALL THE TIME HIGH", 29 + artist: "Juicy J featuring Kaash Paige", 30 + album: "THE HUSTLE STILL CONTINUES (Deluxe)", 31 + cover: 32 + "https://resources.tidal.com/images/aeb814c6/7767/421f/a121/46fdc0895c53/320x320.jpg", 33 + duration: 120141.99829101562, 34 + artistId: "c96c630a2fd2edd0eec175c4f65e0329", 35 + albumId: "229251493", 36 + }, 37 + ]; 38 + 39 + export const previousTracks = [ 40 + { 41 + id: "93b7e61e27b4e5cab4c6ac4d45a4dc9f", 42 + title: "Sobriety Sucks", 43 + artist: "Yelawolf", 44 + album: "Trunk Muzic Returns (Deluxe Edition)", 45 + cover: 46 + "https://resources.tidal.com/images/3b8d6b45/b3e4/4f0c/a69d/2f31a1fceadf/320x320.jpg", 47 + duration: 255001.00708007812, 48 + artistId: "052dcb0741a91da6d26a35600ec46cd9", 49 + }, 50 + ];
+109
webui/rockbox/src/Components/ControlBar/PlayQueue/styles.tsx
··· 1 + import styled from "@emotion/styled"; 2 + 3 + export const Container = styled.div` 4 + height: calc(100vh - 113px); 5 + width: 370px; 6 + `; 7 + 8 + export const Header = styled.div` 9 + display: flex; 10 + flex-direction: row; 11 + `; 12 + 13 + export const Title = styled.div` 14 + font-size: 14px; 15 + margin-left: 16px; 16 + margin-right: 16px; 17 + padding-top: 20px; 18 + margin-bottom: 20px; 19 + flex: 1; 20 + `; 21 + 22 + export const Switch = styled(Title)` 23 + color: #fe099c; 24 + flex: initial; 25 + cursor: pointer; 26 + -webkit-user-select: none; 27 + -ms-user-select: none; 28 + user-select: none; 29 + `; 30 + 31 + export const List = styled.div` 32 + height: calc(100% - 59.5px); 33 + overflow-y: scroll; 34 + `; 35 + 36 + export const ListItem = styled.div` 37 + display: flex; 38 + flex-direction: row; 39 + height: 64px; 40 + align-items: center; 41 + padding-left: 16px; 42 + padding-right: 16px; 43 + cursor: pointer; 44 + &:hover { 45 + background-color: ${({ theme }) => theme.colors.hover}; 46 + } 47 + `; 48 + 49 + export const TrackTitle = styled.div` 50 + font-size: 14px; 51 + font-family: RockfordSansMedium; 52 + text-overflow: ellipsis; 53 + overflow: hidden; 54 + white-space: nowrap; 55 + `; 56 + 57 + export const Artist = styled.div` 58 + font-size: 14px; 59 + color: ${({ theme }) => theme.colors.secondaryText}; 60 + text-overflow: ellipsis; 61 + overflow: hidden; 62 + white-space: nowrap; 63 + `; 64 + 65 + export const TrackDetails = styled.div` 66 + display: flex; 67 + min-width: 222px; 68 + flex-direction: column; 69 + flex: 1; 70 + `; 71 + 72 + export const AlbumCover = styled.img<{ current?: boolean }>` 73 + height: 48px; 74 + width: 48px; 75 + border-radius: 4px; 76 + margin-right: 18px; 77 + cursor: pointer; 78 + ${({ current }) => `opacity: ${current ? 0.4 : 1};`} 79 + `; 80 + 81 + export const AlbumCoverAlt = styled.div<{ current?: boolean }>` 82 + height: 48px; 83 + width: 48px; 84 + border-radius: 4px; 85 + cursor: pointer; 86 + background-color: ${(props) => props.theme.colors.cover}; 87 + display: flex; 88 + justify-content: center; 89 + align-items: center; 90 + margin-right: 18px; 91 + ${({ current }) => `opacity: ${current ? 0 : 1};`} 92 + `; 93 + 94 + export const Remove = styled.button` 95 + background-color: transparent; 96 + cursor: pointer; 97 + border: none; 98 + `; 99 + 100 + export const Placeholder = styled.div` 101 + display: flex; 102 + align-items: center; 103 + justify-content: center; 104 + height: 100%; 105 + text-align: center; 106 + padding-left: 20px; 107 + padding-right: 20px; 108 + font-size: 14px; 109 + `;
+6
webui/rockbox/src/Components/Sidebar/Sidebar.tsx
··· 4 4 import { HardDrive } from "@styled-icons/feather"; 5 5 import Artist from "../Icons/Artist"; 6 6 import Track from "../Icons/Track"; 7 + import RockboxLogo from "../../Assets/rockbox-icon.svg"; 7 8 8 9 export type SidebarProps = { 9 10 active: string; ··· 12 13 const Sidebar: FC<SidebarProps> = ({ active }) => { 13 14 return ( 14 15 <SidebarContainer> 16 + <img 17 + src={RockboxLogo} 18 + alt="Rockbox" 19 + style={{ width: 40, marginBottom: 20, marginLeft: 12 }} 20 + /> 15 21 <MenuItem 16 22 color={active === "albums" ? "#fe099c" : "initial"} 17 23 to="/albums"
+2 -1
webui/rockbox/src/Types/track.ts
··· 4 4 title: string; 5 5 artist: string; 6 6 album?: string; 7 - time: string; 7 + time?: string; 8 + duration?: number; 8 9 albumArt?: string; 9 10 cover?: string; 10 11 albumId?: string;