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.

Revert "add drag and drop bookmark reordering"

This reverts commit c90e77ddf34c4bcb7036b353e411fe215fbc17ba.

Pas 0e4724f9 c90e77dd

+89 -447
-201
src/hooks/useBookmarkDragAndDrop.tsx
··· 1 - import { 2 - DragEndEvent, 3 - KeyboardSensor, 4 - MouseSensor, 5 - TouchSensor, 6 - useSensor, 7 - useSensors, 8 - } from "@dnd-kit/core"; 9 - import { 10 - arrayMove, 11 - sortableKeyboardCoordinates, 12 - useSortable, 13 - } from "@dnd-kit/sortable"; 14 - import { CSS } from "@dnd-kit/utilities"; 15 - import React, { useEffect, useRef, useState } from "react"; 16 - 17 - import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; 18 - import { useBookmarkStore } from "@/stores/bookmarks"; 19 - import { MediaItem } from "@/utils/mediaTypes"; 20 - 21 - interface SortableMediaCardProps { 22 - media: MediaItem; 23 - closable?: boolean; 24 - onClose?: () => void; 25 - onShowDetails?: (media: MediaItem) => void; 26 - editable?: boolean; 27 - onEdit?: () => void; 28 - isEditing?: boolean; 29 - } 30 - 31 - export function SortableMediaCard({ 32 - media, 33 - closable, 34 - onClose, 35 - onShowDetails, 36 - editable, 37 - onEdit, 38 - isEditing, 39 - }: SortableMediaCardProps): JSX.Element { 40 - const { 41 - attributes, 42 - listeners, 43 - setNodeRef, 44 - transform, 45 - transition, 46 - isDragging, 47 - } = useSortable({ id: media.id }); 48 - 49 - const style = { 50 - transform: CSS.Transform.toString(transform), 51 - transition, 52 - opacity: isDragging ? 0.5 : 1, 53 - }; 54 - 55 - return ( 56 - <div 57 - ref={setNodeRef} 58 - style={style} 59 - {...(isEditing ? { ...attributes, ...listeners } : {})} 60 - className={isEditing ? "cursor-grab active:cursor-grabbing" : ""} 61 - > 62 - <WatchedMediaCard 63 - media={media} 64 - closable={closable} 65 - onClose={onClose} 66 - onShowDetails={onShowDetails} 67 - editable={editable} 68 - onEdit={onEdit} 69 - /> 70 - </div> 71 - ); 72 - } 73 - 74 - interface UseBookmarkDragAndDropProps { 75 - editing: boolean; 76 - items: MediaItem[]; 77 - groupedItems: Record<string, MediaItem[]>; 78 - } 79 - 80 - export function useBookmarkDragAndDrop({ 81 - editing, 82 - items, 83 - groupedItems, 84 - }: UseBookmarkDragAndDropProps) { 85 - const bookmarks = useBookmarkStore((s) => s.bookmarks); 86 - const updateBookmarkOrder = useBookmarkStore((s) => s.updateBookmarkOrder); 87 - 88 - // Drag and drop sensors 89 - const sensors = useSensors( 90 - useSensor(TouchSensor, { 91 - activationConstraint: { 92 - delay: 75, 93 - tolerance: 1, 94 - }, 95 - }), 96 - useSensor(MouseSensor), 97 - useSensor(KeyboardSensor, { 98 - coordinateGetter: sortableKeyboardCoordinates, 99 - }), 100 - ); 101 - 102 - // Track order during editing 103 - const [orderedItems, setOrderedItems] = useState<MediaItem[]>([]); 104 - const [orderedGroupedItems, setOrderedGroupedItems] = useState< 105 - Record<string, MediaItem[]> 106 - >({}); 107 - const isApplyingOrderRef = useRef(false); 108 - 109 - // Initialize ordered items when entering edit mode 110 - useEffect(() => { 111 - if (editing) { 112 - setOrderedItems([...items]); 113 - setOrderedGroupedItems({ ...groupedItems }); 114 - isApplyingOrderRef.current = false; 115 - } 116 - }, [editing, items, groupedItems]); 117 - 118 - // Apply order when exiting edit mode 119 - useEffect(() => { 120 - if (!editing && orderedItems.length > 0 && !isApplyingOrderRef.current) { 121 - isApplyingOrderRef.current = true; 122 - 123 - // Apply order for regular items 124 - const regularOrder = orderedItems 125 - .filter((item) => { 126 - const bookmark = bookmarks[item.id]; 127 - return !Array.isArray(bookmark?.group) || bookmark.group.length === 0; 128 - }) 129 - .map((item) => item.id); 130 - if (regularOrder.length > 0) { 131 - updateBookmarkOrder(regularOrder); 132 - } 133 - 134 - // Apply order for grouped items 135 - Object.entries(orderedGroupedItems).forEach( 136 - ([_groupName, groupItems]) => { 137 - const groupOrderIds = groupItems.map((item) => item.id); 138 - if (groupOrderIds.length > 0) { 139 - updateBookmarkOrder(groupOrderIds); 140 - } 141 - }, 142 - ); 143 - 144 - // Reset ordered items after a short delay to allow state updates to complete 145 - setTimeout(() => { 146 - setOrderedItems([]); 147 - setOrderedGroupedItems({}); 148 - isApplyingOrderRef.current = false; 149 - }, 0); 150 - } 151 - }, [ 152 - editing, 153 - orderedItems, 154 - orderedGroupedItems, 155 - bookmarks, 156 - updateBookmarkOrder, 157 - ]); 158 - 159 - const handleDragEnd = (event: DragEndEvent, groupName?: string) => { 160 - const { active, over } = event; 161 - if (!over || active.id === over.id) return; 162 - 163 - if (groupName) { 164 - // Handle grouped items 165 - const currentItems = orderedGroupedItems[groupName] || []; 166 - const oldIndex = currentItems.findIndex((item) => item.id === active.id); 167 - const newIndex = currentItems.findIndex((item) => item.id === over.id); 168 - if (oldIndex !== -1 && newIndex !== -1) { 169 - const newItems = arrayMove(currentItems, oldIndex, newIndex); 170 - setOrderedGroupedItems({ 171 - ...orderedGroupedItems, 172 - [groupName]: newItems, 173 - }); 174 - } 175 - } else { 176 - // Handle regular items 177 - const currentItems = orderedItems.filter((item) => { 178 - const bookmark = bookmarks[item.id]; 179 - return !Array.isArray(bookmark?.group) || bookmark.group.length === 0; 180 - }); 181 - const oldIndex = currentItems.findIndex((item) => item.id === active.id); 182 - const newIndex = currentItems.findIndex((item) => item.id === over.id); 183 - if (oldIndex !== -1 && newIndex !== -1) { 184 - const newItems = arrayMove(currentItems, oldIndex, newIndex); 185 - // Update orderedItems with the new order 186 - const otherItems = orderedItems.filter((item) => { 187 - const bookmark = bookmarks[item.id]; 188 - return Array.isArray(bookmark?.group) && bookmark.group.length > 0; 189 - }); 190 - setOrderedItems([...newItems, ...otherItems]); 191 - } 192 - } 193 - }; 194 - 195 - return { 196 - sensors, 197 - orderedItems, 198 - orderedGroupedItems, 199 - handleDragEnd, 200 - }; 201 - }
+48 -122
src/pages/parts/home/BookmarksCarousel.tsx
··· 1 - import { DndContext, closestCenter } from "@dnd-kit/core"; 2 - import { SortableContext, rectSortingStrategy } from "@dnd-kit/sortable"; 3 1 import React, { useMemo, useState } from "react"; 4 2 import { useTranslation } from "react-i18next"; 5 3 import { Link } from "react-router-dom"; ··· 9 7 import { Item } from "@/components/form/SortableList"; 10 8 import { Icon, Icons } from "@/components/Icon"; 11 9 import { SectionHeading } from "@/components/layout/SectionHeading"; 10 + import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; 12 11 import { EditBookmarkModal } from "@/components/overlays/EditBookmarkModal"; 13 12 import { EditGroupModal } from "@/components/overlays/EditGroupModal"; 14 13 import { EditGroupOrderModal } from "@/components/overlays/EditGroupOrderModal"; ··· 16 15 import { UserIcon, UserIcons } from "@/components/UserIcon"; 17 16 import { Flare } from "@/components/utils/Flare"; 18 17 import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; 19 - import { 20 - SortableMediaCard, 21 - useBookmarkDragAndDrop, 22 - } from "@/hooks/useBookmarkDragAndDrop"; 23 18 import { useIsMobile } from "@/hooks/useIsMobile"; 24 19 import { CarouselNavButtons } from "@/pages/discover/components/CarouselNavButtons"; 25 20 import { useAuthStore } from "@/stores/auth"; ··· 185 180 186 181 return { groupedItems: grouped, regularItems: regular }; 187 182 }, [items, bookmarks, progressItems]); 188 - 189 - // Drag and drop hook 190 - const { sensors, orderedItems, orderedGroupedItems, handleDragEnd } = 191 - useBookmarkDragAndDrop({ 192 - editing, 193 - items, 194 - groupedItems, 195 - }); 196 183 197 184 // group sorting 198 185 const allGroups = useMemo(() => { ··· 448 435 > 449 436 <div className="md:w-12" /> 450 437 451 - <DndContext 452 - sensors={sensors} 453 - collisionDetection={closestCenter} 454 - onDragEnd={(e) => handleDragEnd(e, section.group)} 455 - > 456 - <SortableContext 457 - items={ 458 - editing && orderedGroupedItems[section.group || ""] 459 - ? orderedGroupedItems[section.group || ""] 460 - .slice(0, MAX_ITEMS_PER_SECTION) 461 - .map((item) => item.id) 462 - : section.items 463 - .slice(0, MAX_ITEMS_PER_SECTION) 464 - .map((item) => item.id) 465 - } 466 - strategy={rectSortingStrategy} 467 - > 468 - {(editing && orderedGroupedItems[section.group || ""] 469 - ? orderedGroupedItems[section.group || ""] 470 - : section.items 471 - ) 472 - .slice(0, MAX_ITEMS_PER_SECTION) 473 - .map((media) => ( 474 - <div 475 - key={media.id} 476 - onContextMenu={( 477 - e: React.MouseEvent<HTMLDivElement>, 478 - ) => e.preventDefault()} 479 - className="relative mt-4 group cursor-pointer rounded-xl p-2 bg-transparent transition-colors duration-300 w-[10rem] md:w-[11.5rem] h-auto" 480 - > 481 - <SortableMediaCard 482 - key={media.id} 483 - media={media} 484 - onShowDetails={onShowDetails} 485 - closable={editing} 486 - onClose={() => removeBookmark(media.id)} 487 - editable={editing} 488 - onEdit={() => handleEditBookmark(media.id)} 489 - isEditing={editing} 490 - /> 491 - </div> 492 - ))} 493 - </SortableContext> 494 - </DndContext> 438 + {section.items 439 + .slice(0, MAX_ITEMS_PER_SECTION) 440 + .map((media) => ( 441 + <div 442 + key={media.id} 443 + onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => 444 + e.preventDefault() 445 + } 446 + className="relative mt-4 group cursor-pointer rounded-xl p-2 bg-transparent transition-colors duration-300 w-[10rem] md:w-[11.5rem] h-auto" 447 + > 448 + <WatchedMediaCard 449 + key={media.id} 450 + media={media} 451 + onShowDetails={onShowDetails} 452 + closable={editing} 453 + onClose={() => removeBookmark(media.id)} 454 + editable={editing} 455 + onEdit={() => handleEditBookmark(media.id)} 456 + /> 457 + </div> 458 + ))} 495 459 496 460 {section.items.length > MAX_ITEMS_PER_SECTION && ( 497 461 <MoreBookmarksCard /> ··· 545 509 > 546 510 <div className="md:w-12" /> 547 511 548 - {section.items.length > 0 ? ( 549 - <DndContext 550 - sensors={sensors} 551 - collisionDetection={closestCenter} 552 - onDragEnd={(e) => handleDragEnd(e)} 553 - > 554 - <SortableContext 555 - items={ 556 - editing 557 - ? orderedItems 558 - .filter((item) => { 559 - const bookmark = bookmarks[item.id]; 560 - return ( 561 - !Array.isArray(bookmark?.group) || 562 - bookmark.group.length === 0 563 - ); 564 - }) 565 - .slice(0, MAX_ITEMS_PER_SECTION) 566 - .map((item) => item.id) 567 - : section.items 568 - .slice(0, MAX_ITEMS_PER_SECTION) 569 - .map((item) => item.id) 570 - } 571 - strategy={rectSortingStrategy} 572 - > 573 - {(editing 574 - ? orderedItems.filter((item) => { 575 - const bookmark = bookmarks[item.id]; 576 - return ( 577 - !Array.isArray(bookmark?.group) || 578 - bookmark.group.length === 0 579 - ); 580 - }) 581 - : section.items 582 - ) 583 - .slice(0, MAX_ITEMS_PER_SECTION) 584 - .map((media) => ( 585 - <div 512 + {section.items.length > 0 513 + ? section.items 514 + .slice(0, MAX_ITEMS_PER_SECTION) 515 + .map((media) => ( 516 + <div 517 + key={media.id} 518 + onContextMenu={( 519 + e: React.MouseEvent<HTMLDivElement>, 520 + ) => e.preventDefault()} 521 + className="relative mt-4 group cursor-pointer rounded-xl p-2 bg-transparent transition-colors duration-300 w-[10rem] md:w-[11.5rem] h-auto" 522 + > 523 + <WatchedMediaCard 586 524 key={media.id} 587 - onContextMenu={( 588 - e: React.MouseEvent<HTMLDivElement>, 589 - ) => e.preventDefault()} 590 - className="relative mt-4 group cursor-pointer rounded-xl p-2 bg-transparent transition-colors duration-300 w-[10rem] md:w-[11.5rem] h-auto" 591 - > 592 - <SortableMediaCard 593 - key={media.id} 594 - media={media} 595 - onShowDetails={onShowDetails} 596 - closable={editing} 597 - onClose={() => removeBookmark(media.id)} 598 - editable={editing} 599 - onEdit={() => handleEditBookmark(media.id)} 600 - isEditing={editing} 601 - /> 602 - </div> 603 - ))} 604 - </SortableContext> 605 - </DndContext> 606 - ) : ( 607 - Array.from({ length: SKELETON_COUNT }).map(() => ( 608 - <MediaCardSkeleton 609 - key={`skeleton-${categorySlug}-${Math.random().toString(36).substring(7)}`} 610 - /> 611 - )) 612 - )} 525 + media={media} 526 + onShowDetails={onShowDetails} 527 + closable={editing} 528 + onClose={() => removeBookmark(media.id)} 529 + editable={editing} 530 + onEdit={() => handleEditBookmark(media.id)} 531 + /> 532 + </div> 533 + )) 534 + : Array.from({ length: SKELETON_COUNT }).map(() => ( 535 + <MediaCardSkeleton 536 + key={`skeleton-${categorySlug}-${Math.random().toString(36).substring(7)}`} 537 + /> 538 + ))} 613 539 614 540 {section.items.length > MAX_ITEMS_PER_SECTION && ( 615 541 <MoreBookmarksCard />
+41 -108
src/pages/parts/home/BookmarksPart.tsx
··· 1 - import { DndContext, closestCenter } from "@dnd-kit/core"; 2 - import { SortableContext, rectSortingStrategy } from "@dnd-kit/sortable"; 3 1 import { useAutoAnimate } from "@formkit/auto-animate/react"; 4 2 import { useEffect, useMemo, useState } from "react"; 5 3 import { useTranslation } from "react-i18next"; ··· 10 8 import { Icons } from "@/components/Icon"; 11 9 import { SectionHeading } from "@/components/layout/SectionHeading"; 12 10 import { MediaGrid } from "@/components/media/MediaGrid"; 11 + import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; 13 12 import { EditBookmarkModal } from "@/components/overlays/EditBookmarkModal"; 14 13 import { EditGroupModal } from "@/components/overlays/EditGroupModal"; 15 14 import { EditGroupOrderModal } from "@/components/overlays/EditGroupOrderModal"; 16 15 import { useModal } from "@/components/overlays/Modal"; 17 16 import { UserIcon, UserIcons } from "@/components/UserIcon"; 18 17 import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; 19 - import { 20 - SortableMediaCard, 21 - useBookmarkDragAndDrop, 22 - } from "@/hooks/useBookmarkDragAndDrop"; 23 18 import { useAuthStore } from "@/stores/auth"; 24 19 import { useBookmarkStore } from "@/stores/bookmarks"; 25 20 import { useGroupOrderStore } from "@/stores/groupOrder"; ··· 124 119 125 120 return { groupedItems: grouped, regularItems: regular }; 126 121 }, [items, bookmarks, progressItems]); 127 - 128 - // Drag and drop hook 129 - const { sensors, orderedItems, orderedGroupedItems, handleDragEnd } = 130 - useBookmarkDragAndDrop({ 131 - editing, 132 - items, 133 - groupedItems, 134 - }); 135 122 136 123 // group sorting 137 124 const allGroups = useMemo(() => { ··· 351 338 /> 352 339 </div> 353 340 </SectionHeading> 354 - <DndContext 355 - sensors={sensors} 356 - collisionDetection={closestCenter} 357 - onDragEnd={(e) => handleDragEnd(e, section.group)} 358 - > 359 - <SortableContext 360 - items={ 361 - editing && orderedGroupedItems[section.group || ""] 362 - ? orderedGroupedItems[section.group || ""].map( 363 - (item) => item.id, 364 - ) 365 - : section.items.map((item) => item.id) 366 - } 367 - strategy={rectSortingStrategy} 368 - > 369 - <MediaGrid> 370 - {(editing && orderedGroupedItems[section.group || ""] 371 - ? orderedGroupedItems[section.group || ""] 372 - : section.items 373 - ).map((v) => ( 374 - <div 375 - key={v.id} 376 - onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => 377 - e.preventDefault() 378 - } 379 - className="relative group" 380 - > 381 - <SortableMediaCard 382 - media={v} 383 - closable={editing} 384 - onClose={() => removeBookmark(v.id)} 385 - onShowDetails={onShowDetails} 386 - editable={editing} 387 - onEdit={() => handleEditBookmark(v.id)} 388 - isEditing={editing} 389 - /> 390 - </div> 391 - ))} 392 - </MediaGrid> 393 - </SortableContext> 394 - </DndContext> 341 + <MediaGrid> 342 + {section.items.map((v) => ( 343 + <div 344 + key={v.id} 345 + onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => 346 + e.preventDefault() 347 + } 348 + className="relative group" 349 + > 350 + <WatchedMediaCard 351 + media={v} 352 + closable={editing} 353 + onClose={() => removeBookmark(v.id)} 354 + onShowDetails={onShowDetails} 355 + editable={editing} 356 + onEdit={() => handleEditBookmark(v.id)} 357 + /> 358 + </div> 359 + ))} 360 + </MediaGrid> 395 361 </div> 396 362 ); 397 363 } // regular items ··· 418 384 /> 419 385 </div> 420 386 </SectionHeading> 421 - <DndContext 422 - sensors={sensors} 423 - collisionDetection={closestCenter} 424 - onDragEnd={(e) => handleDragEnd(e)} 425 - > 426 - <SortableContext 427 - items={ 428 - editing 429 - ? orderedItems 430 - .filter((item) => { 431 - const bookmark = bookmarks[item.id]; 432 - return ( 433 - !Array.isArray(bookmark?.group) || 434 - bookmark.group.length === 0 435 - ); 436 - }) 437 - .map((item) => item.id) 438 - : section.items.map((item) => item.id) 439 - } 440 - strategy={rectSortingStrategy} 441 - > 442 - <MediaGrid ref={gridRef}> 443 - {(editing 444 - ? orderedItems.filter((item) => { 445 - const bookmark = bookmarks[item.id]; 446 - return ( 447 - !Array.isArray(bookmark?.group) || 448 - bookmark.group.length === 0 449 - ); 450 - }) 451 - : section.items 452 - ).map((v) => ( 453 - <div 454 - key={v.id} 455 - onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => 456 - e.preventDefault() 457 - } 458 - className="relative group" 459 - > 460 - <SortableMediaCard 461 - media={v} 462 - closable={editing} 463 - onClose={() => removeBookmark(v.id)} 464 - onShowDetails={onShowDetails} 465 - editable={editing} 466 - onEdit={() => handleEditBookmark(v.id)} 467 - isEditing={editing} 468 - /> 469 - </div> 470 - ))} 471 - </MediaGrid> 472 - </SortableContext> 473 - </DndContext> 387 + <MediaGrid ref={gridRef}> 388 + {section.items.map((v) => ( 389 + <div 390 + key={v.id} 391 + onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => 392 + e.preventDefault() 393 + } 394 + className="relative group" 395 + > 396 + <WatchedMediaCard 397 + media={v} 398 + closable={editing} 399 + onClose={() => removeBookmark(v.id)} 400 + onShowDetails={onShowDetails} 401 + editable={editing} 402 + onEdit={() => handleEditBookmark(v.id)} 403 + /> 404 + </div> 405 + ))} 406 + </MediaGrid> 474 407 </div> 475 408 ); 476 409 })}
-16
src/stores/bookmarks/index.ts
··· 54 54 modifyBookmarksByGroup( 55 55 options: BulkGroupModificationOptions, 56 56 ): BookmarkModificationResult; 57 - updateBookmarkOrder(bookmarkIds: string[]): void; 58 57 clear(): void; 59 58 clearUpdateQueue(): void; 60 59 removeUpdateItem(id: string): void; ··· 277 276 }); 278 277 279 278 return result; 280 - }, 281 - updateBookmarkOrder(bookmarkIds: string[]) { 282 - set((s) => { 283 - const baseTime = Date.now(); 284 - bookmarkIds.forEach((bookmarkId, index) => { 285 - const bookmark = s.bookmarks[bookmarkId]; 286 - if (bookmark) { 287 - // Update timestamp to reflect order (earlier items have higher timestamps) 288 - // This ensures they appear first when sorted by date descending 289 - // Note: We don't add to update queue here to avoid quota errors. 290 - // Order is persisted locally via updatedAt timestamps. 291 - bookmark.updatedAt = baseTime - index; 292 - } 293 - }); 294 - }); 295 279 }, 296 280 })), 297 281 {