Просмотр исходного кода

Merge pull request #8299 from weseek/feat/132740-setting-ui-in-me-page

feat: 132740 setting UI in me page
Yuki Takei 2 лет назад
Родитель
Сommit
f83f3583c2

+ 8 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -249,6 +249,14 @@
       "page_create": "Subscribe to the page when you create it."
       "page_create": "Subscribe to the page when you create it."
     }
     }
   },
   },
+  "ui_settings": {
+    "ui_settings": "UI Settings",
+    "side_bar_mode": {
+      "settings": "Sidebar mode settings",
+      "side_bar_mode_setting": "Set the sidebar mode",
+      "description": "You can set whether or not the sidebar will always be open when the screen width is large. If the screen width is small, the sidebar will always be closed."
+    }
+  },
   "editor_settings": {
   "editor_settings": {
     "editor_settings": "Editor Settings"
     "editor_settings": "Editor Settings"
   },
   },

+ 8 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -250,6 +250,14 @@
       "page_create": "ページを作成した時にそのページをサブスクライブします。"
       "page_create": "ページを作成した時にそのページをサブスクライブします。"
     }
     }
   },
   },
+  "ui_settings": {
+    "ui_settings": "UI設定",
+    "side_bar_mode": {
+      "settings": "サイドバーモードの設定",
+      "side_bar_mode_setting": "サイドバーのモードを設定する",
+      "description": "画面幅が大きい場合に、サイドバーを常時開いた状態にするかどうかを設定できます。画面幅が小さい場合はサイドバーは常に閉じた状態となります。"
+    }
+  },
   "editor_settings": {
   "editor_settings": {
     "editor_settings": "エディター設定",
     "editor_settings": "エディター設定",
     "common_settings": {
     "common_settings": {

+ 8 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -240,6 +240,14 @@
       "page_create": "创建页面时订阅页面。"
       "page_create": "创建页面时订阅页面。"
     }
     }
   },
   },
+  "ui_settings": {
+    "ui_settings": "用户界面设置",
+    "side_bar_mode": {
+      "settings": "侧边栏模式设置",
+      "side_bar_mode_setting": "设置侧边栏模式",
+      "description": "您可以设置当屏幕宽度较大时,侧边栏是否始终打开。 如果屏幕宽度较小,侧边栏将始终关闭。"
+    }
+  },
   "editor_settings": {
   "editor_settings": {
     "editor_settings": "编辑器设置"
     "editor_settings": "编辑器设置"
   },
   },

+ 6 - 0
apps/app/src/client/services/user-ui-settings.ts

@@ -25,3 +25,9 @@ export const scheduleToPut = (settings: Partial<IUserUISettings>): void => {
 
 
   _putUserUISettingsInBulkDebounced();
   _putUserUISettingsInBulkDebounced();
 };
 };
+
+export const updateUserUISettings = async(settings: Partial<IUserUISettings>): Promise<AxiosResponse<IUserUISettings>> => {
+  const result = await apiv3Put<IUserUISettings>('/user-ui-settings', { settings });
+
+  return result;
+};

+ 6 - 95
apps/app/src/components/Me/OtherSettings.tsx

@@ -1,105 +1,16 @@
-import {
-  useState, useEffect, useCallback,
-} from 'react';
-
-import { useTranslation } from 'next-i18next';
-import { UncontrolledTooltip } from 'reactstrap';
-
-import { apiv3Put } from '~/client/util/apiv3-client';
-import { toastSuccess, toastError } from '~/client/util/toastr';
-import { useSWRxIsQuestionnaireEnabled } from '~/features/questionnaire/client/stores/questionnaire';
-import { useCurrentUser } from '~/stores/context';
+import { QuestionnaireSettings } from './QuestionnaireSettings';
+import { UISettings } from './UISettings';
 
 
 const OtherSettings = (): JSX.Element => {
 const OtherSettings = (): JSX.Element => {
-  const { t } = useTranslation();
-  const { data: currentUser, error: errorCurrentUser } = useCurrentUser();
-  const { data: growiIsQuestionnaireEnabled } = useSWRxIsQuestionnaireEnabled();
-
-  const [isQuestionnaireEnabled, setIsQuestionnaireEnabled] = useState(currentUser?.isQuestionnaireEnabled);
-
-  const onChangeIsQuestionnaireEnabledHandler = useCallback(async() => {
-    setIsQuestionnaireEnabled(prev => !prev);
-  }, []);
-
-  const onClickUpdateIsQuestionnaireEnabledHandler = useCallback(async() => {
-    try {
-      await apiv3Put('/personal-setting/questionnaire-settings', {
-        isQuestionnaireEnabled,
-      });
-      toastSuccess(t('toaster.update_successed', { target: t('questionnaire.settings'), ns: 'commons' }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }, [isQuestionnaireEnabled, t]);
-
-  // Sync currentUser and state
-  useEffect(() => {
-    setIsQuestionnaireEnabled(currentUser?.isQuestionnaireEnabled);
-  }, [currentUser?.isQuestionnaireEnabled]);
-
-  const isLoadingCurrentUser = currentUser === undefined && errorCurrentUser === undefined;
 
 
   return (
   return (
     <>
     <>
-      <h2 className="border-bottom my-4">{t('questionnaire.settings')}</h2>
-
-      {isLoadingCurrentUser && (
-        <div className="text-muted text-center mb-5">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1" />
-        </div>
-      )}
-
-      <div className="row">
-        <div className="offset-md-3 col-md-6 text-start">
-          {!isLoadingCurrentUser && (
-            <div className="form-check form-switch form-check-primary">
-              <span id="grw-questionnaire-settings-toggle-wrapper">
-                <input
-                  type="checkbox"
-                  className="form-check-input"
-                  id="isQuestionnaireEnabled"
-                  checked={growiIsQuestionnaireEnabled && isQuestionnaireEnabled}
-                  onChange={onChangeIsQuestionnaireEnabledHandler}
-                  disabled={!growiIsQuestionnaireEnabled}
-                />
-                <label className="form-label form-check-label" htmlFor="isQuestionnaireEnabled">
-                  {t('questionnaire.enable_questionnaire')}
-                </label>
-              </span>
-              <p className="form-text text-muted small">
-                {t('questionnaire.personal_settings_explanation')}
-              </p>
-              {!growiIsQuestionnaireEnabled && (
-                <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-toggle-wrapper">
-                  {t('questionnaire.disabled_by_admin')}
-                </UncontrolledTooltip>
-              ) }
-            </div>
-          )}
-        </div>
+      <div className="mt-4">
+        <QuestionnaireSettings />
       </div>
       </div>
 
 
-      <div className="row my-3">
-        <div className="offset-4 col-5">
-          <span className="d-inline-block" id="grw-questionnaire-settings-update-btn-wrapper">
-            <button
-              data-testid="grw-questionnaire-settings-update-btn"
-              type="button"
-              className="btn btn-primary"
-              onClick={onClickUpdateIsQuestionnaireEnabledHandler}
-              disabled={!growiIsQuestionnaireEnabled}
-              style={growiIsQuestionnaireEnabled ? {} : { pointerEvents: 'none' }}
-            >
-              {t('Update')}
-            </button>
-          </span>
-          {!growiIsQuestionnaireEnabled && (
-            <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-update-btn-wrapper">
-              {t('questionnaire.disabled_by_admin')}
-            </UncontrolledTooltip>
-          )}
-        </div>
+      <div className="mt-4">
+        <UISettings />
       </div>
       </div>
     </>
     </>
   );
   );

+ 106 - 0
apps/app/src/components/Me/QuestionnaireSettings.tsx

@@ -0,0 +1,106 @@
+import { useCallback, useEffect, useState } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import { UncontrolledTooltip } from 'reactstrap';
+
+import { apiv3Put } from '~/client/util/apiv3-client';
+import { toastError, toastSuccess } from '~/client/util/toastr';
+import { useSWRxIsQuestionnaireEnabled } from '~/features/questionnaire/client/stores/questionnaire';
+import { useCurrentUser } from '~/stores/context';
+
+
+export const QuestionnaireSettings = (): JSX.Element => {
+  const { t } = useTranslation();
+  const { data: currentUser, error: errorCurrentUser } = useCurrentUser();
+  const { data: growiIsQuestionnaireEnabled } = useSWRxIsQuestionnaireEnabled();
+
+  const [isQuestionnaireEnabled, setIsQuestionnaireEnabled] = useState(currentUser?.isQuestionnaireEnabled);
+
+  const onChangeIsQuestionnaireEnabledHandler = useCallback(async() => {
+    setIsQuestionnaireEnabled(prev => !prev);
+  }, []);
+
+  const onClickUpdateIsQuestionnaireEnabledHandler = useCallback(async() => {
+    try {
+      await apiv3Put('/personal-setting/questionnaire-settings', {
+        isQuestionnaireEnabled,
+      });
+      toastSuccess(t('toaster.update_successed', { target: t('questionnaire.settings'), ns: 'commons' }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [isQuestionnaireEnabled, t]);
+
+  // Sync currentUser and state
+  useEffect(() => {
+    setIsQuestionnaireEnabled(currentUser?.isQuestionnaireEnabled);
+  }, [currentUser?.isQuestionnaireEnabled]);
+
+  const isLoadingCurrentUser = currentUser === undefined && errorCurrentUser === undefined;
+
+  return (
+    <>
+      <h2 className="border-bottom mb-4">{t('questionnaire.settings')}</h2>
+
+      {isLoadingCurrentUser && (
+        <div className="text-muted text-center mb-5">
+          <i className="fa fa-2x fa-spinner fa-pulse me-1" />
+        </div>
+      )}
+
+      <div className="row">
+        <div className="offset-md-3 col-md-6 text-start">
+          {!isLoadingCurrentUser && (
+            <div className="form-check form-switch">
+              <span id="grw-questionnaire-settings-toggle-wrapper">
+                <input
+                  type="checkbox"
+                  className="form-check-input"
+                  id="isQuestionnaireEnabled"
+                  checked={growiIsQuestionnaireEnabled && isQuestionnaireEnabled}
+                  onChange={onChangeIsQuestionnaireEnabledHandler}
+                  disabled={!growiIsQuestionnaireEnabled}
+                />
+                <label className="form-label form-check-label" htmlFor="isQuestionnaireEnabled">
+                  {t('questionnaire.enable_questionnaire')}
+                </label>
+              </span>
+              <p className="form-text text-muted small">
+                {t('questionnaire.personal_settings_explanation')}
+              </p>
+              {!growiIsQuestionnaireEnabled && (
+                <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-toggle-wrapper">
+                  {t('questionnaire.disabled_by_admin')}
+                </UncontrolledTooltip>
+              ) }
+            </div>
+          )}
+        </div>
+      </div>
+
+      <div className="row my-3">
+        <div className="offset-4 col-5">
+          <span className="d-inline-block" id="grw-questionnaire-settings-update-btn-wrapper">
+            <button
+              data-testid="grw-questionnaire-settings-update-btn"
+              type="button"
+              className="btn btn-primary"
+              onClick={onClickUpdateIsQuestionnaireEnabledHandler}
+              disabled={!growiIsQuestionnaireEnabled}
+              style={growiIsQuestionnaireEnabled ? {} : { pointerEvents: 'none' }}
+            >
+              {t('Update')}
+            </button>
+          </span>
+          {!growiIsQuestionnaireEnabled && (
+            <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-update-btn-wrapper">
+              {t('questionnaire.disabled_by_admin')}
+            </UncontrolledTooltip>
+          )}
+        </div>
+      </div>
+    </>
+
+  );
+};

+ 2 - 2
apps/app/src/components/Icons/SidebarDrawerIcon.jsx → apps/app/src/components/Me/SidebarCollapsedIcon.jsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import React from 'react';
 
 
-const SidebarDrawerIcon = () => (
+const SidebarCollapsedIcon = () => (
   <svg
   <svg
     xmlns="http://www.w3.org/2000/svg"
     xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 23 23"
     viewBox="0 0 23 23"
@@ -22,4 +22,4 @@ const SidebarDrawerIcon = () => (
   </svg>
   </svg>
 );
 );
 
 
-export default SidebarDrawerIcon;
+export default SidebarCollapsedIcon;

+ 0 - 0
apps/app/src/components/Icons/SidebarDockIcon.jsx → apps/app/src/components/Me/SidebarDockIcon.jsx


+ 7 - 0
apps/app/src/components/Me/UISettings.module.scss

@@ -0,0 +1,7 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+.grw-sidebar-mode-icon {
+  width: 20px;
+  height: 20px;
+  color: bs.$secondary;
+}

+ 114 - 0
apps/app/src/components/Me/UISettings.tsx

@@ -0,0 +1,114 @@
+import { useCallback } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import { UncontrolledTooltip } from 'reactstrap';
+
+import { updateUserUISettings } from '~/client/services/user-ui-settings';
+import { toastError, toastSuccess } from '~/client/util/toastr';
+import { useCollapsedContentsOpened, usePreferCollapsedMode, useSidebarMode } from '~/stores/ui';
+
+import SidebarCollapsedIcon from './SidebarCollapsedIcon';
+import SidebarDockIcon from './SidebarDockIcon';
+
+import styles from './UISettings.module.scss';
+
+const IconWithTooltip = ({
+  id, label, children, additionalClasses,
+}: {
+id: string,
+label: string,
+children: JSX.Element,
+additionalClasses: string
+}) => (
+  <>
+    <div id={id} className={`${additionalClasses != null ? additionalClasses : ''}`}>{children}</div>
+    <UncontrolledTooltip placement="bottom" fade={false} target={id}>{label}</UncontrolledTooltip>
+  </>
+);
+
+export const UISettings = (): JSX.Element => {
+  const { t } = useTranslation();
+  const {
+    isDockMode, isCollapsedMode,
+  } = useSidebarMode();
+  const { mutate: mutatePreferCollapsedMode } = usePreferCollapsedMode();
+  const { mutate: mutateCollapsedContentsOpened } = useCollapsedContentsOpened();
+
+  const toggleCollapsed = useCallback(() => {
+    mutatePreferCollapsedMode(!isCollapsedMode());
+    mutateCollapsedContentsOpened(false);
+  }, [mutatePreferCollapsedMode, isCollapsedMode, mutateCollapsedContentsOpened]);
+
+  const updateButtonHandler = useCallback(async() => {
+    try {
+      await updateUserUISettings({ preferCollapsedModeByUser: isCollapsedMode() });
+      toastSuccess(t('toaster.update_successed', { target: t('ui_settings.side_bar_mode.settings'), ns: 'commons' }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+
+  }, [isCollapsedMode, t]);
+
+  const renderSidebarModeSwitch = () => {
+    return (
+      <>
+        <div className="d-flex align-items-start">
+          <div className="d-flex align-items-center">
+            <IconWithTooltip
+              id="iwt-sidebar-collapsed"
+              label="Collapsed"
+              additionalClasses={styles['grw-sidebar-mode-icon']}
+            >
+              <SidebarCollapsedIcon />
+            </IconWithTooltip>
+            <div className="form-check form-switch ms-2">
+
+              <input
+                id="swSidebarMode"
+                className="form-check-input"
+                type="checkbox"
+                checked={isDockMode()}
+                onChange={toggleCollapsed}
+              />
+              <label className="form-label form-check-label" htmlFor="swSidebarMode"></label>
+            </div>
+            <IconWithTooltip id="iwt-sidebar-dock" label="Dock" additionalClasses={styles['grw-sidebar-mode-icon']}>
+              <SidebarDockIcon />
+            </IconWithTooltip>
+          </div>
+          <div className="ms-2">
+            <label className="form-label form-check-label" htmlFor="swSidebarMode">
+              {t('ui_settings.side_bar_mode.side_bar_mode_setting')}
+            </label>
+            <p className="form-text text-muted small">{t('ui_settings.side_bar_mode.description')}</p>
+          </div>
+        </div>
+      </>
+    );
+  };
+
+  return (
+    <>
+      <h2 className="border-bottom mb-4">{t('ui_settings.ui_settings')}</h2>
+
+      <div className="row justify-content-center">
+        <div className="col-md-6">
+
+          { renderSidebarModeSwitch() }
+
+          <div>
+          </div>
+        </div>
+      </div>
+
+      <div className="row my-3">
+        <div className="offset-4 col-5">
+          <button data-testid="" type="button" className="btn btn-primary" onClick={updateButtonHandler}>
+            {t('Update')}
+          </button>
+        </div>
+      </div>
+    </>
+  );
+};