diff --git a/src/backend/accounts/bookmarks.ts b/src/backend/accounts/bookmarks.ts index 3b8f0d16..c1f9d38f 100644 --- a/src/backend/accounts/bookmarks.ts +++ b/src/backend/accounts/bookmarks.ts @@ -10,7 +10,7 @@ export interface BookmarkMetaInput { year: number; poster?: string; type: string; - group?: string; + group?: string[]; } export interface BookmarkInput { diff --git a/src/components/form/GroupDropdown.tsx b/src/components/form/GroupDropdown.tsx index 3ad50d25..9896c754 100644 --- a/src/components/form/GroupDropdown.tsx +++ b/src/components/form/GroupDropdown.tsx @@ -5,10 +5,10 @@ import { UserIcon, UserIcons } from "@/components/UserIcon"; interface GroupDropdownProps { groups: string[]; - currentGroup?: string; - onSelectGroup: (group: string) => void; + currentGroups: string[]; + onSelectGroups: (groups: string[]) => void; onCreateGroup: (group: string, icon: UserIcons) => void; - onRemoveGroup: () => void; + onRemoveGroup: (groupToRemove?: string) => void; } const userIconList = Object.values(UserIcons); @@ -26,8 +26,8 @@ function parseGroupString(group: string): { icon: UserIcons; name: string } { export function GroupDropdown({ groups, - currentGroup, - onSelectGroup, + currentGroups, + onSelectGroups, onCreateGroup, onRemoveGroup, }: GroupDropdownProps) { @@ -36,11 +36,14 @@ export function GroupDropdown({ const [showInput, setShowInput] = useState(false); const [selectedIcon, setSelectedIcon] = useState(userIconList[0]); - const handleSelect = (group: string) => { - setOpen(false); - setShowInput(false); - setNewGroup(""); - onSelectGroup(group); + const handleToggleGroup = (group: string) => { + let newGroups; + if (currentGroups.includes(group)) { + newGroups = currentGroups.filter((g) => g !== group); + } else { + newGroups = [...currentGroups, group]; + } + onSelectGroups(newGroups); }; const handleCreate = (group: string, icon: UserIcons) => { @@ -59,18 +62,21 @@ export function GroupDropdown({ className="w-full px-3 py-2 text-xs bg-gray-700/50 border border-gray-600 rounded-lg text-white flex justify-between items-center" onClick={() => setOpen((v) => !v)} > - {currentGroup ? ( - (() => { - const { icon, name } = parseGroupString(currentGroup); - return ( - - - + {currentGroups.length > 0 ? ( + + {currentGroups.map((group) => { + const { icon, name } = parseGroupString(group); + return ( + + + {name} - {name} - - ); - })() + ); + })} + ) : ( Add to group )} @@ -82,24 +88,23 @@ export function GroupDropdown({ {open && ( -
+
{groups.length === 0 && !showInput && (
No groups
)} {groups.map((group) => { const { icon, name } = parseGroupString(group); return ( - + ); })}
@@ -159,17 +164,36 @@ export function GroupDropdown({
)}
- {currentGroup && ( - + {currentGroups.length > 0 && ( +
+
+ Remove from group: +
+
+ {currentGroups.map((group) => { + const { icon, name } = parseGroupString(group); + return ( + + ); + })} + +
+
)}
)} diff --git a/src/components/media/MediaBookmark.tsx b/src/components/media/MediaBookmark.tsx index 832cae6b..64e8ce97 100644 --- a/src/components/media/MediaBookmark.tsx +++ b/src/components/media/MediaBookmark.tsx @@ -9,12 +9,14 @@ import { IconPatch } from "../buttons/IconPatch"; interface MediaBookmarkProps { media: MediaItem; - group?: string; + group?: string[]; } export function MediaBookmarkButton({ media, group }: MediaBookmarkProps) { const addBookmark = useBookmarkStore((s) => s.addBookmark); - const addBookmarkWithGroup = useBookmarkStore((s) => s.addBookmarkWithGroup); + const addBookmarkWithGroups = useBookmarkStore( + (s) => s.addBookmarkWithGroups, + ); const removeBookmark = useBookmarkStore((s) => s.removeBookmark); const bookmarks = useBookmarkStore((s) => s.bookmarks); const meta: PlayerMeta | undefined = useMemo(() => { @@ -33,13 +35,13 @@ export function MediaBookmarkButton({ media, group }: MediaBookmarkProps) { const toggleBookmark = useCallback(() => { if (!meta) return; if (isBookmarked) removeBookmark(meta.tmdbId); - else if (group) addBookmarkWithGroup(meta, group); + else if (group && group.length > 0) addBookmarkWithGroups(meta, group); else addBookmark(meta); }, [ isBookmarked, meta, addBookmark, - addBookmarkWithGroup, + addBookmarkWithGroups, removeBookmark, group, ]); diff --git a/src/components/overlays/details/DetailsBody.tsx b/src/components/overlays/details/DetailsBody.tsx index 80e7dbf2..0bce30ce 100644 --- a/src/components/overlays/details/DetailsBody.tsx +++ b/src/components/overlays/details/DetailsBody.tsx @@ -30,21 +30,22 @@ export function DetailsBody({ const [releaseInfo, setReleaseInfo] = useState( null, ); - const addBookmarkWithGroup = useBookmarkStore((s) => s.addBookmarkWithGroup); - const removeBookmark = useBookmarkStore((s) => s.removeBookmark); - const addBookmark = useBookmarkStore((s) => s.addBookmark); + const addBookmarkWithGroups = useBookmarkStore( + (s) => s.addBookmarkWithGroups, + ); + const bookmarks = useBookmarkStore((s) => s.bookmarks); - const currentGroup = bookmarks[data.id?.toString() ?? ""]?.group; + const currentGroups = bookmarks[data.id?.toString() ?? ""]?.group || []; const allGroups = Array.from( new Set( Object.values(bookmarks) - .map((b) => b.group) + .flatMap((b) => b.group || []) .filter(Boolean), ), ) as string[]; - const handleSelectGroup = (group: string) => { + const handleSelectGroups = (groups: string[]) => { if (!data.id) return; const meta = { tmdbId: data.id.toString(), @@ -55,14 +56,14 @@ export function DetailsBody({ : 0, poster: data.posterUrl, }; - addBookmarkWithGroup(meta, group); + addBookmarkWithGroups(meta, groups); }; const handleCreateGroup = (group: string) => { - handleSelectGroup(group); + handleSelectGroups([...currentGroups, group]); }; - const handleRemoveGroup = () => { + const handleRemoveGroup = (groupToRemove?: string) => { if (!data.id) return; const meta = { tmdbId: data.id.toString(), @@ -73,8 +74,13 @@ export function DetailsBody({ : 0, poster: data.posterUrl, }; - removeBookmark(data.id.toString()); - addBookmark(meta); + if (groupToRemove) { + const newGroups = currentGroups.filter((g) => g !== groupToRemove); + addBookmarkWithGroups(meta, newGroups); + } else { + // Remove all groups + addBookmarkWithGroups(meta, []); + } }; useEffect(() => { @@ -266,8 +272,8 @@ export function DetailsBody({ {/* Group Dropdown */} diff --git a/src/pages/parts/home/BookmarksCarousel.tsx b/src/pages/parts/home/BookmarksCarousel.tsx index 1e95aae4..3f6f5b23 100644 --- a/src/pages/parts/home/BookmarksCarousel.tsx +++ b/src/pages/parts/home/BookmarksCarousel.tsx @@ -91,11 +91,13 @@ export function BookmarksCarousel({ items.forEach((item) => { const bookmark = bookmarks[item.id]; - if (bookmark?.group) { - if (!grouped[bookmark.group]) { - grouped[bookmark.group] = []; - } - grouped[bookmark.group].push(item); + if (Array.isArray(bookmark?.group)) { + bookmark.group.forEach((groupName) => { + if (!grouped[groupName]) { + grouped[groupName] = []; + } + grouped[groupName].push(item); + }); } else { regular.push(item); } diff --git a/src/pages/parts/home/BookmarksPart.tsx b/src/pages/parts/home/BookmarksPart.tsx index f82b555a..de4602b7 100644 --- a/src/pages/parts/home/BookmarksPart.tsx +++ b/src/pages/parts/home/BookmarksPart.tsx @@ -69,11 +69,13 @@ export function BookmarksPart({ items.forEach((item) => { const bookmark = bookmarks[item.id]; - if (bookmark?.group) { - if (!grouped[bookmark.group]) { - grouped[bookmark.group] = []; - } - grouped[bookmark.group].push(item); + if (Array.isArray(bookmark?.group)) { + bookmark.group.forEach((groupName) => { + if (!grouped[groupName]) { + grouped[groupName] = []; + } + grouped[groupName].push(item); + }); } else { regular.push(item); } diff --git a/src/stores/bookmarks/index.ts b/src/stores/bookmarks/index.ts index 031263d3..7d3ade87 100644 --- a/src/stores/bookmarks/index.ts +++ b/src/stores/bookmarks/index.ts @@ -10,7 +10,7 @@ export interface BookmarkMediaItem { poster?: string; type: "show" | "movie"; updatedAt: number; - group?: string; + group?: string[]; } export interface BookmarkUpdateItem { @@ -20,7 +20,7 @@ export interface BookmarkUpdateItem { id: string; poster?: string; type?: "show" | "movie"; - group?: string; + group?: string[]; action: "delete" | "add"; } @@ -28,7 +28,7 @@ export interface BookmarkStore { bookmarks: Record; updateQueue: BookmarkUpdateItem[]; addBookmark(meta: PlayerMeta): void; - addBookmarkWithGroup(meta: PlayerMeta, group?: string): void; + addBookmarkWithGroups(meta: PlayerMeta, groups?: string[]): void; removeBookmark(id: string): void; replaceBookmarks(items: Record): void; clear(): void; @@ -77,7 +77,7 @@ export const useBookmarkStore = create( }; }); }, - addBookmarkWithGroup(meta, group) { + addBookmarkWithGroups(meta, groups) { set((s) => { updateId += 1; s.updateQueue.push({ @@ -88,7 +88,7 @@ export const useBookmarkStore = create( title: meta.title, year: meta.releaseYear, poster: meta.poster, - group, + group: groups, }); s.bookmarks[meta.tmdbId] = { @@ -97,7 +97,7 @@ export const useBookmarkStore = create( year: meta.releaseYear, poster: meta.poster, updatedAt: Date.now(), - group, + group: groups, }; }); },