diff --git a/packages/asc-web-common/api/people/index.js b/packages/asc-web-common/api/people/index.js index f9323a7d448..cb26736c347 100644 --- a/packages/asc-web-common/api/people/index.js +++ b/packages/asc-web-common/api/people/index.js @@ -303,3 +303,13 @@ export function getSelectorUserList() { url: "/people/filter.json?fields=id,displayName,groups", }); } + +export function changeTheme(key) { + const data = { Theme: key }; + + return request({ + method: "put", + url: `/people/theme.json`, + data, + }); +} diff --git a/packages/asc-web-common/constants/index.js b/packages/asc-web-common/constants/index.js index a3cda8f3a3b..81eb1e82976 100644 --- a/packages/asc-web-common/constants/index.js +++ b/packages/asc-web-common/constants/index.js @@ -199,3 +199,16 @@ export const FileStatus = Object.freeze({ export const TenantStatus = Object.freeze({ PortalRestore: 4, }); + +/** + * Enum for theme keys. + * @readonly + */ +export const ThemeKeys = Object.freeze({ + Base: "0", + BaseStr: "Base", + Dark: "1", + DarkStr: "Dark", + System: "2", + SystemStr: "System", +}); diff --git a/packages/asc-web-common/store/SettingsStore.js b/packages/asc-web-common/store/SettingsStore.js index 305bacfec79..c2ad3980a06 100644 --- a/packages/asc-web-common/store/SettingsStore.js +++ b/packages/asc-web-common/store/SettingsStore.js @@ -3,7 +3,7 @@ import api from "../api"; import { LANGUAGE, TenantStatus } from "../constants"; import { combineUrl } from "../utils"; import FirebaseHelper from "../utils/firebase"; -import { AppServerConfig } from "../constants"; +import { AppServerConfig, ThemeKeys } from "../constants"; import { version } from "../package.json"; import SocketIOHelper from "../utils/socket"; @@ -26,9 +26,7 @@ class SettingsStore { currentProductId = ""; culture = "en"; cultures = []; - theme = !!localStorage.getItem("theme") - ? themes[localStorage.getItem("theme")] - : Base; + theme = Base; trustedDomains = []; trustedDomainsType = 0; timezone = "UTC"; @@ -440,19 +438,28 @@ class SettingsStore { this.buildVersionInfo.documentServer = "6.4.1"; }; - changeTheme = () => { - const currentTheme = - JSON.stringify(this.theme) === JSON.stringify(Base) ? Dark : Base; - localStorage.setItem( - "theme", - JSON.stringify(this.theme) === JSON.stringify(Base) ? "Dark" : "Base" - ); - this.theme = currentTheme; - }; + setTheme = (key) => { + let theme = null; + switch (key) { + case ThemeKeys.Base: + case ThemeKeys.BaseStr: + theme = ThemeKeys.BaseStr; + break; + case ThemeKeys.Dark: + case ThemeKeys.DarkStr: + theme = ThemeKeys.DarkStr; + break; + case ThemeKeys.System: + case ThemeKeys.SystemStr: + default: + theme = + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ? ThemeKeys.DarkStr + : ThemeKeys.BaseStr; + } - setTheme = (theme) => { this.theme = themes[theme]; - localStorage.setItem("theme", theme); }; setMailDomainSettings = async (data) => { diff --git a/packages/asc-web-common/store/UserStore.js b/packages/asc-web-common/store/UserStore.js index b21cbfcf6c9..1a235ac16b4 100644 --- a/packages/asc-web-common/store/UserStore.js +++ b/packages/asc-web-common/store/UserStore.js @@ -48,6 +48,18 @@ class UserStore { this.setIsLoading(false); }; + changeTheme = async (key) => { + this.setIsLoading(true); + + const { theme } = await api.people.changeTheme(key); + + this.user.theme = theme; + + this.setIsLoading(false); + + return theme; + }; + setUserIsUpdate = (isUpdate) => { //console.log("setUserIsUpdate"); this.userIsUpdate = isUpdate; diff --git a/products/ASC.People/Client/public/locales/en/Profile.json b/products/ASC.People/Client/public/locales/en/Profile.json index b2861d18f3c..8bf5adae615 100644 --- a/products/ASC.People/Client/public/locales/en/Profile.json +++ b/products/ASC.People/Client/public/locales/en/Profile.json @@ -3,11 +3,14 @@ "ChangesApplied": "Changes are applied", "ContactInformation": "Contact Information", "CountCodesRemaining": "codes remaining", + "DarkTheme": "Dark theme", "Disconnect": "Disconnect", "EditPhoto": "Edit photo", "EditSubscriptionsBtn": "Edit subscriptions", "EditUser": "Edit profile", + "InterfaceTheme": "Interface theme", "InviteAgainLbl": "Invite again", + "LightTheme": "Light theme", "LoginSettings": "Login settings", "MessageEmailActivationInstuctionsSentOnEmail": "The email activation instructions have been sent to the {{ email }} email address", "PhoneLbl": "Phone", @@ -17,6 +20,7 @@ "Subscriptions": "Subscriptions", "SubscriptionEmailTipsToggleLbl": "Email notification with tips and tricks", "SubscriptionTurnOffToast": "You have been successfully unsubscribed from the the mailing list. <1>Subscribe again", + "SystemTheme": "Use system theme", "TfaLoginSettings": "Login settings", "TwoFactorDescription": "Two-factor authentication via a code-generating app was enabled for all users by the admin." } diff --git a/products/ASC.People/Client/public/locales/ru/Profile.json b/products/ASC.People/Client/public/locales/ru/Profile.json index 910a01ff188..0683e9992e3 100644 --- a/products/ASC.People/Client/public/locales/ru/Profile.json +++ b/products/ASC.People/Client/public/locales/ru/Profile.json @@ -3,11 +3,14 @@ "ChangesApplied": "Изменения успешно применены", "ContactInformation": "Контактные данные", "CountCodesRemaining": "оставшиеся коды", + "DarkTheme": "Тёмная тема", "Disconnect": "Отключить", "EditPhoto": "Изменить фотографию", "EditSubscriptionsBtn": "Изменить подписки", "EditUser": "Редактировать", + "InterfaceTheme": "Тема интерфейса", "InviteAgainLbl": "Активировать адрес email ещё раз", + "LightTheme": "Светлая тема", "LoginSettings": "Вход через социальные сети", "MessageEmailActivationInstuctionsSentOnEmail": "Инструкция по активации почты пользователя была отправлена по адресу {{ email }}", "PhoneLbl": "Основной телефон", @@ -15,6 +18,7 @@ "ProviderSuccessfullyDisconnected": "Провайдер успешно отключен", "ShowBackupCodes": "Показать резервные коды", "Subscriptions": "Подписки", + "SystemTheme": "Использовать тему системы", "TfaLoginSettings": "Настройки входа", "TwoFactorDescription": "Двухфакторная аутентификация с помощью приложения для генерации кодов включена администратором для всех пользователей." } diff --git a/products/ASC.People/Client/src/pages/Profile/Section/Body/index.js b/products/ASC.People/Client/src/pages/Profile/Section/Body/index.js index 1b837cdaaca..5a8f4496676 100644 --- a/products/ASC.People/Client/src/pages/Profile/Section/Body/index.js +++ b/products/ASC.People/Client/src/pages/Profile/Section/Body/index.js @@ -26,8 +26,16 @@ import { getUserRole, } from "../../../../helpers/people-helpers"; import config from "../../../../../package.json"; -import { AppServerConfig, providersData } from "@appserver/common/constants"; -import { unlinkOAuth, linkOAuth } from "@appserver/common/api/people"; +import { + AppServerConfig, + providersData, + ThemeKeys, +} from "@appserver/common/constants"; +import { + unlinkOAuth, + linkOAuth, + changeTheme, +} from "@appserver/common/api/people"; import { getAuthProviders } from "@appserver/common/api/settings"; import { Trans, useTranslation } from "react-i18next"; import { @@ -37,6 +45,7 @@ import { import Loaders from "@appserver/common/components/Loaders"; import withLoader from "../../../../HOCs/withLoader"; +import RadioButtonGroup from "@appserver/components/radio-button-group"; const ProfileWrapper = styled.div` display: flex; @@ -344,6 +353,18 @@ class SectionBodyContent extends React.PureComponent { this.props.changeEmailSubscription(checked); }; + onChangeTheme = async (e) => { + const { setIsLoading, changeTheme, setTheme } = this.props; + + const value = e.currentTarget.value; + + setIsLoading(true); + + await changeTheme(value); + + setIsLoading(false); + }; + render() { const { resetAppDialogVisible, backupCodesDialogVisible, tfa } = this.state; const { @@ -359,6 +380,8 @@ class SectionBodyContent extends React.PureComponent { personal, tipsSubscription, theme, + setTheme, + selectedTheme, } = this.props; const contacts = profile.contacts && getUserContacts(profile.contacts); const role = getUserRole(profile); @@ -483,8 +506,27 @@ class SectionBodyContent extends React.PureComponent { )} - {profile.notes && ( + {isSelf && ( + + + + + )} + + {profile.notes && ( + {profile.notes} @@ -535,9 +577,21 @@ class SectionBodyContent extends React.PureComponent { export default withRouter( inject(({ auth, peopleStore }) => { const { isAdmin, userStore, settingsStore, tfaStore } = auth; - const { user: viewer } = userStore; - const { isTabletView, getOAuthToken, getLoginLink, theme } = settingsStore; - const { targetUserStore, avatarEditorStore, usersStore } = peopleStore; + const { user: viewer, changeTheme } = userStore; + + const { + isTabletView, + getOAuthToken, + getLoginLink, + theme, + setTheme, + } = settingsStore; + const { + targetUserStore, + avatarEditorStore, + usersStore, + loadingStore, + } = peopleStore; const { targetUser: profile, isMe: isSelf, @@ -579,6 +633,10 @@ export default withRouter( changeEmailSubscription, tipsSubscription, theme, + setTheme, + changeTheme, + selectedTheme: viewer.theme, + setIsLoading: loadingStore.setIsLoading, }; })( observer( diff --git a/public/locales/en/Common.json b/public/locales/en/Common.json index 9cd8870a4ba..f7701531db8 100644 --- a/public/locales/en/Common.json +++ b/public/locales/en/Common.json @@ -52,7 +52,6 @@ "Culture_uk": "Ukrainian (Ukraine)", "Culture_vi": "Vietnamese (Vietnam)", "Culture_zh-CN": "Chinese (Simplified, PRC)", - "DarkMode": "Dark mode", "Duplicate": "Create a copy", "Delete": "Delete", "Department": "Department", diff --git a/public/locales/ru/Common.json b/public/locales/ru/Common.json index bf79805831c..7f765619f43 100644 --- a/public/locales/ru/Common.json +++ b/public/locales/ru/Common.json @@ -52,7 +52,6 @@ "Culture_uk": "Украинский (Украина)", "Culture_vi": "Вьетнамский (Вьетнам)", "Culture_zh-CN": "Китайский (упрощенный, КНР)", - "DarkMode": "Тёмная тема", "Duplicate": "Создать копию", "Delete": "Удалить", "Department": "Отдел", diff --git a/web/ASC.Web.Client/src/Shell.jsx b/web/ASC.Web.Client/src/Shell.jsx index c2510d75288..2e138afc60f 100644 --- a/web/ASC.Web.Client/src/Shell.jsx +++ b/web/ASC.Web.Client/src/Shell.jsx @@ -175,21 +175,6 @@ const InvalidRoute = (props) => ( const RedirectToHome = () => ; -const checkTheme = () => { - const theme = localStorage.getItem("theme"); - - if (theme) return theme; - - if ( - window.matchMedia && - window.matchMedia("(prefers-color-scheme: dark)").matches - ) { - return "Dark"; - } - - return "Base"; -}; - const Shell = ({ items = [], page = "home", ...rest }) => { const { isLoaded, @@ -206,6 +191,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => { setMaintenanceExist, roomsMode, setSnackbarExist, + userTheme, } = rest; useEffect(() => { @@ -431,8 +417,8 @@ const Shell = ({ items = [], page = "home", ...rest }) => { }, [page]); useEffect(() => { - setTheme(checkTheme()); - }, []); + if (userTheme) setTheme(userTheme); + }, [userTheme]); const pathname = window.location.pathname.toLowerCase(); const isEditor = pathname.indexOf("doceditor") !== -1; @@ -567,6 +553,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => { const ShellWrapper = inject(({ auth, backup }) => { const { init, isLoaded, settingsStore, setProductVersion, language } = auth; + const { personal, roomsMode, @@ -605,11 +592,13 @@ const ShellWrapper = inject(({ auth, backup }) => { setTheme, roomsMode, setSnackbarExist, + userTheme: auth?.userStore?.user?.theme, }; })(observer(Shell)); const ThemeProviderWrapper = inject(({ auth }) => { const { settingsStore } = auth; + return { theme: settingsStore.theme }; })(observer(ThemeProvider));