diff --git a/src/components/card/ThemeDetailCard.tsx b/src/components/card/ThemeDetailCard.tsx index 7db42f9..e3f364e 100644 --- a/src/components/card/ThemeDetailCard.tsx +++ b/src/components/card/ThemeDetailCard.tsx @@ -24,7 +24,7 @@ const StyledThemeCard = styled(Card)` const StyledRow = styled.div` display: grid; - grid-template-columns: 2rem 1fr auto; + grid-template-columns: 4ch 1fr auto; align-items: baseline; grid-gap: 1rem; diff --git a/src/components/card/VideoSummaryCard.tsx b/src/components/card/VideoSummaryCard.tsx index fe9d863..41ec8e2 100644 --- a/src/components/card/VideoSummaryCard.tsx +++ b/src/components/card/VideoSummaryCard.tsx @@ -11,7 +11,7 @@ import { TextLink } from "@/components/text/TextLink"; import { Performances } from "@/components/utils/Performances"; import { SongTitle } from "@/components/utils/SongTitle"; import { SongTitleWithArtists } from "@/components/utils/SongTitleWithArtists"; -import type { VideoSummaryCardVideoFragment } from "@/generated/graphql"; +import type { VideoSummaryCardEntryFragment, VideoSummaryCardVideoFragment } from "@/generated/graphql"; import theme from "@/theme"; import createVideoSlug from "@/utils/createVideoSlug"; import extractImages from "@/utils/extractImages"; @@ -55,6 +55,7 @@ const StyledCoverOverlay = styled.div` interface VideoSummaryCardProps { video: VideoSummaryCardVideoFragment; + entry?: VideoSummaryCardEntryFragment; menu?: ReactNode; append?: ReactNode; onPlay?(): void; @@ -62,10 +63,10 @@ interface VideoSummaryCardProps { } export const VideoSummaryCard = forwardRef(function VideoSummaryCard( - { video, menu, append, onPlay, isPlaying, ...props }: VideoSummaryCardProps, + { video, entry: filterEntry, menu, append, onPlay, isPlaying, ...props }: VideoSummaryCardProps, ref: ForwardedRef, ) { - const entry = video.entries[0]; + const entry = video.entries.find((entry) => entry.id === filterEntry?.id) ?? video.entries[0]; const theme = entry.theme; const anime = theme?.anime; @@ -148,3 +149,9 @@ export const VideoSummaryCardFragmentVideo = gql` } } `; + +export const VideoSummaryCardFragmentEntry = gql` + fragment VideoSummaryCardEntry on Entry { + id + } +`; diff --git a/src/components/dialog/PlaylistTrackAddDialog.tsx b/src/components/dialog/PlaylistTrackAddDialog.tsx index 27596e4..3afe542 100644 --- a/src/components/dialog/PlaylistTrackAddDialog.tsx +++ b/src/components/dialog/PlaylistTrackAddDialog.tsx @@ -21,6 +21,7 @@ import { PlaylistTrackRemoveToast } from "@/components/toast/PlaylistTrackRemove import { Busy } from "@/components/utils/Busy"; import { useToasts } from "@/context/toastContext"; import type { + PlaylistTrackAddDialogEntryFragment, PlaylistTrackAddDialogVideoFragment, PlaylistTrackAddFormPlaylistQuery, PlaylistTrackAddFormPlaylistQueryVariables, @@ -30,10 +31,11 @@ import axios from "@/lib/client/axios"; interface PlaylistTrackAddDialogProps { video: PlaylistTrackAddDialogVideoFragment; + entry: PlaylistTrackAddDialogEntryFragment; trigger?: ReactNode; } -export function PlaylistTrackAddDialog({ video, trigger }: PlaylistTrackAddDialogProps) { +export function PlaylistTrackAddDialog({ video, entry, trigger }: PlaylistTrackAddDialogProps) { const [open, setOpen] = useState(false); return ( @@ -49,7 +51,7 @@ export function PlaylistTrackAddDialog({ video, trigger }: PlaylistTrackAddDialo {/* Only render the form when dialog is open, so it will reset after closing. */} {open ? ( - setOpen(false)} /> + setOpen(false)} /> ) : null} @@ -68,14 +70,20 @@ PlaylistTrackAddDialog.fragments = { id } `, + entry: gql` + fragment PlaylistTrackAddDialogEntry on Entry { + id + } + `, }; interface PlaylistTrackAddFormProps { video: PlaylistTrackAddDialogVideoFragment; + entry: PlaylistTrackAddDialogEntryFragment; onCancel(): void; } -function PlaylistTrackAddForm({ video, onCancel }: PlaylistTrackAddFormProps) { +function PlaylistTrackAddForm({ video, entry, onCancel }: PlaylistTrackAddFormProps) { const { data: playlists } = useSWR(["PlaylistTrackAddFormPlaylist", "/api/me/playlist", video.id], async () => { const { data } = await fetchDataClient< PlaylistTrackAddFormPlaylistQuery, @@ -135,7 +143,7 @@ function PlaylistTrackAddForm({ video, onCancel }: PlaylistTrackAddFormProps) { {playlists?.length ? ( playlists.map((playlist) => ( - + )) ) : ( You have not created a playlist, yet. @@ -162,9 +170,10 @@ interface PlaylistTrackAddCardProps { playlist: NonNullable[number] & Partial[number]>; video: PlaylistTrackAddDialogVideoFragment; + entry: PlaylistTrackAddDialogEntryFragment; } -function PlaylistTrackAddCard({ playlist, video }: PlaylistTrackAddCardProps) { +function PlaylistTrackAddCard({ playlist, video, entry }: PlaylistTrackAddCardProps) { const { dispatchToast } = useToasts(); const [isBusy, setBusy] = useState(false); @@ -173,7 +182,7 @@ function PlaylistTrackAddCard({ playlist, video }: PlaylistTrackAddCardProps) { setBusy(true); try { - await axios.post(`/playlist/${playlist.id}/track`, { video_id: video.id }); + await axios.post(`/playlist/${playlist.id}/track`, { video_id: video.id, entry_id: entry.id }); await mutate((key) => [key].flat().some((key) => key === `/api/playlist/${playlist.id}` || key === "/api/me/playlist"), ); diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx index 1ecd186..a2a7703 100644 --- a/src/components/footer/Footer.tsx +++ b/src/components/footer/Footer.tsx @@ -53,9 +53,12 @@ export function Footer() { - Transparency - Donate FAQ + Donate + Transparency + + API Documentation + Terms of Service diff --git a/src/components/menu/ThemeMenu.tsx b/src/components/menu/ThemeMenu.tsx index 22d8c98..aded9e4 100644 --- a/src/components/menu/ThemeMenu.tsx +++ b/src/components/menu/ThemeMenu.tsx @@ -49,6 +49,7 @@ export function ThemeMenu({ theme }: ThemeMenuProps) { event.preventDefault()}> diff --git a/src/components/video-player/VideoPlayerBar.tsx b/src/components/video-player/VideoPlayerBar.tsx index 9411fd2..8811503 100644 --- a/src/components/video-player/VideoPlayerBar.tsx +++ b/src/components/video-player/VideoPlayerBar.tsx @@ -189,6 +189,7 @@ export function VideoPlayerBar() { }, ], }} + entry={entry} trigger={ Add to Playlist diff --git a/src/components/video-player/VideoPlayerOverlay.tsx b/src/components/video-player/VideoPlayerOverlay.tsx index fae9fd6..c0e0cf5 100644 --- a/src/components/video-player/VideoPlayerOverlay.tsx +++ b/src/components/video-player/VideoPlayerOverlay.tsx @@ -89,6 +89,7 @@ export function VideoPlayerOverlay({ anime, themeIndex, entryIndex, videoIndex } }, ], }} + entry={entry} trigger={} /> ; playlist: Playlist; @@ -1112,6 +1113,7 @@ export type PlaylistTrackResolvers< ContextType = any, ParentType extends ResolversParentTypes["PlaylistTrack"] = ResolversParentTypes["PlaylistTrack"], > = { + entry?: Resolver; id?: Resolver; next?: Resolver, ParentType, ContextType>; playlist?: Resolver; diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index e719e63..40f113f 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -196,6 +196,7 @@ export type PlaylistSearchResult = EntitySearchResult & { }; export type PlaylistTrack = { + entry: Entry; id: Scalars["String"]["output"]; next: Maybe; playlist: Playlist; @@ -743,6 +744,8 @@ export type VideoSummaryCardVideoFragment = { audio: { basename: string }; }; +export type VideoSummaryCardEntryFragment = { id: number }; + export type PlaylistEditDialogPlaylistFragment = { id: string; name: string; visibility: PlaylistVisibility }; export type PlaylistRemoveDialogPlaylistFragment = { @@ -774,6 +777,8 @@ export type PlaylistTrackAddDialogVideoFragment = { audio: { basename: string }; }; +export type PlaylistTrackAddDialogEntryFragment = { id: number }; + export type PlaylistTrackAddFormPlaylistQueryVariables = Exact<{ filterVideoId: Scalars["Int"]["input"]; }>; @@ -2395,6 +2400,7 @@ export type PlaylistDetailPagePlaylistQuery = { }>; audio: { basename: string }; }; + entry: { id: number }; previous: { id: string } | null; next: { id: string } | null; }>; @@ -2441,6 +2447,7 @@ export type PlaylistDetailPagePlaylistFragment = { }>; audio: { basename: string }; }; + entry: { id: number }; previous: { id: string } | null; next: { id: string } | null; }>; @@ -2495,6 +2502,7 @@ export type PlaylistDetailPageQuery = { }>; audio: { basename: string }; }; + entry: { id: number }; previous: { id: string } | null; next: { id: string } | null; }>; diff --git a/src/lib/client/search.ts b/src/lib/client/search.ts index 71e566a..e7caf25 100644 --- a/src/lib/client/search.ts +++ b/src/lib/client/search.ts @@ -121,18 +121,18 @@ export const searchResolvers: Resolvers = { ); searchParams.append("include[artist]", "images,songs"); searchParams.append("include[playlist]", "user"); - searchParams.append("fields[anime]", "name,slug,year,season"); - searchParams.append("fields[animetheme]", "type,sequence,id"); - searchParams.append("fields[group]", "name,slug"); + searchParams.append("fields[anime]", "id,name,slug,year,season"); + searchParams.append("fields[animetheme]", "id,type,sequence"); + searchParams.append("fields[group]", "id,name,slug"); searchParams.append("fields[animethemeentry]", "id,version,episodes,spoiler,nsfw"); searchParams.append("fields[video]", "id,tags,resolution,nc,subbed,lyrics,uncen,source,overlap,basename"); - searchParams.append("fields[image]", "facet,link"); + searchParams.append("fields[image]", "id,facet,link"); searchParams.append("fields[song]", "id,title"); - searchParams.append("fields[artist]", "name,slug"); - searchParams.append("fields[series]", "name,slug"); - searchParams.append("fields[studio]", "name,slug"); + searchParams.append("fields[artist]", "id,name,slug"); + searchParams.append("fields[series]", "id,name,slug"); + searchParams.append("fields[studio]", "id,name,slug"); searchParams.append("fields[playlist]", "id,name,visibility,tracks_count"); - searchParams.append("fields[user]", "name"); + searchParams.append("fields[user]", "id,name"); const result = await fetchJson(`/search?${searchParams}`); diff --git a/src/lib/common/animethemes/api.ts b/src/lib/common/animethemes/api.ts index bb2314e..17b5f77 100644 --- a/src/lib/common/animethemes/api.ts +++ b/src/lib/common/animethemes/api.ts @@ -77,6 +77,7 @@ export const INCLUDES = { user: "user", }, PlaylistTrack: { + entry: "animethemeentry", video: "video", playlist: "playlist", previous: "previous", @@ -178,6 +179,7 @@ const ALLOWED_INCLUDES: Record> = { "tracks.video.animethemeentries.animetheme.anime.images", "tracks.video", "tracks.video.animethemeentries.animetheme.group", + "tracks.animethemeentry", "tracks.previous", "tracks.next", ], @@ -186,6 +188,7 @@ const ALLOWED_INCLUDES: Record> = { "video.animethemeentries.animetheme.song.artists", "video.animethemeentries.animetheme.group", "video.audio", + "animethemeentry", ], UserAuth: ["permissions", "roles.permissions"], FeaturedTheme: [ diff --git a/src/lib/common/animethemes/resolvers.ts b/src/lib/common/animethemes/resolvers.ts index c003df9..8ffe211 100644 --- a/src/lib/common/animethemes/resolvers.ts +++ b/src/lib/common/animethemes/resolvers.ts @@ -433,6 +433,13 @@ const resolvers: Resolvers = { }), }, PlaylistTrack: { + entry: createApiResolverNotNull>()({ + extractFromParent: (track) => track.animethemeentry, + endpoint: (track) => `/playlist/${track.playlist.id}/track/${track.id}`, + extractFromResponse: (response) => response.track.animethemeentry, + type: "PlaylistTrack", + baseInclude: INCLUDES.PlaylistTrack.entry, + }), video: createApiResolverNotNull>()({ extractFromParent: (track) => track.video, endpoint: (track) => `/playlist/${track.playlist.id}/track/${track.id}`, diff --git a/src/lib/common/animethemes/type-defs.ts b/src/lib/common/animethemes/type-defs.ts index 5fa0a0a..0a8cf6f 100644 --- a/src/lib/common/animethemes/type-defs.ts +++ b/src/lib/common/animethemes/type-defs.ts @@ -259,6 +259,7 @@ const typeDefs = gql` type PlaylistTrack { id: String! + entry: Entry! video: Video! playlist: Playlist! previous: PlaylistTrack diff --git a/src/lib/common/animethemes/types.ts b/src/lib/common/animethemes/types.ts index a55f9f2..bb81a7e 100644 --- a/src/lib/common/animethemes/types.ts +++ b/src/lib/common/animethemes/types.ts @@ -134,6 +134,7 @@ export interface ApiPlaylist { export interface ApiPlaylistTrack { id: string; playlist: ApiPlaylist; + animethemeentry?: ApiEntry; video?: ApiVideo; } diff --git a/src/pages/playlist/[playlistId]/index.tsx b/src/pages/playlist/[playlistId]/index.tsx index fb5ebb7..37d7d64 100644 --- a/src/pages/playlist/[playlistId]/index.tsx +++ b/src/pages/playlist/[playlistId]/index.tsx @@ -28,7 +28,11 @@ import { Button } from "@/components/button/Button"; import { FilterToggleButton } from "@/components/button/FilterToggleButton"; import { IconTextButton } from "@/components/button/IconTextButton"; import { Card } from "@/components/card/Card"; -import { VideoSummaryCard, VideoSummaryCardFragmentVideo } from "@/components/card/VideoSummaryCard"; +import { + VideoSummaryCard, + VideoSummaryCardFragmentEntry, + VideoSummaryCardFragmentVideo, +} from "@/components/card/VideoSummaryCard"; import { SidebarContainer } from "@/components/container/SidebarContainer"; import { DescriptionList } from "@/components/description-list/DescriptionList"; import { PlaylistEditDialog } from "@/components/dialog/PlaylistEditDialog"; @@ -306,7 +310,9 @@ export default function PlaylistDetailPage({ playlist: initialPlaylist, me: init const coverImageResources = useMemo( () => playlist.tracks.flatMap((track) => { - const anime = track.video.entries[0].theme?.anime; + const entry = + track.video.entries.find((entry) => entry.id === track.entry.id) ?? track.video.entries[0]; + const anime = entry.theme?.anime; return anime ? [anime] : []; }), @@ -612,6 +618,7 @@ function PlaylistTrack({ playlist, track, isOwner, isRanking, isDraggable, onPla onPlay()} menu={ @@ -623,6 +630,7 @@ function PlaylistTrack({ playlist, track, isOwner, isRanking, isDraggable, onPla event.preventDefault()}> @@ -698,6 +706,7 @@ function PlaylistTrack({ playlist, track, isOwner, isRanking, isDraggable, onPla PlaylistDetailPage.fragments = { playlist: gql` ${VideoSummaryCardFragmentVideo} + ${VideoSummaryCardFragmentEntry} ${PlaylistEditDialog.fragments.playlist} ${PlaylistTrackRemoveDialog.fragments.playlist} @@ -723,6 +732,9 @@ PlaylistDetailPage.fragments = { } } } + entry { + ...VideoSummaryCardEntry + } previous { id }