118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
/**
|
|
* Keeps a page's list of playlists in step with the socket's `playlist_*`
|
|
* messages, and their owners' names. The user's profile, the playlists page
|
|
* and the search call it.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { useEffect, useLayoutEffect, useRef } from "react";
|
|
import type { Playlist } from "../../../shared/playlist.ts";
|
|
import { useWS, useWSEvent } from "../ws/useWS.ts";
|
|
import { insertAt, usePositionMemory } from "../feed/usePositionMemory.ts";
|
|
import { withByDate } from "../../utils/feed/pagedList.ts";
|
|
|
|
/** Passed to {@link usePlaylistListSync} by each page, to fit its list. */
|
|
export interface PlaylistListSyncOptions {
|
|
/** Keeps private playlists in the list, for their owner. */
|
|
isOwner?: boolean;
|
|
/**
|
|
* Adds only this user's playlists, when one is created or comes back into
|
|
* view. Without it, anyone's are added.
|
|
*/
|
|
ownerId?: string;
|
|
/**
|
|
* Adds no new playlist, for a list of followed playlists, since the socket
|
|
* doesn't say what the user follows, or of search results. Playlists
|
|
* already listed still change, go, and come back where they were.
|
|
*/
|
|
noNewEntries?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Applies the socket's playlist events to `items`, the loaded list, newest
|
|
* first, which `setPlaylists` updates. A created playlist goes where its date
|
|
* puts it, first when new, an updated one changes in place or leaves when it
|
|
* turns private (unless `isOwner`), one that left comes back where it was
|
|
* when it returns, a deleted one leaves, and a playlist's `dumpCount` follows
|
|
* its dumps. A renamed user's playlists take their new name.
|
|
*/
|
|
export function usePlaylistListSync(
|
|
items: Playlist[],
|
|
setPlaylists: (fn: (prev: Playlist[]) => Playlist[]) => void,
|
|
options?: PlaylistListSyncOptions,
|
|
): void {
|
|
const { deletedPlaylistIds } = useWS();
|
|
const takePosition = usePositionMemory(items);
|
|
|
|
// A ref, so that the effect runs on new deletions only, not on new
|
|
// callbacks.
|
|
const setPlaylistsRef = useRef(setPlaylists);
|
|
useLayoutEffect(() => {
|
|
setPlaylistsRef.current = setPlaylists;
|
|
});
|
|
|
|
useWSEvent("playlist", (ev) => {
|
|
const { isOwner, ownerId, noNewEntries } = options ?? {};
|
|
const shown = ev.playlist &&
|
|
(ev.playlist.isPublic || isOwner) &&
|
|
(!ownerId || ev.playlist.userId === ownerId);
|
|
// Read here, not in the updater, which React may call twice.
|
|
const oldIdx = shown ? takePosition(ev.playlistId) : undefined;
|
|
|
|
setPlaylists((prev) => {
|
|
if ((ev.type === "created" || ev.type === "updated") && ev.playlist) {
|
|
const playlist = ev.playlist;
|
|
const idx = prev.findIndex((p) => p.id === playlist.id);
|
|
if (idx !== -1) {
|
|
if (!shown) return prev.filter((p) => p.id !== playlist.id);
|
|
const next = [...prev];
|
|
next[idx] = playlist;
|
|
return next;
|
|
}
|
|
if (!shown) return prev;
|
|
if (oldIdx !== undefined) return insertAt(prev, playlist, oldIdx);
|
|
// An update never adds a playlist the list doesn't hold.
|
|
if (ev.type === "updated" || noNewEntries) return prev;
|
|
return withByDate(prev, playlist);
|
|
}
|
|
|
|
if (ev.type === "deleted") {
|
|
return prev.filter((p) => p.id !== ev.playlistId);
|
|
}
|
|
|
|
if (ev.type === "dumps_updated" && ev.dumpIds) {
|
|
const idx = prev.findIndex((p) => p.id === ev.playlistId);
|
|
if (idx === -1) return prev;
|
|
const next = [...prev];
|
|
next[idx] = {
|
|
...next[idx],
|
|
dumpCount: ev.dumpCount ?? ev.dumpIds.length,
|
|
};
|
|
return next;
|
|
}
|
|
|
|
return prev;
|
|
});
|
|
});
|
|
|
|
useWSEvent("user", ({ user }) => {
|
|
setPlaylists((prev) =>
|
|
prev.some((p) =>
|
|
p.userId === user.id && p.ownerUsername !== user.username
|
|
)
|
|
? prev.map((p) =>
|
|
p.userId === user.id ? { ...p, ownerUsername: user.username } : p
|
|
)
|
|
: prev
|
|
);
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (deletedPlaylistIds.size === 0) return;
|
|
setPlaylistsRef.current((prev) =>
|
|
prev.filter((p) => !deletedPlaylistIds.has(p.id))
|
|
);
|
|
}, [deletedPlaylistIds]);
|
|
}
|