mirror of
https://github.com/AnimeThemes/animethemes-web.git
synced 2026-07-11 01:24:31 +02:00
feat: Added shuffle feature for playlists (#199)
* Improved page transition when opening video player for the first time. * Disabled prefetching for index pages.
This commit is contained in:
@@ -49,7 +49,6 @@ export function AlphabeticalIndex<T extends AlphabeticalIndexItem>({ items, chil
|
||||
key={firstLetter}
|
||||
href={`#${firstLetter}`}
|
||||
passHref
|
||||
prefetch={false}
|
||||
legacyBehavior>
|
||||
<Text as="a" link>{firstLetter.toUpperCase()} </Text>
|
||||
</Link>
|
||||
|
||||
@@ -8,6 +8,7 @@ const axios = Axios.create({
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
withCredentials: true,
|
||||
withXSRFToken: true,
|
||||
});
|
||||
|
||||
export default axios;
|
||||
|
||||
+16
-1
@@ -88,6 +88,8 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
});
|
||||
const currentWatchListItem = watchList.find((item) => item.watchListId === currentWatchListItemId) ?? null;
|
||||
|
||||
const [isWaitingForVideoPage, setWaitingForVideoPage] = useState(!isVideoPage);
|
||||
|
||||
const [isAutoPlay, setAutoPlay] = useLocalStorageState("auto-play", { defaultValue: false });
|
||||
const [isForceAutoPlay, setForceAutoPlay] = useState(false);
|
||||
|
||||
@@ -125,6 +127,19 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return () => window.removeEventListener("keydown", hotkeyListener);
|
||||
}, [pageProps.isSearch, router]);
|
||||
|
||||
// Prevent the video player from opening in the background while the video page is still loading.
|
||||
// This should only happen if the video player is opening for the first time and not on
|
||||
// subsequent page transitions.
|
||||
if (isVideoPage && currentWatchListItem && isWaitingForVideoPage) {
|
||||
setWaitingForVideoPage(false);
|
||||
|
||||
return null;
|
||||
} else if (currentWatchListItem === null && !isWaitingForVideoPage) {
|
||||
setWaitingForVideoPage(true);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isVideoPage && currentBasename !== previousBasename) {
|
||||
setPreviousBasename(currentBasename);
|
||||
if (currentBasename !== currentWatchListItem?.basename) {
|
||||
@@ -240,7 +255,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
<Footer />
|
||||
</>
|
||||
) : null}
|
||||
{currentWatchListItem && (
|
||||
{currentWatchListItem && !isWaitingForVideoPage && (
|
||||
<VideoPlayer2
|
||||
video={currentWatchListItem}
|
||||
background={!isVideoPage}
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function AnimeIndexPage({ animeAll }: AnimeIndexPageProps) {
|
||||
key={anime.slug}
|
||||
href={`/anime/${anime.slug}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
legacyBehavior
|
||||
prefetch={false}
|
||||
>
|
||||
<Text as="a" block link>{anime.name}</Text>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function ArtistIndexPage({ artistAll }: ArtistIndexPageProps) {
|
||||
key={artist.slug}
|
||||
href={`/artist/${artist.slug}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
legacyBehavior
|
||||
prefetch={false}
|
||||
>
|
||||
<Text as="a" block link>{artist.name}</Text>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -31,14 +31,14 @@ import { SEO } from "components/seo";
|
||||
import { SidebarContainer } from "components/container";
|
||||
import { MultiCoverImage } from "components/image";
|
||||
import { Column } from "components/box";
|
||||
import { Button, FilterToggleButton } from "components/button";
|
||||
import { Button, FilterToggleButton, IconTextButton } from "components/button";
|
||||
import { Collapse } from "components/utils";
|
||||
import { SearchFilterGroup, SearchFilterSortBy } from "components/search-filter";
|
||||
import styled from "styled-components";
|
||||
import theme from "theme";
|
||||
import { DescriptionList } from "components/description-list";
|
||||
import { Icon } from "components/icon";
|
||||
import { faEllipsisV, faListMusic, faMinus, faPlus, faTrophy } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faEllipsisV, faListMusic, faMinus, faPlus, faShuffle, faTrophy } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { PlaylistTrackAddDialog } from "components/dialog/PlaylistTrackAddDialog";
|
||||
import { PlaylistTrackRemoveDialog } from "components/dialog/PlaylistTrackRemoveDialog";
|
||||
import useSWR from "swr";
|
||||
@@ -49,6 +49,9 @@ import { PlaylistEditDialog } from "components/dialog/PlaylistEditDialog";
|
||||
import { Reorder } from "framer-motion";
|
||||
import axios from "lib/client/axios";
|
||||
import { FeaturedTheme } from "components/featured-theme";
|
||||
import { shuffle } from "lodash-es";
|
||||
import createVideoSlug from "../../../utils/createVideoSlug";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const StyledDesktopOnly = styled.div`
|
||||
gap: 24px;
|
||||
@@ -59,8 +62,12 @@ const StyledDesktopOnly = styled.div`
|
||||
`;
|
||||
const StyledHeader = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
& > :last-child {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
`;
|
||||
const StyledReorderContainer = styled.div`
|
||||
// Hack to style the framer motion reorder component
|
||||
@@ -113,6 +120,7 @@ interface PlaylistDetailPageParams extends ParsedUrlQuery {
|
||||
|
||||
export default function PlaylistDetailPage({ playlist: initialPlaylist, me: initialMe }: PlaylistDetailPageProps) {
|
||||
const { setWatchList, setWatchListFactory, setCurrentWatchListItem, addWatchListItem, addWatchListItemNext } = useContext(PlayerContext);
|
||||
const router = useRouter();
|
||||
|
||||
const { data: playlist, mutate } = useSWR(
|
||||
["PlaylistDetailPagePlaylist", `/api/playlist/${initialPlaylist.id}`],
|
||||
@@ -193,6 +201,26 @@ export default function PlaylistDetailPage({ playlist: initialPlaylist, me: init
|
||||
setCurrentWatchListItem(watchList[initiatingVideoIndex]);
|
||||
}
|
||||
|
||||
function shuffleAll() {
|
||||
if (tracksSorted.length === 0) {
|
||||
return;
|
||||
}
|
||||
const watchList = shuffle(tracksSorted.map((track) => createWatchListItem(track.video)));
|
||||
setWatchList(watchList, true);
|
||||
setWatchListFactory(null);
|
||||
setCurrentWatchListItem(watchList[0]);
|
||||
|
||||
const video = watchList[0];
|
||||
const entry = video.entries[0];
|
||||
const theme = entry.theme;
|
||||
const anime = theme?.anime;
|
||||
|
||||
if (anime && entry && video) {
|
||||
const videoSlug = createVideoSlug(theme, entry, video);
|
||||
void router.push(`/anime/${anime.slug}/${videoSlug}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTrackOrder(newTracks: typeof tracks) {
|
||||
await mutate({
|
||||
...playlist,
|
||||
@@ -315,6 +343,9 @@ export default function PlaylistDetailPage({ playlist: initialPlaylist, me: init
|
||||
Themes
|
||||
<Text color="text-disabled"> ({playlist.tracks_count})</Text>
|
||||
</Text>
|
||||
{tracksSorted.length > 0 && (
|
||||
<IconTextButton icon={faShuffle} collapsible onClick={shuffleAll}>Shuffle All</IconTextButton>
|
||||
)}
|
||||
<FilterToggleButton onClick={toggleShowFilter}/>
|
||||
</StyledHeader>
|
||||
<Collapse collapse={!showFilter}>
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function SeriesIndexPage({ seriesAll }: SeriesIndexPageProps) {
|
||||
key={series.slug}
|
||||
href={`/series/${series.slug}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
legacyBehavior
|
||||
prefetch={false}
|
||||
>
|
||||
<Text as="a" block link>{series.name}</Text>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,9 @@ export default function StudioIndexPage({ studioAll }: StudioIndexPageProps) {
|
||||
key={studio.slug}
|
||||
href={`/studio/${studio.slug}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
legacyBehavior
|
||||
prefetch={false}
|
||||
>
|
||||
<Text as="a" block link>{studio.name}</Text>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user