29 changed files with 1803 additions and 78 deletions
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
import { useSettings } from "@/state/settings"; |
||||
import { Icon, Icons } from "./Icon"; |
||||
|
||||
export const colors = ["#ffffff", "#00ffff", "#ffff00"]; |
||||
export default function CaptionColorSelector({ color }: { color: string }) { |
||||
const { captionSettings, setCaptionColor } = useSettings(); |
||||
return ( |
||||
<div |
||||
className={`flex h-8 w-8 items-center justify-center rounded transition-[background-color,transform] duration-100 hover:bg-[#1c161b79] active:scale-110 ${ |
||||
color === captionSettings.style.color ? "bg-[#1C161B]" : "" |
||||
}`}
|
||||
onClick={() => setCaptionColor(color)} |
||||
> |
||||
<div |
||||
className="h-4 w-4 cursor-pointer appearance-none rounded-full" |
||||
style={{ |
||||
backgroundColor: color, |
||||
}} |
||||
/> |
||||
<Icon |
||||
className={[ |
||||
"absolute text-xs text-[#1C161B]", |
||||
color === captionSettings.style.color ? "" : "hidden", |
||||
].join(" ")} |
||||
icon={Icons.CHECKMARK} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
import { Icon, Icons } from "@/components/Icon"; |
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks"; |
||||
import { useInterface } from "@/video/state/logic/interface"; |
||||
import { useMediaPlaying } from "@/video/state/logic/mediaplaying"; |
||||
|
||||
export function VolumeAdjustedAction() { |
||||
const descriptor = useVideoPlayerDescriptor(); |
||||
const videoInterface = useInterface(descriptor); |
||||
const mediaPlaying = useMediaPlaying(descriptor); |
||||
|
||||
return ( |
||||
<div |
||||
className={[ |
||||
videoInterface.volumeChangedWithKeybind |
||||
? "mt-10 scale-100 opacity-100" |
||||
: "mt-5 scale-75 opacity-0", |
||||
"absolute left-1/2 z-[100] flex -translate-x-1/2 items-center space-x-4 rounded-full bg-bink-300 bg-opacity-50 py-2 px-5 transition-all duration-100", |
||||
].join(" ")} |
||||
> |
||||
<Icon |
||||
icon={mediaPlaying.volume > 0 ? Icons.VOLUME : Icons.VOLUME_X} |
||||
className="text-xl text-white" |
||||
/> |
||||
<div className="h-2 w-44 overflow-hidden rounded-full bg-denim-100"> |
||||
<div |
||||
className="h-full rounded-r-full bg-bink-500 transition-[width] duration-100" |
||||
style={{ width: `${mediaPlaying.volume * 100}%` }} |
||||
/> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
import { Dropdown } from "@/components/Dropdown"; |
||||
import { Icon, Icons } from "@/components/Icon"; |
||||
import { Modal, ModalCard } from "@/components/layout/Modal"; |
||||
import { useSettings } from "@/state/settings"; |
||||
import { useTranslation } from "react-i18next"; |
||||
import { CaptionCue } from "@/video/components/actions/CaptionRendererAction"; |
||||
import { |
||||
CaptionLanguageOption, |
||||
LangCode, |
||||
captionLanguages, |
||||
} from "@/setup/iso6391"; |
||||
import { useMemo } from "react"; |
||||
import { appLanguageOptions } from "@/setup/i18n"; |
||||
import CaptionColorSelector, { |
||||
colors, |
||||
} from "@/components/CaptionColorSelector"; |
||||
import { Slider } from "@/components/Slider"; |
||||
import { conf } from "@/setup/config"; |
||||
|
||||
export default function SettingsModal(props: { |
||||
onClose: () => void; |
||||
show: boolean; |
||||
}) { |
||||
const { |
||||
captionSettings, |
||||
language, |
||||
setLanguage, |
||||
setCaptionLanguage, |
||||
setCaptionBackgroundColor, |
||||
setCaptionFontSize, |
||||
} = useSettings(); |
||||
const { t, i18n } = useTranslation(); |
||||
|
||||
const selectedCaptionLanguage = useMemo( |
||||
() => captionLanguages.find((l) => l.id === captionSettings.language), |
||||
[captionSettings.language] |
||||
) as CaptionLanguageOption; |
||||
const appLanguage = useMemo( |
||||
() => appLanguageOptions.find((l) => l.id === language), |
||||
[language] |
||||
) as CaptionLanguageOption; |
||||
const captionBackgroundOpacity = ( |
||||
(parseInt(captionSettings.style.backgroundColor.substring(7, 9), 16) / |
||||
255) * |
||||
100 |
||||
).toFixed(0); |
||||
return ( |
||||
<Modal show={props.show}> |
||||
<ModalCard className="text-white"> |
||||
<div className="flex flex-col gap-4"> |
||||
<div className="flex flex-row justify-between"> |
||||
<span className="text-xl font-bold">{t("settings.title")}</span> |
||||
<div |
||||
onClick={() => props.onClose()} |
||||
className="hover:cursor-pointer" |
||||
> |
||||
<Icon icon={Icons.X} /> |
||||
</div> |
||||
</div> |
||||
<div className="flex flex-col gap-10 lg:flex-row"> |
||||
<div className="lg:w-1/2"> |
||||
<div className="flex flex-col justify-between"> |
||||
<label className="text-md font-semibold"> |
||||
{t("settings.language")} |
||||
</label> |
||||
<Dropdown |
||||
selectedItem={appLanguage} |
||||
setSelectedItem={(val) => { |
||||
i18n.changeLanguage(val.id); |
||||
setLanguage(val.id as LangCode); |
||||
}} |
||||
options={appLanguageOptions} |
||||
/> |
||||
</div> |
||||
<div className="flex flex-col justify-between"> |
||||
<label className="text-md font-semibold"> |
||||
{t("settings.captionLanguage")} |
||||
</label> |
||||
<Dropdown |
||||
selectedItem={selectedCaptionLanguage} |
||||
setSelectedItem={(val) => { |
||||
setCaptionLanguage(val.id as LangCode); |
||||
}} |
||||
options={captionLanguages} |
||||
/> |
||||
</div> |
||||
<div className="flex flex-col justify-between"> |
||||
<Slider |
||||
label={ |
||||
t( |
||||
"videoPlayer.popouts.captionPreferences.fontSize" |
||||
) as string |
||||
} |
||||
min={14} |
||||
step={1} |
||||
max={60} |
||||
value={captionSettings.style.fontSize} |
||||
onChange={(e) => setCaptionFontSize(e.target.valueAsNumber)} |
||||
/> |
||||
<Slider |
||||
label={ |
||||
t( |
||||
"videoPlayer.popouts.captionPreferences.opacity" |
||||
) as string |
||||
} |
||||
step={1} |
||||
min={0} |
||||
max={255} |
||||
valueDisplay={`${captionBackgroundOpacity}%`} |
||||
value={parseInt( |
||||
captionSettings.style.backgroundColor.substring(7, 9), |
||||
16 |
||||
)} |
||||
onChange={(e) => |
||||
setCaptionBackgroundColor(e.target.valueAsNumber) |
||||
} |
||||
/> |
||||
<div className="flex flex-row justify-between"> |
||||
<label className="font-bold" htmlFor="color"> |
||||
{t("videoPlayer.popouts.captionPreferences.color")} |
||||
</label> |
||||
<div className="flex flex-row gap-2"> |
||||
{colors.map((color) => ( |
||||
<CaptionColorSelector color={color} /> |
||||
))} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div /> |
||||
</div> |
||||
<div className="flex w-full flex-col justify-center"> |
||||
<div className="flex aspect-video flex-col justify-end rounded bg-zinc-800"> |
||||
<div className="pointer-events-none flex w-full flex-col items-center transition-[bottom]"> |
||||
<CaptionCue |
||||
scale={0.5} |
||||
text={selectedCaptionLanguage.nativeName} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div className="float-right mt-1 text-sm">v{conf().APP_VERSION}</div> |
||||
</ModalCard> |
||||
</Modal> |
||||
); |
||||
} |
||||
Loading…
Reference in new issue