Skip to content

Commit

Permalink
Merge pull request #305 from SotSF/playlist-page-updates
Browse files Browse the repository at this point in the history
Playlist page updates
  • Loading branch information
brollin authored Nov 6, 2024
2 parents 7226aab + e4b2aff commit e4120e6
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 104 deletions.
90 changes: 47 additions & 43 deletions src/components/ExperiencesTable/ExperiencesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import { Experience } from "@/src/types/Experience";
export const ExperiencesTable = observer(function ExperiencesTable({
experiencesAndUsers,
onLoadExperience,
omitIds,
selectable,
selectedExperiences,
setSelectedExperiences,
}: {
experiencesAndUsers: { user: { username: string }; experience: Experience }[];
onLoadExperience: (experience: Experience) => void;
omitIds?: number[];
// TODO: implement selectable experiences in the table
selectable?: boolean;
selectedExperiences?: string[];
Expand All @@ -44,51 +46,53 @@ export const ExperiencesTable = observer(function ExperiencesTable({
</Tr>
</Thead>
<Tbody>
{experiencesAndUsers.map(({ user, experience }) => (
<Tr key={experience.id}>
<Td>
<Button
ml={-3}
size="md"
height={8}
variant="solid"
onClick={() => onLoadExperience(experience)}
>
{experience.name}
</Button>
</Td>
<Td>{user.username}</Td>
<Td>
{experience.song?.artist} - {experience.song?.name}
</Td>
<Td>
{user.username === username && (
<IconButton
variant="ghost"
size="sm"
aria-label="Delete experience"
title="Delete experience"
icon={<FaTrashAlt size={14} />}
disabled={true}
onClick={action(() => {
if (
!confirm(
"Are you sure you want to delete this experience? This will permanently cast the experience into the fires of Mount Doom. (jk doesn't work yet)"
{experiencesAndUsers
.filter(({ experience }) => !omitIds?.includes(experience.id!))
.map(({ user, experience }) => (
<Tr key={experience.id}>
<Td>
<Button
ml={-3}
size="md"
height={8}
variant="solid"
onClick={() => onLoadExperience(experience)}
>
{experience.name}
</Button>
</Td>
<Td>{user.username}</Td>
<Td>
{experience.song?.artist} - {experience.song?.name}
</Td>
<Td>
{user.username === username && (
<IconButton
variant="ghost"
size="sm"
aria-label="Delete experience"
title="Delete experience"
icon={<FaTrashAlt size={14} />}
disabled={true}
onClick={action(() => {
if (
!confirm(
"Are you sure you want to delete this experience? This will permanently cast the experience into the fires of Mount Doom. (jk doesn't work yet)"
)
)
)
return;
return;

// TODO:
// trpc.experience.deleteExperience.mutate({
// name: experience.name,
// usingLocalData,
// });
})}
/>
)}
</Td>
</Tr>
))}
// TODO:
// trpc.experience.deleteExperience.mutate({
// name: experience.name,
// usingLocalData,
// });
})}
/>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
Expand Down
1 change: 1 addition & 0 deletions src/components/PlaylistEditor/AddExperienceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const AddExperienceModal = observer(function AddExperienceModal({
});
onClose();
})}
omitIds={playlist.orderedExperienceIds}
/>
)}
</ModalBody>
Expand Down
9 changes: 8 additions & 1 deletion src/components/PlaylistEditor/PlaylistEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useStore } from "@/src/types/StoreContext";
import { observer } from "mobx-react-lite";
import { PlaylistItem } from "@/src/components/PlaylistEditor/PlaylistItem";
import { MdOutlinePlaylistAdd } from "react-icons/md";
import { action } from "mobx";
import { action, runInAction } from "mobx";
import { AddExperienceModal } from "@/src/components/PlaylistEditor/AddExperienceModal";
import { BiShuffle } from "react-icons/bi";
import { ImLoop } from "react-icons/im";
Expand Down Expand Up @@ -46,6 +46,13 @@ export const PlaylistEditor = observer(function PlaylistEditor() {
}
);

useEffect(() => {
runInAction(() => {
// this is very hacky and I hate it but it works
if (data?.playlist) playlistStore.selectedPlaylist = data.playlist;
});
}, [data?.playlist]);

useEffect(() => {
if (!data?.experiencesAndUsers.length || store.experienceName) return;
// once experiences are fetched, load the first experience in the playlist
Expand Down
2 changes: 1 addition & 1 deletion src/components/PlaylistEditor/PlaylistLibrary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const PlaylistLibrary = observer(function PlaylistLibrary() {
username,
usingLocalData,
name: "New Playlist",
description: "New Playlist",
description: "",
orderedExperienceIds: [],
});
runInAction(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Timeline/TimerAndWaveform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export const TimerAndWaveform = observer(function TimerAndWaveform() {
zIndex={18}
bgColor="gray.500"
>
<TimerControls />
{!embeddedViewer && <TimerReadout />}
<TimerControls />
</VStack>

<WavesurferWaveform />
Expand Down
109 changes: 76 additions & 33 deletions src/components/Timeline/TimerControls.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,88 @@
import { observer } from "mobx-react-lite";
import { HStack, IconButton } from "@chakra-ui/react";
import { ButtonGroup, HStack, IconButton, VStack } from "@chakra-ui/react";
import { FaPlay, FaPause, FaStepForward, FaStepBackward } from "react-icons/fa";
import { MAX_TIME } from "@/src/utils/time";
import { useStore } from "@/src/types/StoreContext";
import { action } from "mobx";
import { MdForward10, MdReplay10 } from "react-icons/md";

export const TimerControls = observer(function TimerControls() {
const store = useStore();
const { audioStore } = store;
const { audioStore, playlistStore } = store;
const playing = audioStore.audioState === "playing";

return (
<HStack width="100%" justify="center" py={2} spacing={1} overflowX="clip">
<IconButton
aria-label="Backward"
title="Backward"
height={6}
bgColor="gray.600"
icon={<FaStepBackward size={10} />}
onClick={action(() => audioStore.setTimeWithCursor(0))}
/>
<IconButton
aria-label="Play"
title="Play"
color={playing ? "orange" : "green"}
height={6}
bgColor="gray.600"
icon={playing ? <FaPause size={10} /> : <FaPlay size={10} />}
onClick={action(store.togglePlaying)}
/>
<IconButton
aria-label="Forward"
title="Forward"
height={6}
bgColor="gray.600"
icon={<FaStepForward size={10} />}
onClick={action(() => {
audioStore.setTimeWithCursor(MAX_TIME);
store.pause();
})}
/>
</HStack>
<VStack pb={1} spacing={0}>
<HStack width="100%" justify="center" overflowX="clip">
<ButtonGroup isAttached>
<IconButton
borderStyle="solid"
borderWidth={1}
aria-label={
store.context === "playlistEditor"
? "Go to previous"
: "Go to start"
}
title={
store.context === "playlistEditor"
? "Go to previous"
: "Go to start"
}
height={6}
bgColor="gray.600"
icon={<FaStepBackward size={12} />}
onClick={() => playlistStore.playPreviousExperience()}
/>
<IconButton
borderStyle="solid"
borderWidth={1}
aria-label="Play"
title="Play"
color={playing ? "orange" : "green"}
height={6}
bgColor="gray.600"
icon={playing ? <FaPause size={12} /> : <FaPlay size={12} />}
onClick={action(store.togglePlaying)}
/>
<IconButton
borderStyle="solid"
borderWidth={1}
aria-label={
store.context === "playlistEditor" ? "Go to next" : "Go to end"
}
title={
store.context === "playlistEditor" ? "Go to next" : "Go to end"
}
height={6}
bgColor="gray.600"
icon={<FaStepForward size={12} />}
onClick={() => playlistStore.playNextExperience()}
/>
</ButtonGroup>
</HStack>
<HStack width="100%" justify="center" overflowX="clip">
<ButtonGroup isAttached>
<IconButton
borderStyle="solid"
borderWidth={1}
aria-label="Go back 10 seconds "
title="Go back 10 seconds "
height={6}
bgColor="gray.600"
icon={<MdReplay10 size={17} />}
onClick={action(() => audioStore.skip(-10))}
/>
<IconButton
borderStyle="solid"
borderWidth={1}
aria-label="Go forward 10 seconds"
title="Go forward 10 seconds"
height={6}
bgColor="gray.600"
icon={<MdForward10 size={17} />}
onClick={action(() => audioStore.skip(10))}
/>
</ButtonGroup>
</HStack>
</VStack>
);
});
2 changes: 1 addition & 1 deletion src/components/Timeline/TimerReadout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const TimerReadout = observer(function TimerReadout() {
const { audioStore } = useStore();

return (
<VStack height={10} justify="center">
<VStack justify="center">
<Text
color="black"
fontSize={18}
Expand Down
3 changes: 2 additions & 1 deletion src/components/Wavesurfer/WavesurferWaveform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ const DEFAULT_WAVESURFER_OPTIONS: Partial<WaveSurferOptions> = {
autoScroll: false,
autoCenter: false,
interact: true,
dragToSeek: { debounceTime: 50 },
};

const DEFAULT_TIMELINE_OPTIONS: TimelinePluginOptions = {
insertPosition: "beforebegin",
timeInterval: 0.25,
style: {
fontSize: "14px",
color: "#000000",
Expand Down Expand Up @@ -106,6 +106,7 @@ export const WavesurferWaveform = observer(function WavesurferWaveform() {
height: uiStore.canTimelineZoom ? 60 : 80,
primaryLabelInterval: uiStore.canTimelineZoom ? 5 : 30,
secondaryLabelInterval: uiStore.canTimelineZoom ? 1 : 0,
timeInterval: uiStore.canTimelineZoom ? 0.25 : 5,
};

// initialize wavesurfer
Expand Down
13 changes: 9 additions & 4 deletions src/types/AudioStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,22 @@ export class AudioStore {

setTimeWithCursor = (time: number) => {
if (!this.wavesurfer) return;
this.lastCursorPosition = time;
this.globalTime = time;

const validTime = Math.max(0, time);
this.lastCursorPosition = validTime;
this.globalTime = validTime;

const duration = this.wavesurfer.getDuration();
if (this.wavesurfer.getCurrentTime() === time || duration === 0) return;
this.wavesurfer.seekTo(time / duration);
if (this.wavesurfer.getCurrentTime() === validTime || duration === 0)
return;
this.wavesurfer.seekTo(validTime / duration);
};

skipForward = () => this.setTimeWithCursor(this.globalTime + 0.01);
skipBackward = () => this.setTimeWithCursor(this.globalTime - 0.01);

skip = (delta: number) => this.setTimeWithCursor(this.globalTime + delta);

// called by wavesurfer, which defaults to 60fps
onTick = (time: number) => {
this.globalTime = time;
Expand Down
Loading

0 comments on commit e4120e6

Please sign in to comment.