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 again1>",
+ "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));