1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-20 14:37:43 +01:00

Compare commits

...

12 commits

Author SHA1 Message Date
Ivan Evans
5d2bad7a92
Merge 9083f110c8 into fad6316020 2024-09-23 03:08:09 -06:00
Cooper
fad6316020
update all packages 2024-09-23 00:31:38 +00:00
Cooper
af31bdb1cc
hopefully fix werid build errors 2024-09-23 00:23:14 +00:00
Cooper
c06e455453
attempt to fix manual chunk loading 2024-09-23 00:19:13 +00:00
Ivan Evans
9083f110c8
Merge branch 'sussy-code:main' into pwa-improvements 2024-09-16 22:58:54 -06:00
Ivan Evans
3543ec093f Revert "Disable scrolling in player"
This reverts commit 1a36d96f48.

Reverted due to it disabling pull to refresh on mobile :(
2024-08-26 22:45:26 -06:00
Ivan Evans
1e4ec0cbb2
Merge branch 'sussy-code:main' into pwa-improvements 2024-08-24 13:32:43 -06:00
Ivan Evans
d6a2b91486 Fix top padding and safe areas for iOS PWA 2024-08-15 13:51:18 -06:00
Ivan Evans
2e08ed9771 Add Widescreen button*
*Primarily adds a Widescreen button to replace a native iOS fullscreen feature. (Due to it being disabled now)

Also available by holding shift and pressing the fullscreen button on desktop.
2024-08-15 13:06:59 -06:00
Ivan Evans
c748d0c179 Add info button for iOS PWA*
Explains why PiP and Fullscreen are disabled.

*Needs translations
2024-08-15 12:51:47 -06:00
Ivan Evans
05c4988066 Disable PiP and Fullscreen for iOS PWA 2024-08-15 12:47:14 -06:00
Ivan Evans
1a36d96f48 Disable scrolling in player 2024-08-15 12:33:48 -06:00
13 changed files with 401 additions and 312 deletions

View file

@ -21,6 +21,8 @@
<meta name="theme-color" content="#120f1d" /> <meta name="theme-color" content="#120f1d" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-startup-image" <link rel="apple-touch-startup-image"
media="screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" media="screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
href="/splash_screens/iPhone_15_Pro_Max__iPhone_15_Plus__iPhone_14_Pro_Max_landscape.png"> href="/splash_screens/iPhone_15_Pro_Max__iPhone_15_Plus__iPhone_14_Pro_Max_landscape.png">

View file

@ -46,7 +46,7 @@
"detect-browser": "^5.3.0", "detect-browser": "^5.3.0",
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
"flag-icons": "^7.2.3", "flag-icons": "^7.2.3",
"focus-trap-react": "^10.2.3", "focus-trap-react": "^10.3.0",
"fscreen": "^1.2.0", "fscreen": "^1.2.0",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"hls.js": "^1.5.15", "hls.js": "^1.5.15",
@ -58,7 +58,7 @@
"million": "^2.6.4", "million": "^2.6.4",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"ofetch": "^1.3.4", "ofetch": "^1.4.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
@ -78,7 +78,7 @@
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4", "@babel/preset-env": "^7.25.4",
"@babel/preset-typescript": "^7.24.7", "@babel/preset-typescript": "^7.24.7",
"@rollup/wasm-node": "^4.21.2", "@rollup/wasm-node": "^4.22.4",
"@types/chromecast-caf-sender": "^1.0.10", "@types/chromecast-caf-sender": "^1.0.10",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
@ -88,7 +88,7 @@
"@types/lodash.throttle": "^4.1.9", "@types/lodash.throttle": "^4.1.9",
"@types/node": "^20.16.5", "@types/node": "^20.16.5",
"@types/pako": "^2.0.3", "@types/pako": "^2.0.3",
"@types/react": "^18.3.5", "@types/react": "^18.3.8",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"@types/react-lazy-load-image-component": "^1.6.4", "@types/react-lazy-load-image-component": "^1.6.4",
@ -102,7 +102,7 @@
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.3", "eslint-import-resolver-typescript": "^3.6.3",
@ -114,18 +114,18 @@
"glob": "^10.4.5", "glob": "^10.4.5",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"jsdom": "^23.2.0", "jsdom": "^23.2.0",
"postcss": "^8.4.45", "postcss": "^8.4.47",
"postcss-rtl": "^2.0.0", "postcss-rtl": "^2.0.0",
"postcss-rtlcss": "^4.0.9", "postcss-rtlcss": "^4.0.9",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.5.14", "prettier-plugin-tailwindcss": "^0.5.14",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"tailwind-scrollbar": "^3.1.0", "tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.12",
"tailwindcss-themer": "^4.0.0", "tailwindcss-themer": "^4.0.0",
"type-fest": "^4.26.1", "type-fest": "^4.26.1",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"vite": "^5.4.3", "vite": "^5.4.7",
"vite-plugin-checker": "^0.6.4", "vite-plugin-checker": "^0.6.4",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-plugin-pwa": "^0.17.5", "vite-plugin-pwa": "^0.17.5",

File diff suppressed because it is too large Load diff

View file

@ -35,6 +35,10 @@ html[data-no-scroll], html[data-no-scroll] body {
overflow: hidden; overflow: hidden;
} }
.top-content {
padding-top: calc(env(safe-area-inset-top) - 20px);
}
.roll { .roll {
animation: roll 1s; animation: roll 1s;
} }

View file

@ -68,6 +68,8 @@ export enum Icons {
BRUSH = "brush", BRUSH = "brush",
UPLOAD = "upload", UPLOAD = "upload",
WEB = "web", WEB = "web",
SHRINK = "shrink",
STRETCH = "stretch",
} }
export interface IconProps { export interface IconProps {
@ -153,6 +155,8 @@ const iconList: Record<Icons, string> = {
<path d="M22.0182 15.0781C20.9582 15.403 18.7915 16.0311 16.4781 16.4781C16.0311 18.7915 15.403 20.9581 15.0781 22.0182L15.0702 22.044C18.4002 21.0274 21.0274 18.4002 22.044 15.0702L22.0182 15.0781Z" fill="currentColor"/> <path d="M22.0182 15.0781C20.9582 15.403 18.7915 16.0311 16.4781 16.4781C16.0311 18.7915 15.403 20.9581 15.0781 22.0182L15.0702 22.044C18.4002 21.0274 21.0274 18.4002 22.044 15.0702L22.0182 15.0781Z" fill="currentColor"/>
<path d="M1.6103 13.323C1.64665 13.3277 1.67628 13.3327 1.68611 13.3349C1.69472 13.337 1.70821 13.3406 1.7131 13.3419L1.72391 13.345L1.72973 13.3468L1.73585 13.3487L1.74098 13.3503C1.7381 13.3494 1.67976 13.3348 1.6103 13.323Z" fill="currentColor"/> <path d="M1.6103 13.323C1.64665 13.3277 1.67628 13.3327 1.68611 13.3349C1.69472 13.337 1.70821 13.3406 1.7131 13.3419L1.72391 13.345L1.72973 13.3468L1.73585 13.3487L1.74098 13.3503C1.7381 13.3494 1.67976 13.3348 1.6103 13.323Z" fill="currentColor"/>
</svg>`, </svg>`,
shrink: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M439 7c9.4-9.4 24.6-9.4 33.9 0l32 32c9.4 9.4 9.4 24.6 0 33.9l-87 87 39 39c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8l-144 0c-13.3 0-24-10.7-24-24l0-144c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2l39 39L439 7zM72 272l144 0c13.3 0 24 10.7 24 24l0 144c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-39-39L73 505c-9.4 9.4-24.6 9.4-33.9 0L7 473c-9.4-9.4-9.4-24.6 0-33.9l87-87L55 313c-6.9-6.9-8.9-17.2-5.2-26.2s12.5-14.8 22.2-14.8z"/></svg>`,
stretch: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" fill="currentColor"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M344 0L488 0c13.3 0 24 10.7 24 24l0 144c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-39-39-87 87c-9.4 9.4-24.6 9.4-33.9 0l-32-32c-9.4-9.4-9.4-24.6 0-33.9l87-87L327 41c-6.9-6.9-8.9-17.2-5.2-26.2S334.3 0 344 0zM168 512L24 512c-13.3 0-24-10.7-24-24L0 344c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2l39 39 87-87c9.4-9.4 24.6-9.4 33.9 0l32 32c9.4 9.4 9.4 24.6 0 33.9l-87 87 39 39c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8z"/></svg>`,
}; };
function ChromeCastButton() { function ChromeCastButton() {

View file

@ -0,0 +1,23 @@
/* eslint-disable no-alert */
import { Icon, Icons } from "../Icon";
function IosPwaLimitations() {
const showAlert = () => {
alert(
"Due to Apples limitations, Picture-in-Picture (PiP) and Fullscreen are disabled on iOS PWAs. Use the browser vertion to re-enable these features.\n" +
"Tip: To hide the iOS home indicator, use guided access within the PWA!",
);
};
return (
<button
type="button"
onClick={showAlert}
className="tabbable p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-50 transition-transform duration-100 flex items-center gap-3 active:scale-110 active:bg-opacity-75 active:text-white"
>
<Icon className="text-2xl" icon={Icons.CIRCLE_QUESTION} />
</button>
);
}
export default IosPwaLimitations;

View file

@ -47,14 +47,14 @@ export function Navigation(props: NavigationProps) {
{/* backgrounds - these are seperate because of z-index issues */} {/* backgrounds - these are seperate because of z-index issues */}
<div <div
className="fixed z-[20] pointer-events-none left-0 right-0 top-0 min-h-[150px]" className="top-content fixed z-[20] pointer-events-none left-0 right-0 top-0 min-h-[150px]"
style={{ style={{
top: `${bannerHeight}px`, top: `${bannerHeight}px`,
}} }}
> >
<div <div
className={classNames( className={classNames(
"fixed left-0 right-0 h-20 flex items-center", "fixed left-0 right-0 top-0 flex items-center",
props.doBackground props.doBackground
? "bg-background-main border-b border-utils-divider border-opacity-50" ? "bg-background-main border-b border-utils-divider border-opacity-50"
: null, : null,
@ -78,7 +78,7 @@ export function Navigation(props: NavigationProps) {
{/* content */} {/* content */}
<div <div
className="fixed pointer-events-none left-0 right-0 z-[60] top-0 min-h-[150px]" className="top-content fixed pointer-events-none left-0 right-0 z-[60] top-0 min-h-[150px]"
style={{ style={{
top: `${bannerHeight}px`, top: `${bannerHeight}px`,
}} }}

View file

@ -0,0 +1,23 @@
import { useState } from "react";
import { Icons } from "@/components/Icon";
import { VideoPlayerButton } from "@/components/player/internals/Button";
export function Widescreen() {
// Add widescreen status
const [isWideScreen, setIsWideScreen] = useState(false);
return (
<VideoPlayerButton
icon={isWideScreen ? Icons.SHRINK : Icons.STRETCH}
className="text-white"
onClick={() => {
const videoElement = document.getElementById("video-element");
if (videoElement) {
videoElement.classList.toggle("object-cover");
setIsWideScreen(!isWideScreen);
}
}}
/>
);
}

View file

@ -44,7 +44,7 @@ export function TopControls(props: {
<Transition <Transition
animation="slide-down" animation="slide-down"
show={props.show} show={props.show}
className="text-white" className="top-content text-white"
> >
{props.children} {props.children}
</Transition> </Transition>

View file

@ -101,6 +101,7 @@ function VideoElement() {
return ( return (
<video <video
id="video-element"
className="absolute inset-0 w-full h-screen bg-black" className="absolute inset-0 w-full h-screen bg-black"
autoPlay autoPlay
playsInline playsInline

View file

@ -39,20 +39,30 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
}, },
[setShowBg, setIsSticky], [setShowBg, setIsSticky],
); );
const { width: windowWidth, height: windowHeight } = useWindowSize();
const { width: windowWidth } = useWindowSize(); // Detect if running as a PWA on iOS
const isIOSPWA =
/iPad|iPhone|iPod/i.test(navigator.userAgent) &&
window.matchMedia("(display-mode: standalone)").matches;
const topSpacing = 16; const topSpacing = isIOSPWA ? 60 : 16;
const [stickyOffset, setStickyOffset] = useState(topSpacing); const [stickyOffset, setStickyOffset] = useState(topSpacing);
const isLandscape = windowHeight < windowWidth && isIOSPWA;
const adjustedOffset = isLandscape
? -40 // landscape
: 0; // portrait
useEffect(() => { useEffect(() => {
if (windowWidth > 1200) { if (windowWidth > 1280) {
// On large screens the bar goes inline with the nav elements // On large screens the bar goes inline with the nav elements
setStickyOffset(topSpacing); setStickyOffset(topSpacing);
} else { } else {
// On smaller screens the bar goes below the nav elements // On smaller screens the bar goes below the nav elements
setStickyOffset(topSpacing + 60); setStickyOffset(topSpacing + 60 + adjustedOffset);
} }
}, [windowWidth]); }, [adjustedOffset, topSpacing, windowWidth]);
const time = getTimeOfDay(new Date()); const time = getTimeOfDay(new Date());
const title = randomT(`home.titles.${time}`); const title = randomT(`home.titles.${time}`);

View file

@ -1,7 +1,9 @@
import { ReactNode } from "react"; import { ReactNode, useState } from "react";
import IosPwaLimitations from "@/components/buttons/IosPwaLimitations";
import { BrandPill } from "@/components/layout/BrandPill"; import { BrandPill } from "@/components/layout/BrandPill";
import { Player } from "@/components/player"; import { Player } from "@/components/player";
import { Widescreen } from "@/components/player/atoms/Widescreen";
import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls"; import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls";
import { useIsMobile } from "@/hooks/useIsMobile"; import { useIsMobile } from "@/hooks/useIsMobile";
import { PlayerMeta, playerStatus } from "@/stores/player/slices/source"; import { PlayerMeta, playerStatus } from "@/stores/player/slices/source";
@ -20,6 +22,26 @@ export function PlayerPart(props: PlayerPartProps) {
const { isMobile } = useIsMobile(); const { isMobile } = useIsMobile();
const isLoading = usePlayerStore((s) => s.mediaPlaying.isLoading); const isLoading = usePlayerStore((s) => s.mediaPlaying.isLoading);
// Detect if running as a PWA on iOS
const isIOSPWA =
/iPad|iPhone|iPod/i.test(navigator.userAgent) &&
window.matchMedia("(display-mode: standalone)").matches;
// Detect if Shift key is being held
const [isShifting, setIsShifting] = useState(false);
document.addEventListener("keydown", (event) => {
if (event.key === "Shift") {
setIsShifting(true);
}
});
document.addEventListener("keyup", (event) => {
if (event.key === "Shift") {
setIsShifting(false);
}
});
return ( return (
<Player.Container onLoad={props.onLoad} showingControls={showTargets}> <Player.Container onLoad={props.onLoad} showingControls={showTargets}>
{props.children} {props.children}
@ -116,18 +138,41 @@ export function PlayerPart(props: PlayerPartProps) {
<Player.Settings /> <Player.Settings />
</> </>
) : null} ) : null}
<Player.Fullscreen /> {/* Fullscreen on when not shifting */}
{!isShifting && <Player.Fullscreen />}
{/* Expand button visible when shifting */}
{isShifting && (
<div>
<Widescreen />
</div>
)}
</div> </div>
</div> </div>
<div className="grid grid-cols-[2.5rem,1fr,2.5rem] gap-3 lg:hidden"> <div className="grid grid-cols-[2.5rem,1fr,2.5rem] gap-3 lg:hidden">
<div /> <div />
<div className="flex justify-center space-x-3"> <div className="flex justify-center space-x-3">
{status === playerStatus.PLAYING ? <Player.Pip /> : null} {/* Disable PiP for iOS PWA */}
{!isIOSPWA &&
(status === playerStatus.PLAYING ? <Player.Pip /> : null)}
<Player.Episodes /> <Player.Episodes />
{status === playerStatus.PLAYING ? <Player.Settings /> : null} {status === playerStatus.PLAYING ? <Player.Settings /> : null}
{/* Expand button for iOS PWA only */}
{isIOSPWA && status === playerStatus.PLAYING && <Widescreen />}
</div> </div>
<div> <div>
<Player.Fullscreen /> {/* Disable for iOS PWA */}
{!isIOSPWA && (
<div>
<Player.Fullscreen />
</div>
)}
{/* Add info for iOS PWA */}
{isIOSPWA && (
<div>
<IosPwaLimitations />
</div>
)}
</div> </div>
</div> </div>
</Player.BottomControls> </Player.BottomControls>

View file

@ -122,32 +122,30 @@ export default defineConfig(({ mode }) => {
build: { build: {
sourcemap: true, sourcemap: true,
chunkSizeWarningLimit: 1600,
rollupOptions: { rollupOptions: {
output: { output: {},
manualChunks(id: string) { manualChunks(id: string) {
if (id.includes("@sozialhelden+ietf-language-tags") || id.includes("country-language")) { if (id.includes("@sozialhelden+ietf-language-tags") || id.includes("country-language")) {
return "language-db"; return "language-db";
} }
if (id.includes("hls.js")) { if (id.includes("hls.js")) {
return "hls"; return "hls";
} }
if (id.includes("node-forge") || id.includes("crypto-js")) { if (id.includes("node-forge") || id.includes("crypto-js")) {
return "auth"; return "auth";
} }
if (id.includes("locales") && !id.includes("en.json")) { if (id.includes("locales") && !id.includes("en.json")) {
return "locales"; return "locales";
} }
if (id.includes("react-dom")) { if (id.includes("react-dom")) {
return "react-dom"; return "react-dom";
} }
if (id.includes("Icon.tsx")) { if (id.includes("Icon.tsx")) {
return "Icons"; return "Icons";
} }
const isCaptioningPackage = captioningPackages.some(packageName => id.includes(packageName)); const isCaptioningPackage = captioningPackages.some(packageName => id.includes(packageName));
if (isCaptioningPackage) { if (isCaptioningPackage) {
return "caption-parsing"; return "caption-parsing";
}
} }
} }
} }