pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #537 from movie-web/mobile-fixes

Several fixes

authored by

mrjvs and committed by
GitHub
7537b8c1 58c91d1b

+111 -44
+7 -2
src/components/Avatar.tsx
··· 66 66 <> 67 67 <Avatar 68 68 profile={auth.account.profile} 69 - sizeClass={props.sizeClass ?? "w-[2rem] h-[2rem]"} 69 + sizeClass={ 70 + props.sizeClass ?? "w-[1.5rem] h-[1.5rem] ssm:w-[2rem] ssm:h-[2rem]" 71 + } 70 72 iconClass={props.iconClass} 71 73 bottom={props.bottom} 72 74 /> ··· 84 86 export function NoUserAvatar(props: { iconClass?: string }) { 85 87 return ( 86 88 <div className="relative inline-block p-1 text-type-dimmed"> 87 - <Icon className={props.iconClass ?? "text-xl"} icon={Icons.MENU} /> 89 + <Icon 90 + className={props.iconClass ?? "text-base ssm:text-xl"} 91 + icon={Icons.MENU} 92 + /> 88 93 </div> 89 94 ); 90 95 }
+2
src/components/Icon.tsx
··· 5 5 BOOKMARK = "bookmark", 6 6 BOOKMARK_OUTLINE = "bookmark_outline", 7 7 CLOCK = "clock", 8 + EYE = "eye", 8 9 EYE_SLASH = "eyeSlash", 9 10 ARROW_LEFT = "arrowLeft", 10 11 ARROW_RIGHT = "arrowRight", ··· 73 74 search: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z"/></svg>`, 74 75 bookmark: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M384 48V512l-192-112L0 512V48C0 21.5 21.5 0 48 0h288C362.5 0 384 21.5 384 48z"/></svg>`, 75 76 clock: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512zM232 256C232 264 236 271.5 242.7 275.1L338.7 339.1C349.7 347.3 364.6 344.3 371.1 333.3C379.3 322.3 376.3 307.4 365.3 300L280 243.2V120C280 106.7 269.3 96 255.1 96C242.7 96 231.1 106.7 231.1 120L232 256z"/></svg>`, 77 + eye: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>`, 76 78 eyeSlash: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M150.7 92.77C195 58.27 251.8 32 320 32C400.8 32 465.5 68.84 512.6 112.6C559.4 156 590.7 207.1 605.5 243.7C608.8 251.6 608.8 260.4 605.5 268.3C592.1 300.6 565.2 346.1 525.6 386.7L630.8 469.1C641.2 477.3 643.1 492.4 634.9 502.8C626.7 513.2 611.6 515.1 601.2 506.9L9.196 42.89C-1.236 34.71-3.065 19.63 5.112 9.196C13.29-1.236 28.37-3.065 38.81 5.112L150.7 92.77zM223.1 149.5L313.4 220.3C317.6 211.8 320 202.2 320 191.1C320 180.5 316.1 169.7 311.6 160.4C314.4 160.1 317.2 159.1 320 159.1C373 159.1 416 202.1 416 255.1C416 269.7 413.1 282.7 407.1 294.5L446.6 324.7C457.7 304.3 464 280.9 464 255.1C464 176.5 399.5 111.1 320 111.1C282.7 111.1 248.6 126.2 223.1 149.5zM320 480C239.2 480 174.5 443.2 127.4 399.4C80.62 355.1 49.34 304 34.46 268.3C31.18 260.4 31.18 251.6 34.46 243.7C44 220.8 60.29 191.2 83.09 161.5L177.4 235.8C176.5 242.4 176 249.1 176 255.1C176 335.5 240.5 400 320 400C338.7 400 356.6 396.4 373 389.9L446.2 447.5C409.9 467.1 367.8 480 320 480H320z"/></svg>`, 77 79 arrowLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>`, 78 80 chevronDown: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>`,
+2 -2
src/components/form/PassphraseDisplay.tsx
··· 38 38 <span className="text-sm">{t("actions.copy")}</span> 39 39 </button> 40 40 </div> 41 - <div className="px-4 py-4 grid grid-cols-4 gap-2"> 41 + <div className="px-4 py-4 grid grid-cols-3 text-sm sm:text-base sm:grid-cols-4 gap-2"> 42 42 {individualWords.map((word, i) => ( 43 43 <div 44 - className="px-4 rounded-md py-2 bg-authentication-wordBackground text-white font-medium text-center" 44 + className="rounded-md py-2 bg-authentication-wordBackground text-white font-medium text-center" 45 45 // this doesn't get rerendered nor does it have state so its fine 46 46 // eslint-disable-next-line react/no-array-index-key 47 47 key={i}
+5 -2
src/components/layout/Navigation.tsx
··· 79 79 > 80 80 <div className={classNames("fixed left-0 right-0 flex items-center")}> 81 81 <div className="px-7 py-5 relative z-[60] flex flex-1 items-center justify-between"> 82 - <div className="flex items-center space-x-3 pointer-events-auto"> 83 - <Link className="block tabbable rounded-full" to="/"> 82 + <div className="flex items-center space-x-1.5 ssm:space-x-3 pointer-events-auto"> 83 + <Link 84 + className="block tabbable rounded-full text-xs ssm:text-base" 85 + to="/" 86 + > 84 87 <BrandPill clickable /> 85 88 </Link> 86 89 <a
+2 -2
src/components/overlays/Modal.tsx
··· 18 18 19 19 export function ModalCard(props: { children?: ReactNode }) { 20 20 return ( 21 - <div className="w-[30rem] max-w-full"> 22 - <div className="w-full bg-dropdown-background rounded-xl p-8 m-4 pointer-events-auto"> 21 + <div className="w-full max-w-[30rem] m-4"> 22 + <div className="w-full bg-dropdown-background rounded-xl p-8 pointer-events-auto"> 23 23 {props.children} 24 24 </div> 25 25 </div>
+1 -1
src/components/player/atoms/settings/PlaybackSettingsView.tsx
··· 47 47 [display] 48 48 ); 49 49 50 - const options = [0.25, 0.5, 1, 1.25, 2]; 50 + const options = [0.25, 0.5, 1, 1.5, 2]; 51 51 52 52 return ( 53 53 <>
+5
src/components/player/hooks/useCaptions.ts
··· 46 46 else await selectLastUsedLanguage(); 47 47 }, [selectLastUsedLanguage, disable, enabled]); 48 48 49 + const selectLastUsedLanguageIfEnabled = useCallback(async () => { 50 + if (enabled) await selectLastUsedLanguage(); 51 + }, [selectLastUsedLanguage, enabled]); 52 + 49 53 return { 50 54 selectLanguage, 51 55 disable, 52 56 selectLastUsedLanguage, 53 57 toggleLastUsed, 58 + selectLastUsedLanguageIfEnabled, 54 59 }; 55 60 }
+21 -1
src/components/player/hooks/useInitializePlayer.ts
··· 1 - import { useCallback } from "react"; 1 + import { useCallback, useEffect, useMemo, useRef } from "react"; 2 2 3 3 import { usePlayerStore } from "@/stores/player/store"; 4 4 import { useVolumeStore } from "@/stores/volume"; 5 + 6 + import { useCaptions } from "./useCaptions"; 5 7 6 8 export function useInitializePlayer() { 7 9 const display = usePlayerStore((s) => s.display); ··· 15 17 init, 16 18 }; 17 19 } 20 + 21 + export function useInitializeSource() { 22 + const source = usePlayerStore((s) => s.source); 23 + const sourceIdentifier = useMemo( 24 + () => (source ? JSON.stringify(source) : null), 25 + [source] 26 + ); 27 + const { selectLastUsedLanguageIfEnabled } = useCaptions(); 28 + 29 + const funRef = useRef(selectLastUsedLanguageIfEnabled); 30 + useEffect(() => { 31 + funRef.current = selectLastUsedLanguageIfEnabled; 32 + }, [selectLastUsedLanguageIfEnabled]); 33 + 34 + useEffect(() => { 35 + if (sourceIdentifier) funRef.current(); 36 + }, [sourceIdentifier]); 37 + }
+3
src/components/player/internals/VideoContainer.tsx
··· 5 5 import { playerStatus } from "@/stores/player/slices/source"; 6 6 import { usePlayerStore } from "@/stores/player/store"; 7 7 8 + import { useInitializeSource } from "../hooks/useInitializePlayer"; 9 + 8 10 // initialize display interface 9 11 function useDisplayInterface() { 10 12 const display = usePlayerStore((s) => s.display); ··· 112 114 export function VideoContainer() { 113 115 const show = useShouldShowVideoElement(); 114 116 useDisplayInterface(); 117 + useInitializeSource(); 115 118 116 119 if (!show) return null; 117 120 return <VideoElement />;
+2
src/components/text-inputs/AuthInputBox.tsx
··· 7 7 autoComplete?: string; 8 8 placeholder?: string; 9 9 onChange?: (data: string) => void; 10 + passwordToggleable?: boolean; 10 11 }) { 11 12 return ( 12 13 <div className="space-y-3"> ··· 19 20 autoComplete={props.autoComplete} 20 21 onChange={props.onChange} 21 22 placeholder={props.placeholder} 23 + passwordToggleable={props.passwordToggleable} 22 24 className="w-full flex-1 bg-authentication-inputBg px-4 py-3 text-search-text focus:outline-none rounded-lg placeholder:text-gray-700" 23 25 /> 24 26 </div>
+36 -16
src/components/text-inputs/TextInputControl.tsx
··· 1 - import { forwardRef } from "react"; 1 + import classNames from "classnames"; 2 + import { forwardRef, useState } from "react"; 3 + 4 + import { Icon, Icons } from "../Icon"; 2 5 3 6 export interface TextInputControlPropsNoLabel { 4 7 onChange?: (data: string) => void; ··· 9 12 autoComplete?: string; 10 13 placeholder?: string; 11 14 className?: string; 15 + passwordToggleable?: boolean; 12 16 } 13 17 14 18 export interface TextInputControlProps extends TextInputControlPropsNoLabel { ··· 30 34 className, 31 35 placeholder, 32 36 onFocus, 37 + passwordToggleable, 33 38 }, 34 39 ref 35 40 ) => { 41 + let inputType = "text"; 42 + const [showPassword, setShowPassword] = useState(true); 43 + if (passwordToggleable) inputType = showPassword ? "password" : "text"; 44 + 36 45 const input = ( 37 - <input 38 - type="text" 39 - ref={ref} 40 - className={className} 41 - placeholder={placeholder} 42 - onChange={(e) => onChange && onChange(e.target.value)} 43 - value={value} 44 - name={name} 45 - autoComplete={autoComplete} 46 - onBlur={() => onUnFocus && onUnFocus()} 47 - onFocus={() => onFocus?.()} 48 - onKeyDown={(e) => 49 - e.key === "Enter" ? (e.target as HTMLInputElement).blur() : null 50 - } 51 - /> 46 + <div className="relative"> 47 + <input 48 + type={inputType} 49 + ref={ref} 50 + className={classNames(className, passwordToggleable && "pr-12")} 51 + placeholder={placeholder} 52 + onChange={(e) => onChange && onChange(e.target.value)} 53 + value={value} 54 + name={name} 55 + autoComplete={autoComplete} 56 + onBlur={() => onUnFocus && onUnFocus()} 57 + onFocus={() => onFocus?.()} 58 + onKeyDown={(e) => 59 + e.key === "Enter" ? (e.target as HTMLInputElement).blur() : null 60 + } 61 + /> 62 + {passwordToggleable ? ( 63 + <button 64 + type="button" 65 + className="absolute top-1/2 -translate-y-1/2 right-1 text-xl p-3" 66 + onClick={() => setShowPassword(!showPassword)} 67 + > 68 + <Icon icon={showPassword ? Icons.EYE : Icons.EYE_SLASH} /> 69 + </button> 70 + ) : null} 71 + </div> 52 72 ); 53 73 54 74 if (label) {
-7
src/pages/PlayerView.tsx
··· 1 1 import { RunOutput } from "@movie-web/providers"; 2 2 import { useCallback, useEffect, useState } from "react"; 3 3 import { useHistory, useParams } from "react-router-dom"; 4 - import { useEffectOnce } from "react-use"; 5 4 6 - import { useCaptions } from "@/components/player/hooks/useCaptions"; 7 5 import { usePlayer } from "@/components/player/hooks/usePlayer"; 8 6 import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta"; 9 7 import { convertProviderCaption } from "@/components/player/utils/captions"; ··· 41 39 } = usePlayer(); 42 40 const { setPlayerMeta, scrapeMedia } = usePlayerMeta(); 43 41 const backUrl = useLastNonPlayerLink(); 44 - const { disable } = useCaptions(); 45 42 46 43 const paramsData = JSON.stringify({ 47 44 media: params.media, ··· 85 82 setShouldStartFromBeginning, 86 83 ] 87 84 ); 88 - 89 - useEffectOnce(() => { 90 - disable(); 91 - }); 92 85 93 86 return ( 94 87 <PlayerPart backUrl={backUrl} onMetaChange={metaChange}>
+1
src/pages/parts/auth/LoginFormPart.tsx
··· 74 74 name="username" 75 75 onChange={setMnemonic} 76 76 placeholder={t("auth.login.passphrasePlaceholder") ?? undefined} 77 + passwordToggleable 77 78 /> 78 79 <AuthInputBox 79 80 label={t("auth.deviceNameLabel") ?? undefined}
+1
src/pages/parts/auth/VerifyPassphrasePart.tsx
··· 98 98 name="username" 99 99 value={mnemonic} 100 100 onChange={setMnemonic} 101 + passwordToggleable 101 102 /> 102 103 {result.error ? ( 103 104 <p className="mt-3 text-authentication-errorText">
+8 -2
src/stores/history/index.ts
··· 43 43 44 44 export function useLastNonPlayerLink() { 45 45 const routes = useHistoryStore((s) => s.routes); 46 + const location = useLocation(); 46 47 const lastNonPlayerLink = useMemo(() => { 47 48 const reversedRoutes = [...routes]; 48 49 reversedRoutes.reverse(); 49 - const route = reversedRoutes.find((v) => !v.path.startsWith("/media")); 50 + const route = reversedRoutes.find( 51 + (v) => 52 + !v.path.startsWith("/media") && // cannot be a player link 53 + location.pathname !== v.path && // cannot be current link 54 + !v.path.startsWith("/s/") // cannot be a quick search link 55 + ); 50 56 return route?.path ?? "/"; 51 - }, [routes]); 57 + }, [routes, location]); 52 58 return lastNonPlayerLink; 53 59 }
+15 -9
tailwind.config.ts
··· 1 1 import { allThemes, defaultTheme, safeThemeList } from "./themes"; 2 - import type { Config } from "tailwindcss" 2 + import type { Config } from "tailwindcss"; 3 3 4 4 const themer = require("tailwindcss-themer"); 5 5 ··· 8 8 safelist: safeThemeList, 9 9 theme: { 10 10 extend: { 11 + /* breakpoints */ 12 + screens: { 13 + ssm: "400px", 14 + }, 15 + 11 16 /* fonts */ 12 17 fontFamily: { 13 - "open-sans": "'Open Sans'" 18 + "open-sans": "'Open Sans'", 14 19 }, 15 20 16 21 /* animations */ 17 22 keyframes: { 18 23 "loading-pin": { 19 24 "0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" }, 20 - "20%": { height: "1em", "background-color": "white" } 21 - } 25 + "20%": { height: "1em", "background-color": "white" }, 26 + }, 22 27 }, 23 - animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" } 24 - } 28 + animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }, 29 + }, 25 30 }, 26 31 plugins: [ 27 32 require("tailwind-scrollbar"), ··· 33 38 selectors: [".theme-default"], 34 39 ...defaultTheme, 35 40 }, 36 - ...allThemes] 37 - }) 38 - ] 41 + ...allThemes, 42 + ], 43 + }), 44 + ], 39 45 }; 40 46 41 47 export default config;