Skip to content

Commit

Permalink
Merge pull request #43 from tsirysndr/feat/seek
Browse files Browse the repository at this point in the history
webui: make playback progressbar clickable (seek)
  • Loading branch information
tsirysndr authored Oct 24, 2024
2 parents 9b97469 + d1bb54e commit 91b9d18
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 5 deletions.
1 change: 1 addition & 0 deletions crates/graphql/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub async fn start(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>) -> Result<(), Err
.route("/artists", web::get().to(index_spa))
.route("/albums", web::get().to(index_spa))
.route("/files", web::get().to(index_spa))
.route("/likes", web::get().to(index_spa))
.route("/artists/{_:.*}", web::get().to(index_spa))
.route("/albums/{_:.*}", web::get().to(index_spa))
.route("/playlists/{_:.*}", web::get().to(index_spa))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const Default: Story = {
onRepeat: fn(),
onLike: fn(),
onUnlike: fn(),
onSeek: fn(),
liked: false,
},
};
Expand All @@ -61,5 +62,6 @@ export const Playing: Story = {
onRepeat: fn(),
onLike: fn(),
onUnlike: fn(),
onSeek: fn(),
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("ControlBar", () => {
onRepeat={vi.fn()}
onLike={vi.fn()}
onUnlike={vi.fn()}
onSeek={vi.fn()}
/>
</MockedProvider>
</MemoryRouter>
Expand Down
2 changes: 2 additions & 0 deletions webui/rockbox/src/Components/ControlBar/ControlBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type ControlBarProps = {
liked?: boolean;
onLike: (trackId: string) => void;
onUnlike: (trackId: string) => void;
onSeek: (time: number) => void;
};

const ControlBar: FC<ControlBarProps> = (props) => {
Expand Down Expand Up @@ -57,6 +58,7 @@ const ControlBar: FC<ControlBarProps> = (props) => {
liked={props.liked}
onLike={props.onLike}
onUnlike={props.onUnlike}
onSeek={props.onSeek}
/>
<RightMenu />
</Container>
Expand Down
16 changes: 16 additions & 0 deletions webui/rockbox/src/Components/ControlBar/ControlBarWithData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
usePlaybackStatusSubscription,
usePreviousMutation,
useResumeMutation,
useSeekMutation,
useUnlikeTrackMutation,
} from "../../Hooks/GraphQL";
import { CurrentTrack } from "../../Types/track";
Expand Down Expand Up @@ -39,6 +40,7 @@ const ControlBarWithData: FC = () => {
const { resumePlaylistTrack } = useResumePlaylist();
const [likeTrack] = useLikeTrackMutation();
const [unlikeTrack] = useUnlikeTrackMutation();
const [seek] = useSeekMutation();

const [likes, setLikes] = useRecoilState(likesState);
const { data: likedTracksData, loading: likedTracksLoading } =
Expand Down Expand Up @@ -250,6 +252,19 @@ const ControlBarWithData: FC = () => {
}
};

const onSeek = (elapsed: number) => {
if (!nowPlaying) {
return;
}

seek({
variables: {
elapsed,
offset: 0,
},
});
};

return (
<ControlBar
nowPlaying={nowPlaying}
Expand All @@ -262,6 +277,7 @@ const ControlBarWithData: FC = () => {
liked={likes[nowPlaying?.id || ""]}
onLike={onLike}
onUnlike={onUnlike}
onSeek={onSeek}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from "react";
import { FC, useRef } from "react";
import { ProgressBar } from "baseui/progress-bar";
import {
Actions,
Expand Down Expand Up @@ -27,16 +27,32 @@ export type CurrentTrackProps = {
liked?: boolean;
onLike: (trackId: string) => void;
onUnlike: (trackId: string) => void;
onSeek: (time: number) => void;
};

const CurrentTrack: FC<CurrentTrackProps> = ({
nowPlaying,
liked,
onLike,
onUnlike,
onSeek,
}) => {
const progressbarRef = useRef<HTMLDivElement>(null);
const { formatTime } = useTimeFormat();
const album = `${nowPlaying?.artist} - ${nowPlaying?.album}`;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleClick = (e: any) => {
if (progressbarRef.current) {
const rect = progressbarRef.current.getBoundingClientRect();
const x = e.clientX - rect.left < 0 ? 0 : e.clientX - rect.left;
const width = rect.width;
const percentage = (x / width) * 100;
const time = (percentage / 100) * nowPlaying!.duration;
onSeek(Math.floor(time));
}
};

return (
<Container>
{!nowPlaying?.cover && (
Expand Down Expand Up @@ -97,7 +113,11 @@ const CurrentTrack: FC<CurrentTrackProps> = ({
</ArtistAlbum>
<Time>{formatTime(nowPlaying.duration)}</Time>
</div>
<ProgressbarContainer>
<ProgressbarContainer
ref={progressbarRef}
onClick={handleClick}
active={nowPlaying!.duration > 0}
>
<ProgressBar
value={
nowPlaying!.duration > 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { Link } from "react-router-dom";

Expand Down Expand Up @@ -30,10 +31,15 @@ export const AlbumCover = styled.img`
border-bottom-left-radius: 4px;
`;

export const ProgressbarContainer = styled.div`
export const ProgressbarContainer = styled.div<{ active?: boolean }>`
width: 100%;
position: absolute;
bottom: -12px;
${({ active }) =>
active &&
css`
cursor: pointer;
`}
`;

export const TrackInfo = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ exports[`ControlBar > should render 1`] = `
</div>
</div>
<div
class="css-1n7uud4"
class="css-xaw6oh"
>
<div
aria-label="48% Loaded"
Expand Down
6 changes: 6 additions & 0 deletions webui/rockbox/src/GraphQL/Playback/Mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,9 @@ export const PLAY_ALL_TRACKS = gql`
playAllTracks(shuffle: $shuffle, position: $position)
}
`;

export const SEEK = gql`
mutation Seek($elapsed: Int!, $offset: Int!) {
play(elapsed: $elapsed, offset: $offset)
}
`;
40 changes: 40 additions & 0 deletions webui/rockbox/src/Hooks/GraphQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,14 @@ export type PlayAllTracksMutationVariables = Exact<{

export type PlayAllTracksMutation = { __typename?: 'Mutation', playAllTracks: number };

export type SeekMutationVariables = Exact<{
elapsed: Scalars['Int']['input'];
offset: Scalars['Int']['input'];
}>;


export type SeekMutation = { __typename?: 'Mutation', play: number };

export type GetCurrentTrackQueryVariables = Exact<{ [key: string]: never; }>;


Expand Down Expand Up @@ -1837,6 +1845,38 @@ export function usePlayAllTracksMutation(baseOptions?: Apollo.MutationHookOption
export type PlayAllTracksMutationHookResult = ReturnType<typeof usePlayAllTracksMutation>;
export type PlayAllTracksMutationResult = Apollo.MutationResult<PlayAllTracksMutation>;
export type PlayAllTracksMutationOptions = Apollo.BaseMutationOptions<PlayAllTracksMutation, PlayAllTracksMutationVariables>;
export const SeekDocument = gql`
mutation Seek($elapsed: Int!, $offset: Int!) {
play(elapsed: $elapsed, offset: $offset)
}
`;
export type SeekMutationFn = Apollo.MutationFunction<SeekMutation, SeekMutationVariables>;

/**
* __useSeekMutation__
*
* To run a mutation, you first call `useSeekMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSeekMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [seekMutation, { data, loading, error }] = useSeekMutation({
* variables: {
* elapsed: // value for 'elapsed'
* offset: // value for 'offset'
* },
* });
*/
export function useSeekMutation(baseOptions?: Apollo.MutationHookOptions<SeekMutation, SeekMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SeekMutation, SeekMutationVariables>(SeekDocument, options);
}
export type SeekMutationHookResult = ReturnType<typeof useSeekMutation>;
export type SeekMutationResult = Apollo.MutationResult<SeekMutation>;
export type SeekMutationOptions = Apollo.BaseMutationOptions<SeekMutation, SeekMutationVariables>;
export const GetCurrentTrackDocument = gql`
query GetCurrentTrack {
currentTrack {
Expand Down
2 changes: 1 addition & 1 deletion webui/rockbox/tsconfig.app.tsbuildinfo

Large diffs are not rendered by default.

0 comments on commit 91b9d18

Please sign in to comment.