mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
Merge pull request #922 from marcoslor/868-add-appearance-preview
Add appearance preview
This commit is contained in:
commit
843ec84936
5 changed files with 63 additions and 14 deletions
|
@ -9,6 +9,7 @@ import {
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import { SubtitleStyling } from "@/stores/subtitles";
|
import { SubtitleStyling } from "@/stores/subtitles";
|
||||||
|
import { usePreviewThemeStore } from "@/stores/theme";
|
||||||
|
|
||||||
export function useDerived<T>(
|
export function useDerived<T>(
|
||||||
initial: T,
|
initial: T,
|
||||||
|
@ -56,6 +57,11 @@ export function useSettingsState(
|
||||||
const [backendUrlState, setBackendUrl, resetBackendUrl, backendUrlChanged] =
|
const [backendUrlState, setBackendUrl, resetBackendUrl, backendUrlChanged] =
|
||||||
useDerived(backendUrl);
|
useDerived(backendUrl);
|
||||||
const [themeState, setTheme, resetTheme, themeChanged] = useDerived(theme);
|
const [themeState, setTheme, resetTheme, themeChanged] = useDerived(theme);
|
||||||
|
const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
|
||||||
|
const resetPreviewTheme = useCallback(
|
||||||
|
() => setPreviewTheme(theme),
|
||||||
|
[setPreviewTheme, theme],
|
||||||
|
);
|
||||||
const [
|
const [
|
||||||
appLanguageState,
|
appLanguageState,
|
||||||
setAppLanguage,
|
setAppLanguage,
|
||||||
|
@ -81,6 +87,7 @@ export function useSettingsState(
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
resetTheme();
|
resetTheme();
|
||||||
|
resetPreviewTheme();
|
||||||
resetAppLanguage();
|
resetAppLanguage();
|
||||||
resetSubStyling();
|
resetSubStyling();
|
||||||
resetProxyUrls();
|
resetProxyUrls();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAsyncFn } from "react-use";
|
import { useAsyncFn } from "react-use";
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
||||||
import { useLanguageStore } from "@/stores/language";
|
import { useLanguageStore } from "@/stores/language";
|
||||||
import { usePreferencesStore } from "@/stores/preferences";
|
import { usePreferencesStore } from "@/stores/preferences";
|
||||||
import { useSubtitleStore } from "@/stores/subtitles";
|
import { useSubtitleStore } from "@/stores/subtitles";
|
||||||
import { useThemeStore } from "@/stores/theme";
|
import { usePreviewThemeStore, useThemeStore } from "@/stores/theme";
|
||||||
|
|
||||||
import { SubPageLayout } from "./layouts/SubPageLayout";
|
import { SubPageLayout } from "./layouts/SubPageLayout";
|
||||||
import { PreferencesPart } from "./parts/settings/PreferencesPart";
|
import { PreferencesPart } from "./parts/settings/PreferencesPart";
|
||||||
|
@ -102,8 +102,10 @@ export function AccountSettings(props: {
|
||||||
|
|
||||||
export function SettingsPage() {
|
export function SettingsPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const activeTheme = useThemeStore((s) => s.theme);
|
const activeTheme = useThemeStore((s) => s.theme) ?? "default";
|
||||||
const setTheme = useThemeStore((s) => s.setTheme);
|
const setTheme = useThemeStore((s) => s.setTheme);
|
||||||
|
const previewTheme = usePreviewThemeStore((s) => s.previewTheme) ?? "default";
|
||||||
|
const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
|
||||||
|
|
||||||
const appLanguage = useLanguageStore((s) => s.language);
|
const appLanguage = useLanguageStore((s) => s.language);
|
||||||
const setAppLanguage = useLanguageStore((s) => s.setLanguage);
|
const setAppLanguage = useLanguageStore((s) => s.setLanguage);
|
||||||
|
@ -144,6 +146,22 @@ export function SettingsPage() {
|
||||||
enableThumbnails,
|
enableThumbnails,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset the preview theme when the settings page is unmounted
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
setPreviewTheme(null);
|
||||||
|
},
|
||||||
|
[setPreviewTheme],
|
||||||
|
);
|
||||||
|
|
||||||
|
const setThemeWithPreview = useCallback(
|
||||||
|
(v: string | null) => {
|
||||||
|
state.theme.set(v === "default" ? null : v);
|
||||||
|
setPreviewTheme(v);
|
||||||
|
},
|
||||||
|
[state.theme, setPreviewTheme],
|
||||||
|
);
|
||||||
|
|
||||||
const saveChanges = useCallback(async () => {
|
const saveChanges = useCallback(async () => {
|
||||||
if (account && backendUrl) {
|
if (account && backendUrl) {
|
||||||
if (
|
if (
|
||||||
|
@ -242,7 +260,11 @@ export function SettingsPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="settings-appearance" className="mt-48">
|
<div id="settings-appearance" className="mt-48">
|
||||||
<ThemePart active={state.theme.state} setTheme={state.theme.set} />
|
<ThemePart
|
||||||
|
active={previewTheme}
|
||||||
|
inUse={activeTheme}
|
||||||
|
setTheme={setThemeWithPreview}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="settings-captions" className="mt-48">
|
<div id="settings-captions" className="mt-48">
|
||||||
<CaptionsPart
|
<CaptionsPart
|
||||||
|
|
|
@ -10,13 +10,13 @@ export function BlurEllipsis(props: { positionClass?: string }) {
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.positionClass ?? "fixed",
|
props.positionClass ?? "fixed",
|
||||||
"top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25",
|
"top-0 -right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentA blur-[100px] pointer-events-none opacity-25 transition-colors duration-75",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.positionClass ?? "fixed",
|
props.positionClass ?? "fixed",
|
||||||
"top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25",
|
"top-0 right-48 rotate-[32deg] w-[50rem] h-[15rem] rounded-[70rem] bg-background-accentB blur-[100px] pointer-events-none opacity-25 transition-colors duration-75",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -5,6 +5,10 @@ import { Icon, Icons } from "@/components/Icon";
|
||||||
import { Heading1 } from "@/components/utils/Text";
|
import { Heading1 } from "@/components/utils/Text";
|
||||||
|
|
||||||
const availableThemes = [
|
const availableThemes = [
|
||||||
|
{
|
||||||
|
id: "default",
|
||||||
|
key: "settings.appearance.themes.default",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "blue",
|
id: "blue",
|
||||||
key: "settings.appearance.themes.blue",
|
key: "settings.appearance.themes.blue",
|
||||||
|
@ -26,6 +30,7 @@ const availableThemes = [
|
||||||
function ThemePreview(props: {
|
function ThemePreview(props: {
|
||||||
selector?: string;
|
selector?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
inUse?: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
@ -105,7 +110,7 @@ function ThemePreview(props: {
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-block px-3 py-1 leading-tight text-sm transition-opacity duration-150 rounded-full bg-pill-activeBackground text-white/85",
|
"inline-block px-3 py-1 leading-tight text-sm transition-opacity duration-150 rounded-full bg-pill-activeBackground text-white/85",
|
||||||
props.active ? "opacity-100" : "opacity-0 pointer-events-none",
|
props.inUse ? "opacity-100" : "opacity-0 pointer-events-none",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{t("settings.appearance.activeTheme")}
|
{t("settings.appearance.activeTheme")}
|
||||||
|
@ -117,6 +122,7 @@ function ThemePreview(props: {
|
||||||
|
|
||||||
export function ThemePart(props: {
|
export function ThemePart(props: {
|
||||||
active: string | null;
|
active: string | null;
|
||||||
|
inUse: string | null;
|
||||||
setTheme: (theme: string | null) => void;
|
setTheme: (theme: string | null) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -126,16 +132,11 @@ export function ThemePart(props: {
|
||||||
<Heading1 border>{t("settings.appearance.title")}</Heading1>
|
<Heading1 border>{t("settings.appearance.title")}</Heading1>
|
||||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-6 max-w-[700px]">
|
<div className="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-6 max-w-[700px]">
|
||||||
{/* default theme */}
|
{/* default theme */}
|
||||||
<ThemePreview
|
|
||||||
name={t("settings.appearance.themes.default")}
|
|
||||||
selector="theme-default"
|
|
||||||
active={props.active === null}
|
|
||||||
onClick={() => props.setTheme(null)}
|
|
||||||
/>
|
|
||||||
{availableThemes.map((v) => (
|
{availableThemes.map((v) => (
|
||||||
<ThemePreview
|
<ThemePreview
|
||||||
selector={`theme-${v.id}`}
|
selector={`theme-${v.id}`}
|
||||||
active={props.active === v.id}
|
active={props.active === v.id}
|
||||||
|
inUse={props.inUse === v.id}
|
||||||
name={t(v.key)}
|
name={t(v.key)}
|
||||||
key={v.id}
|
key={v.id}
|
||||||
onClick={() => props.setTheme(v.id)}
|
onClick={() => props.setTheme(v.id)}
|
||||||
|
|
|
@ -25,12 +25,31 @@ export const useThemeStore = create(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export interface PreviewThemeStore {
|
||||||
|
previewTheme: string | null;
|
||||||
|
setPreviewTheme(v: string | null): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePreviewThemeStore = create(
|
||||||
|
immer<PreviewThemeStore>((set) => ({
|
||||||
|
previewTheme: null,
|
||||||
|
setPreviewTheme(v) {
|
||||||
|
set((s) => {
|
||||||
|
s.previewTheme = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
export function ThemeProvider(props: {
|
export function ThemeProvider(props: {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
applyGlobal?: boolean;
|
applyGlobal?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const previewTheme = usePreviewThemeStore((s) => s.previewTheme);
|
||||||
const theme = useThemeStore((s) => s.theme);
|
const theme = useThemeStore((s) => s.theme);
|
||||||
const themeSelector = theme ? `theme-${theme}` : undefined;
|
|
||||||
|
const themeToDisplay = previewTheme ?? theme;
|
||||||
|
const themeSelector = themeToDisplay ? `theme-${themeToDisplay}` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={themeSelector}>
|
<div className={themeSelector}>
|
||||||
|
|
Loading…
Reference in a new issue