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

Merge pull request #6055 from weseek/feat/96846-implement-backend

feat: 96846 implement backend
Yuki Takei 3 лет назад
Родитель
Сommit
a735a2fba6

+ 2 - 2
packages/app/src/client/services/ContextExtractor.tsx

@@ -99,9 +99,9 @@ const ContextExtractorOnce: FC = () => {
   useCurrentUser(currentUser);
 
   // UserUISettings
-  usePreferDrawerModeByUser(userUISettings?.preferDrawerModeByUser);
+  usePreferDrawerModeByUser(userUISettings?.preferDrawerModeByUser ?? configByContextHydrate.isSidebarDrawerMode);
   usePreferDrawerModeOnEditByUser(userUISettings?.preferDrawerModeOnEditByUser);
-  useSidebarCollapsed(userUISettings?.isSidebarCollapsed);
+  useSidebarCollapsed(userUISettings?.isSidebarCollapsed ?? configByContextHydrate.isSidebarClosedAtDockMode);
   useCurrentSidebarContents(userUISettings?.currentSidebarContents);
   useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
 

+ 29 - 20
packages/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx

@@ -1,23 +1,32 @@
-import React, { useState, useCallback } from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'react-i18next';
 import { Card, CardBody } from 'reactstrap';
 
+import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { isDarkMode as isDarkModeByUtil } from '~/client/util/color-scheme';
+import { useSWRxSidebarConfig } from '~/stores/ui';
 
 const CustomizeSidebarsetting = (): JSX.Element => {
   const { t } = useTranslation();
-  const [isDrawerMode, setIsDrawerMode] = useState(false);
-  const [isDefaultOpenAtDockMode, setIsDefaultOpenAtDockMode] = useState(false);
+  const {
+    update, isSidebarDrawerMode, isSidebarClosedAtDockMode, setIsSidebarDrawerMode, setIsSidebarClosedAtDockMode,
+  } = useSWRxSidebarConfig();
 
   const isDarkMode = isDarkModeByUtil();
   const colorText = isDarkMode ? 'dark' : 'light';
   const drawerIconFileName = `/images/customize-settings/drawer-${colorText}.svg`;
   const dockIconFileName = `/images/customize-settings/dock-${colorText}.svg`;
 
-  const onClickSubmit = () => {
-    console.log('update!');
-  };
+  const onClickSubmit = useCallback(async() => {
+    try {
+      await update();
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.default_sidebar_mode.title') }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [t, update]);
 
   return (
     <React.Fragment>
@@ -35,8 +44,8 @@ const CustomizeSidebarsetting = (): JSX.Element => {
           <div className="d-flex justify-content-around mt-5">
             <div id="layoutOptions" className="card-deck">
               <div
-                className={`card customize-layout-card ${isDrawerMode ? 'border-active' : ''}`}
-                onClick={() => setIsDrawerMode(true)}
+                className={`card customize-layout-card ${isSidebarDrawerMode ? 'border-active' : ''}`}
+                onClick={() => setIsSidebarDrawerMode(true)}
                 role="button"
               >
                 <img src={drawerIconFileName} />
@@ -45,8 +54,8 @@ const CustomizeSidebarsetting = (): JSX.Element => {
                 </div>
               </div>
               <div
-                className={`card customize-layout-card ${!isDrawerMode ? 'border-active' : ''}`}
-                onClick={() => setIsDrawerMode(false)}
+                className={`card customize-layout-card ${!isSidebarDrawerMode ? 'border-active' : ''}`}
+                onClick={() => setIsSidebarDrawerMode(false)}
                 role="button"
               >
                 <img src={dockIconFileName} />
@@ -67,28 +76,28 @@ const CustomizeSidebarsetting = (): JSX.Element => {
             <div className="custom-control custom-radio my-3">
               <input
                 type="radio"
-                id="radio-email-show"
+                id="is-open"
                 className="custom-control-input"
                 name="mailVisibility"
-                checked={!isDrawerMode && isDefaultOpenAtDockMode}
-                disabled={isDrawerMode}
-                onChange={() => { setIsDefaultOpenAtDockMode(true) }}
+                checked={!isSidebarDrawerMode && !isSidebarClosedAtDockMode}
+                disabled={isSidebarDrawerMode}
+                onChange={() => setIsSidebarClosedAtDockMode(false)}
               />
-              <label className="custom-control-label" htmlFor="radio-email-show">
+              <label className="custom-control-label" htmlFor="is-open">
                 {t('admin:customize_setting.default_sidebar_mode.dock_mode_default_open')}
               </label>
             </div>
             <div className="custom-control custom-radio my-3">
               <input
                 type="radio"
-                id="radio-email-show"
+                id="is-closed"
                 className="custom-control-input"
                 name="mailVisibility"
-                checked={!isDrawerMode && !isDefaultOpenAtDockMode}
-                disabled={isDrawerMode}
-                onChange={() => { setIsDefaultOpenAtDockMode(false) }}
+                checked={!isSidebarDrawerMode && isSidebarClosedAtDockMode}
+                disabled={isSidebarDrawerMode}
+                onChange={() => setIsSidebarClosedAtDockMode(true)}
               />
-              <label className="custom-control-label" htmlFor="radio-email-show">
+              <label className="custom-control-label" htmlFor="is-closed">
                 {t('admin:customize_setting.default_sidebar_mode.dock_mode_default_close')}
               </label>
             </div>

+ 5 - 0
packages/app/src/interfaces/sidebar-config.ts

@@ -0,0 +1,5 @@
+
+export interface ISidebarConfig {
+  isSidebarDrawerMode: boolean,
+  isSidebarClosedAtDockMode: boolean
+}

+ 5 - 1
packages/app/src/server/models/config.ts

@@ -1,7 +1,7 @@
+import { getOrCreateModel } from '@growi/core';
 import { Types, Schema } from 'mongoose';
 import uniqueValidator from 'mongoose-unique-validator';
 
-import { getOrCreateModel } from '@growi/core';
 
 export interface Config {
   _id: Types.ObjectId;
@@ -135,6 +135,8 @@ export const defaultCrowiConfigs: { [key: string]: any } = {
   'customize:isEnabledStaleNotification': false,
   'customize:isAllReplyShown': false,
   'customize:isSearchScopeChildrenAsDefault': false,
+  'customize:isSidebarDrawerMode': false,
+  'customize:isSidebarClosedAtDockMode': false,
 
   'notification:owner-page:isEnabled': false,
   'notification:group-page:isEnabled': false,
@@ -242,6 +244,8 @@ schema.statics.getLocalconfig = function(crowi) {
     globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
     pageLimitationL: crowi.configManager.getConfig('crowi', 'customize:showPageLimitationL'),
     pageLimitationXL: crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL'),
+    isSidebarDrawerMode: crowi.configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'),
+    isSidebarClosedAtDockMode: crowi.configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
   };
 
   return localConfig;

+ 41 - 0
packages/app/src/server/routes/apiv3/customize-setting.js

@@ -10,6 +10,7 @@ const express = require('express');
 const router = express.Router();
 
 const { body, query } = require('express-validator');
+
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 /**
@@ -105,6 +106,10 @@ module.exports = (crowi) => {
     theme: [
       body('themeType').isString(),
     ],
+    sidebar: [
+      body('isSidebarDrawerMode').isBoolean(),
+      body('isSidebarClosedAtDockMode').isBoolean(),
+    ],
     function: [
       body('isEnabledTimeline').isBoolean(),
       body('isSavedStatesOfTabChanges').isBoolean(),
@@ -334,6 +339,42 @@ module.exports = (crowi) => {
     }
   });
 
+  // sidebar
+  router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
+
+    try {
+      const isSidebarDrawerMode = await crowi.configManager.getConfig('crowi', 'customize:isSidebarDrawerMode');
+      const isSidebarClosedAtDockMode = await crowi.configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode');
+      return res.apiv3({ isSidebarDrawerMode, isSidebarClosedAtDockMode });
+    }
+    catch (err) {
+      const msg = 'Error occurred in getting sidebar';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'get-sidebar-failed'));
+    }
+  });
+
+  router.put('/sidebar', loginRequiredStrictly, adminRequired, csrf, validator.sidebar, apiV3FormValidator, async(req, res) => {
+    const requestParams = {
+      'customize:isSidebarDrawerMode': req.body.isSidebarDrawerMode,
+      'customize:isSidebarClosedAtDockMode': req.body.isSidebarClosedAtDockMode,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      const customizedParams = {
+        isSidebarDrawerMode: await crowi.configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'),
+        isSidebarClosedAtDockMode: await crowi.configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'),
+      };
+      return res.apiv3({ customizedParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating sidebar';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-sidebar-failed'));
+    }
+  });
+
   /**
    * @swagger
    *

+ 68 - 0
packages/app/src/stores/ui.tsx

@@ -10,7 +10,9 @@ import useSWRImmutable from 'swr/immutable';
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { useUserUISettings } from '~/client/services/user-ui-settings';
+import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
 import { Nullable } from '~/interfaces/common';
+import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { UpdateDescCountData } from '~/interfaces/websocket';
 import loggerFactory from '~/utils/logger';
@@ -278,6 +280,72 @@ export const useDrawerMode = (): SWRResponse<boolean, Error> => {
   );
 };
 
+type SidebarConfigOption = {
+  update: () => Promise<void>,
+  isSidebarDrawerMode: boolean|undefined,
+  isSidebarClosedAtDockMode: boolean|undefined,
+  setIsSidebarDrawerMode: (isSidebarDrawerMode: boolean) => void,
+  setIsSidebarClosedAtDockMode: (isSidebarClosedAtDockMode: boolean) => void
+}
+
+export const useSWRxSidebarConfig = (): SWRResponse<ISidebarConfig, Error> & SidebarConfigOption => {
+  const swrResponse = useSWRImmutable<ISidebarConfig>(
+    '/customize-setting/sidebar',
+    endpoint => apiv3Get(endpoint).then(result => result.data),
+  );
+  return {
+    ...swrResponse,
+    update: async() => {
+      const { data } = swrResponse;
+
+      if (data == null) {
+        return;
+      }
+
+      const { isSidebarDrawerMode, isSidebarClosedAtDockMode } = data;
+
+      const updateData = {
+        isSidebarDrawerMode,
+        isSidebarClosedAtDockMode,
+      };
+
+      // invoke API
+      await apiv3Put('/customize-setting/sidebar', updateData);
+    },
+    isSidebarDrawerMode: swrResponse.data?.isSidebarDrawerMode,
+    isSidebarClosedAtDockMode: swrResponse.data?.isSidebarClosedAtDockMode,
+    setIsSidebarDrawerMode: (isSidebarDrawerMode) => {
+      const { data, mutate } = swrResponse;
+
+      if (data == null) {
+        return;
+      }
+
+      const updateData = {
+        isSidebarDrawerMode,
+      };
+
+      // update isSidebarDrawerMode in cache, not revalidate
+      mutate({ ...data, ...updateData }, false);
+
+    },
+    setIsSidebarClosedAtDockMode: (isSidebarClosedAtDockMode) => {
+      const { data, mutate } = swrResponse;
+
+      if (data == null) {
+        return;
+      }
+
+      const updateData = {
+        isSidebarClosedAtDockMode,
+      };
+
+      // update isSidebarClosedAtDockMode in cache, not revalidate
+      mutate({ ...data, ...updateData }, false);
+    },
+  };
+};
+
 export const useDrawerOpened = (isOpened?: boolean): SWRResponse<boolean, Error> => {
   return useStaticSWR('isDrawerOpened', isOpened, { fallbackData: false });
 };