pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix groups and order initialization for new accounts

Pas 0acb0439 100d7cfe

+141 -53
+2 -1
src/backend/accounts/bookmarks.ts
··· 10 10 year: number; 11 11 poster?: string; 12 12 type: string; 13 - group?: string[]; 14 13 } 15 14 16 15 export interface BookmarkInput { 17 16 tmdbId: string; 18 17 meta: BookmarkMetaInput; 18 + group?: string[]; 19 19 } 20 20 21 21 export function bookmarkMediaToInput( ··· 30 30 year: item.year ?? 0, 31 31 }, 32 32 tmdbId, 33 + group: item.group, 33 34 }; 34 35 } 35 36
+2 -1
src/backend/accounts/user.ts
··· 33 33 year: number; 34 34 poster?: string; 35 35 type: "show" | "movie"; 36 - group?: string[]; 37 36 }; 37 + group: string[]; 38 38 updatedAt: string; 39 39 } 40 40 ··· 63 63 const entries = responses.map((bookmark) => { 64 64 const item: BookmarkMediaItem = { 65 65 ...bookmark.meta, 66 + group: bookmark.group.length > 0 ? bookmark.group : undefined, 66 67 updatedAt: new Date(bookmark.updatedAt).getTime(), 67 68 }; 68 69 return [bookmark.tmdbId, item] as const;
+2 -1
src/hooks/auth/useAuthData.ts
··· 11 11 } from "@/backend/accounts/user"; 12 12 import { useAuthStore } from "@/stores/auth"; 13 13 import { useBookmarkStore } from "@/stores/bookmarks"; 14 + import { useGroupOrderStore } from "@/stores/groupOrder"; 14 15 import { useLanguageStore } from "@/stores/language"; 15 16 import { usePreferencesStore } from "@/stores/preferences"; 16 17 import { useProgressStore } from "@/stores/progress"; ··· 102 103 replaceItems(progressResponsesToEntries(progress)); 103 104 104 105 if (groupOrder?.groupOrder) { 105 - useBookmarkStore.getState().setGroupOrder(groupOrder.groupOrder); 106 + useGroupOrderStore.getState().setGroupOrder(groupOrder.groupOrder); 106 107 } 107 108 108 109 if (settings.applicationLanguage) {
+2
src/index.tsx
··· 25 25 import { conf } from "@/setup/config"; 26 26 import { useAuthStore } from "@/stores/auth"; 27 27 import { BookmarkSyncer } from "@/stores/bookmarks/BookmarkSyncer"; 28 + import { GroupSyncer } from "@/stores/groupOrder/GroupSyncer"; 28 29 import { changeAppLanguage, useLanguageStore } from "@/stores/language"; 29 30 import { ProgressSyncer } from "@/stores/progress/ProgressSyncer"; 30 31 import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer"; ··· 185 186 <ThemeProvider applyGlobal> 186 187 <ProgressSyncer /> 187 188 <BookmarkSyncer /> 189 + <GroupSyncer /> 188 190 <SettingsSyncer /> 189 191 <TheRouter> 190 192 <MigrationRunner />
+6 -3
src/pages/parts/home/BookmarksCarousel.tsx
··· 16 16 import { CarouselNavButtons } from "@/pages/discover/components/CarouselNavButtons"; 17 17 import { useAuthStore } from "@/stores/auth"; 18 18 import { useBookmarkStore } from "@/stores/bookmarks"; 19 + import { useGroupOrderStore } from "@/stores/groupOrder"; 19 20 import { useProgressStore } from "@/stores/progress"; 20 21 import { MediaItem } from "@/utils/mediaTypes"; 21 22 ··· 64 65 const account = useAuthStore((s) => s.account); 65 66 66 67 // Group order editing state 67 - const groupOrder = useBookmarkStore((s) => s.groupOrder); 68 - const setGroupOrder = useBookmarkStore((s) => s.setGroupOrder); 68 + const groupOrder = useGroupOrderStore((s) => s.groupOrder); 69 + const setGroupOrder = useGroupOrderStore((s) => s.setGroupOrder); 69 70 const editOrderModal = useModal("bookmark-edit-order-carousel"); 70 71 const [tempGroupOrder, setTempGroupOrder] = useState<string[]>([]); 71 72 ··· 323 324 324 325 // Save to backend 325 326 if (backendUrl && account) { 326 - useBookmarkStore.getState().saveGroupOrderToBackend(backendUrl, account); 327 + useGroupOrderStore 328 + .getState() 329 + .saveGroupOrderToBackend(backendUrl, account); 327 330 } 328 331 }; 329 332
+6 -3
src/pages/parts/home/BookmarksPart.tsx
··· 16 16 import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; 17 17 import { useAuthStore } from "@/stores/auth"; 18 18 import { useBookmarkStore } from "@/stores/bookmarks"; 19 + import { useGroupOrderStore } from "@/stores/groupOrder"; 19 20 import { useProgressStore } from "@/stores/progress"; 20 21 import { MediaItem } from "@/utils/mediaTypes"; 21 22 ··· 42 43 const { t } = useTranslation(); 43 44 const progressItems = useProgressStore((s) => s.items); 44 45 const bookmarks = useBookmarkStore((s) => s.bookmarks); 45 - const groupOrder = useBookmarkStore((s) => s.groupOrder); 46 - const setGroupOrder = useBookmarkStore((s) => s.setGroupOrder); 46 + const groupOrder = useGroupOrderStore((s) => s.groupOrder); 47 + const setGroupOrder = useGroupOrderStore((s) => s.setGroupOrder); 47 48 const removeBookmark = useBookmarkStore((s) => s.removeBookmark); 48 49 const [editing, setEditing] = useState(false); 49 50 const [gridRef] = useAutoAnimate<HTMLDivElement>(); ··· 278 279 279 280 // Save to backend 280 281 if (backendUrl && account) { 281 - useBookmarkStore.getState().saveGroupOrderToBackend(backendUrl, account); 282 + useGroupOrderStore 283 + .getState() 284 + .saveGroupOrderToBackend(backendUrl, account); 282 285 } 283 286 }; 284 287
+1 -1
src/stores/bookmarks/BookmarkSyncer.tsx
··· 32 32 title: item.title ?? "", 33 33 type: item.type ?? "", 34 34 year: item.year ?? NaN, 35 - group: item.group, 36 35 }, 37 36 tmdbId: item.tmdbId, 37 + group: item.group, 38 38 }); 39 39 continue; 40 40 }
-43
src/stores/bookmarks/index.ts
··· 2 2 import { persist } from "zustand/middleware"; 3 3 import { immer } from "zustand/middleware/immer"; 4 4 5 - import { getGroupOrder, updateGroupOrder } from "@/backend/accounts/groupOrder"; 6 - import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; 7 - import { AccountWithToken, useAuthStore } from "@/stores/auth"; 8 5 import { PlayerMeta } from "@/stores/player/slices/source"; 9 6 10 7 export interface BookmarkMediaItem { ··· 30 27 export interface BookmarkStore { 31 28 bookmarks: Record<string, BookmarkMediaItem>; 32 29 updateQueue: BookmarkUpdateItem[]; 33 - groupOrder: string[]; 34 30 addBookmark(meta: PlayerMeta): void; 35 31 addBookmarkWithGroups(meta: PlayerMeta, groups?: string[]): void; 36 32 removeBookmark(id: string): void; 37 33 replaceBookmarks(items: Record<string, BookmarkMediaItem>): void; 38 - setGroupOrder(order: string[]): void; 39 - saveGroupOrderToBackend( 40 - backendUrl: string, 41 - account: AccountWithToken, 42 - ): Promise<void>; 43 - loadGroupOrderFromBackend( 44 - backendUrl: string, 45 - account: AccountWithToken, 46 - ): Promise<void>; 47 34 clear(): void; 48 35 clearUpdateQueue(): void; 49 36 removeUpdateItem(id: string): void; ··· 56 43 immer<BookmarkStore>((set) => ({ 57 44 bookmarks: {}, 58 45 updateQueue: [], 59 - groupOrder: [], 60 46 removeBookmark(id) { 61 47 set((s) => { 62 48 updateId += 1; ··· 133 119 removeUpdateItem(id: string) { 134 120 set((s) => { 135 121 s.updateQueue = [...s.updateQueue.filter((v) => v.id !== id)]; 136 - }); 137 - }, 138 - setGroupOrder(order: string[]) { 139 - set((s) => { 140 - s.groupOrder = order; 141 - }); 142 - }, 143 - async saveGroupOrderToBackend( 144 - backendUrl: string, 145 - account: AccountWithToken, 146 - ) { 147 - if (!account || !backendUrl) { 148 - throw new Error("No authenticated account or backend URL"); 149 - } 150 - 151 - const currentState = useBookmarkStore.getState(); 152 - await updateGroupOrder(backendUrl, account, currentState.groupOrder); 153 - }, 154 - async loadGroupOrderFromBackend( 155 - backendUrl: string, 156 - account: AccountWithToken, 157 - ) { 158 - if (!account || !backendUrl) { 159 - throw new Error("No authenticated account or backend URL"); 160 - } 161 - 162 - const response = await getGroupOrder(backendUrl, account); 163 - set((s) => { 164 - s.groupOrder = response.groupOrder; 165 122 }); 166 123 }, 167 124 })),
+55
src/stores/groupOrder/GroupSyncer.tsx
··· 1 + import { useEffect, useRef } from "react"; 2 + 3 + import { updateGroupOrder } from "@/backend/accounts/groupOrder"; 4 + import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; 5 + import { useAuthStore } from "@/stores/auth"; 6 + import { useGroupOrderStore } from "@/stores/groupOrder"; 7 + 8 + const syncIntervalMs = 5 * 1000; 9 + 10 + export function GroupSyncer() { 11 + const url = useBackendUrl(); 12 + const groupOrder = useGroupOrderStore((s) => s.groupOrder); 13 + const lastSyncedOrder = useRef<string[]>([]); 14 + const isInitialized = useRef(false); 15 + 16 + // Initialize lastSyncedOrder on first render 17 + useEffect(() => { 18 + if (!isInitialized.current) { 19 + lastSyncedOrder.current = [...groupOrder]; 20 + isInitialized.current = true; 21 + } 22 + }, [groupOrder]); 23 + 24 + useEffect(() => { 25 + const interval = setInterval(() => { 26 + (async () => { 27 + if (!url) return; 28 + 29 + const user = useAuthStore.getState(); 30 + if (!user.account) return; // not logged in, dont sync to server 31 + 32 + // Check if group order has changed since last sync 33 + const currentOrder = useGroupOrderStore.getState().groupOrder; 34 + const hasChanged = 35 + JSON.stringify(currentOrder) !== 36 + JSON.stringify(lastSyncedOrder.current); 37 + 38 + if (hasChanged) { 39 + try { 40 + await updateGroupOrder(url, user.account, currentOrder); 41 + lastSyncedOrder.current = [...currentOrder]; 42 + } catch (err) { 43 + console.error("Failed to sync group order:", err); 44 + } 45 + } 46 + })(); 47 + }, syncIntervalMs); 48 + 49 + return () => { 50 + clearInterval(interval); 51 + }; 52 + }, [url]); 53 + 54 + return null; 55 + }
+65
src/stores/groupOrder/index.ts
··· 1 + import { create } from "zustand"; 2 + import { persist } from "zustand/middleware"; 3 + import { immer } from "zustand/middleware/immer"; 4 + 5 + import { getGroupOrder, updateGroupOrder } from "@/backend/accounts/groupOrder"; 6 + import { AccountWithToken } from "@/stores/auth"; 7 + 8 + export interface GroupOrderStore { 9 + groupOrder: string[]; 10 + setGroupOrder(order: string[]): void; 11 + saveGroupOrderToBackend( 12 + backendUrl: string, 13 + account: AccountWithToken, 14 + ): Promise<void>; 15 + loadGroupOrderFromBackend( 16 + backendUrl: string, 17 + account: AccountWithToken, 18 + ): Promise<void>; 19 + clear(): void; 20 + } 21 + 22 + export const useGroupOrderStore = create( 23 + persist( 24 + immer<GroupOrderStore>((set) => ({ 25 + groupOrder: [], 26 + setGroupOrder(order: string[]) { 27 + set((s) => { 28 + s.groupOrder = order; 29 + }); 30 + }, 31 + async saveGroupOrderToBackend( 32 + backendUrl: string, 33 + account: AccountWithToken, 34 + ) { 35 + if (!account || !backendUrl) { 36 + throw new Error("No authenticated account or backend URL"); 37 + } 38 + 39 + const currentState = useGroupOrderStore.getState(); 40 + await updateGroupOrder(backendUrl, account, currentState.groupOrder); 41 + }, 42 + async loadGroupOrderFromBackend( 43 + backendUrl: string, 44 + account: AccountWithToken, 45 + ) { 46 + if (!account || !backendUrl) { 47 + throw new Error("No authenticated account or backend URL"); 48 + } 49 + 50 + const response = await getGroupOrder(backendUrl, account); 51 + set((s) => { 52 + s.groupOrder = response.groupOrder; 53 + }); 54 + }, 55 + clear() { 56 + set((s) => { 57 + s.groupOrder = []; 58 + }); 59 + }, 60 + })), 61 + { 62 + name: "__MW::groupOrder", 63 + }, 64 + ), 65 + );