feat(plex): grouped collapsible playlist view + drag-and-drop + bulk add
Playlist view now groups a run of episodes from the same show into a collapsible season/show block so a whole series doesn't sprawl into an endless flat list; movies stay standalone (larger poster card in the accordion). Two per-account layouts — Accordion and Tree — persisted via LS.plexPlaylistLayout; show groups start collapsed. Reorder is drag & drop (@dnd-kit): a show block moves as one unit, episodes reorder within their show, keyboard-draggable via KeyboardSensor. Remove works per item, per season, or per whole show. The add-to-playlist dialog is generalised to a single leaf or a whole group (tri-state none/some/all with an in/size count); the show page gains add buttons for an episode, a season, and the whole show. i18n en/hu/de.
This commit is contained in:
parent
e6b22f971a
commit
2ef22982bb
9 changed files with 603 additions and 139 deletions
|
|
@ -653,6 +653,7 @@ export interface PlexCard {
|
|||
season_number?: number | null; // episode
|
||||
episode_number?: number | null; // episode
|
||||
show_title?: string | null; // episode (in a mixed playlist)
|
||||
show_id?: string | null; // episode: the show's rating_key (for grouping a playlist by show/season)
|
||||
summary?: string | null; // episode
|
||||
}
|
||||
export interface PlexBrowseResult {
|
||||
|
|
@ -737,6 +738,7 @@ export interface PlexPlaylist {
|
|||
item_count: number;
|
||||
thumb?: string | null;
|
||||
has_item?: boolean; // only when the list was fetched with ?contains=<rating_key>
|
||||
group_in?: number; // only when fetched with ?contains_group=<rk,rk,…>: how many of the group it holds
|
||||
}
|
||||
export interface PlexPlaylistDetail {
|
||||
id: number;
|
||||
|
|
@ -1178,11 +1180,25 @@ export const api = {
|
|||
plexSetCollectionEditable: (rk: string, editable: boolean): Promise<PlexCollection> =>
|
||||
req(`/api/plex/collections/${encodeURIComponent(rk)}/editable`, { method: "POST", body: JSON.stringify({ editable }) }),
|
||||
// --- Playlists (Siftlode-native, per-user, ordered) ---
|
||||
plexPlaylists: (contains?: string): Promise<{ playlists: PlexPlaylist[] }> =>
|
||||
req(`/api/plex/playlists${contains ? `?contains=${encodeURIComponent(contains)}` : ""}`),
|
||||
// `contains`=single rating_key → per-playlist has_item; `containsGroup`=a set of rating_keys (a whole
|
||||
// season/show) → per-playlist group_in + a top-level group_size (for the bulk add-to-playlist dialog).
|
||||
plexPlaylists: (
|
||||
contains?: string,
|
||||
containsGroup?: string[],
|
||||
): Promise<{ playlists: PlexPlaylist[]; group_size?: number }> => {
|
||||
const p = new URLSearchParams();
|
||||
if (contains) p.set("contains", contains);
|
||||
if (containsGroup) p.set("contains_group", containsGroup.join(","));
|
||||
const qs = p.toString();
|
||||
return req(`/api/plex/playlists${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
plexPlaylist: (id: number): Promise<PlexPlaylistDetail> => req(`/api/plex/playlists/${id}`),
|
||||
plexCreatePlaylist: (title: string, item_rating_key?: string): Promise<PlexPlaylist> =>
|
||||
req(`/api/plex/playlists`, { method: "POST", body: JSON.stringify({ title, item_rating_key }) }),
|
||||
plexPlaylistAddBulk: (id: number, itemRks: string[]): Promise<{ added: number; item_count: number }> =>
|
||||
req(`/api/plex/playlists/${id}/items/bulk`, { method: "POST", body: JSON.stringify({ item_rating_keys: itemRks }) }),
|
||||
plexPlaylistRemoveBulk: (id: number, itemRks: string[]): Promise<{ removed: number; item_count: number }> =>
|
||||
req(`/api/plex/playlists/${id}/items/remove-bulk`, { method: "POST", body: JSON.stringify({ item_rating_keys: itemRks }) }),
|
||||
plexRenamePlaylist: (id: number, title: string): Promise<PlexPlaylist> =>
|
||||
req(`/api/plex/playlists/${id}`, { method: "PATCH", body: JSON.stringify({ title }) }),
|
||||
plexDeletePlaylist: (id: number): Promise<{ deleted: number }> =>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export const LS = {
|
|||
plexShow: "siftlode.plexShow",
|
||||
plexSort: "siftlode.plexSort",
|
||||
plexFilters: "siftlode.plexFilters",
|
||||
plexPlaylistLayout: "siftlode.plexPlaylistLayout",
|
||||
notifHistory: "siftlode.notifications",
|
||||
notifSettings: "siftlode.notifSettings",
|
||||
onboardingDismissed: "siftlode.onboarding.dismissed",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue