Przeglądaj źródła

Imprv/inject user UI settings (#4830)

* make user unique

* add middleware

* inject userUISettings with swig

* disable api

* extract userUISettings on init

* remove skelton

* improve the trigger for resetScrollbarDebounced

* BugFix

* set the default value of preferDrawerModeOnEditByUser true

* improve resetKey

* improve transition enabling code

* clean code

* clean code
Yuki Takei 4 lat temu
rodzic
commit
d1f965572d

+ 15 - 1
packages/app/src/client/services/ContextExtractor.tsx

@@ -8,8 +8,10 @@ import {
   useShareLinkId, useShareLinksNumber, useTemplateTagData, useUpdatedAt, useCreator, useRevisionAuthor, useCurrentUser,
   useShareLinkId, useShareLinksNumber, useTemplateTagData, useUpdatedAt, useCreator, useRevisionAuthor, useCurrentUser,
 } from '../../stores/context';
 } from '../../stores/context';
 import {
 import {
-  useIsDeviceSmallerThanMd, usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser,
+  useIsDeviceSmallerThanMd,
+  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
 } from '~/stores/ui';
 } from '~/stores/ui';
+import { IUserUISettings } from '~/interfaces/user-ui-settings';
 
 
 const { isTrashPage: _isTrashPage } = pagePathUtils;
 const { isTrashPage: _isTrashPage } = pagePathUtils;
 
 
@@ -24,6 +26,11 @@ const ContextExtractorOnce: FC = () => {
    */
    */
   const currentUser = JSON.parse(document.getElementById('growi-current-user')?.textContent || jsonNull);
   const currentUser = JSON.parse(document.getElementById('growi-current-user')?.textContent || jsonNull);
 
 
+  /*
+   * UserUISettings from DOM
+   */
+  const userUISettings: Partial<IUserUISettings> = JSON.parse(document.getElementById('growi-user-ui-settings')?.textContent || jsonNull);
+
   /*
   /*
    * Page Context from DOM
    * Page Context from DOM
    */
    */
@@ -60,6 +67,13 @@ const ContextExtractorOnce: FC = () => {
   // App
   // App
   useCurrentUser(currentUser);
   useCurrentUser(currentUser);
 
 
+  // UserUISettings
+  usePreferDrawerModeByUser(userUISettings?.preferDrawerModeByUser);
+  usePreferDrawerModeOnEditByUser(userUISettings?.preferDrawerModeOnEditByUser);
+  useSidebarCollapsed(userUISettings?.isSidebarCollapsed);
+  useCurrentSidebarContents(userUISettings?.currentSidebarContents);
+  useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
+
   // Page
   // Page
   useCreatedAt(createdAt);
   useCreatedAt(createdAt);
   useDeleteUsername(deleteUsername);
   useDeleteUsername(deleteUsername);

+ 12 - 71
packages/app/src/components/Sidebar.tsx

@@ -43,20 +43,9 @@ const GlobalNavigation = () => {
   return <SidebarNav onItemSelected={itemSelectedHandler} />;
   return <SidebarNav onItemSelected={itemSelectedHandler} />;
 };
 };
 
 
-// dummy skelton contents
-const GlobalNavigationSkelton = () => {
-  return (
-    <div className="grw-sidebar-nav">
-      <div className="grw-sidebar-nav-primary-container">
-      </div>
-      <div className="grw-sidebar-nav-secondary-container">
-      </div>
-    </div>
-  );
-};
-
-
 const SidebarContentsWrapper = () => {
 const SidebarContentsWrapper = () => {
+  const [resetKey, setResetKey] = useState(0);
+
   const scrollTargetSelector = '#grw-sidebar-contents-scroll-target';
   const scrollTargetSelector = '#grw-sidebar-contents-scroll-target';
 
 
   const calcViewHeight = useCallback(() => {
   const calcViewHeight = useCallback(() => {
@@ -73,10 +62,11 @@ const SidebarContentsWrapper = () => {
         contentsElemSelector="#grw-sidebar-content-container"
         contentsElemSelector="#grw-sidebar-content-container"
         stickyElemSelector=".grw-sidebar"
         stickyElemSelector=".grw-sidebar"
         calcViewHeightFunc={calcViewHeight}
         calcViewHeightFunc={calcViewHeight}
+        resetKey={resetKey}
       />
       />
 
 
       <div id="grw-sidebar-contents-scroll-target">
       <div id="grw-sidebar-contents-scroll-target">
-        <div id="grw-sidebar-content-container">
+        <div id="grw-sidebar-content-container" onLoad={() => setResetKey(Math.random())}>
           <SidebarContents />
           <SidebarContents />
         </div>
         </div>
       </div>
       </div>
@@ -86,13 +76,6 @@ const SidebarContentsWrapper = () => {
   );
   );
 };
 };
 
 
-// dummy skelton contents
-const SidebarSkeltonContents = () => {
-  return (
-    <div>Skelton Contents!!!</div>
-  );
-};
-
 
 
 type Props = {
 type Props = {
 }
 }
@@ -104,26 +87,12 @@ const Sidebar: FC<Props> = (props: Props) => {
   const { data: isCollapsed, mutate: mutateSidebarCollapsed } = useSidebarCollapsed();
   const { data: isCollapsed, mutate: mutateSidebarCollapsed } = useSidebarCollapsed();
   const { data: isResizeDisabled, mutate: mutateSidebarResizeDisabled } = useSidebarResizeDisabled();
   const { data: isResizeDisabled, mutate: mutateSidebarResizeDisabled } = useSidebarResizeDisabled();
 
 
+  const [isTransitionEnabled, setTransitionEnabled] = useState(false);
+
   const [isHover, setHover] = useState(false);
   const [isHover, setHover] = useState(false);
   const [isDragging, setDrag] = useState(false);
   const [isDragging, setDrag] = useState(false);
-  const [isMounted, setMounted] = useState(false);
 
 
   const isResizableByDrag = !isResizeDisabled && !isDrawerMode && (!isCollapsed || isHover);
   const isResizableByDrag = !isResizeDisabled && !isDrawerMode && (!isCollapsed || isHover);
-  /**
-   * hack and override UIController.storeState
-   *
-   * Since UIController is an unstated container, setState() in storeState method should be awaited before writing to cache.
-   */
-  // hackUIController() {
-  //   const { navigationUIController } = this.props;
-
-  //   // see: @atlaskit/navigation-next/dist/esm/ui-controller/UIController.js
-  //   const orgStoreState = navigationUIController.storeState;
-  //   navigationUIController.storeState = async(state) => {
-  //     await navigationUIController.setState(state);
-  //     orgStoreState(state);
-  //   };
-  // }
 
 
   const toggleDrawerMode = useCallback((bool) => {
   const toggleDrawerMode = useCallback((bool) => {
     const isStateModified = isResizeDisabled !== bool;
     const isStateModified = isResizeDisabled !== bool;
@@ -133,52 +102,24 @@ const Sidebar: FC<Props> = (props: Props) => {
 
 
     // Drawer <-- Dock
     // Drawer <-- Dock
     if (bool) {
     if (bool) {
-      // // cache state
-      // this.sidebarCollapsedCached = navigationUIController.state.isCollapsed;
-      // this.sidebarWidthCached = navigationUIController.state.productNavWidth;
-
-      // // clear transition temporary
-      // if (this.sidebarCollapsedCached) {
-      //   this.addCssClassTemporary('grw-sidebar-supress-transitions-to-drawer');
-      // }
-
       // disable resize
       // disable resize
       mutateSidebarResizeDisabled(true, false);
       mutateSidebarResizeDisabled(true, false);
     }
     }
     // Drawer --> Dock
     // Drawer --> Dock
     else {
     else {
-      // // clear transition temporary
-      // if (this.sidebarCollapsedCached) {
-      //   this.addCssClassTemporary('grw-sidebar-supress-transitions-to-dock');
-      // }
-
       // enable resize
       // enable resize
       mutateSidebarResizeDisabled(false, false);
       mutateSidebarResizeDisabled(false, false);
-
-      // // restore width
-      // if (this.sidebarWidthCached != null) {
-      //   navigationUIController.setState({ productNavWidth: this.sidebarWidthCached });
-      // }
     }
     }
   }, [isResizeDisabled, mutateSidebarResizeDisabled]);
   }, [isResizeDisabled, mutateSidebarResizeDisabled]);
 
 
-  // addCssClassTemporary(className) {
-  //   // clear
-  //   this.sidebarElem.classList.add(className);
-
-  //   // restore after 300ms
-  //   setTimeout(() => {
-  //     this.sidebarElem.classList.remove(className);
-  //   }, 300);
-  // }
-
   const backdropClickedHandler = useCallback(() => {
   const backdropClickedHandler = useCallback(() => {
     mutateDrawerOpened(false, false);
     mutateDrawerOpened(false, false);
   }, [mutateDrawerOpened]);
   }, [mutateDrawerOpened]);
 
 
   useEffect(() => {
   useEffect(() => {
-    // this.hackUIController();
-    setMounted(true);
+    setTimeout(() => {
+      setTransitionEnabled(true);
+    }, 1000);
   }, []);
   }, []);
 
 
   useEffect(() => {
   useEffect(() => {
@@ -285,10 +226,10 @@ const Sidebar: FC<Props> = (props: Props) => {
     <>
     <>
       <div className={`grw-sidebar d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
       <div className={`grw-sidebar d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
         <div className="data-layout-container">
         <div className="data-layout-container">
-          <div className="navigation" onMouseLeave={hoverOutHandler}>
+          <div className={`navigation ${isTransitionEnabled ? 'transition-enabled' : ''}`} onMouseLeave={hoverOutHandler}>
             <div className="grw-navigation-wrap">
             <div className="grw-navigation-wrap">
               <div className="grw-global-navigation">
               <div className="grw-global-navigation">
-                { isMounted ? <GlobalNavigation></GlobalNavigation> : <GlobalNavigationSkelton></GlobalNavigationSkelton> }
+                <GlobalNavigation></GlobalNavigation>
               </div>
               </div>
               <div
               <div
                 ref={resizableContainer}
                 ref={resizableContainer}
@@ -298,7 +239,7 @@ const Sidebar: FC<Props> = (props: Props) => {
               >
               >
                 <div className="grw-contextual-navigation-child">
                 <div className="grw-contextual-navigation-child">
                   <div role="group" className={`grw-contextual-navigation-sub ${!isHover && isCollapsed ? 'collapsed' : ''}`}>
                   <div role="group" className={`grw-contextual-navigation-sub ${!isHover && isCollapsed ? 'collapsed' : ''}`}>
-                    { isMounted ? <SidebarContentsWrapper></SidebarContentsWrapper> : <SidebarSkeltonContents></SidebarSkeltonContents> }
+                    <SidebarContentsWrapper></SidebarContentsWrapper>
                   </div>
                   </div>
                 </div>
                 </div>
               </div>
               </div>

+ 8 - 3
packages/app/src/components/StickyStretchableScroller.jsx

@@ -48,6 +48,7 @@ const StickyStretchableScroller = (props) => {
   const {
   const {
     children, contentsElemSelector, stickyElemSelector,
     children, contentsElemSelector, stickyElemSelector,
     calcViewHeightFunc, calcContentsHeightFunc,
     calcViewHeightFunc, calcContentsHeightFunc,
+    resetKey,
   } = props;
   } = props;
 
 
   if (scrollTargetSelector == null && children == null) {
   if (scrollTargetSelector == null && children == null) {
@@ -137,10 +138,12 @@ const StickyStretchableScroller = (props) => {
     };
     };
   }, [resetScrollbarDebounced]);
   }, [resetScrollbarDebounced]);
 
 
-  // setup effect by update props
+  // setup effect on init
   useEffect(() => {
   useEffect(() => {
-    resetScrollbarDebounced();
-  }, [resetScrollbarDebounced]);
+    if (resetKey != null) {
+      resetScrollbarDebounced();
+    }
+  }, [resetKey, resetScrollbarDebounced]);
 
 
   return (
   return (
     <>
     <>
@@ -156,6 +159,8 @@ StickyStretchableScroller.propTypes = {
   scrollTargetSelector: PropTypes.string,
   scrollTargetSelector: PropTypes.string,
   stickyElemSelector: PropTypes.string,
   stickyElemSelector: PropTypes.string,
 
 
+  resetKey: PropTypes.any,
+
   calcViewHeightFunc: PropTypes.func,
   calcViewHeightFunc: PropTypes.func,
   calcContentsHeightFunc: PropTypes.func,
   calcContentsHeightFunc: PropTypes.func,
 };
 };

+ 31 - 0
packages/app/src/server/middlewares/inject-user-ui-settings-to-localvars.ts

@@ -0,0 +1,31 @@
+import { IUserUISettings } from '~/interfaces/user-ui-settings';
+import loggerFactory from '~/utils/logger';
+
+import UserUISettings from '../models/user-ui-settings';
+
+const logger = loggerFactory('growi:middleware:inject-user-ui-settings-to-localvars');
+
+async function getSettings(userId: string): Promise<Partial<IUserUISettings> | null> {
+  const doc = await UserUISettings.findOne({ user: userId }).exec();
+
+  let partialDoc: Partial<IUserUISettings> | null = null;
+  if (doc != null) {
+    partialDoc = doc.toObject();
+    delete partialDoc.user;
+  }
+
+  return partialDoc;
+}
+
+module.exports = () => {
+  return async(req, res, next) => {
+    try {
+      res.locals.userUISettings = await getSettings(req.user._id);
+    }
+    catch (err: unknown) {
+      logger.error(err);
+    }
+
+    next();
+  };
+};

+ 2 - 2
packages/app/src/server/models/user-ui-settings.ts

@@ -12,7 +12,7 @@ export interface UserUISettingsDocument extends IUserUISettings, Document {}
 export type UserUISettingsModel = Model<UserUISettingsDocument>
 export type UserUISettingsModel = Model<UserUISettingsDocument>
 
 
 const schema = new Schema<UserUISettingsDocument, UserUISettingsModel>({
 const schema = new Schema<UserUISettingsDocument, UserUISettingsModel>({
-  user: { type: Schema.Types.ObjectId, ref: 'User', index: true },
+  user: { type: Schema.Types.ObjectId, ref: 'User', unique: true },
   isSidebarCollapsed: { type: Boolean, default: false },
   isSidebarCollapsed: { type: Boolean, default: false },
   currentSidebarContents: {
   currentSidebarContents: {
     type: String,
     type: String,
@@ -21,7 +21,7 @@ const schema = new Schema<UserUISettingsDocument, UserUISettingsModel>({
   },
   },
   currentProductNavWidth: { type: Number },
   currentProductNavWidth: { type: Number },
   preferDrawerModeByUser: { type: Boolean, default: false },
   preferDrawerModeByUser: { type: Boolean, default: false },
-  preferDrawerModeOnEditByUser: { type: Boolean, default: false },
+  preferDrawerModeOnEditByUser: { type: Boolean, default: true },
 });
 });
 
 
 
 

+ 0 - 18
packages/app/src/server/routes/apiv3/user-ui-settings.ts

@@ -25,24 +25,6 @@ module.exports = (crowi) => {
     body('settings.preferDrawerModeOnEditByUser').optional().isBoolean(),
     body('settings.preferDrawerModeOnEditByUser').optional().isBoolean(),
   ];
   ];
 
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  router.get('/', loginRequiredStrictly, async(req: any, res: any) => {
-    const { user } = req;
-
-    try {
-      const updatedSettings = await UserUISettings.findOneAndUpdate(
-        { user: user._id },
-        { user: user._id },
-        { upsert: true, new: true },
-      );
-      return res.apiv3(updatedSettings);
-    }
-    catch (err) {
-      logger.error('Error', err);
-      return res.apiv3Err(new ErrorV3(err));
-    }
-  });
-
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   router.put('/', loginRequiredStrictly, csrf, validatorForPut, apiV3FormValidator, async(req: any, res: any) => {
   router.put('/', loginRequiredStrictly, csrf, validatorForPut, apiV3FormValidator, async(req: any, res: any) => {
     const { user } = req;
     const { user } = req;

+ 18 - 17
packages/app/src/server/routes/index.js

@@ -27,6 +27,7 @@ module.exports = function(crowi, app) {
   const adminRequired = require('../middlewares/admin-required')(crowi);
   const adminRequired = require('../middlewares/admin-required')(crowi);
   const certifySharedFile = require('../middlewares/certify-shared-file')(crowi);
   const certifySharedFile = require('../middlewares/certify-shared-file')(crowi);
   const csrf = require('../middlewares/csrf')(crowi);
   const csrf = require('../middlewares/csrf')(crowi);
+  const injectUserUISettings = require('../middlewares/inject-user-ui-settings-to-localvars')();
 
 
   const uploads = multer({ dest: `${crowi.tmpDir}uploads` });
   const uploads = multer({ dest: `${crowi.tmpDir}uploads` });
   const form = require('../form');
   const form = require('../form');
@@ -51,7 +52,7 @@ module.exports = function(crowi, app) {
   app.use('/api-docs', require('./apiv3/docs')(crowi));
   app.use('/api-docs', require('./apiv3/docs')(crowi));
   app.use('/_api/v3', require('./apiv3')(crowi));
   app.use('/_api/v3', require('./apiv3')(crowi));
 
 
-  app.get('/'                         , applicationInstalled, loginRequired , autoReconnectToSearch, page.showTopPage);
+  app.get('/'                         , applicationInstalled, loginRequired, autoReconnectToSearch, injectUserUISettings, page.showTopPage);
 
 
   app.get('/login/error/:reason'      , applicationInstalled, login.error);
   app.get('/login/error/:reason'      , applicationInstalled, login.error);
   app.get('/login'                    , applicationInstalled, login.preLogin, login.login);
   app.get('/login'                    , applicationInstalled, login.preLogin, login.login);
@@ -131,23 +132,23 @@ module.exports = function(crowi, app) {
   app.get('/admin/export'                       , loginRequiredStrictly , adminRequired ,admin.export.index);
   app.get('/admin/export'                       , loginRequiredStrictly , adminRequired ,admin.export.index);
   app.get('/admin/export/:fileName'             , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
   app.get('/admin/export/:fileName'             , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
 
 
-  app.get('/admin/*'                       , loginRequiredStrictly ,adminRequired, admin.notFound.index);
+  app.get('/admin/*'                            , loginRequiredStrictly ,adminRequired, admin.notFound.index);
 
 
-  app.get('/me'                       , loginRequiredStrictly , me.index);
+  app.get('/me'                                 , loginRequiredStrictly, injectUserUISettings, me.index);
   // external-accounts
   // external-accounts
-  app.get('/me/external-accounts'                         , loginRequiredStrictly , me.externalAccounts.list);
+  app.get('/me/external-accounts'               , loginRequiredStrictly, injectUserUISettings, me.externalAccounts.list);
   // my drafts
   // my drafts
-  app.get('/me/drafts'                , loginRequiredStrictly, me.drafts.list);
+  app.get('/me/drafts'                          , loginRequiredStrictly, injectUserUISettings, me.drafts.list);
 
 
-  app.get('/:id([0-9a-z]{24})'       , loginRequired , page.redirector);
-  app.get('/_r/:id([0-9a-z]{24})'    , loginRequired , page.redirector); // alias
-  app.get('/attachment/:id([0-9a-z]{24})' , certifySharedFile , loginRequired, attachment.api.get);
+  app.get('/:id([0-9a-z]{24})'                  , loginRequired , page.redirector);
+  app.get('/_r/:id([0-9a-z]{24})'               , loginRequired , page.redirector); // alias
+  app.get('/attachment/:id([0-9a-z]{24})'       , certifySharedFile , loginRequired, attachment.api.get);
   app.get('/attachment/profile/:id([0-9a-z]{24})' , loginRequired, attachment.api.get);
   app.get('/attachment/profile/:id([0-9a-z]{24})' , loginRequired, attachment.api.get);
-  app.get('/attachment/:pageId/:fileName', loginRequired, attachment.api.obsoletedGetForMongoDB); // DEPRECATED: remains for backward compatibility for v3.3.x or below
-  app.get('/download/:id([0-9a-z]{24})'    , loginRequired, attachment.api.download);
+  app.get('/attachment/:pageId/:fileName'       , loginRequired, attachment.api.obsoletedGetForMongoDB); // DEPRECATED: remains for backward compatibility for v3.3.x or below
+  app.get('/download/:id([0-9a-z]{24})'         , loginRequired, attachment.api.download);
 
 
-  app.get('/_search'                 , loginRequired , search.searchPage);
-  app.get('/_api/search'             , accessTokenParser , loginRequired , search.api.search);
+  app.get('/_search'                            , loginRequired, injectUserUISettings, search.searchPage);
+  app.get('/_api/search'                        , accessTokenParser , loginRequired , search.api.search);
 
 
   app.get('/_api/check_username'           , user.api.checkUsername);
   app.get('/_api/check_username'           , user.api.checkUsername);
   app.get('/_api/me/user-group-relations'  , accessTokenParser , loginRequiredStrictly , me.api.userGroupRelations);
   app.get('/_api/me/user-group-relations'  , accessTokenParser , loginRequiredStrictly , me.api.userGroupRelations);
@@ -178,9 +179,9 @@ module.exports = function(crowi, app) {
   app.post('/_api/attachments.removeProfileImage'   , accessTokenParser , loginRequiredStrictly , csrf, attachment.api.removeProfileImage);
   app.post('/_api/attachments.removeProfileImage'   , accessTokenParser , loginRequiredStrictly , csrf, attachment.api.removeProfileImage);
   app.get('/_api/attachments.limit'   , accessTokenParser , loginRequiredStrictly, attachment.api.limit);
   app.get('/_api/attachments.limit'   , accessTokenParser , loginRequiredStrictly, attachment.api.limit);
 
 
-  app.get('/trash$'                   , loginRequired , page.trashPageShowWrapper);
-  app.get('/trash/$'                  , loginRequired , page.trashPageListShowWrapper);
-  app.get('/trash/*/$'                , loginRequired , page.deletedPageListShowWrapper);
+  app.get('/trash$'                   , loginRequired, injectUserUISettings, page.trashPageShowWrapper);
+  app.get('/trash/$'                  , loginRequired, injectUserUISettings, page.trashPageListShowWrapper);
+  app.get('/trash/*/$'                , loginRequired, injectUserUISettings, page.deletedPageListShowWrapper);
 
 
   app.get('/_hackmd/load-agent'          , hackmd.loadAgent);
   app.get('/_hackmd/load-agent'          , hackmd.loadAgent);
   app.get('/_hackmd/load-styles'         , hackmd.loadStyles);
   app.get('/_hackmd/load-styles'         , hackmd.loadStyles);
@@ -195,7 +196,7 @@ module.exports = function(crowi, app) {
 
 
   app.get('/share/:linkId', page.showSharedPage);
   app.get('/share/:linkId', page.showSharedPage);
 
 
-  app.get('/*/$'                   , loginRequired , page.showPageWithEndOfSlash, page.notFound);
-  app.get('/*'                     , loginRequired , autoReconnectToSearch, page.showPage, page.notFound);
+  app.get('/*/$'                   , loginRequired, injectUserUISettings, page.showPageWithEndOfSlash, page.notFound);
+  app.get('/*'                     , loginRequired, autoReconnectToSearch, injectUserUISettings, page.showPage, page.notFound);
 
 
 };
 };

+ 6 - 0
packages/app/src/server/views/layout/layout.html

@@ -120,6 +120,12 @@
   {{ user|json|safe|preventXss }}
   {{ user|json|safe|preventXss }}
   </script>
   </script>
 {% endif %}
 {% endif %}
+{% if userUISettings != null %}
+  <script type="application/json" id="growi-user-ui-settings">
+  {{ userUISettings|json|safe }}
+  </script>
+{% endif %}
+
 
 
 {% block custom_script %}
 {% block custom_script %}
 <script>
 <script>

+ 15 - 69
packages/app/src/stores/ui.tsx

@@ -1,17 +1,14 @@
 import {
 import {
-  useSWRConfig, SWRResponse, Key, Fetcher, Middleware,
+  useSWRConfig, SWRResponse, Key, Fetcher,
 } from 'swr';
 } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
 import { Breakpoint, addBreakpointListener } from '@growi/ui';
 import { Breakpoint, addBreakpointListener } from '@growi/ui';
 
 
-import { apiv3Get } from '~/client/util/apiv3-client';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { SidebarContentsType } from '~/interfaces/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { sessionStorageMiddleware } from './middlewares/sync-to-storage';
 import { useStaticSWR } from './use-static-swr';
 import { useStaticSWR } from './use-static-swr';
-import { IUserUISettings } from '~/interfaces/user-ui-settings';
 import { useIsEditable } from './context';
 import { useIsEditable } from './context';
 
 
 const logger = loggerFactory('growi:stores:ui');
 const logger = loggerFactory('growi:stores:ui');
@@ -36,16 +33,6 @@ export type EditorMode = typeof EditorMode[keyof typeof EditorMode];
  *                      for switching UI
  *                      for switching UI
  *********************************************************** */
  *********************************************************** */
 
 
-export const useSWRxUserUISettings = (): SWRResponse<IUserUISettings, Error> => {
-  const key = isServer ? null : 'userUISettings';
-
-  return useSWRImmutable(
-    key,
-    () => apiv3Get<IUserUISettings>('/user-ui-settings').then(response => response.data),
-  );
-};
-
-
 export const useIsMobile = (): SWRResponse<boolean|null, Error> => {
 export const useIsMobile = (): SWRResponse<boolean|null, Error> => {
   const key = isServer ? null : 'isMobile';
   const key = isServer ? null : 'isMobile';
 
 
@@ -167,20 +154,24 @@ export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean|null, Error> =>
   return useStaticSWR(key);
   return useStaticSWR(key);
 };
 };
 
 
-export const usePreferDrawerModeByUser = (isPrefered?: boolean): SWRResponse<boolean, Error> => {
-  const { data } = useSWRxUserUISettings();
-  const key: Key = data === undefined ? null : 'preferDrawerModeByUser';
-  const initialData = data?.preferDrawerModeByUser;
+export const usePreferDrawerModeByUser = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useStaticSWR('preferDrawerModeByUser', initialData ?? null, { fallbackData: false });
+};
+
+export const usePreferDrawerModeOnEditByUser = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useStaticSWR('preferDrawerModeOnEditByUser', initialData ?? null, { fallbackData: true });
+};
 
 
-  return useStaticSWR(key, isPrefered || null, { fallbackData: initialData, use: [sessionStorageMiddleware] });
+export const useSidebarCollapsed = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useStaticSWR('isSidebarCollapsed', initialData ?? null, { fallbackData: false });
 };
 };
 
 
-export const usePreferDrawerModeOnEditByUser = (isPrefered?: boolean): SWRResponse<boolean, Error> => {
-  const { data } = useSWRxUserUISettings();
-  const key: Key = data === undefined ? null : 'preferDrawerModeOnEditByUser';
-  const initialData = data?.preferDrawerModeOnEditByUser;
+export const useCurrentSidebarContents = (initialData?: SidebarContentsType): SWRResponse<SidebarContentsType, Error> => {
+  return useStaticSWR('sidebarContents', initialData ?? null, { fallbackData: SidebarContentsType.RECENT });
+};
 
 
-  return useStaticSWR(key, isPrefered || null, { fallbackData: initialData, use: [sessionStorageMiddleware] });
+export const useCurrentProductNavWidth = (initialData?: number): SWRResponse<number, Error> => {
+  return useStaticSWR('productNavWidth', initialData ?? null, { fallbackData: 320 });
 };
 };
 
 
 export const useDrawerMode = (): SWRResponse<boolean, Error> => {
 export const useDrawerMode = (): SWRResponse<boolean, Error> => {
@@ -215,51 +206,6 @@ export const useDrawerOpened = (isOpened?: boolean): SWRResponse<boolean, Error>
   return useStaticSWR('isDrawerOpened', isOpened || null, { fallbackData: initialData });
   return useStaticSWR('isDrawerOpened', isOpened || null, { fallbackData: initialData });
 };
 };
 
 
-export const useSidebarCollapsed = (): SWRResponse<boolean, Error> => {
-  const { data } = useSWRxUserUISettings();
-  const key = data === undefined ? null : 'isSidebarCollapsed';
-  const initialData = data?.isSidebarCollapsed || false;
-
-  return useStaticSWR(
-    key,
-    null,
-    {
-      fallbackData: initialData,
-      use: [sessionStorageMiddleware],
-    },
-  );
-};
-
-export const useCurrentSidebarContents = (): SWRResponse<SidebarContentsType, Error> => {
-  const { data } = useSWRxUserUISettings();
-  const key = data === undefined ? null : 'sidebarContents';
-  const initialData = data?.currentSidebarContents || SidebarContentsType.RECENT;
-
-  return useStaticSWR(
-    key,
-    null,
-    {
-      fallbackData: initialData,
-      use: [sessionStorageMiddleware],
-    },
-  );
-};
-
-export const useCurrentProductNavWidth = (): SWRResponse<number, Error> => {
-  const { data } = useSWRxUserUISettings();
-  const key = data === undefined ? null : 'productNavWidth';
-  const initialData = data?.currentProductNavWidth || 320;
-
-  return useStaticSWR(
-    key,
-    null,
-    {
-      fallbackData: initialData,
-      use: [sessionStorageMiddleware],
-    },
-  );
-};
-
 export const useSidebarResizeDisabled = (isDisabled?: boolean): SWRResponse<boolean, Error> => {
 export const useSidebarResizeDisabled = (isDisabled?: boolean): SWRResponse<boolean, Error> => {
   const initialData = false;
   const initialData = false;
   return useStaticSWR('isSidebarResizeDisabled', isDisabled || null, { fallbackData: initialData });
   return useStaticSWR('isSidebarResizeDisabled', isDisabled || null, { fallbackData: initialData });

+ 1 - 17
packages/app/src/styles/_sidebar.scss

@@ -260,7 +260,7 @@
     top: 0;
     top: 0;
     width: 0;
     width: 0;
   }
   }
-  div.navigation {
+  div.navigation.transition-enabled {
     max-width: 80vw;
     max-width: 80vw;
 
 
     // apply transition
     // apply transition
@@ -322,22 +322,6 @@
   }
   }
 }
 }
 
 
-// supress transition
-.grw-sidebar {
-  &.grw-sidebar-supress-transitions-to-drawer {
-    div.navigation {
-      transition: none !important;
-    }
-  }
-
-  &.grw-sidebar-supress-transitions-to-dock {
-    div.content,
-    div.contextual-navigation {
-      transition: none !important;
-    }
-  }
-}
-
 .grw-sidebar-backdrop.modal-backdrop {
 .grw-sidebar-backdrop.modal-backdrop {
   z-index: $zindex-fixed + 1;
   z-index: $zindex-fixed + 1;
 }
 }