Skip to content

Commit

Permalink
Merge branch 'next' of github:HolodexNet/Holodex into next
Browse files Browse the repository at this point in the history
  • Loading branch information
P-man2976 committed Nov 3, 2023
2 parents 9d54a50 + 33bddaf commit 4e3d863
Show file tree
Hide file tree
Showing 19 changed files with 846 additions and 14 deletions.
39 changes: 39 additions & 0 deletions packages/react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@hookform/resolvers": "^3.3.2",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-context-menu": "^2.1.5",
Expand Down Expand Up @@ -83,6 +84,7 @@
"@types/node": "^20.8.7",
"@types/react": "^18.2.31",
"@types/react-dom": "^18.2.14",
"@types/react-grid-layout": "^1.3.4",
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
"@unocss/eslint-config": "^0.56.5",
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/layout/Frame.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ $responsive-breakpoint: 768px;

> main {
grid-area: content;
overflow-y: auto;
// disabling for now so we can use position sticky
// overflow-y: auto;
}

> footer {
Expand Down
56 changes: 56 additions & 0 deletions packages/react/src/components/playlist/DeletePlaylistDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/shadcn/ui/alert-dialog";
import React from "react";
import { useTranslation } from "react-i18next";
import { usePlaylistDeleteMutation } from "@/services/playlist.service";

interface Props {
triggerElement: React.ReactNode;
playlistId: number;
playlistName: string;
}

export default function DeletePlaylistDialog({
triggerElement,
playlistId,
playlistName,
}: Props) {
const { t } = useTranslation();
const deleteMutation = usePlaylistDeleteMutation();

return (
<AlertDialog>
<AlertDialogTrigger asChild>{triggerElement}</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("component.playlist.menu.delete-playlist")}
</AlertDialogTitle>
<AlertDialogDescription>
{/* todo: intl this */}
Are you sure you want to delete playlist <b>{playlistName}</b>?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{t("views.library.deleteConfirmationCancel")}
</AlertDialogCancel>
<AlertDialogAction
onClick={() => deleteMutation.mutate({ playlistId })}
>
{t("views.library.deleteConfirmationOK")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
232 changes: 232 additions & 0 deletions packages/react/src/components/playlist/IndividualPlaylist.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import {
usePlaylist,
usePlaylistSaveMutation,
} from "@/services/playlist.service";
import { VideoCard } from "@/components/video/VideoCard";
import { useTranslation } from "react-i18next";
import { TypographyH3, TypographyP } from "@/shadcn/ui/typography";
import { Button } from "@/shadcn/ui/button";
import { Separator } from "@/shadcn/ui/separator";
import { useAtomValue } from "jotai";
import { userAtom } from "@/store/auth";
import { useState } from "react";
import { useToast } from "@/shadcn/ui/use-toast";
import { Input } from "@/shadcn/ui/input";
import DeletePlaylistDialog from "@/components/playlist/DeletePlaylistDialog";

interface Props {
playlistId?: string;
}

export default function IndividualPlaylist({ playlistId }: Props) {
const { data: playlist, status } = usePlaylist(parseInt(playlistId!));

const { t } = useTranslation();

const { toast } = useToast();

const user = useAtomValue(userAtom);

const saveMutation = usePlaylistSaveMutation({
onSuccess: () => {
setEditedPlaylist(null);
toast({
title: "Playlist saved.",
});
},
});

const [editedPlaylist, setEditedPlaylist] = useState<Playlist | null>(null);
const [renaming, setRenaming] = useState(false);

if (status === "pending") {
return <div>Loading...</div>;
}

if (status === "error") {
return <div>{t("component.apiError.title")}</div>;
}

const shiftVideo = (origIdx: number, shift: number) =>
setEditedPlaylist((prevState) => {
const newVideoArr = prevState
? [...prevState.videos]
: [...playlist.videos];

const video = newVideoArr.splice(origIdx, 1)[0];

newVideoArr.splice(origIdx + shift, 0, video);

return prevState
? {
...prevState,
videos: newVideoArr,
}
: {
...playlist,
videos: newVideoArr,
};
});

const deleteVideo = (origIdx: number) =>
setEditedPlaylist((prevState) => {
const newVideoArr = prevState
? [...prevState.videos]
: [...playlist.videos];

newVideoArr.splice(origIdx, 1);

return prevState
? {
...prevState,
videos: newVideoArr,
}
: {
...playlist,
videos: newVideoArr,
};
});

const rename = (newName: string) =>
setEditedPlaylist((prevState) => {
return prevState
? {
...prevState,
name: newName,
}
: {
...playlist,
name: newName,
};
});

const playlistToRender = editedPlaylist ? editedPlaylist : playlist;
const userOwnsPlaylist = playlist.user_id === user?.id;

return (
<div className="container">
<div className="sticky top-0 z-10 bg-mauve-2">
<div className="flex items-center">
<span className="i-solar:playlist-bold hidden text-9xl !text-base-7 md:block" />
<div className="ml-6">
<div className="flex gap-3">
{renaming ? (
<Input
value={playlistToRender.name}
onChange={(e) => rename(e.target.value)}
></Input>
) : (
<TypographyH3>{playlistToRender.name}</TypographyH3>
)}
{userOwnsPlaylist ? (
<Button
size="icon"
variant="ghost"
onClick={() => setRenaming((prev) => !prev)}
>
<span className="i-heroicons:pencil-solid" />
</Button>
) : null}
</div>
<TypographyP className="!mt-1">
{playlist.videos.length} Videos
</TypographyP>
<div className="mt-4 flex gap-3">
<Button size="icon" variant="secondary">
<span className="i-heroicons:play-solid" />
</Button>
{userOwnsPlaylist ? (
<>
<DeletePlaylistDialog
triggerElement={
<Button size="icon" variant="ghost">
<span className="i-heroicons:trash-solid" />
</Button>
}
playlistId={playlist.id}
playlistName={playlist.name}
/>
<Button
disabled={editedPlaylist === null}
onClick={() =>
saveMutation.mutate({
id: editedPlaylist!.id,
user_id: editedPlaylist!.user_id,
name: editedPlaylist!.name,
video_ids: editedPlaylist!.videos.map((v) => v.id),
})
}
>
{/* todo: intl this */}
<span className="i-lucide:save" /> Save Changes
</Button>
{editedPlaylist !== null ? (
<Button
size="icon"
variant="ghost"
onClick={() => setEditedPlaylist(null)}
>
<span className="i-heroicons:arrow-path-solid" />
</Button>
) : null}
</>
) : null}
</div>
</div>
</div>
<Separator className="mb-7 mt-3" />
</div>
{playlistToRender.videos.map((video, index) => {
return (
<div className="flex gap-2" key={video.id}>
{userOwnsPlaylist ? (
<div className="flex flex-col justify-between py-2">
<Button
size="icon"
variant="ghost"
onClick={() => shiftVideo(index, -1)}
>
<span className="i-heroicons:chevron-up-solid" />
</Button>
<Button
size="icon"
variant="ghost"
onClick={() => deleteVideo(index)}
>
<span className="i-heroicons:x-mark-solid" />
</Button>
<Button
size="icon"
variant="ghost"
onClick={() => shiftVideo(index, 1)}
>
<span className="i-heroicons:chevron-down-solid" />
</Button>
</div>
) : null}
<div className="grow">
<VideoCard
id={video.id}
size="sm"
channel={video.channel}
channel_id={video.channel.id}
type={video.type}
title={`${index + 1}. ${video.title}`}
description=""
duration={video.duration}
topic_id={video.topic_id}
published_at={video.published_at}
status={video.status}
start_scheduled={null}
start_actual={null}
end_actual={null}
live_viewers={null}
songcount={0}
/>
</div>
</div>
);
})}
</div>
);
}
Loading

0 comments on commit 4e3d863

Please sign in to comment.