mirror of
https://github.com/usememos/memos.git
synced 2025-04-03 04:11:20 +02:00
feat: update appearance selector (#645)
This commit is contained in:
parent
eaebc6dcef
commit
7c6d7226f5
@ -61,9 +61,6 @@ func NewServer(profile *profile.Profile) *Server {
|
|||||||
rootGroup := e.Group("")
|
rootGroup := e.Group("")
|
||||||
s.registerRSSRoutes(rootGroup)
|
s.registerRSSRoutes(rootGroup)
|
||||||
|
|
||||||
webhookGroup := e.Group("/h")
|
|
||||||
s.registerResourcePublicRoutes(webhookGroup)
|
|
||||||
|
|
||||||
publicGroup := e.Group("/o")
|
publicGroup := e.Group("/o")
|
||||||
s.registerResourcePublicRoutes(publicGroup)
|
s.registerResourcePublicRoutes(publicGroup)
|
||||||
s.registerGetterPublicRoutes(publicGroup)
|
s.registerGetterPublicRoutes(publicGroup)
|
||||||
|
@ -6,12 +6,12 @@ import { useAppSelector } from "./store";
|
|||||||
import Loading from "./pages/Loading";
|
import Loading from "./pages/Loading";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import * as storage from "./helpers/storage";
|
import * as storage from "./helpers/storage";
|
||||||
import useApperance from "./hooks/useApperance";
|
import useAppearance from "./hooks/useAppearance";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const { locale, systemStatus } = useAppSelector((state) => state.global);
|
const { locale, systemStatus } = useAppSelector((state) => state.global);
|
||||||
useApperance();
|
useAppearance();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
locationService.updateStateWithLocation();
|
locationService.updateStateWithLocation();
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { Option, Select } from "@mui/joy";
|
import { Option, Select } from "@mui/joy";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { globalService } from "../services";
|
||||||
|
import { useAppSelector } from "../store";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { APPERANCE_OPTIONS } from "../helpers/consts";
|
|
||||||
import useApperance, { Apperance } from "../hooks/useApperance";
|
|
||||||
|
|
||||||
const ApperanceSelect = () => {
|
const appearanceList = ["system", "light", "dark"];
|
||||||
const [apperance, setApperance] = useApperance();
|
|
||||||
|
const AppearanceSelect = () => {
|
||||||
|
const appearance = useAppSelector((state) => state.global.appearance);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getPrefixIcon = (apperance: Apperance) => {
|
const getPrefixIcon = (apperance: Appearance) => {
|
||||||
const className = "w-4 h-auto";
|
const className = "w-4 h-auto";
|
||||||
if (apperance === "light") {
|
if (apperance === "light") {
|
||||||
return <Icon.Sun className={className} />;
|
return <Icon.Sun className={className} />;
|
||||||
@ -19,16 +21,22 @@ const ApperanceSelect = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (appearance: Appearance) => {
|
||||||
|
globalService.setAppearance(appearance);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
className="!min-w-[10rem] w-auto text-sm"
|
className="!min-w-[10rem] w-auto text-sm"
|
||||||
value={apperance}
|
value={appearance}
|
||||||
onChange={(_, value) => {
|
onChange={(_, appearance) => {
|
||||||
setApperance(value as Apperance);
|
if (appearance) {
|
||||||
|
handleSelectChange(appearance);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
startDecorator={getPrefixIcon(apperance)}
|
startDecorator={getPrefixIcon(appearance)}
|
||||||
>
|
>
|
||||||
{APPERANCE_OPTIONS.map((item) => (
|
{appearanceList.map((item) => (
|
||||||
<Option key={item} value={item} className="whitespace-nowrap">
|
<Option key={item} value={item} className="whitespace-nowrap">
|
||||||
{t(`setting.apperance-option.${item}`)}
|
{t(`setting.apperance-option.${item}`)}
|
||||||
</Option>
|
</Option>
|
||||||
@ -37,4 +45,4 @@ const ApperanceSelect = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ApperanceSelect;
|
export default AppearanceSelect;
|
@ -82,6 +82,7 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|||||||
<p className="text-sm mb-1">{t("common.new-password")}</p>
|
<p className="text-sm mb-1">{t("common.new-password")}</p>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
className="input-text"
|
className="input-text"
|
||||||
placeholder={t("common.repeat-new-password")}
|
placeholder={t("common.repeat-new-password")}
|
||||||
value={newPassword}
|
value={newPassword}
|
||||||
@ -90,6 +91,7 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
|||||||
<p className="text-sm mb-1 mt-2">{t("common.repeat-new-password")}</p>
|
<p className="text-sm mb-1 mt-2">{t("common.repeat-new-password")}</p>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
className="input-text"
|
className="input-text"
|
||||||
placeholder={t("common.repeat-new-password")}
|
placeholder={t("common.repeat-new-password")}
|
||||||
value={newPasswordAgain}
|
value={newPasswordAgain}
|
||||||
|
@ -34,7 +34,14 @@ const SearchBar = () => {
|
|||||||
<div className="search-bar-container">
|
<div className="search-bar-container">
|
||||||
<div className="search-bar-inputer">
|
<div className="search-bar-inputer">
|
||||||
<Icon.Search className="icon-img" />
|
<Icon.Search className="icon-img" />
|
||||||
<input className="text-input" autoComplete="off" type="text" placeholder="" value={queryText} onChange={handleTextQueryInput} />
|
<input
|
||||||
|
className="text-input"
|
||||||
|
autoComplete="new-password"
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
value={queryText}
|
||||||
|
onChange={handleTextQueryInput}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="quickly-action-wrapper">
|
<div className="quickly-action-wrapper">
|
||||||
<div className="quickly-action-container">
|
<div className="quickly-action-container">
|
||||||
|
@ -12,7 +12,6 @@ import "../../less/settings/member-section.less";
|
|||||||
interface State {
|
interface State {
|
||||||
createUserUsername: string;
|
createUserUsername: string;
|
||||||
createUserPassword: string;
|
createUserPassword: string;
|
||||||
repeatUserPassword: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PreferencesSection = () => {
|
const PreferencesSection = () => {
|
||||||
@ -21,7 +20,6 @@ const PreferencesSection = () => {
|
|||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
createUserUsername: "",
|
createUserUsername: "",
|
||||||
createUserPassword: "",
|
createUserPassword: "",
|
||||||
repeatUserPassword: "",
|
|
||||||
});
|
});
|
||||||
const [userList, setUserList] = useState<User[]>([]);
|
const [userList, setUserList] = useState<User[]>([]);
|
||||||
|
|
||||||
@ -48,22 +46,11 @@ const PreferencesSection = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRepeatPasswordInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
repeatUserPassword: event.target.value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCreateUserBtnClick = async () => {
|
const handleCreateUserBtnClick = async () => {
|
||||||
if (state.createUserUsername === "" || state.createUserPassword === "") {
|
if (state.createUserUsername === "" || state.createUserPassword === "") {
|
||||||
toastHelper.error(t("message.fill-form"));
|
toastHelper.error(t("message.fill-form"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.createUserPassword !== state.repeatUserPassword) {
|
|
||||||
toastHelper.error(t("message.password-not-match"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userCreate: UserCreate = {
|
const userCreate: UserCreate = {
|
||||||
username: state.createUserUsername,
|
username: state.createUserUsername,
|
||||||
@ -80,7 +67,6 @@ const PreferencesSection = () => {
|
|||||||
setState({
|
setState({
|
||||||
createUserUsername: "",
|
createUserUsername: "",
|
||||||
createUserPassword: "",
|
createUserPassword: "",
|
||||||
repeatUserPassword: "",
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,19 +117,22 @@ const PreferencesSection = () => {
|
|||||||
<div className="create-member-container">
|
<div className="create-member-container">
|
||||||
<div className="input-form-container">
|
<div className="input-form-container">
|
||||||
<span className="field-text">{t("common.username")}</span>
|
<span className="field-text">{t("common.username")}</span>
|
||||||
<input type="text" placeholder={t("common.username")} value={state.createUserUsername} onChange={handleUsernameInputChange} />
|
<input
|
||||||
|
type="text"
|
||||||
|
autoComplete="new-password"
|
||||||
|
placeholder={t("common.username")}
|
||||||
|
value={state.createUserUsername}
|
||||||
|
onChange={handleUsernameInputChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="input-form-container">
|
<div className="input-form-container">
|
||||||
<span className="field-text">{t("common.password")}</span>
|
<span className="field-text">{t("common.password")}</span>
|
||||||
<input type="password" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} />
|
|
||||||
</div>
|
|
||||||
<div className="input-form-container">
|
|
||||||
<span className="field-text">{t("common.repeat-password-short")}</span>
|
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder={t("common.repeat-password")}
|
autoComplete="new-password"
|
||||||
value={state.repeatUserPassword}
|
placeholder={t("common.password")}
|
||||||
onChange={handleRepeatPasswordInputChange}
|
value={state.createUserPassword}
|
||||||
|
onChange={handlePasswordInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="btns-container">
|
<div className="btns-container">
|
||||||
|
@ -4,6 +4,7 @@ import { globalService, userService } from "../../services";
|
|||||||
import { useAppSelector } from "../../store";
|
import { useAppSelector } from "../../store";
|
||||||
import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
|
import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts";
|
||||||
import Selector from "../common/Selector";
|
import Selector from "../common/Selector";
|
||||||
|
import AppearanceSelect from "../AppearanceSelect";
|
||||||
import "../../less/settings/preferences-section.less";
|
import "../../less/settings/preferences-section.less";
|
||||||
|
|
||||||
const localeSelectorItems = [
|
const localeSelectorItems = [
|
||||||
@ -66,6 +67,10 @@ const PreferencesSection = () => {
|
|||||||
<span className="normal-text">{t("common.language")}</span>
|
<span className="normal-text">{t("common.language")}</span>
|
||||||
<Selector className="ml-2 w-32" value={setting.locale} dataSource={localeSelectorItems} handleValueChanged={handleLocaleChanged} />
|
<Selector className="ml-2 w-32" value={setting.locale} dataSource={localeSelectorItems} handleValueChanged={handleLocaleChanged} />
|
||||||
</label>
|
</label>
|
||||||
|
<label className="form-label selector">
|
||||||
|
<span className="normal-text">Theme</span>
|
||||||
|
<AppearanceSelect />
|
||||||
|
</label>
|
||||||
<p className="title-text">{t("setting.preference")}</p>
|
<p className="title-text">{t("setting.preference")}</p>
|
||||||
<label className="form-label selector">
|
<label className="form-label selector">
|
||||||
<span className="normal-text">{t("setting.preference-section.default-memo-visibility")}</span>
|
<span className="normal-text">{t("setting.preference-section.default-memo-visibility")}</span>
|
||||||
|
@ -14,8 +14,8 @@ import toastHelper from "./Toast";
|
|||||||
import MemoContent from "./MemoContent";
|
import MemoContent from "./MemoContent";
|
||||||
import MemoResources from "./MemoResources";
|
import MemoResources from "./MemoResources";
|
||||||
import Selector from "./common/Selector";
|
import Selector from "./common/Selector";
|
||||||
|
import useAppearance from "../hooks/useAppearance";
|
||||||
import "../less/share-memo-image-dialog.less";
|
import "../less/share-memo-image-dialog.less";
|
||||||
import useApperance from "../hooks/useApperance";
|
|
||||||
|
|
||||||
interface Props extends DialogProps {
|
interface Props extends DialogProps {
|
||||||
memo: Memo;
|
memo: Memo;
|
||||||
@ -36,7 +36,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
|||||||
shortcutImgUrl: "",
|
shortcutImgUrl: "",
|
||||||
memoVisibility: propsMemo.visibility,
|
memoVisibility: propsMemo.visibility,
|
||||||
});
|
});
|
||||||
const [apperance] = useApperance();
|
const [appearance] = useAppearance();
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
const memoElRef = useRef<HTMLDivElement>(null);
|
const memoElRef = useRef<HTMLDivElement>(null);
|
||||||
const memo = {
|
const memo = {
|
||||||
@ -72,7 +72,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toImage(memoElRef.current, {
|
toImage(memoElRef.current, {
|
||||||
backgroundColor: apperance === "light" ? "#f4f4f5" : "#27272a",
|
backgroundColor: appearance === "light" ? "#f4f4f5" : "#27272a",
|
||||||
pixelRatio: window.devicePixelRatio * 2,
|
pixelRatio: window.devicePixelRatio * 2,
|
||||||
})
|
})
|
||||||
.then((url) => {
|
.then((url) => {
|
||||||
|
@ -19,6 +19,3 @@ export const MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const TAB_SPACE_WIDTH = 2;
|
export const TAB_SPACE_WIDTH = 2;
|
||||||
|
|
||||||
export const APPERANCE_OPTIONS = ["auto", "light", "dark"] as const;
|
|
||||||
export const APPERANCE_OPTIONS_STORAGE_KEY = "setting_APPERANCE_OPTIONS";
|
|
||||||
|
65
web/src/hooks/useAppearance.ts
Normal file
65
web/src/hooks/useAppearance.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useColorScheme } from "@mui/joy/styles";
|
||||||
|
import { useAppSelector } from "../store";
|
||||||
|
import { globalService } from "../services";
|
||||||
|
|
||||||
|
const getSystemColorScheme = () => {
|
||||||
|
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
|
return "dark";
|
||||||
|
} else {
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useAppearance = () => {
|
||||||
|
const user = useAppSelector((state) => state.user.user);
|
||||||
|
const appearance = useAppSelector((state) => state.global.appearance);
|
||||||
|
const { mode, setMode } = useColorScheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
globalService.setAppearance(user.setting.appearance);
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let mode = appearance;
|
||||||
|
if (appearance === "system") {
|
||||||
|
mode = getSystemColorScheme();
|
||||||
|
}
|
||||||
|
setMode(mode);
|
||||||
|
}, [appearance]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const colorSchemeChangeHandler = (event: MediaQueryListEvent) => {
|
||||||
|
const newColorScheme = event.matches ? "dark" : "light";
|
||||||
|
if (globalService.getState().appearance === "system") {
|
||||||
|
setMode(newColorScheme);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (appearance !== "system") {
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", colorSchemeChangeHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", colorSchemeChangeHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", colorSchemeChangeHandler);
|
||||||
|
};
|
||||||
|
}, [appearance]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
if (mode === "dark") {
|
||||||
|
root.classList.add("dark");
|
||||||
|
} else if (mode === "light") {
|
||||||
|
root.classList.remove("dark");
|
||||||
|
}
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
|
return [appearance, globalService.setAppearance] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAppearance;
|
@ -1,30 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { useColorScheme } from "@mui/joy/styles";
|
|
||||||
|
|
||||||
import { APPERANCE_OPTIONS, APPERANCE_OPTIONS_STORAGE_KEY } from "../helpers/consts";
|
|
||||||
import useLocalStorage from "./useLocalStorage";
|
|
||||||
import useMediaQuery from "./useMediaQuery";
|
|
||||||
|
|
||||||
export type Apperance = typeof APPERANCE_OPTIONS[number];
|
|
||||||
|
|
||||||
const useApperance = () => {
|
|
||||||
const [apperance, setApperance] = useLocalStorage<Apperance>(APPERANCE_OPTIONS_STORAGE_KEY, APPERANCE_OPTIONS[0]);
|
|
||||||
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
|
|
||||||
|
|
||||||
const { setMode } = useColorScheme();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const root = document.documentElement;
|
|
||||||
if (apperance === "dark" || (apperance === "auto" && prefersDarkMode)) {
|
|
||||||
root.classList.add("dark");
|
|
||||||
setMode("dark");
|
|
||||||
} else {
|
|
||||||
root.classList.remove("dark");
|
|
||||||
setMode("light");
|
|
||||||
}
|
|
||||||
}, [apperance, prefersDarkMode]);
|
|
||||||
|
|
||||||
return [apperance, setApperance] as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useApperance;
|
|
@ -1,26 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
const useLocalStorage = <T>(key: string, initialValue: T) => {
|
|
||||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
||||||
try {
|
|
||||||
const item = window.localStorage.getItem(key);
|
|
||||||
return item ? JSON.parse(item) : initialValue;
|
|
||||||
} catch (error) {
|
|
||||||
return initialValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const setValue = (value: T | ((val: T) => T)) => {
|
|
||||||
try {
|
|
||||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
||||||
setStoredValue(valueToStore);
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [storedValue, setValue] as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useLocalStorage;
|
|
@ -152,7 +152,7 @@
|
|||||||
"enable-folding-memo": "Enable folding memo",
|
"enable-folding-memo": "Enable folding memo",
|
||||||
"editor-font-style": "Editor font style",
|
"editor-font-style": "Editor font style",
|
||||||
"mobile-editor-style": "Mobile editor style",
|
"mobile-editor-style": "Mobile editor style",
|
||||||
"default-memo-sort-option": "Display by created/updated time",
|
"default-memo-sort-option": "Memo display time",
|
||||||
"created_ts": "Created Time",
|
"created_ts": "Created Time",
|
||||||
"updated_ts": "Updated Time"
|
"updated_ts": "Updated Time"
|
||||||
},
|
},
|
||||||
@ -168,9 +168,9 @@
|
|||||||
"additional-script-placeholder": "Additional JavaScript codes"
|
"additional-script-placeholder": "Additional JavaScript codes"
|
||||||
},
|
},
|
||||||
"apperance-option": {
|
"apperance-option": {
|
||||||
"auto": "Follow system",
|
|
||||||
"light": "Always light",
|
"light": "Always light",
|
||||||
"dark": "Always dark"
|
"dark": "Always dark",
|
||||||
|
"system": "Follow system"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"amount-text": {
|
"amount-text": {
|
||||||
|
@ -152,7 +152,7 @@
|
|||||||
"enable-folding-memo": "Activer le mémo pliable",
|
"enable-folding-memo": "Activer le mémo pliable",
|
||||||
"editor-font-style": "Style de police de l'éditeur",
|
"editor-font-style": "Style de police de l'éditeur",
|
||||||
"mobile-editor-style": "Style de l'éditeur mobile",
|
"mobile-editor-style": "Style de l'éditeur mobile",
|
||||||
"default-memo-sort-option": "Affichage par heure de création/mise à jour",
|
"default-memo-sort-option": "Memo display time",
|
||||||
"created_ts": "Heure de création",
|
"created_ts": "Heure de création",
|
||||||
"updated_ts": "Heure de mise à jour"
|
"updated_ts": "Heure de mise à jour"
|
||||||
},
|
},
|
||||||
|
@ -151,7 +151,7 @@
|
|||||||
"enable-folding-memo": "Enable folding memo",
|
"enable-folding-memo": "Enable folding memo",
|
||||||
"editor-font-style": "Thay đổi font cho trình soạn thảo",
|
"editor-font-style": "Thay đổi font cho trình soạn thảo",
|
||||||
"mobile-editor-style": "Vị trí editor trên mobile",
|
"mobile-editor-style": "Vị trí editor trên mobile",
|
||||||
"default-memo-sort-option": "Sắp xếp theo thời gian đã tạo",
|
"default-memo-sort-option": "Memo display time",
|
||||||
"created_ts": "tạo thời gian",
|
"created_ts": "tạo thời gian",
|
||||||
"updated_ts": "Thời gian cập nhật"
|
"updated_ts": "Thời gian cập nhật"
|
||||||
},
|
},
|
||||||
|
@ -152,7 +152,7 @@
|
|||||||
"enable-folding-memo": "开启折叠 Memo",
|
"enable-folding-memo": "开启折叠 Memo",
|
||||||
"editor-font-style": "编辑器字体样式",
|
"editor-font-style": "编辑器字体样式",
|
||||||
"mobile-editor-style": "移动端编辑器样式",
|
"mobile-editor-style": "移动端编辑器样式",
|
||||||
"default-memo-sort-option": "按创建时间/更新时间显示",
|
"default-memo-sort-option": "Memo 显示时间",
|
||||||
"created_ts": "创建时间",
|
"created_ts": "创建时间",
|
||||||
"updated_ts": "更新时间"
|
"updated_ts": "更新时间"
|
||||||
},
|
},
|
||||||
@ -168,9 +168,9 @@
|
|||||||
"additional-script-placeholder": "自定义 JavaScript 代码"
|
"additional-script-placeholder": "自定义 JavaScript 代码"
|
||||||
},
|
},
|
||||||
"apperance-option": {
|
"apperance-option": {
|
||||||
"auto": "跟随系统",
|
|
||||||
"light": "总是浅色",
|
"light": "总是浅色",
|
||||||
"dark": "总是深色"
|
"dark": "总是深色",
|
||||||
|
"system": "跟随系统"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"amount-text": {
|
"amount-text": {
|
||||||
|
@ -9,7 +9,7 @@ import useLoading from "../hooks/useLoading";
|
|||||||
import { globalService, userService } from "../services";
|
import { globalService, userService } from "../services";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
import ApperanceSelect from "../components/ApperanceSelect";
|
import AppearanceSelect from "../components/AppearanceSelect";
|
||||||
import "../less/auth.less";
|
import "../less/auth.less";
|
||||||
|
|
||||||
const validateConfig: ValidatorConfig = {
|
const validateConfig: ValidatorConfig = {
|
||||||
@ -177,7 +177,7 @@ const Auth = () => {
|
|||||||
<Option value="vi">Tiếng Việt</Option>
|
<Option value="vi">Tiếng Việt</Option>
|
||||||
<Option value="fr">French</Option>
|
<Option value="fr">French</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<ApperanceSelect />
|
<AppearanceSelect />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +18,7 @@ const router = createBrowserRouter([
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,6 +38,7 @@ const router = createBrowserRouter([
|
|||||||
} else if (isNullorUndefined(user)) {
|
} else if (isNullorUndefined(user)) {
|
||||||
return redirect("/explore");
|
return redirect("/explore");
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,6 +56,7 @@ const router = createBrowserRouter([
|
|||||||
if (isNullorUndefined(host)) {
|
if (isNullorUndefined(host)) {
|
||||||
return redirect("/auth");
|
return redirect("/auth");
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -71,6 +74,7 @@ const router = createBrowserRouter([
|
|||||||
if (isNullorUndefined(host)) {
|
if (isNullorUndefined(host)) {
|
||||||
return redirect("/auth");
|
return redirect("/auth");
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -88,6 +92,7 @@ const router = createBrowserRouter([
|
|||||||
if (isNullorUndefined(host)) {
|
if (isNullorUndefined(host)) {
|
||||||
return redirect("/auth");
|
return redirect("/auth");
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import store from "../store";
|
import store from "../store";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import * as storage from "../helpers/storage";
|
import * as storage from "../helpers/storage";
|
||||||
import { setGlobalState, setLocale } from "../store/modules/global";
|
import { setAppearance, setGlobalState, setLocale } from "../store/modules/global";
|
||||||
|
|
||||||
const globalService = {
|
const globalService = {
|
||||||
getState: () => {
|
getState: () => {
|
||||||
@ -11,6 +11,7 @@ const globalService = {
|
|||||||
initialState: async () => {
|
initialState: async () => {
|
||||||
const defaultGlobalState = {
|
const defaultGlobalState = {
|
||||||
locale: "en" as Locale,
|
locale: "en" as Locale,
|
||||||
|
appearance: "system" as Appearance,
|
||||||
systemStatus: {
|
systemStatus: {
|
||||||
allowSignUp: false,
|
allowSignUp: false,
|
||||||
additionalStyle: "",
|
additionalStyle: "",
|
||||||
@ -38,6 +39,10 @@ const globalService = {
|
|||||||
setLocale: (locale: Locale) => {
|
setLocale: (locale: Locale) => {
|
||||||
store.dispatch(setLocale(locale));
|
store.dispatch(setLocale(locale));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setAppearance: (appearance: Appearance) => {
|
||||||
|
store.dispatch(setAppearance(appearance));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default globalService;
|
export default globalService;
|
||||||
|
@ -8,6 +8,7 @@ import { setUser, patchUser, setHost, setOwner } from "../store/modules/user";
|
|||||||
|
|
||||||
const defaultSetting: Setting = {
|
const defaultSetting: Setting = {
|
||||||
locale: "en",
|
locale: "en",
|
||||||
|
appearance: "system",
|
||||||
memoVisibility: "PRIVATE",
|
memoVisibility: "PRIVATE",
|
||||||
memoDisplayTsOption: "created_ts",
|
memoDisplayTsOption: "created_ts",
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
locale: Locale;
|
locale: Locale;
|
||||||
|
appearance: Appearance;
|
||||||
systemStatus: SystemStatus;
|
systemStatus: SystemStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ const globalSlice = createSlice({
|
|||||||
name: "global",
|
name: "global",
|
||||||
initialState: {
|
initialState: {
|
||||||
locale: "en",
|
locale: "en",
|
||||||
|
appearance: "system",
|
||||||
systemStatus: {
|
systemStatus: {
|
||||||
host: undefined,
|
host: undefined,
|
||||||
profile: {
|
profile: {
|
||||||
@ -31,9 +33,15 @@ const globalSlice = createSlice({
|
|||||||
locale: action.payload,
|
locale: action.payload,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
setAppearance: (state, action: PayloadAction<Appearance>) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
appearance: action.payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setGlobalState, setLocale } = globalSlice.actions;
|
export const { setGlobalState, setLocale, setAppearance } = globalSlice.actions;
|
||||||
|
|
||||||
export default globalSlice.reducer;
|
export default globalSlice.reducer;
|
||||||
|
@ -3,10 +3,8 @@ import { extendTheme } from "@mui/joy";
|
|||||||
const theme = extendTheme({
|
const theme = extendTheme({
|
||||||
components: {
|
components: {
|
||||||
JoySelect: {
|
JoySelect: {
|
||||||
styleOverrides: {
|
defaultProps: {
|
||||||
root: {
|
size: "sm",
|
||||||
fontSize: "0.875rem",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
10
web/src/types/modules/setting.d.ts
vendored
10
web/src/types/modules/setting.d.ts
vendored
@ -1,5 +1,8 @@
|
|||||||
|
type Appearance = "light" | "dark" | "system";
|
||||||
|
|
||||||
interface Setting {
|
interface Setting {
|
||||||
locale: Locale;
|
locale: Locale;
|
||||||
|
appearance: Appearance;
|
||||||
memoVisibility: Visibility;
|
memoVisibility: Visibility;
|
||||||
memoDisplayTsOption: "created_ts" | "updated_ts";
|
memoDisplayTsOption: "created_ts" | "updated_ts";
|
||||||
}
|
}
|
||||||
@ -13,12 +16,17 @@ interface UserLocaleSetting {
|
|||||||
value: Locale;
|
value: Locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UserAppearanceSetting {
|
||||||
|
key: "appearance";
|
||||||
|
value: Appearance;
|
||||||
|
}
|
||||||
|
|
||||||
interface UserMemoVisibilitySetting {
|
interface UserMemoVisibilitySetting {
|
||||||
key: "memoVisibility";
|
key: "memoVisibility";
|
||||||
value: Visibility;
|
value: Visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSetting = UserLocaleSetting | UserMemoVisibilitySetting;
|
type UserSetting = UserLocaleSetting | UserAppearanceSetting | UserMemoVisibilitySetting;
|
||||||
|
|
||||||
interface UserSettingUpsert {
|
interface UserSettingUpsert {
|
||||||
key: keyof Setting;
|
key: keyof Setting;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user