v3: added content slugs, fixed real-time updates in client, added @mentions across the app, added new file selector and drop zone
This commit is contained in:
@@ -17,7 +17,7 @@ import type {
|
||||
} from "../model.ts";
|
||||
import { deserializePlaylist, deserializePublicUser } from "../model.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { usePlaylistListSync } from "../hooks/usePlaylistListSync.ts";
|
||||
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
|
||||
import { useFeedCache } from "../hooks/useFeedCache.ts";
|
||||
import { Avatar } from "../components/Avatar.tsx";
|
||||
@@ -55,7 +55,6 @@ function initialFeed(items: Playlist[], hasMore: boolean): PlaylistFeed {
|
||||
export function UserPlaylists() {
|
||||
const { username } = useParams();
|
||||
const { user: me, authFetch, token } = useAuth();
|
||||
const { lastPlaylistEvent, deletedPlaylistIds } = useWS();
|
||||
|
||||
const { cached: cachedCreated, saveState: saveCreated } = useFeedCache<
|
||||
Playlist
|
||||
@@ -73,6 +72,28 @@ export function UserPlaylists() {
|
||||
const [state, setState] = useState<State>({ status: "loading" });
|
||||
const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null);
|
||||
|
||||
const profileUserId = state.status === "loaded" ? state.profileUser.id : null;
|
||||
const isOwnProfile = me?.id === profileUserId;
|
||||
|
||||
const setCreated = useCallback((fn: (prev: Playlist[]) => Playlist[]) => {
|
||||
setState((s) =>
|
||||
s.status !== "loaded" ? s : { ...s, created: { ...s.created, items: fn(s.created.items) } }
|
||||
);
|
||||
}, []);
|
||||
usePlaylistListSync(setCreated, {
|
||||
isOwner: isOwnProfile,
|
||||
ownerId: profileUserId ?? undefined,
|
||||
});
|
||||
|
||||
const setFollowed = useCallback((fn: (prev: Playlist[]) => Playlist[]) => {
|
||||
setState((s) =>
|
||||
s.status !== "loaded"
|
||||
? s
|
||||
: { ...s, followed: { ...s.followed, items: fn(s.followed.items) } }
|
||||
);
|
||||
}, []);
|
||||
usePlaylistListSync(setFollowed, { noNewEntries: true });
|
||||
|
||||
useEffect(() => {
|
||||
if (!username) return;
|
||||
setState({ status: "loading" });
|
||||
@@ -246,77 +267,6 @@ export function UserPlaylists() {
|
||||
!state.followed.loadingMore,
|
||||
);
|
||||
|
||||
// Real-time WS playlist updates
|
||||
useEffect(() => {
|
||||
if (!lastPlaylistEvent || state.status !== "loaded") return;
|
||||
const ev = lastPlaylistEvent;
|
||||
const isOwnProfile = me?.username === state.profileUser.username;
|
||||
|
||||
if (ev.type === "created" && ev.playlist?.userId === state.profileUser.id) {
|
||||
if (ev.playlist.isPublic || isOwnProfile) {
|
||||
setState((s) => {
|
||||
if (s.status !== "loaded") return s;
|
||||
if (s.created.items.some((p) => p.id === ev.playlist!.id)) return s;
|
||||
return {
|
||||
...s,
|
||||
created: {
|
||||
...s.created,
|
||||
items: [ev.playlist!, ...s.created.items],
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
} else if (ev.type === "updated") {
|
||||
setState((s) => {
|
||||
if (s.status !== "loaded") return s;
|
||||
const updatedCreated = ev.playlist?.userId === state.profileUser.id
|
||||
? s.created.items
|
||||
.map((p) => p.id === ev.playlist!.id ? ev.playlist! : p)
|
||||
.filter((p) => p.isPublic || isOwnProfile)
|
||||
: s.created.items;
|
||||
const updatedFollowed = s.followed.items.map((p) =>
|
||||
p.id === ev.playlist?.id ? ev.playlist! : p
|
||||
).filter((p) => p.isPublic);
|
||||
return {
|
||||
...s,
|
||||
created: { ...s.created, items: updatedCreated },
|
||||
followed: { ...s.followed, items: updatedFollowed },
|
||||
};
|
||||
});
|
||||
} else if (ev.type === "deleted") {
|
||||
setState((s) =>
|
||||
s.status !== "loaded" ? s : {
|
||||
...s,
|
||||
created: {
|
||||
...s.created,
|
||||
items: s.created.items.filter((p) => p.id !== ev.playlistId),
|
||||
},
|
||||
followed: {
|
||||
...s.followed,
|
||||
items: s.followed.items.filter((p) => p.id !== ev.playlistId),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [lastPlaylistEvent, me]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!deletedPlaylistIds.size || state.status !== "loaded") return;
|
||||
setState((s) =>
|
||||
s.status !== "loaded" ? s : {
|
||||
...s,
|
||||
created: {
|
||||
...s.created,
|
||||
items: s.created.items.filter((p) => !deletedPlaylistIds.has(p.id)),
|
||||
},
|
||||
followed: {
|
||||
...s.followed,
|
||||
items: s.followed.items.filter((p) => !deletedPlaylistIds.has(p.id)),
|
||||
},
|
||||
}
|
||||
);
|
||||
}, [deletedPlaylistIds]);
|
||||
|
||||
// Scroll save
|
||||
useEffect(() => {
|
||||
if (state.status !== "loaded") return;
|
||||
@@ -395,7 +345,6 @@ export function UserPlaylists() {
|
||||
}
|
||||
|
||||
const { profileUser, created, followed } = state;
|
||||
const isOwnProfile = me?.username === profileUser.username;
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
|
||||
Reference in New Issue
Block a user