diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 7a7b4882..41e1042a 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -66,7 +66,9 @@ export function UserAvatar(props: { <> @@ -84,7 +86,10 @@ export function UserAvatar(props: { export function NoUserAvatar(props: { iconClass?: string }) { return (
- +
); } diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index ef08938e..ec5e26cb 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -6,6 +6,7 @@ export enum Icons { BOOKMARK = "bookmark", BOOKMARK_OUTLINE = "bookmark_outline", CLOCK = "clock", + EYE = "eye", EYE_SLASH = "eyeSlash", ARROW_LEFT = "arrowLeft", ARROW_RIGHT = "arrowRight", @@ -74,6 +75,7 @@ const iconList: Record = { search: ``, bookmark: ``, clock: ``, + eye: ``, eyeSlash: ``, arrowLeft: ``, chevronDown: ``, diff --git a/src/components/form/PassphraseDisplay.tsx b/src/components/form/PassphraseDisplay.tsx index b6ea0a0f..8eb4a2b9 100644 --- a/src/components/form/PassphraseDisplay.tsx +++ b/src/components/form/PassphraseDisplay.tsx @@ -38,10 +38,10 @@ export function PassphraseDisplay(props: { mnemonic: string }) { {t("actions.copy")} -
+
{individualWords.map((word, i) => (
-
- +
+ -
+
+
{props.children}
diff --git a/src/components/player/atoms/settings/PlaybackSettingsView.tsx b/src/components/player/atoms/settings/PlaybackSettingsView.tsx index 23bec683..a734e810 100644 --- a/src/components/player/atoms/settings/PlaybackSettingsView.tsx +++ b/src/components/player/atoms/settings/PlaybackSettingsView.tsx @@ -47,7 +47,7 @@ export function PlaybackSettingsView({ id }: { id: string }) { [display] ); - const options = [0.25, 0.5, 1, 1.25, 2]; + const options = [0.25, 0.5, 1, 1.5, 2]; return ( <> diff --git a/src/components/player/hooks/useCaptions.ts b/src/components/player/hooks/useCaptions.ts index ec99c4fb..b646ee8a 100644 --- a/src/components/player/hooks/useCaptions.ts +++ b/src/components/player/hooks/useCaptions.ts @@ -46,10 +46,15 @@ export function useCaptions() { else await selectLastUsedLanguage(); }, [selectLastUsedLanguage, disable, enabled]); + const selectLastUsedLanguageIfEnabled = useCallback(async () => { + if (enabled) await selectLastUsedLanguage(); + }, [selectLastUsedLanguage, enabled]); + return { selectLanguage, disable, selectLastUsedLanguage, toggleLastUsed, + selectLastUsedLanguageIfEnabled, }; } diff --git a/src/components/player/hooks/useInitializePlayer.ts b/src/components/player/hooks/useInitializePlayer.ts index 378cdd5e..5964a359 100644 --- a/src/components/player/hooks/useInitializePlayer.ts +++ b/src/components/player/hooks/useInitializePlayer.ts @@ -1,8 +1,10 @@ -import { useCallback } from "react"; +import { useCallback, useEffect, useMemo, useRef } from "react"; import { usePlayerStore } from "@/stores/player/store"; import { useVolumeStore } from "@/stores/volume"; +import { useCaptions } from "./useCaptions"; + export function useInitializePlayer() { const display = usePlayerStore((s) => s.display); const volume = useVolumeStore((s) => s.volume); @@ -15,3 +17,21 @@ export function useInitializePlayer() { init, }; } + +export function useInitializeSource() { + const source = usePlayerStore((s) => s.source); + const sourceIdentifier = useMemo( + () => (source ? JSON.stringify(source) : null), + [source] + ); + const { selectLastUsedLanguageIfEnabled } = useCaptions(); + + const funRef = useRef(selectLastUsedLanguageIfEnabled); + useEffect(() => { + funRef.current = selectLastUsedLanguageIfEnabled; + }, [selectLastUsedLanguageIfEnabled]); + + useEffect(() => { + if (sourceIdentifier) funRef.current(); + }, [sourceIdentifier]); +} diff --git a/src/components/player/internals/VideoContainer.tsx b/src/components/player/internals/VideoContainer.tsx index 45476eab..b1282c34 100644 --- a/src/components/player/internals/VideoContainer.tsx +++ b/src/components/player/internals/VideoContainer.tsx @@ -5,6 +5,8 @@ import { convertSubtitlesToObjectUrl } from "@/components/player/utils/captions" import { playerStatus } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; +import { useInitializeSource } from "../hooks/useInitializePlayer"; + // initialize display interface function useDisplayInterface() { const display = usePlayerStore((s) => s.display); @@ -112,6 +114,7 @@ function VideoElement() { export function VideoContainer() { const show = useShouldShowVideoElement(); useDisplayInterface(); + useInitializeSource(); if (!show) return null; return ; diff --git a/src/components/text-inputs/AuthInputBox.tsx b/src/components/text-inputs/AuthInputBox.tsx index 8ff9553e..c79c079d 100644 --- a/src/components/text-inputs/AuthInputBox.tsx +++ b/src/components/text-inputs/AuthInputBox.tsx @@ -7,6 +7,7 @@ export function AuthInputBox(props: { autoComplete?: string; placeholder?: string; onChange?: (data: string) => void; + passwordToggleable?: boolean; }) { return (
@@ -19,6 +20,7 @@ export function AuthInputBox(props: { autoComplete={props.autoComplete} onChange={props.onChange} placeholder={props.placeholder} + passwordToggleable={props.passwordToggleable} className="w-full flex-1 bg-authentication-inputBg px-4 py-3 text-search-text focus:outline-none rounded-lg placeholder:text-gray-700" />
diff --git a/src/components/text-inputs/TextInputControl.tsx b/src/components/text-inputs/TextInputControl.tsx index b555c4f8..91d20c3a 100644 --- a/src/components/text-inputs/TextInputControl.tsx +++ b/src/components/text-inputs/TextInputControl.tsx @@ -1,4 +1,7 @@ -import { forwardRef } from "react"; +import classNames from "classnames"; +import { forwardRef, useState } from "react"; + +import { Icon, Icons } from "../Icon"; export interface TextInputControlPropsNoLabel { onChange?: (data: string) => void; @@ -9,6 +12,7 @@ export interface TextInputControlPropsNoLabel { autoComplete?: string; placeholder?: string; className?: string; + passwordToggleable?: boolean; } export interface TextInputControlProps extends TextInputControlPropsNoLabel { @@ -30,25 +34,41 @@ export const TextInputControl = forwardRef< className, placeholder, onFocus, + passwordToggleable, }, ref ) => { + let inputType = "text"; + const [showPassword, setShowPassword] = useState(true); + if (passwordToggleable) inputType = showPassword ? "password" : "text"; + const input = ( - onChange && onChange(e.target.value)} - value={value} - name={name} - autoComplete={autoComplete} - onBlur={() => onUnFocus && onUnFocus()} - onFocus={() => onFocus?.()} - onKeyDown={(e) => - e.key === "Enter" ? (e.target as HTMLInputElement).blur() : null - } - /> +
+ onChange && onChange(e.target.value)} + value={value} + name={name} + autoComplete={autoComplete} + onBlur={() => onUnFocus && onUnFocus()} + onFocus={() => onFocus?.()} + onKeyDown={(e) => + e.key === "Enter" ? (e.target as HTMLInputElement).blur() : null + } + /> + {passwordToggleable ? ( + + ) : null} +
); if (label) { diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx index 37784759..61077177 100644 --- a/src/pages/PlayerView.tsx +++ b/src/pages/PlayerView.tsx @@ -1,9 +1,7 @@ import { RunOutput } from "@movie-web/providers"; import { useCallback, useEffect, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; -import { useEffectOnce } from "react-use"; -import { useCaptions } from "@/components/player/hooks/useCaptions"; import { usePlayer } from "@/components/player/hooks/usePlayer"; import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta"; import { convertProviderCaption } from "@/components/player/utils/captions"; @@ -41,7 +39,6 @@ export function PlayerView() { } = usePlayer(); const { setPlayerMeta, scrapeMedia } = usePlayerMeta(); const backUrl = useLastNonPlayerLink(); - const { disable } = useCaptions(); const paramsData = JSON.stringify({ media: params.media, @@ -86,10 +83,6 @@ export function PlayerView() { ] ); - useEffectOnce(() => { - disable(); - }); - return ( {status === playerStatus.IDLE ? ( diff --git a/src/pages/parts/auth/LoginFormPart.tsx b/src/pages/parts/auth/LoginFormPart.tsx index 1ff43422..39b81b96 100644 --- a/src/pages/parts/auth/LoginFormPart.tsx +++ b/src/pages/parts/auth/LoginFormPart.tsx @@ -74,6 +74,7 @@ export function LoginFormPart(props: LoginFormPartProps) { name="username" onChange={setMnemonic} placeholder={t("auth.login.passphrasePlaceholder") ?? undefined} + passwordToggleable /> {result.error ? (

diff --git a/src/stores/history/index.ts b/src/stores/history/index.ts index 572358cf..8a85f777 100644 --- a/src/stores/history/index.ts +++ b/src/stores/history/index.ts @@ -43,11 +43,17 @@ export function useHistoryListener() { export function useLastNonPlayerLink() { const routes = useHistoryStore((s) => s.routes); + const location = useLocation(); const lastNonPlayerLink = useMemo(() => { const reversedRoutes = [...routes]; reversedRoutes.reverse(); - const route = reversedRoutes.find((v) => !v.path.startsWith("/media")); + const route = reversedRoutes.find( + (v) => + !v.path.startsWith("/media") && // cannot be a player link + location.pathname !== v.path && // cannot be current link + !v.path.startsWith("/s/") // cannot be a quick search link + ); return route?.path ?? "/"; - }, [routes]); + }, [routes, location]); return lastNonPlayerLink; } diff --git a/tailwind.config.ts b/tailwind.config.ts index ee5ef13f..913a59fd 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,6 +9,11 @@ const config: Config = { safelist: safeThemeList, theme: { extend: { + /* breakpoints */ + screens: { + ssm: "400px", + }, + /* fonts */ fontFamily: { "open-sans": "'Open Sans'",