Skip to content

Commit

Permalink
Merge pull request #309 from SotSF/playlist-library-improvements
Browse files Browse the repository at this point in the history
Playlist library improvements
  • Loading branch information
brollin authored Nov 7, 2024
2 parents e4120e6 + 9966661 commit 30c0a52
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 99 deletions.
52 changes: 39 additions & 13 deletions src/components/ExperiencesTable/ExperiencesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { observer } from "mobx-react-lite";
import {
Button,
Checkbox,
IconButton,
Table,
TableContainer,
Expand All @@ -15,30 +16,40 @@ import { action } from "mobx";
import { FaTrashAlt } from "react-icons/fa";
import { Experience } from "@/src/types/Experience";

type ExperiencesTableProps = {
experiencesAndUsers: { user: { username: string }; experience: Experience }[];
omitIds?: number[];
onClickExperience?: (experience: Experience) => void;
selectable?: boolean;
selectedExperienceIds?: number[];
setSelectedExperienceIds?: (experiences: number[]) => void;
};

export const ExperiencesTable = observer(function ExperiencesTable({
experiencesAndUsers,
onLoadExperience,
onClickExperience,
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[];
setSelectedExperiences?: (experiences: string[]) => void;
}) {
selectedExperienceIds,
setSelectedExperienceIds,
}: ExperiencesTableProps) {
const store = useStore();
const { username } = store;

const toggleExperienceIdSelection = (id: number) => {
setSelectedExperienceIds?.(
selectedExperienceIds?.includes(id)
? selectedExperienceIds.filter((selectedId) => selectedId !== id)
: [...(selectedExperienceIds ?? []), id]
);
};

return (
<TableContainer>
<Table size="sm" colorScheme="blue">
<Thead>
<Tr>
{selectable && <Th />}
<Th>Name</Th>
<Th>Author</Th>
<Th>Song</Th>
Expand All @@ -50,13 +61,28 @@ export const ExperiencesTable = observer(function ExperiencesTable({
.filter(({ experience }) => !omitIds?.includes(experience.id!))
.map(({ user, experience }) => (
<Tr key={experience.id}>
{selectable && (
<Td>
<Checkbox
isChecked={selectedExperienceIds?.includes(
experience.id!
)}
onChange={() =>
toggleExperienceIdSelection(experience.id!)
}
/>
</Td>
)}
<Td>
<Button
ml={-3}
size="md"
height={8}
variant="solid"
onClick={() => onLoadExperience(experience)}
onClick={() =>
onClickExperience?.(experience) ??
toggleExperienceIdSelection(experience.id!)
}
>
{experience.name}
</Button>
Expand Down
4 changes: 3 additions & 1 deletion src/components/LoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ export const LoginButton = observer(function LoginButton() {
/>
<Button
isDisabled={
!newUser || users?.some((u) => u.username === newUser)
!newUser ||
users?.some((u) => u.username === newUser) ||
newUser === "conjurer" // reserved username
}
onClick={action(async () => {
await createUser.mutateAsync({
Expand Down
2 changes: 1 addition & 1 deletion src/components/Menu/OpenExperienceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const OpenExperienceModal = observer(function OpenExperienceModal() {
{!isPending && (
<ExperiencesTable
experiencesAndUsers={experiencesAndUsers}
onLoadExperience={action(async (experience) => {
onClickExperience={action(async (experience) => {
setIsLoadingNewExperience(true);
await experienceStore.load(experience.name);
setIsLoadingNewExperience(false);
Expand Down
42 changes: 29 additions & 13 deletions src/components/PlaylistEditor/AddExperienceModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { observer } from "mobx-react-lite";
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Spinner,
Expand All @@ -27,6 +29,9 @@ export const AddExperienceModal = observer(function AddExperienceModal({
const { username, uiStore } = store;

const [viewingAllExperiences, setViewingAllExperiences] = useState(false);
const [selectedExperienceIds, setSelectedExperienceIds] = useState<number[]>(
[]
);

const { isPending, isError, experiencesAndUsers } = useExperiencesAndUsers({
username: viewingAllExperiences ? undefined : username,
Expand All @@ -37,9 +42,10 @@ export const AddExperienceModal = observer(function AddExperienceModal({

if (isError) return null;

const onClose = action(
() => (uiStore.showingPlaylistAddExperienceModal = false)
);
const onClose = action(() => {
setSelectedExperienceIds([]);
uiStore.showingPlaylistAddExperienceModal = false;
});

return (
<Modal
Expand Down Expand Up @@ -69,20 +75,30 @@ export const AddExperienceModal = observer(function AddExperienceModal({
{!isPending && (
<ExperiencesTable
experiencesAndUsers={experiencesAndUsers}
onLoadExperience={action((experience) => {
savePlaylist({
...playlist,
orderedExperienceIds: [
...playlist.orderedExperienceIds,
experience.id!,
],
});
onClose();
})}
omitIds={playlist.orderedExperienceIds}
selectable
selectedExperienceIds={selectedExperienceIds}
setSelectedExperienceIds={setSelectedExperienceIds}
/>
)}
</ModalBody>
<ModalFooter>
<Button
isDisabled={!selectedExperienceIds.length || isPending}
onClick={action(() => {
savePlaylist({
...playlist,
orderedExperienceIds: [
...playlist.orderedExperienceIds,
...selectedExperienceIds,
],
});
onClose();
})}
>
{isPending ? <Spinner /> : "Add"}
</Button>
</ModalFooter>
<ModalCloseButton />
</ModalContent>
</Modal>
Expand Down
3 changes: 3 additions & 0 deletions src/components/PlaylistEditor/PlaylistEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ export const PlaylistEditor = observer(function PlaylistEditor() {
<Td>
<Text>No experiences added yet!</Text>
</Td>
<Td>-</Td>
<Td>-</Td>
<Td></Td>
</Tr>
</>
) : (
Expand Down
61 changes: 28 additions & 33 deletions src/components/PlaylistEditor/PlaylistItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,40 +190,35 @@ export const PlaylistItem = observer(function PlaylistItem({

<Td px={0}>
<HStack height={10} alignItems="center" spacing={0}>
{store.username === user.username && (
<>
<IconButton
variant="link"
aria-label="Edit experience"
title="Edit experience"
height={6}
_hover={{ color: "orange.500" }}
icon={<FaPencilAlt size={10} />}
onClick={action(() =>
router.push(`/experience/${experience.name}`)
)}
/>
</>
)}
<IconButton
variant="link"
aria-label="Edit experience"
title="Edit experience"
height={6}
_hover={{ color: "orange.500" }}
icon={<FaPencilAlt size={10} />}
onClick={action(() => {
store.role = "experience creator";
router.push(`/experience/${experience.name}`);
})}
/>
{editable && (
<>
<IconButton
variant="link"
aria-label="Remove experience from playlist"
title="Remove experience from playlist"
height={6}
_hover={{ color: "red.500" }}
icon={<ImCross size={10} />}
onClick={() => {
savePlaylist({
...playlist,
orderedExperienceIds: playlist.orderedExperienceIds.filter(
(id) => id !== experience.id
),
});
}}
/>
</>
<IconButton
variant="link"
aria-label="Remove experience from playlist"
title="Remove experience from playlist"
height={6}
_hover={{ color: "red.500" }}
icon={<ImCross size={10} />}
onClick={() => {
savePlaylist({
...playlist,
orderedExperienceIds: playlist.orderedExperienceIds.filter(
(id) => id !== experience.id
),
});
}}
/>
)}
</HStack>
</Td>
Expand Down
41 changes: 32 additions & 9 deletions src/components/PlaylistEditor/PlaylistLibrary.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { Button, HStack, Text, VStack } from "@chakra-ui/react";
import {
Button,
HStack,
Spinner,
Switch,
Text,
VStack,
} from "@chakra-ui/react";
import { useStore } from "@/src/types/StoreContext";
import { observer } from "mobx-react-lite";
import { FaPlus } from "react-icons/fa";
import { runInAction } from "mobx";
import { trpc } from "@/src/utils/trpc";
import { SelectablePlaylist } from "@/src/components/PlaylistEditor/SelectablePlaylist";
import { useEffect } from "react";
import { useEffect, useState } from "react";

export const PlaylistLibrary = observer(function PlaylistLibrary() {
const store = useStore();
const { username, usingLocalData, playlistStore } = store;

const [viewingAllPlaylists, setViewingAllPlaylists] = useState(false);

const isEditable = !!store.username;

const utils = trpc.useUtils();
Expand All @@ -20,10 +29,11 @@ export const PlaylistLibrary = observer(function PlaylistLibrary() {
isPending,
isError,
data: playlists,
} = trpc.playlist.listPlaylistsForUser.useQuery(
} = trpc.playlist.listPlaylists.useQuery(
{
usingLocalData,
username,
allPlaylists: viewingAllPlaylists,
},
{ staleTime: 1000 * 60 * 10 }
);
Expand All @@ -36,7 +46,7 @@ export const PlaylistLibrary = observer(function PlaylistLibrary() {
});
}, [playlists]);

if (isPending || isError) return null;
if (isError) return null;

return (
<VStack
Expand Down Expand Up @@ -70,20 +80,33 @@ export const PlaylistLibrary = observer(function PlaylistLibrary() {
runInAction(() => {
playlistStore.selectedPlaylist = newPlaylist;
});
utils.playlist.listPlaylistsForUser.invalidate();
utils.playlist.listPlaylists.invalidate();
}}
>
Playlist
</Button>
)}
</HStack>

<VStack width="100%" height={1}></VStack>
<VStack width="100%" height={2}></VStack>

<Switch
my={2}
mx={6}
size="sm"
isChecked={viewingAllPlaylists}
onChange={(e) => setViewingAllPlaylists(e.target.checked)}
>
All playlists
</Switch>
<VStack width="100%" spacing={0}>
{playlists.map((playlist) => (
<SelectablePlaylist key={playlist.id} playlist={playlist} />
))}
{isPending ? (
<Spinner />
) : (
playlists.map((playlist) => (
<SelectablePlaylist key={playlist.id} playlist={playlist} />
))
)}
</VStack>
</VStack>
);
Expand Down
21 changes: 16 additions & 5 deletions src/components/PlaylistEditor/SelectablePlaylist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FaTrashAlt } from "react-icons/fa";
import { action, runInAction } from "mobx";
import { trpc } from "@/src/utils/trpc";
import { Playlist } from "@/src/types/Playlist";
import { HiSparkles } from "react-icons/hi";

export const SelectablePlaylist = observer(function SelectablePlaylist({
playlist,
Expand Down Expand Up @@ -36,10 +37,20 @@ export const SelectablePlaylist = observer(function SelectablePlaylist({
transition: "background-color 0.2s",
}}
>
<VStack width="100%" height="100%" justify="start" alignItems="start">
<Text fontSize="md" fontWeight="bold">
{playlist.name}
</Text>
<VStack
width="100%"
height="100%"
justify="start"
alignItems="start"
spacing={0}
>
<HStack spacing={1} justify="start">
{playlist.user.username === "conjurer" && <HiSparkles />}
<Text fontSize="md" fontWeight="bold">
{playlist.name}
</Text>
</HStack>

<HStack spacing={4} justify="space-between">
<Text fontSize="sm" color="gray.400">
{playlist.user.username}{playlist.orderedExperienceIds.length}{" "}
Expand Down Expand Up @@ -67,7 +78,7 @@ export const SelectablePlaylist = observer(function SelectablePlaylist({
usingLocalData,
id: playlist.id,
});
utils.playlist.listPlaylistsForUser.invalidate();
utils.playlist.listPlaylists.invalidate();

if (playlistStore.selectedPlaylist?.id === playlist.id) {
runInAction(() => {
Expand Down
Loading

0 comments on commit 30c0a52

Please sign in to comment.