Merge pull request #167 from movie-web/dev

version 3.0.3
This commit is contained in:
James Hawkins 2023-02-21 20:55:50 +00:00 committed by GitHub
commit 7f28e7be3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2115 additions and 2069 deletions

View File

@ -40,7 +40,7 @@ To run this project locally for contributing or testing, run the following comma
git clone https://github.com/movie-web/movie-web git clone https://github.com/movie-web/movie-web
cd movie-web cd movie-web
yarn install yarn install
yarn start yarn dev
``` ```
To build production files, simply run `yarn build`. To build production files, simply run `yarn build`.
@ -78,4 +78,4 @@ This project would not be possible without our amazing contributors and the comm
<div style="display:flex;align-items:center;grid-gap:10px"> <div style="display:flex;align-items:center;grid-gap:10px">
<img src="https://github.com/lem6ns.png?size=20" width="20"><span><a href="https://github.com/lem6ns">@lem6ns</a> for helpfully implementing extra scrapers.</span> <img src="https://github.com/lem6ns.png?size=20" width="20"><span><a href="https://github.com/lem6ns">@lem6ns</a> for helpfully implementing extra scrapers.</span>
</div> </div>

View File

@ -1,21 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-44YVXRL61C"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-44YVXRL61C");
</script>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
@ -42,6 +27,9 @@
<script src="config.js"></script> <script src="config.js"></script>
<script src="https://cdn.jsdelivr.net/gh/movie-web/6C6F6C7A@3744edbc5f64a77985b6421ea5040e688663634b/out.js"></script> <script src="https://cdn.jsdelivr.net/gh/movie-web/6C6F6C7A@3744edbc5f64a77985b6421ea5040e688663634b/out.js"></script>
<!-- prevent darkreader extension from messing with our already dark site -->
<meta name="darkreader-lock" />
<title>movie-web</title> <title>movie-web</title>
</head> </head>
<body> <body>

View File

@ -1,6 +1,6 @@
{ {
"name": "movie-web", "name": "movie-web",
"version": "3.0.2", "version": "3.0.3",
"private": true, "private": true,
"homepage": "https://movie.squeezebox.dev", "homepage": "https://movie.squeezebox.dev",
"dependencies": { "dependencies": {
@ -20,6 +20,7 @@
"pako": "^2.1.0", "pako": "^2.1.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-ga4": "^2.0.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-i18next": "^12.1.1", "react-i18next": "^12.1.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
@ -85,4 +86,4 @@
"vite-plugin-checker": "^0.5.6", "vite-plugin-checker": "^0.5.6",
"vite-plugin-package-version": "^1.0.2" "vite-plugin-package-version": "^1.0.2"
} }
} }

View File

@ -1,7 +1,10 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
export function BrandPill(props: { clickable?: boolean }) { export function BrandPill(props: {
clickable?: boolean;
hideTextOnMobile?: boolean;
}) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -13,7 +16,14 @@ export function BrandPill(props: { clickable?: boolean }) {
}`} }`}
> >
<Icon className="text-xl" icon={Icons.MOVIE_WEB} /> <Icon className="text-xl" icon={Icons.MOVIE_WEB} />
<span className="font-semibold text-white">{t("global.name")}</span> <span
className={[
"font-semibold text-white",
props.hideTextOnMobile ? "hidden sm:block" : "",
].join(" ")}
>
{t("global.name")}
</span>
</div> </div>
); );
} }

View File

@ -12,43 +12,45 @@ export interface NavigationProps {
export function Navigation(props: NavigationProps) { export function Navigation(props: NavigationProps) {
return ( return (
<div className="fixed left-0 right-0 top-0 z-10 flex min-h-[88px] items-center justify-between py-5 px-7"> <div className="fixed left-0 right-0 top-0 z-20 min-h-[150px] bg-gradient-to-b from-denim-300 via-denim-300 to-transparent sm:from-transparent">
<div <div className="fixed left-0 right-0 flex items-center justify-between py-5 px-7">
className={`${ <div
props.bg ? "opacity-100" : "opacity-0" className={`${
} absolute inset-0 block bg-denim-100 transition-opacity duration-300`} props.bg ? "opacity-100" : "opacity-0"
> } absolute inset-0 block bg-denim-100 transition-opacity duration-300`}
<div className="pointer-events-none absolute -bottom-24 h-24 w-full bg-gradient-to-b from-denim-100 to-transparent" /> >
</div> <div className="pointer-events-none absolute -bottom-24 h-24 w-full bg-gradient-to-b from-denim-100 to-transparent" />
<div className="relative flex w-full items-center justify-center sm:w-fit">
<div className="mr-auto sm:mr-6">
<Link to="/">
<BrandPill clickable />
</Link>
</div> </div>
{props.children} <div className="relative flex w-full items-center justify-center sm:w-fit">
</div> <div className="mr-auto sm:mr-6">
<div <Link to="/">
className={`${ <BrandPill clickable />
props.children ? "hidden sm:flex" : "flex" </Link>
} relative flex-row gap-4`} </div>
> {props.children}
<a </div>
href={conf().DISCORD_LINK} <div
target="_blank" className={`${
rel="noreferrer" props.children ? "hidden sm:flex" : "flex"
className="text-2xl text-white" } relative flex-row gap-4`}
> >
<IconPatch icon={Icons.DISCORD} clickable /> <a
</a> href={conf().DISCORD_LINK}
<a target="_blank"
href={conf().GITHUB_LINK} rel="noreferrer"
target="_blank" className="text-2xl text-white"
rel="noreferrer" >
className="text-2xl text-white" <IconPatch icon={Icons.DISCORD} clickable />
> </a>
<IconPatch icon={Icons.GITHUB} clickable /> <a
</a> href={conf().GITHUB_LINK}
target="_blank"
rel="noreferrer"
className="text-2xl text-white"
>
<IconPatch icon={Icons.GITHUB} clickable />
</a>
</div>
</div> </div>
</div> </div>
); );

View File

@ -10,8 +10,8 @@ interface SectionHeadingProps {
export function SectionHeading(props: SectionHeadingProps) { export function SectionHeading(props: SectionHeadingProps) {
return ( return (
<div className={`mt-12 ${props.className}`}> <div className={props.className}>
<div className="mb-4 flex items-end"> <div className="mb-5 flex items-center">
<p className="flex flex-1 items-center font-bold uppercase text-denim-700"> <p className="flex flex-1 items-center font-bold uppercase text-denim-700">
{props.icon ? ( {props.icon ? (
<span className="mr-2 text-xl"> <span className="mr-2 text-xl">

View File

@ -45,14 +45,27 @@ function MediaCardContent({
}`} }`}
> >
<div <div
className="relative mb-4 aspect-[2/3] w-full overflow-hidden rounded-xl bg-denim-500 bg-cover bg-center transition-[border-radius] duration-100 group-hover:rounded-lg" className={[
"relative mb-4 aspect-[2/3] w-full overflow-hidden rounded-xl bg-denim-500 bg-cover bg-center transition-[border-radius] duration-100",
closable ? "" : "group-hover:rounded-lg",
].join(" ")}
style={{ style={{
backgroundImage: media.poster ? `url(${media.poster})` : undefined, backgroundImage: media.poster ? `url(${media.poster})` : undefined,
}} }}
> >
{series ? ( {series ? (
<div className="absolute right-2 top-2 rounded-md bg-denim-200 py-1 px-2 transition-colors group-hover:bg-denim-500"> <div
<p className="text-center text-xs font-bold text-slate-400 transition-colors group-hover:text-white"> className={[
"absolute right-2 top-2 rounded-md bg-denim-200 py-1 px-2 transition-colors",
closable ? "" : "group-hover:bg-denim-500",
].join(" ")}
>
<p
className={[
"text-center text-xs font-bold text-slate-400 transition-colors",
closable ? "" : "group-hover:text-white",
].join(" ")}
>
{t("seasons.seasonAndEpisode", { {t("seasons.seasonAndEpisode", {
season: series.season, season: series.season,
episode: series.episode, episode: series.episode,
@ -125,5 +138,9 @@ export function MediaCard(props: MediaCardProps) {
)}`; )}`;
if (!props.linkable) return <span>{content}</span>; if (!props.linkable) return <span>{content}</span>;
return <Link to={link}>{content}</Link>; return (
<Link to={link} className={props.closable ? "hover:cursor-default" : ""}>
{content}
</Link>
);
} }

View File

@ -1,12 +1,14 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
export function useIsMobile() { export function useIsMobile(horizontal?: boolean) {
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
const isMobileCurrent = useRef<boolean | null>(false); const isMobileCurrent = useRef<boolean | null>(false);
useEffect(() => { useEffect(() => {
function onResize() { function onResize() {
const value = window.innerWidth < 1024; const value = horizontal
? window.innerHeight < 600
: window.innerWidth < 1024;
const isChanged = isMobileCurrent.current !== value; const isChanged = isMobileCurrent.current !== value;
if (!isChanged) return; if (!isChanged) return;
@ -20,7 +22,7 @@ export function useIsMobile() {
return () => { return () => {
window.removeEventListener("resize", onResize); window.removeEventListener("resize", onResize);
}; };
}, []); }, [horizontal]);
return { return {
isMobile, isMobile,

View File

@ -5,6 +5,7 @@ import { ErrorBoundary } from "@/components/layout/ErrorBoundary";
import { conf } from "@/setup/config"; import { conf } from "@/setup/config";
import App from "@/setup/App"; import App from "@/setup/App";
import "@/setup/ga";
import "@/setup/i18n"; import "@/setup/i18n";
import "@/setup/index.css"; import "@/setup/index.css";
import "@/backend"; import "@/backend";

View File

@ -1,3 +1,4 @@
export const DISCORD_LINK = "https://discord.gg/Jhqt4Xzpfb"; export const DISCORD_LINK = "https://discord.gg/Jhqt4Xzpfb";
export const GITHUB_LINK = "https://github.com/movie-web/movie-web"; export const GITHUB_LINK = "https://github.com/movie-web/movie-web";
export const APP_VERSION = "3.0.2"; export const APP_VERSION = "3.0.3";
export const GA_ID = "G-44YVXRL61C";

8
src/setup/ga.ts Normal file
View File

@ -0,0 +1,8 @@
import ReactGA from "react-ga4";
import { GA_ID } from "@/setup/constants";
ReactGA.initialize([
{
trackingId: GA_ID,
},
]);

View File

@ -55,6 +55,7 @@
"noVideos": "Whoops, couldn't find any videos for you", "noVideos": "Whoops, couldn't find any videos for you",
"loading": "Loading...", "loading": "Loading...",
"backToHome": "Back to home", "backToHome": "Back to home",
"backToHomeShort": "Back",
"seasonAndEpisode": "S{{season}} E{{episode}}", "seasonAndEpisode": "S{{season}} E{{episode}}",
"buttons": { "buttons": {
"episodes": "Episodes", "episodes": "Episodes",

View File

@ -9,6 +9,7 @@ import {
import { AirplayAction } from "@/video/components/actions/AirplayAction"; import { AirplayAction } from "@/video/components/actions/AirplayAction";
import { ChromecastAction } from "@/video/components/actions/ChromecastAction"; import { ChromecastAction } from "@/video/components/actions/ChromecastAction";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useIsMobile } from "@/hooks/useIsMobile";
interface VideoPlayerHeaderProps { interface VideoPlayerHeaderProps {
media?: MWMediaMeta; media?: MWMediaMeta;
@ -17,6 +18,7 @@ interface VideoPlayerHeaderProps {
} }
export function VideoPlayerHeader(props: VideoPlayerHeaderProps) { export function VideoPlayerHeader(props: VideoPlayerHeaderProps) {
const { isMobile } = useIsMobile();
const { bookmarkStore, setItemBookmark } = useBookmarkContext(); const { bookmarkStore, setItemBookmark } = useBookmarkContext();
const isBookmarked = props.media const isBookmarked = props.media
? getIfBookmarkedFromPortable(bookmarkStore.bookmarks, props.media) ? getIfBookmarkedFromPortable(bookmarkStore.bookmarks, props.media)
@ -26,24 +28,26 @@ export function VideoPlayerHeader(props: VideoPlayerHeaderProps) {
return ( return (
<div className="flex items-center"> <div className="flex items-center">
<div className="flex flex-1 items-center"> <div className="flex min-w-0 flex-1 items-center">
<p className="flex items-center"> <p className="flex items-center truncate">
{props.onClick ? ( {props.onClick ? (
<span <span
onClick={props.onClick} onClick={props.onClick}
className="flex cursor-pointer items-center py-1 text-white opacity-50 transition-opacity hover:opacity-100" className="flex cursor-pointer items-center py-1 text-white opacity-50 transition-opacity hover:opacity-100"
> >
<Icon className="mr-2" icon={Icons.ARROW_LEFT} /> <Icon className="mr-2" icon={Icons.ARROW_LEFT} />
<span>{t("videoPlayer.backToHome")}</span> {isMobile ? (
<span>{t("videoPlayer.backToHomeShort")}</span>
) : (
<span>{t("videoPlayer.backToHome")}</span>
)}
</span> </span>
) : null} ) : null}
{showDivider ? ( {showDivider ? (
<span className="mx-4 h-6 w-[1.5px] rotate-[30deg] bg-white opacity-50" /> <span className="mx-4 h-6 w-[1.5px] rotate-[30deg] bg-white opacity-50" />
) : null} ) : null}
{props.media ? ( {props.media ? (
<span className="flex items-center text-white"> <span className="truncate text-white">{props.media.title}</span>
<span>{props.media.title}</span>
</span>
) : null} ) : null}
</p> </p>
{props.media && ( {props.media && (
@ -64,7 +68,7 @@ export function VideoPlayerHeader(props: VideoPlayerHeaderProps) {
<ChromecastAction /> <ChromecastAction />
</> </>
) : ( ) : (
<BrandPill /> <BrandPill hideTextOnMobile />
)} )}
</div> </div>
); );

View File

@ -31,7 +31,9 @@ export const VideoPlayerIconButton = forwardRef<
].join(" ")} ].join(" ")}
> >
<Icon icon={props.icon} className={props.iconSize ?? "text-2xl"} /> <Icon icon={props.icon} className={props.iconSize ?? "text-2xl"} />
{props.text ? <span className="ml-2">{props.text}</span> : null} <p className="hidden sm:block">
{props.text ? <span className="ml-2">{props.text}</span> : null}
</p>
</div> </div>
</button> </button>
</div> </div>

View File

@ -5,6 +5,7 @@ import { SourceSelectionPopout } from "@/video/components/popouts/SourceSelectio
import { CaptionSelectionPopout } from "@/video/components/popouts/CaptionSelectionPopout"; import { CaptionSelectionPopout } from "@/video/components/popouts/CaptionSelectionPopout";
import { useVideoPlayerDescriptor } from "@/video/state/hooks"; import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useControls } from "@/video/state/logic/controls"; import { useControls } from "@/video/state/logic/controls";
import { useIsMobile } from "@/hooks/useIsMobile";
import { import {
useInterface, useInterface,
VideoInterfaceEvent, VideoInterfaceEvent,
@ -37,6 +38,8 @@ function PopoutContainer(props: { videoInterface: VideoInterfaceEvent }) {
const [bottom, setBottom] = useState<number>(0); const [bottom, setBottom] = useState<number>(0);
const [width, setWidth] = useState<number>(0); const [width, setWidth] = useState<number>(0);
const { isMobile } = useIsMobile(true);
const calculateAndSetCoords = useCallback((rect: DOMRect, w: number) => { const calculateAndSetCoords = useCallback((rect: DOMRect, w: number) => {
const buttonCenter = rect.left + rect.width / 2; const buttonCenter = rect.left + rect.width / 2;
@ -57,7 +60,10 @@ function PopoutContainer(props: { videoInterface: VideoInterfaceEvent }) {
return ( return (
<div <div
ref={ref} ref={ref}
className="absolute z-10 grid h-[500px] w-80 grid-rows-[auto,minmax(0,1fr)] overflow-hidden rounded-lg bg-ash-200" className={[
"absolute z-10 grid w-80 grid-rows-[auto,minmax(0,1fr)] overflow-hidden rounded-lg bg-ash-200",
isMobile ? "h-[230px]" : " h-[500px]",
].join(" ")}
style={{ style={{
right: `${right}px`, right: `${right}px`,
bottom: `${bottom}px`, bottom: `${bottom}px`,

View File

@ -169,8 +169,6 @@ export function SourceSelectionPopout() {
return entries; return entries;
}); });
console.log(embedsRes);
return embedsRes; return embedsRes;
}, [scrapeResult?.embeds]); }, [scrapeResult?.embeds]);

View File

@ -32,7 +32,7 @@ function MediaViewLoading(props: { onGoBack(): void }) {
<Helmet> <Helmet>
<title>{t("videoPlayer.loading")}</title> <title>{t("videoPlayer.loading")}</title>
</Helmet> </Helmet>
<div className="absolute inset-x-0 top-0 p-6"> <div className="absolute inset-x-0 top-0 py-6 px-8">
<VideoPlayerHeader onClick={props.onGoBack} /> <VideoPlayerHeader onClick={props.onGoBack} />
</div> </div>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">

View File

@ -172,7 +172,7 @@ function NewDomainModal() {
export function HomeView() { export function HomeView() {
return ( return (
<div className="mb-16 mt-32"> <div className="mb-16">
<EmbedMigration /> <EmbedMigration />
<NewDomainModal /> <NewDomainModal />
<Bookmarks /> <Bookmarks />

View File

@ -22,7 +22,7 @@ export function SearchView() {
return ( return (
<> <>
<div className="relative z-10 mb-24"> <div className="relative z-10 mb-16 sm:mb-24">
<Helmet> <Helmet>
<title>{t("global.name")}</title> <title>{t("global.name")}</title>
</Helmet> </Helmet>
@ -32,10 +32,10 @@ export function SearchView() {
<div className="absolute left-0 bottom-0 right-0 flex h-0 justify-center"> <div className="absolute left-0 bottom-0 right-0 flex h-0 justify-center">
<div className="absolute bottom-4 h-[100vh] w-[3000px] rounded-[100%] bg-denim-300 md:w-[200vw]" /> <div className="absolute bottom-4 h-[100vh] w-[3000px] rounded-[100%] bg-denim-300 md:w-[200vw]" />
</div> </div>
<div className="relative z-20"> <div className="relative z-10 mb-16">
<div className="mb-16"> <Title className="mx-auto max-w-xs">{t("search.title")}</Title>
<Title className="mx-auto max-w-xs">{t("search.title")}</Title> </div>
</div> <div className="relative z-30">
<Sticky enabled top={16} onStateChange={stickStateChanged}> <Sticky enabled top={16} onStateChange={stickStateChanged}>
<SearchBarInput <SearchBarInput
onChange={setSearch} onChange={setSearch}

3975
yarn.lock

File diff suppressed because it is too large Load Diff