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

move group reorder handlers to the modal itself

Pas ddcabc14 9ce46dd3

+172 -163
+2 -1
src/assets/locales/en.json
··· 1042 1042 "forceCompactEpisodeViewDescription": "Force the episode carousel in the player to use the \"classic\" compact vertical view. Disabled by default.", 1043 1043 "homeSectionOrder": "Home section order", 1044 1044 "homeSectionOrderDescription": "Drag and drop to reorder the watching and bookmarks sections on your homepage. Group order can be editied from the home page.", 1045 - "forceCompactEpisodeViewLabel": "Compact episodes" 1045 + "forceCompactEpisodeViewLabel": "Compact episodes", 1046 + "homeSectionOrderGroups": "Reorder bookmark groups" 1046 1047 }, 1047 1048 "sections": { 1048 1049 "watching": "Currently Watching",
+92 -7
src/components/overlays/EditGroupOrderModal.tsx
··· 1 + import { useEffect, useMemo, useState } from "react"; 1 2 import { useTranslation } from "react-i18next"; 2 3 3 4 import { Button } from "@/components/buttons/Button"; 4 5 import { Item, SortableList } from "@/components/form/SortableList"; 5 6 import { Modal, ModalCard } from "@/components/overlays/Modal"; 7 + import { UserIcons } from "@/components/UserIcon"; 6 8 import { Heading2, Paragraph } from "@/components/utils/Text"; 9 + import { useBookmarkStore } from "@/stores/bookmarks"; 10 + import { useGroupOrderStore } from "@/stores/groupOrder"; 11 + 12 + function parseGroupString(group: string): { icon: UserIcons; name: string } { 13 + const match = group.match(/^\[([a-zA-Z0-9_]+)\](.*)$/); 14 + if (match) { 15 + const iconKey = match[1].toUpperCase() as keyof typeof UserIcons; 16 + const icon = UserIcons[iconKey] || UserIcons.BOOKMARK; 17 + const name = match[2].trim(); 18 + return { icon, name }; 19 + } 20 + return { icon: UserIcons.BOOKMARK, name: group }; 21 + } 7 22 8 23 interface EditGroupOrderModalProps { 9 24 id: string; 10 25 isShown: boolean; 11 - items: Item[]; 12 26 onCancel: () => void; 13 - onSave: () => void; 14 - onItemsChange: (newItems: Item[]) => void; 27 + onSave: (newOrder: string[]) => void; 15 28 } 16 29 17 30 export function EditGroupOrderModal({ 18 31 id, 19 32 isShown, 20 - items, 21 33 onCancel, 22 34 onSave, 23 - onItemsChange, 24 35 }: EditGroupOrderModalProps) { 25 36 const { t } = useTranslation(); 37 + const bookmarks = useBookmarkStore((s) => s.bookmarks); 38 + const groupOrder = useGroupOrderStore((s) => s.groupOrder); 39 + const [tempGroupOrder, setTempGroupOrder] = useState<string[]>([]); 40 + 41 + // group sorting 42 + const allGroups = useMemo(() => { 43 + const groups = new Set<string>(); 44 + 45 + Object.values(bookmarks).forEach((bookmark) => { 46 + if (Array.isArray(bookmark.group)) { 47 + bookmark.group.forEach((group) => groups.add(group)); 48 + } 49 + }); 50 + 51 + groups.add("bookmarks"); 52 + 53 + return Array.from(groups); 54 + }, [bookmarks]); 55 + 56 + const sortableItems = useMemo(() => { 57 + const currentOrder = isShown ? tempGroupOrder : groupOrder; 58 + 59 + if (currentOrder.length === 0) { 60 + return allGroups.map((group) => { 61 + const { name } = parseGroupString(group); 62 + return { 63 + id: group, 64 + name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 65 + } as Item; 66 + }); 67 + } 68 + 69 + const orderMap = new Map( 70 + currentOrder.map((group, index) => [group, index]), 71 + ); 72 + const sortedGroups = allGroups.sort((groupA, groupB) => { 73 + const orderA = orderMap.has(groupA) 74 + ? orderMap.get(groupA)! 75 + : Number.MAX_SAFE_INTEGER; 76 + const orderB = orderMap.has(groupB) 77 + ? orderMap.get(groupB)! 78 + : Number.MAX_SAFE_INTEGER; 79 + return orderA - orderB; 80 + }); 81 + 82 + return sortedGroups.map((group) => { 83 + const { name } = parseGroupString(group); 84 + return { 85 + id: group, 86 + name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 87 + } as Item; 88 + }); 89 + }, [allGroups, t, isShown, tempGroupOrder, groupOrder]); 90 + 91 + // Initialize tempGroupOrder when modal opens 92 + useEffect(() => { 93 + if (isShown) { 94 + if (groupOrder.length === 0) { 95 + const defaultOrder = allGroups.map((group) => group); 96 + setTempGroupOrder(defaultOrder); 97 + } else { 98 + setTempGroupOrder([...groupOrder]); 99 + } 100 + } 101 + }, [isShown, groupOrder, allGroups]); 102 + 103 + const handleItemsChange = (newItems: Item[]) => { 104 + const newOrder = newItems.map((item) => item.id); 105 + setTempGroupOrder(newOrder); 106 + }; 107 + 108 + const handleSave = () => { 109 + onSave(tempGroupOrder); 110 + }; 26 111 27 112 if (!isShown) return null; 28 113 ··· 36 121 {t("home.bookmarks.groups.reorder.description")} 37 122 </Paragraph> 38 123 <div> 39 - <SortableList items={items} setItems={onItemsChange} /> 124 + <SortableList items={sortableItems} setItems={handleItemsChange} /> 40 125 </div> 41 126 <div className="flex gap-4 mt-6 justify-end"> 42 127 <Button theme="secondary" onClick={onCancel}> 43 128 {t("home.bookmarks.groups.reorder.cancel")} 44 129 </Button> 45 - <Button theme="purple" onClick={onSave}> 130 + <Button theme="purple" onClick={handleSave}> 46 131 {t("home.bookmarks.groups.reorder.save")} 47 132 </Button> 48 133 </div>
+2 -51
src/pages/bookmarks/AllBookmarks.tsx
··· 6 6 import { Button } from "@/components/buttons/Button"; 7 7 import { EditButton } from "@/components/buttons/EditButton"; 8 8 import { EditButtonWithText } from "@/components/buttons/EditButtonWithText"; 9 - import { Item } from "@/components/form/SortableList"; 10 9 import { Icon, Icons } from "@/components/Icon"; 11 10 import { SectionHeading } from "@/components/layout/SectionHeading"; 12 11 import { WideContainer } from "@/components/layout/WideContainer"; ··· 54 53 const [editing, setEditing] = useState(false); 55 54 const [gridRef] = useAutoAnimate<HTMLDivElement>(); 56 55 const editOrderModal = useModal("bookmark-edit-order-all"); 57 - const [tempGroupOrder, setTempGroupOrder] = useState<string[]>([]); 58 56 const backendUrl = useBackendUrl(); 59 57 const account = useAuthStore((s) => s.account); 60 58 const { showModal } = useOverlayStack(); ··· 143 141 return Array.from(groups); 144 142 }, [bookmarks]); 145 143 146 - const sortableItems = useMemo(() => { 147 - const currentOrder = editOrderModal.isShown ? tempGroupOrder : groupOrder; 148 - 149 - if (currentOrder.length === 0) { 150 - return allGroups.map((group) => { 151 - const { name } = parseGroupString(group); 152 - return { 153 - id: group, 154 - name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 155 - } as Item; 156 - }); 157 - } 158 - 159 - const orderMap = new Map( 160 - currentOrder.map((group, index) => [group, index]), 161 - ); 162 - const sortedGroups = allGroups.sort((groupA, groupB) => { 163 - const orderA = orderMap.has(groupA) 164 - ? orderMap.get(groupA)! 165 - : Number.MAX_SAFE_INTEGER; 166 - const orderB = orderMap.has(groupB) 167 - ? orderMap.get(groupB)! 168 - : Number.MAX_SAFE_INTEGER; 169 - return orderA - orderB; 170 - }); 171 - 172 - return sortedGroups.map((group) => { 173 - const { name } = parseGroupString(group); 174 - return { 175 - id: group, 176 - name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 177 - } as Item; 178 - }); 179 - }, [allGroups, t, editOrderModal.isShown, tempGroupOrder, groupOrder]); 180 - 181 144 const sortedSections = useMemo(() => { 182 145 const sections: Array<{ 183 146 type: "grouped" | "regular"; ··· 231 194 }, [groupedItems, regularItems, groupOrder]); 232 195 233 196 const handleEditGroupOrder = () => { 234 - // Initialize with current order or default order 235 - if (groupOrder.length === 0) { 236 - const defaultOrder = allGroups.map((group) => group); 237 - setTempGroupOrder(defaultOrder); 238 - } else { 239 - setTempGroupOrder([...groupOrder]); 240 - } 241 197 editOrderModal.show(); 242 198 }; 243 199 ··· 251 207 editOrderModal.hide(); 252 208 }; 253 209 254 - const handleSaveOrderClick = () => { 255 - setGroupOrder(tempGroupOrder); 210 + const handleSaveOrderClick = (newOrder: string[]) => { 211 + setGroupOrder(newOrder); 256 212 editOrderModal.hide(); 257 213 258 214 // Save to backend ··· 420 376 <EditGroupOrderModal 421 377 id={editOrderModal.id} 422 378 isShown={editOrderModal.isShown} 423 - items={sortableItems} 424 379 onCancel={handleCancelOrder} 425 380 onSave={handleSaveOrderClick} 426 - onItemsChange={(newItems) => { 427 - const newOrder = newItems.map((item) => item.id); 428 - setTempGroupOrder(newOrder); 429 - }} 430 381 /> 431 382 </WideContainer> 432 383 </SubPageLayout>
+2 -51
src/pages/parts/home/BookmarksCarousel.tsx
··· 4 4 5 5 import { EditButton } from "@/components/buttons/EditButton"; 6 6 import { EditButtonWithText } from "@/components/buttons/EditButtonWithText"; 7 - import { Item } from "@/components/form/SortableList"; 8 7 import { Icon, Icons } from "@/components/Icon"; 9 8 import { SectionHeading } from "@/components/layout/SectionHeading"; 10 9 import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; ··· 112 111 const groupOrder = useGroupOrderStore((s) => s.groupOrder); 113 112 const setGroupOrder = useGroupOrderStore((s) => s.setGroupOrder); 114 113 const editOrderModal = useModal("bookmark-edit-order-carousel"); 115 - const [tempGroupOrder, setTempGroupOrder] = useState<string[]>([]); 116 114 117 115 const { isMobile } = useIsMobile(); 118 116 ··· 196 194 return Array.from(groups); 197 195 }, [bookmarks]); 198 196 199 - const sortableItems = useMemo(() => { 200 - const currentOrder = editOrderModal.isShown ? tempGroupOrder : groupOrder; 201 - 202 - if (currentOrder.length === 0) { 203 - return allGroups.map((group) => { 204 - const { name } = parseGroupString(group); 205 - return { 206 - id: group, 207 - name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 208 - } as Item; 209 - }); 210 - } 211 - 212 - const orderMap = new Map( 213 - currentOrder.map((group, index) => [group, index]), 214 - ); 215 - const sortedGroups = allGroups.sort((groupA, groupB) => { 216 - const orderA = orderMap.has(groupA) 217 - ? orderMap.get(groupA)! 218 - : Number.MAX_SAFE_INTEGER; 219 - const orderB = orderMap.has(groupB) 220 - ? orderMap.get(groupB)! 221 - : Number.MAX_SAFE_INTEGER; 222 - return orderA - orderB; 223 - }); 224 - 225 - return sortedGroups.map((group) => { 226 - const { name } = parseGroupString(group); 227 - return { 228 - id: group, 229 - name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 230 - } as Item; 231 - }); 232 - }, [allGroups, t, editOrderModal.isShown, tempGroupOrder, groupOrder]); 233 - 234 197 // Create a unified list of sections including both grouped and regular bookmarks 235 198 const sortedSections = useMemo(() => { 236 199 const sections: Array<{ ··· 310 273 }; 311 274 312 275 const handleEditGroupOrder = () => { 313 - // Initialize with current order or default order 314 - if (groupOrder.length === 0) { 315 - const defaultOrder = allGroups.map((group) => group); 316 - setTempGroupOrder(defaultOrder); 317 - } else { 318 - setTempGroupOrder([...groupOrder]); 319 - } 320 276 editOrderModal.show(); 321 277 }; 322 278 ··· 330 286 editOrderModal.hide(); 331 287 }; 332 288 333 - const handleSaveOrderClick = () => { 334 - setGroupOrder(tempGroupOrder); 289 + const handleSaveOrderClick = (newOrder: string[]) => { 290 + setGroupOrder(newOrder); 335 291 editOrderModal.hide(); 336 292 337 293 // Save to backend ··· 559 515 <EditGroupOrderModal 560 516 id={editOrderModal.id} 561 517 isShown={editOrderModal.isShown} 562 - items={sortableItems} 563 518 onCancel={handleCancelOrder} 564 519 onSave={handleSaveOrderClick} 565 - onItemsChange={(newItems) => { 566 - const newOrder = newItems.map((item) => item.id); 567 - setTempGroupOrder(newOrder); 568 - }} 569 520 /> 570 521 571 522 {/* Edit Bookmark Modal */}
+2 -52
src/pages/parts/home/BookmarksPart.tsx
··· 4 4 5 5 import { EditButton } from "@/components/buttons/EditButton"; 6 6 import { EditButtonWithText } from "@/components/buttons/EditButtonWithText"; 7 - import { Item } from "@/components/form/SortableList"; 8 7 import { Icons } from "@/components/Icon"; 9 8 import { SectionHeading } from "@/components/layout/SectionHeading"; 10 9 import { MediaGrid } from "@/components/media/MediaGrid"; ··· 50 49 const editOrderModal = useModal("bookmark-edit-order"); 51 50 const editBookmarkModal = useModal("bookmark-edit"); 52 51 const editGroupModal = useModal("bookmark-edit-group"); 53 - const [tempGroupOrder, setTempGroupOrder] = useState<string[]>([]); 54 52 const [editingBookmarkId, setEditingBookmarkId] = useState<string | null>( 55 53 null, 56 54 ); ··· 135 133 return Array.from(groups); 136 134 }, [bookmarks]); 137 135 138 - const sortableItems = useMemo(() => { 139 - const currentOrder = editOrderModal.isShown ? tempGroupOrder : groupOrder; 140 - 141 - if (currentOrder.length === 0) { 142 - return allGroups.map((group) => { 143 - const { name } = parseGroupString(group); 144 - return { 145 - id: group, 146 - name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 147 - } as Item; 148 - }); 149 - } 150 - 151 - const orderMap = new Map( 152 - currentOrder.map((group, index) => [group, index]), 153 - ); 154 - const sortedGroups = allGroups.sort((groupA, groupB) => { 155 - const orderA = orderMap.has(groupA) 156 - ? orderMap.get(groupA)! 157 - : Number.MAX_SAFE_INTEGER; 158 - const orderB = orderMap.has(groupB) 159 - ? orderMap.get(groupB)! 160 - : Number.MAX_SAFE_INTEGER; 161 - return orderA - orderB; 162 - }); 163 - 164 - return sortedGroups.map((group) => { 165 - const { name } = parseGroupString(group); 166 - return { 167 - id: group, 168 - name: group === "bookmarks" ? t("home.bookmarks.sectionTitle") : name, 169 - } as Item; 170 - }); 171 - }, [allGroups, t, editOrderModal.isShown, tempGroupOrder, groupOrder]); 172 - 173 136 const sortedSections = useMemo(() => { 174 137 const sections: Array<{ 175 138 type: "grouped" | "regular"; ··· 221 184 222 185 return sections; 223 186 }, [groupedItems, regularItems, groupOrder]); 224 - // kill me 225 187 226 188 useEffect(() => { 227 189 onItemsChange(items.length > 0); 228 190 }, [items, onItemsChange]); 229 191 230 192 const handleEditGroupOrder = () => { 231 - // Initialize with current order or default order 232 - if (groupOrder.length === 0) { 233 - const defaultOrder = allGroups.map((group) => group); 234 - setTempGroupOrder(defaultOrder); 235 - } else { 236 - setTempGroupOrder([...groupOrder]); 237 - } 238 193 editOrderModal.show(); 239 194 }; 240 195 ··· 248 203 editOrderModal.hide(); 249 204 }; 250 205 251 - const handleSaveOrderClick = () => { 252 - setGroupOrder(tempGroupOrder); 206 + const handleSaveOrderClick = (newOrder: string[]) => { 207 + setGroupOrder(newOrder); 253 208 editOrderModal.hide(); 254 209 255 210 // Save to backend ··· 412 367 <EditGroupOrderModal 413 368 id={editOrderModal.id} 414 369 isShown={editOrderModal.isShown} 415 - items={sortableItems} 416 370 onCancel={handleCancelOrder} 417 371 onSave={handleSaveOrderClick} 418 - onItemsChange={(newItems) => { 419 - const newOrder = newItems.map((item) => item.id); 420 - setTempGroupOrder(newOrder); 421 - }} 422 372 /> 423 373 424 374 {/* Edit Bookmark Modal */}
+72 -1
src/pages/parts/settings/AppearancePart.tsx
··· 1 1 import classNames from "classnames"; 2 - import { useEffect, useRef, useState } from "react"; 2 + import { useEffect, useMemo, useRef, useState } from "react"; 3 3 import { useTranslation } from "react-i18next"; 4 4 5 + import { Button } from "@/components/buttons/Button"; 5 6 import { Toggle } from "@/components/buttons/Toggle"; 6 7 import { SortableList } from "@/components/form/SortableList"; 7 8 import { Icon, Icons } from "@/components/Icon"; 9 + import { EditGroupOrderModal } from "@/components/overlays/EditGroupOrderModal"; 10 + import { useModal } from "@/components/overlays/Modal"; 8 11 import { Heading1 } from "@/components/utils/Text"; 12 + import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; 13 + import { useAuthStore } from "@/stores/auth"; 14 + import { useBookmarkStore } from "@/stores/bookmarks"; 15 + import { useGroupOrderStore } from "@/stores/groupOrder"; 9 16 10 17 const availableThemes = [ 11 18 { ··· 243 250 const [isAtTop, setIsAtTop] = useState(true); 244 251 const [isAtBottom, setIsAtBottom] = useState(false); 245 252 253 + // Group order modal 254 + const bookmarks = useBookmarkStore((s) => s.bookmarks); 255 + const groupOrder = useGroupOrderStore((s) => s.groupOrder); 256 + const setGroupOrder = useGroupOrderStore((s) => s.setGroupOrder); 257 + const editGroupOrderModal = useModal("bookmark-edit-order-settings"); 258 + const backendUrl = useBackendUrl(); 259 + const account = useAuthStore((s) => s.account); 260 + 261 + // Check if there are groups 262 + const allGroups = useMemo(() => { 263 + const groups = new Set<string>(); 264 + 265 + Object.values(bookmarks).forEach((bookmark) => { 266 + if (Array.isArray(bookmark.group)) { 267 + bookmark.group.forEach((group) => groups.add(group)); 268 + } 269 + }); 270 + 271 + groups.add("bookmarks"); 272 + 273 + return Array.from(groups); 274 + }, [bookmarks]); 275 + 276 + const hasGroups = allGroups.length > 1; 277 + 246 278 const { 247 279 enableLowPerformanceMode, 248 280 setEnableDiscover, ··· 311 343 } 312 344 }, [props.active]); 313 345 346 + const handleEditGroupOrder = () => { 347 + editGroupOrderModal.show(); 348 + }; 349 + 350 + const handleCancelGroupOrder = () => { 351 + editGroupOrderModal.hide(); 352 + }; 353 + 354 + const handleSaveGroupOrder = (newOrder: string[]) => { 355 + setGroupOrder(newOrder); 356 + editGroupOrderModal.hide(); 357 + 358 + // Save to backend 359 + if (backendUrl && account) { 360 + useGroupOrderStore 361 + .getState() 362 + .saveGroupOrderToBackend(backendUrl, account); 363 + } 364 + }; 365 + 314 366 return ( 315 367 <div className="space-y-12"> 316 368 <Heading1 border>{t("settings.appearance.title")}</Heading1> ··· 500 552 }} 501 553 /> 502 554 </div> 555 + {hasGroups && ( 556 + <div className="mt-4 max-w-[25rem]"> 557 + <Button 558 + theme="secondary" 559 + onClick={handleEditGroupOrder} 560 + className="w-full" 561 + > 562 + {t("settings.appearance.options.homeSectionOrderGroups")} 563 + </Button> 564 + </div> 565 + )} 503 566 </div> 504 567 </div> 505 568 ··· 533 596 </div> 534 597 </div> 535 598 </div> 599 + 600 + {/* Edit Group Order Modal */} 601 + <EditGroupOrderModal 602 + id={editGroupOrderModal.id} 603 + isShown={editGroupOrderModal.isShown} 604 + onCancel={handleCancelGroupOrder} 605 + onSave={handleSaveGroupOrder} 606 + /> 536 607 </div> 537 608 ); 538 609 }