mirror of
https://github.com/p-stream/p-stream.git
synced 2026-08-19 13:45:40 +00:00
Add bookmark groups
This commit is contained in:
parent
5a74886e7d
commit
cc04c9e6c0
7 changed files with 404 additions and 120 deletions
|
|
@ -10,6 +10,7 @@ export interface BookmarkMetaInput {
|
|||
year: number;
|
||||
poster?: string;
|
||||
type: string;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
export interface BookmarkInput {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ import { IconPatch } from "../buttons/IconPatch";
|
|||
|
||||
interface MediaBookmarkProps {
|
||||
media: MediaItem;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
export function MediaBookmarkButton({ media }: MediaBookmarkProps) {
|
||||
export function MediaBookmarkButton({ media, group }: MediaBookmarkProps) {
|
||||
const addBookmark = useBookmarkStore((s) => s.addBookmark);
|
||||
const addBookmarkWithGroup = useBookmarkStore((s) => s.addBookmarkWithGroup);
|
||||
const removeBookmark = useBookmarkStore((s) => s.removeBookmark);
|
||||
const bookmarks = useBookmarkStore((s) => s.bookmarks);
|
||||
const meta: PlayerMeta | undefined = useMemo(() => {
|
||||
|
|
@ -31,8 +33,16 @@ export function MediaBookmarkButton({ media }: MediaBookmarkProps) {
|
|||
const toggleBookmark = useCallback(() => {
|
||||
if (!meta) return;
|
||||
if (isBookmarked) removeBookmark(meta.tmdbId);
|
||||
else if (group) addBookmarkWithGroup(meta, group);
|
||||
else addBookmark(meta);
|
||||
}, [isBookmarked, meta, addBookmark, removeBookmark]);
|
||||
}, [
|
||||
isBookmarked,
|
||||
meta,
|
||||
addBookmark,
|
||||
addBookmarkWithGroup,
|
||||
removeBookmark,
|
||||
group,
|
||||
]);
|
||||
|
||||
const buttonOpacityClass =
|
||||
media.year === undefined ? "hover:opacity-100" : "hover:opacity-95";
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Button } from "@/components/buttons/Button";
|
|||
import { IconPatch } from "@/components/buttons/IconPatch";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { MediaBookmarkButton } from "@/components/media/MediaBookmark";
|
||||
import { useBookmarkStore } from "@/stores/bookmarks";
|
||||
|
||||
import { DetailsBodyProps } from "./types";
|
||||
|
||||
|
|
@ -28,6 +29,37 @@ export function DetailsBody({
|
|||
const [releaseInfo, setReleaseInfo] = useState<TraktReleaseResponse | null>(
|
||||
null,
|
||||
);
|
||||
const [groupName, setGroupName] = useState("");
|
||||
const addBookmarkWithGroup = useBookmarkStore((s) => s.addBookmarkWithGroup);
|
||||
const removeBookmark = useBookmarkStore((s) => s.removeBookmark);
|
||||
const addBookmark = useBookmarkStore((s) => s.addBookmark);
|
||||
const bookmarks = useBookmarkStore((s) => s.bookmarks);
|
||||
const currentGroup = bookmarks[data.id?.toString() ?? ""]?.group;
|
||||
|
||||
const handleGroupSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!data.id) return;
|
||||
|
||||
const meta = {
|
||||
tmdbId: data.id.toString(),
|
||||
title: data.title,
|
||||
type: data.type || "movie",
|
||||
releaseYear: data.releaseDate
|
||||
? new Date(data.releaseDate).getFullYear()
|
||||
: 0,
|
||||
poster: data.posterUrl,
|
||||
};
|
||||
|
||||
if (currentGroup) {
|
||||
// Remove from group by removing bookmark and re-adding without group
|
||||
removeBookmark(data.id.toString());
|
||||
addBookmark(meta);
|
||||
} else if (groupName.trim()) {
|
||||
// Add to group
|
||||
addBookmarkWithGroup(meta, groupName.trim());
|
||||
setGroupName("");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchReleaseInfo = async () => {
|
||||
|
|
@ -152,66 +184,99 @@ export function DetailsBody({
|
|||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
onClick={onPlayClick}
|
||||
theme="purple"
|
||||
className={classNames(
|
||||
"flex-1 sm:flex-initial sm:w-auto",
|
||||
"gap-2 h-12 rounded-lg px-4 py-2 my-1 transition-transform hover:scale-105 duration-100",
|
||||
"text-md text-white flex items-center justify-center",
|
||||
)}
|
||||
>
|
||||
<Icon icon={Icons.PLAY} className="text-white" />
|
||||
<span className="text-white text-sm pr-1">
|
||||
{data.type === "movie"
|
||||
? !data.releaseDate || new Date(data.releaseDate) > new Date()
|
||||
? t("media.unreleased")
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
onClick={onPlayClick}
|
||||
theme="purple"
|
||||
className={classNames(
|
||||
"flex-1 sm:flex-initial sm:w-auto",
|
||||
"gap-2 h-12 rounded-lg px-4 py-2 my-1 transition-transform hover:scale-105 duration-100",
|
||||
"text-md text-white flex items-center justify-center",
|
||||
)}
|
||||
>
|
||||
<Icon icon={Icons.PLAY} className="text-white" />
|
||||
<span className="text-white text-sm pr-1">
|
||||
{data.type === "movie"
|
||||
? !data.releaseDate || new Date(data.releaseDate) > new Date()
|
||||
? t("media.unreleased")
|
||||
: showProgress
|
||||
? t("details.resume")
|
||||
: t("details.play")
|
||||
: showProgress
|
||||
? t("details.resume")
|
||||
: t("details.play")
|
||||
: showProgress
|
||||
? t("details.resume")
|
||||
: t("details.play")}
|
||||
</span>
|
||||
</Button>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
{imdbData?.trailer_url && (
|
||||
: t("details.play")}
|
||||
</span>
|
||||
</Button>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
{imdbData?.trailer_url && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onTrailerClick}
|
||||
className="p-2 opacity-75 transition-opacity duration-300 hover:scale-110 hover:cursor-pointer hover:opacity-95"
|
||||
title={t("details.trailer")}
|
||||
>
|
||||
<IconPatch
|
||||
icon={Icons.FILM}
|
||||
className="transition-transform duration-300 hover:scale-110 hover:cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
<MediaBookmarkButton
|
||||
media={{
|
||||
id: data.id?.toString() || "",
|
||||
title: data.title,
|
||||
year: data.releaseDate
|
||||
? new Date(data.releaseDate).getFullYear()
|
||||
: undefined,
|
||||
poster: data.posterUrl,
|
||||
type: data.type || "movie",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onTrailerClick}
|
||||
onClick={onShareClick}
|
||||
className="p-2 opacity-75 transition-opacity duration-300 hover:scale-110 hover:cursor-pointer hover:opacity-95"
|
||||
title={t("details.trailer")}
|
||||
title="Share"
|
||||
>
|
||||
<IconPatch
|
||||
icon={Icons.FILM}
|
||||
icon={Icons.IOS_SHARE}
|
||||
className="transition-transform duration-300 hover:scale-110 hover:cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
<MediaBookmarkButton
|
||||
media={{
|
||||
id: data.id?.toString() || "",
|
||||
title: data.title,
|
||||
year: data.releaseDate
|
||||
? new Date(data.releaseDate).getFullYear()
|
||||
: undefined,
|
||||
poster: data.backdrop,
|
||||
type: data.type || "movie",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onShareClick}
|
||||
className="p-2 opacity-75 transition-opacity duration-300 hover:scale-110 hover:cursor-pointer hover:opacity-95"
|
||||
title="Share"
|
||||
>
|
||||
<IconPatch
|
||||
icon={Icons.IOS_SHARE}
|
||||
className="transition-transform duration-300 hover:scale-110 hover:cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Group Input */}
|
||||
<form
|
||||
onSubmit={handleGroupSubmit}
|
||||
className="flex items-center gap-2 sm:ml-auto"
|
||||
>
|
||||
{currentGroup ? (
|
||||
<div className="w-64 px-3 py-2 text-xs bg-gray-700/50 border border-gray-600 rounded-lg text-white">
|
||||
In:{" "}
|
||||
<span className="font-semibold text-purple-400">
|
||||
{currentGroup}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
value={groupName}
|
||||
onChange={(e) => setGroupName(e.target.value)}
|
||||
placeholder="Add to group..."
|
||||
className="w-64 px-3 py-2 text-xs bg-gray-700/50 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500"
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleGroupSubmit}
|
||||
disabled={currentGroup ? false : !groupName.trim()}
|
||||
theme={currentGroup ? "danger" : "purple"}
|
||||
className="px-3 py-2 text-xs"
|
||||
>
|
||||
{currentGroup ? "Remove" : "Add"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,40 @@ export function BookmarksCarousel({
|
|||
return output;
|
||||
}, [bookmarks, progressItems]);
|
||||
|
||||
const { groupedItems, regularItems } = useMemo(() => {
|
||||
const grouped: Record<string, MediaItem[]> = {};
|
||||
const regular: MediaItem[] = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
const bookmark = bookmarks[item.id];
|
||||
if (bookmark?.group) {
|
||||
if (!grouped[bookmark.group]) {
|
||||
grouped[bookmark.group] = [];
|
||||
}
|
||||
grouped[bookmark.group].push(item);
|
||||
} else {
|
||||
regular.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort items within each group by date
|
||||
Object.keys(grouped).forEach((group) => {
|
||||
grouped[group].sort((a, b) => {
|
||||
const bookmarkA = bookmarks[a.id];
|
||||
const bookmarkB = bookmarks[b.id];
|
||||
const progressA = progressItems[a.id];
|
||||
const progressB = progressItems[b.id];
|
||||
|
||||
const dateA = Math.max(bookmarkA.updatedAt, progressA?.updatedAt ?? 0);
|
||||
const dateB = Math.max(bookmarkB.updatedAt, progressB?.updatedAt ?? 0);
|
||||
|
||||
return dateB - dateA;
|
||||
});
|
||||
});
|
||||
|
||||
return { groupedItems: grouped, regularItems: regular };
|
||||
}, [items, bookmarks, progressItems]);
|
||||
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
if (isScrolling) return;
|
||||
isScrolling = true;
|
||||
|
|
@ -130,32 +164,34 @@ export function BookmarksCarousel({
|
|||
|
||||
return (
|
||||
<>
|
||||
<SectionHeading
|
||||
title={t("home.bookmarks.sectionTitle") || "Bookmarks"}
|
||||
icon={Icons.BOOKMARK}
|
||||
className="ml-4 md:ml-12 mt-2 -mb-5"
|
||||
>
|
||||
<div className="mr-4 md:mr-8">
|
||||
<EditButton
|
||||
editing={editing}
|
||||
onEdit={setEditing}
|
||||
id="edit-button-bookmark"
|
||||
/>
|
||||
</div>
|
||||
</SectionHeading>
|
||||
<div className="relative overflow-hidden carousel-container md:pb-4">
|
||||
<div
|
||||
id={`carousel-${categorySlug}`}
|
||||
className="grid grid-flow-col auto-cols-max gap-4 pt-0 overflow-x-scroll scrollbar-none rounded-xl overflow-y-hidden md:pl-8 md:pr-8"
|
||||
ref={(el) => {
|
||||
carouselRefs.current[categorySlug] = el;
|
||||
}}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="md:w-12" />
|
||||
{/* Grouped Bookmarks Carousels */}
|
||||
{Object.entries(groupedItems).map(([group, groupItems]) => (
|
||||
<div key={group}>
|
||||
<SectionHeading
|
||||
title={group}
|
||||
icon={Icons.BOOKMARK}
|
||||
className="ml-4 md:ml-12 mt-2 -mb-5"
|
||||
>
|
||||
<div className="mr-4 md:mr-8">
|
||||
<EditButton
|
||||
editing={editing}
|
||||
onEdit={setEditing}
|
||||
id={`edit-button-bookmark-${group}`}
|
||||
/>
|
||||
</div>
|
||||
</SectionHeading>
|
||||
<div className="relative overflow-hidden carousel-container md:pb-4">
|
||||
<div
|
||||
id={`carousel-${group}`}
|
||||
className="grid grid-flow-col auto-cols-max gap-4 pt-0 overflow-x-scroll scrollbar-none rounded-xl overflow-y-hidden md:pl-8 md:pr-8"
|
||||
ref={(el) => {
|
||||
carouselRefs.current[group] = el;
|
||||
}}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="md:w-12" />
|
||||
|
||||
{items.length > 0
|
||||
? items.map((media) => (
|
||||
{groupItems.map((media) => (
|
||||
<div
|
||||
key={media.id}
|
||||
style={{ userSelect: "none" }}
|
||||
|
|
@ -176,23 +212,89 @@ export function BookmarksCarousel({
|
|||
onClose={() => removeBookmark(media.id)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
: Array.from({ length: SKELETON_COUNT }).map(() => (
|
||||
<MediaCardSkeleton
|
||||
key={`skeleton-${categorySlug}-${Math.random().toString(36).substring(7)}`}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="md:w-12" />
|
||||
</div>
|
||||
<div className="md:w-12" />
|
||||
</div>
|
||||
|
||||
{!isMobile && (
|
||||
<CarouselNavButtons
|
||||
categorySlug={categorySlug}
|
||||
carouselRefs={carouselRefs}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<CarouselNavButtons
|
||||
categorySlug={group}
|
||||
carouselRefs={carouselRefs}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Regular Bookmarks Carousel */}
|
||||
{regularItems.length > 0 && (
|
||||
<>
|
||||
<SectionHeading
|
||||
title={t("home.bookmarks.sectionTitle") || "Bookmarks"}
|
||||
icon={Icons.BOOKMARK}
|
||||
className="ml-4 md:ml-12 mt-2 -mb-5"
|
||||
>
|
||||
<div className="mr-4 md:mr-8">
|
||||
<EditButton
|
||||
editing={editing}
|
||||
onEdit={setEditing}
|
||||
id="edit-button-bookmark"
|
||||
/>
|
||||
</div>
|
||||
</SectionHeading>
|
||||
<div className="relative overflow-hidden carousel-container md:pb-4">
|
||||
<div
|
||||
id={`carousel-${categorySlug}`}
|
||||
className="grid grid-flow-col auto-cols-max gap-4 pt-0 overflow-x-scroll scrollbar-none rounded-xl overflow-y-hidden md:pl-8 md:pr-8"
|
||||
ref={(el) => {
|
||||
carouselRefs.current[categorySlug] = el;
|
||||
}}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="md:w-12" />
|
||||
|
||||
{regularItems.length > 0
|
||||
? regularItems.map((media) => (
|
||||
<div
|
||||
key={media.id}
|
||||
style={{ userSelect: "none" }}
|
||||
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
className="relative mt-4 group cursor-pointer user-select-none rounded-xl p-2 bg-transparent transition-colors duration-300 w-[10rem] md:w-[11.5rem] h-auto"
|
||||
>
|
||||
<WatchedMediaCard
|
||||
key={media.id}
|
||||
media={media}
|
||||
onShowDetails={onShowDetails}
|
||||
closable={editing}
|
||||
onClose={() => removeBookmark(media.id)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
: Array.from({ length: SKELETON_COUNT }).map(() => (
|
||||
<MediaCardSkeleton
|
||||
key={`skeleton-${categorySlug}-${Math.random().toString(36).substring(7)}`}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="md:w-12" />
|
||||
</div>
|
||||
|
||||
{!isMobile && (
|
||||
<CarouselNavButtons
|
||||
categorySlug={categorySlug}
|
||||
carouselRefs={carouselRefs}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,40 @@ export function BookmarksPart({
|
|||
return output;
|
||||
}, [bookmarks, progressItems]);
|
||||
|
||||
const { groupedItems, regularItems } = useMemo(() => {
|
||||
const grouped: Record<string, MediaItem[]> = {};
|
||||
const regular: MediaItem[] = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
const bookmark = bookmarks[item.id];
|
||||
if (bookmark?.group) {
|
||||
if (!grouped[bookmark.group]) {
|
||||
grouped[bookmark.group] = [];
|
||||
}
|
||||
grouped[bookmark.group].push(item);
|
||||
} else {
|
||||
regular.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort items within each group by date
|
||||
Object.keys(grouped).forEach((group) => {
|
||||
grouped[group].sort((a, b) => {
|
||||
const bookmarkA = bookmarks[a.id];
|
||||
const bookmarkB = bookmarks[b.id];
|
||||
const progressA = progressItems[a.id];
|
||||
const progressB = progressItems[b.id];
|
||||
|
||||
const dateA = Math.max(bookmarkA.updatedAt, progressA?.updatedAt ?? 0);
|
||||
const dateB = Math.max(bookmarkB.updatedAt, progressB?.updatedAt ?? 0);
|
||||
|
||||
return dateB - dateA;
|
||||
});
|
||||
});
|
||||
|
||||
return { groupedItems: grouped, regularItems: regular };
|
||||
}, [items, bookmarks, progressItems]);
|
||||
|
||||
useEffect(() => {
|
||||
onItemsChange(items.length > 0);
|
||||
}, [items, onItemsChange]);
|
||||
|
|
@ -91,38 +125,82 @@ export function BookmarksPart({
|
|||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SectionHeading
|
||||
title={t("home.bookmarks.sectionTitle") || "Bookmarks"}
|
||||
icon={Icons.BOOKMARK}
|
||||
>
|
||||
<EditButton
|
||||
editing={editing}
|
||||
onEdit={setEditing}
|
||||
id="edit-button-bookmark"
|
||||
/>
|
||||
</SectionHeading>
|
||||
<MediaGrid ref={gridRef}>
|
||||
{items.map((v) => (
|
||||
<div
|
||||
key={v.id}
|
||||
style={{ userSelect: "none" }}
|
||||
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
{/* Grouped Bookmarks */}
|
||||
{Object.entries(groupedItems).map(([group, groupItems]) => (
|
||||
<div key={group} className="mb-6">
|
||||
<SectionHeading
|
||||
title={group}
|
||||
icon={Icons.BOOKMARK}
|
||||
className="mb-8" // margin?
|
||||
>
|
||||
<WatchedMediaCard
|
||||
media={v}
|
||||
closable={editing}
|
||||
onClose={() => removeBookmark(v.id)}
|
||||
onShowDetails={onShowDetails}
|
||||
<EditButton
|
||||
editing={editing}
|
||||
onEdit={setEditing}
|
||||
id={`edit-button-bookmark-${group}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</MediaGrid>
|
||||
</SectionHeading>
|
||||
<MediaGrid>
|
||||
{groupItems.map((v) => (
|
||||
<div
|
||||
key={v.id}
|
||||
style={{ userSelect: "none" }}
|
||||
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
<WatchedMediaCard
|
||||
media={v}
|
||||
closable={editing}
|
||||
onClose={() => removeBookmark(v.id)}
|
||||
onShowDetails={onShowDetails}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</MediaGrid>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Regular Bookmarks */}
|
||||
{regularItems.length > 0 && (
|
||||
<div>
|
||||
<SectionHeading
|
||||
title={t("home.bookmarks.sectionTitle")}
|
||||
icon={Icons.BOOKMARK}
|
||||
>
|
||||
<EditButton
|
||||
editing={editing}
|
||||
onEdit={setEditing}
|
||||
id="edit-button-bookmark"
|
||||
/>
|
||||
</SectionHeading>
|
||||
<MediaGrid ref={gridRef}>
|
||||
{regularItems.map((v) => (
|
||||
<div
|
||||
key={v.id}
|
||||
style={{ userSelect: "none" }}
|
||||
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) =>
|
||||
e.preventDefault()
|
||||
}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
<WatchedMediaCard
|
||||
media={v}
|
||||
closable={editing}
|
||||
onClose={() => removeBookmark(v.id)}
|
||||
onShowDetails={onShowDetails}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</MediaGrid>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ async function syncBookmarks(
|
|||
title: item.title ?? "",
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
group: item.group,
|
||||
},
|
||||
tmdbId: item.tmdbId,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export interface BookmarkMediaItem {
|
|||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
updatedAt: number;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
export interface BookmarkUpdateItem {
|
||||
|
|
@ -19,6 +20,7 @@ export interface BookmarkUpdateItem {
|
|||
id: string;
|
||||
poster?: string;
|
||||
type?: "show" | "movie";
|
||||
group?: string;
|
||||
action: "delete" | "add";
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +28,7 @@ export interface BookmarkStore {
|
|||
bookmarks: Record<string, BookmarkMediaItem>;
|
||||
updateQueue: BookmarkUpdateItem[];
|
||||
addBookmark(meta: PlayerMeta): void;
|
||||
addBookmarkWithGroup(meta: PlayerMeta, group?: string): void;
|
||||
removeBookmark(id: string): void;
|
||||
replaceBookmarks(items: Record<string, BookmarkMediaItem>): void;
|
||||
clear(): void;
|
||||
|
|
@ -74,6 +77,30 @@ export const useBookmarkStore = create(
|
|||
};
|
||||
});
|
||||
},
|
||||
addBookmarkWithGroup(meta, group) {
|
||||
set((s) => {
|
||||
updateId += 1;
|
||||
s.updateQueue.push({
|
||||
id: updateId.toString(),
|
||||
action: "add",
|
||||
tmdbId: meta.tmdbId,
|
||||
type: meta.type,
|
||||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
poster: meta.poster,
|
||||
group,
|
||||
});
|
||||
|
||||
s.bookmarks[meta.tmdbId] = {
|
||||
type: meta.type,
|
||||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
poster: meta.poster,
|
||||
updatedAt: Date.now(),
|
||||
group,
|
||||
};
|
||||
});
|
||||
},
|
||||
replaceBookmarks(items: Record<string, BookmarkMediaItem>) {
|
||||
set((s) => {
|
||||
s.bookmarks = items;
|
||||
|
|
|
|||
Loading…
Reference in a new issue