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

Merge pull request #5858 from weseek/feat/90653-navbar-menu-for-guest

feat: Customize menu in navbar for guest user
Yuki Takei 3 лет назад
Родитель
Сommit
80d22d4d34

+ 174 - 0
packages/app/src/components/Navbar/AppearanceModeDropdown.tsx

@@ -0,0 +1,174 @@
+import React, { FC, useState, useCallback } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import { UncontrolledTooltip } from 'reactstrap';
+
+import { useUserUISettings } from '~/client/services/user-ui-settings';
+import {
+  isUserPreferenceExists,
+  isDarkMode as isDarkModeByUtil,
+  applyColorScheme,
+  removeUserPreference,
+  updateUserPreference,
+  updateUserPreferenceWithOsSettings,
+} from '~/client/util/color-scheme';
+import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
+
+import MoonIcon from '../Icons/MoonIcon';
+import SidebarDockIcon from '../Icons/SidebarDockIcon';
+import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
+import SunIcon from '../Icons/SunIcon';
+
+type AppearanceModeDropdownProps = {
+  isAuthenticated: boolean,
+}
+export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: AppearanceModeDropdownProps) => {
+
+  const { t } = useTranslation();
+
+  const { isAuthenticated } = props;
+
+  const [useOsSettings, setOsSettings] = useState(!isUserPreferenceExists());
+  const [isDarkMode, setIsDarkMode] = useState(isDarkModeByUtil());
+
+  const { data: isPreferDrawerMode, update: updatePreferDrawerMode } = usePreferDrawerModeByUser();
+  const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
+  const { scheduleToPut } = useUserUISettings();
+
+  const preferDrawerModeSwitchModifiedHandler = useCallback((preferDrawerMode: boolean, isEditMode: boolean) => {
+    if (isEditMode) {
+      mutatePreferDrawerModeOnEdit(preferDrawerMode);
+      scheduleToPut({ preferDrawerModeOnEditByUser: preferDrawerMode });
+    }
+    else {
+      updatePreferDrawerMode(preferDrawerMode);
+    }
+  }, [updatePreferDrawerMode, mutatePreferDrawerModeOnEdit, scheduleToPut]);
+
+  const followOsCheckboxModifiedHandler = useCallback((useOsSettings: boolean) => {
+    if (useOsSettings) {
+      removeUserPreference();
+    }
+    else {
+      updateUserPreferenceWithOsSettings();
+    }
+    applyColorScheme();
+
+    // update states
+    setOsSettings(useOsSettings);
+    setIsDarkMode(isDarkModeByUtil());
+  }, []);
+
+  const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
+    updateUserPreference(isDarkMode);
+    applyColorScheme();
+
+    // update state
+    setIsDarkMode(isDarkModeByUtil());
+  }, []);
+
+  /* eslint-disable react/prop-types */
+  const IconWithTooltip = ({
+    id, label, children, additionalClasses,
+  }) => (
+    <>
+      <div id={id} className={`px-2 grw-icon-container ${additionalClasses != null ? additionalClasses : ''}`}>{children}</div>
+      <UncontrolledTooltip placement="bottom" fade={false} target={id}>{label}</UncontrolledTooltip>
+    </>
+  );
+
+  const renderSidebarModeSwitch = useCallback((isEditMode: boolean) => {
+    return (
+      <>
+        <h6 className="dropdown-header">{t(isEditMode ? 'personal_dropdown.sidebar_mode_editor' : 'personal_dropdown.sidebar_mode')}</h6>
+        <form className="px-4">
+          <div className="form-row justify-content-center">
+            <div className="form-group col-auto mb-0 d-flex align-items-center">
+              <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-drawer' : 'iwt-sidebar-drawer'} label="Drawer" additionalClasses="grw-sidebar-mode-icon">
+                <SidebarDrawerIcon />
+              </IconWithTooltip>
+              <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
+                <input
+                  id={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}
+                  className="custom-control-input"
+                  type="checkbox"
+                  checked={isEditMode ? !isPreferDrawerModeOnEdit : !isPreferDrawerMode}
+                  onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked, isEditMode)}
+                />
+                <label className="custom-control-label" htmlFor={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}></label>
+              </div>
+              <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-dock' : 'iwt-sidebar-dock'} label="Dock" additionalClasses="grw-sidebar-mode-icon">
+                <SidebarDockIcon />
+              </IconWithTooltip>
+            </div>
+          </div>
+        </form>
+      </>
+    );
+  }, [isPreferDrawerMode, isPreferDrawerModeOnEdit, preferDrawerModeSwitchModifiedHandler, t]);
+
+  return (
+    <>
+      {/* setting button */}
+      <button className="bg-transparent border-0 nav-link" type="button" data-toggle="dropdown" aria-haspopup="true">
+        <i className="icon-settings"></i>
+      </button>
+
+      {/* dropdown */}
+      <div className="dropdown-menu dropdown-menu-right">
+
+        {/* sidebar mode */}
+        {renderSidebarModeSwitch(false)}
+
+        <div className="dropdown-divider"></div>
+
+        {/* side bar mode on editor */}
+        {isAuthenticated && renderSidebarModeSwitch(true)}
+
+        <div className="dropdown-divider"></div>
+
+        {/* color mode */}
+        <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
+        <form className="px-4">
+          <div className="form-row justify-content-center">
+            <div className="form-group col-auto d-flex align-items-center">
+              <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
+                <SunIcon />
+              </IconWithTooltip>
+              <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
+                <input
+                  id="swUserPreference"
+                  className="custom-control-input"
+                  type="checkbox"
+                  checked={isDarkMode}
+                  disabled={useOsSettings}
+                  onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
+                />
+                <label className="custom-control-label" htmlFor="swUserPreference"></label>
+              </div>
+              <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
+                <MoonIcon />
+              </IconWithTooltip>
+            </div>
+          </div>
+          <div className="form-row">
+            <div className="form-group col-auto">
+              <div className="custom-control custom-checkbox">
+                <input
+                  id="cbFollowOs"
+                  className="custom-control-input"
+                  type="checkbox"
+                  checked={useOsSettings}
+                  onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
+                />
+                <label className="custom-control-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
+              </div>
+            </div>
+          </div>
+        </form>
+      </div>
+
+    </>
+  );
+
+};

+ 46 - 25
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -1,4 +1,4 @@
-import React, { FC, memo } from 'react';
+import React, { FC, memo, useMemo } from 'react';
 
 import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
@@ -14,6 +14,7 @@ import GrowiLogo from '../Icons/GrowiLogo';
 import InAppNotificationDropdown from '../InAppNotification/InAppNotificationDropdown';
 import { withUnstatedContainers } from '../UnstatedUtils';
 
+import { AppearanceModeDropdown } from './AppearanceModeDropdown';
 import GlobalSearch from './GlobalSearch';
 import PersonalDropdown from './PersonalDropdown';
 
@@ -27,33 +28,53 @@ const NavbarRight: FC<NavbarRightProps> = memo((props: NavbarRightProps) => {
   const { open: openCreateModal } = usePageCreateModal();
 
   const { currentUser } = props;
-
-  // render login button
-  if (currentUser == null) {
-    return <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>;
-  }
+  const isAuthenticated = currentUser != null;
+
+  const authenticatedNavItem = useMemo(() => {
+    return (
+      <>
+        <li className="nav-item">
+          <InAppNotificationDropdown />
+        </li>
+
+        <li className="nav-item d-none d-md-block">
+          <button
+            className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
+            type="button"
+            data-testid="newPageBtn"
+            onClick={() => openCreateModal(currentPagePath || '')}
+          >
+            <i className="icon-pencil mr-2"></i>
+            <span className="d-none d-lg-block">{ t('New') }</span>
+          </button>
+        </li>
+
+        <li className="grw-personal-dropdown nav-item dropdown">
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
+        </li>
+
+        <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
+          <PersonalDropdown />
+        </li>
+      </>
+    );
+  }, [t, currentPagePath, openCreateModal, isAuthenticated]);
+
+  const notAuthenticatedNavItem = useMemo(() => {
+    return (
+      <>
+        <li className="grw-personal-dropdown nav-item dropdown">
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
+        </li>
+
+        <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>;
+      </>
+    );
+  }, []);
 
   return (
     <>
-      <li className="nav-item">
-        <InAppNotificationDropdown />
-      </li>
-
-      <li className="nav-item d-none d-md-block">
-        <button
-          className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
-          type="button"
-          data-testid="newPageBtn"
-          onClick={() => openCreateModal(currentPagePath || '')}
-        >
-          <i className="icon-pencil mr-2"></i>
-          <span className="d-none d-lg-block">{ t('New') }</span>
-        </button>
-      </li>
-
-      <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
-        <PersonalDropdown />
-      </li>
+      {isAuthenticated ? authenticatedNavItem : notAuthenticatedNavItem}
     </>
   );
 });

+ 1 - 164
packages/app/src/components/Navbar/PersonalDropdown.jsx

@@ -1,29 +1,11 @@
-import React, { useState, useCallback } from 'react';
+import React from 'react';
 
 import { UserPicture } from '@growi/ui';
 import { useTranslation } from 'react-i18next';
-import { UncontrolledTooltip } from 'reactstrap';
 
-import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
-import {
-  isUserPreferenceExists,
-  isDarkMode as isDarkModeByUtil,
-  applyColorScheme,
-  removeUserPreference,
-  updateUserPreference,
-  updateUserPreferenceWithOsSettings,
-} from '~/client/util/color-scheme';
 import { useCurrentUser } from '~/stores/context';
-import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
-
-
-import MoonIcon from '../Icons/MoonIcon';
-import SidebarDockIcon from '../Icons/SidebarDockIcon';
-import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
-import SunIcon from '../Icons/SunIcon';
-
 
 const PersonalDropdown = () => {
   const { t } = useTranslation();
@@ -31,13 +13,6 @@ const PersonalDropdown = () => {
 
   const user = currentUser || {};
 
-  const [useOsSettings, setOsSettings] = useState(!isUserPreferenceExists());
-  const [isDarkMode, setIsDarkMode] = useState(isDarkModeByUtil());
-
-  const { data: isPreferDrawerMode, mutate: mutatePreferDrawerMode } = usePreferDrawerModeByUser();
-  const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
-  const { scheduleToPut } = useUserUISettings();
-
   const logoutHandler = async() => {
     try {
       await apiv3Post('/logout');
@@ -48,50 +23,6 @@ const PersonalDropdown = () => {
     }
   };
 
-  const preferDrawerModeSwitchModifiedHandler = useCallback((bool) => {
-    mutatePreferDrawerMode(bool);
-    scheduleToPut({ preferDrawerModeByUser: bool });
-  }, [mutatePreferDrawerMode, scheduleToPut]);
-
-  const preferDrawerModeOnEditSwitchModifiedHandler = useCallback((bool) => {
-    mutatePreferDrawerModeOnEdit(bool);
-    scheduleToPut({ preferDrawerModeOnEditByUser: bool });
-  }, [mutatePreferDrawerModeOnEdit, scheduleToPut]);
-
-  const followOsCheckboxModifiedHandler = (bool) => {
-    if (bool) {
-      removeUserPreference();
-    }
-    else {
-      updateUserPreferenceWithOsSettings();
-    }
-    applyColorScheme();
-
-    // update states
-    setOsSettings(bool);
-    setIsDarkMode(isDarkModeByUtil());
-  };
-
-  const userPreferenceSwitchModifiedHandler = (bool) => {
-    updateUserPreference(bool);
-    applyColorScheme();
-
-    // update state
-    setIsDarkMode(isDarkModeByUtil());
-  };
-
-
-  /* eslint-disable react/prop-types */
-  const IconWithTooltip = ({
-    id, label, children, additionalClasses,
-  }) => (
-    <>
-      <div id={id} className={`px-2 grw-icon-container ${additionalClasses != null ? additionalClasses : ''}`}>{children}</div>
-      <UncontrolledTooltip placement="bottom" fade={false} target={id}>{label}</UncontrolledTooltip>
-    </>
-  );
-  /* eslint-enable react/prop-types */
-
   return (
     <>
       {/* Button */}
@@ -128,100 +59,6 @@ const PersonalDropdown = () => {
 
         <div className="dropdown-divider"></div>
 
-        {/* Sidebar Mode */}
-        <h6 className="dropdown-header">{t('personal_dropdown.sidebar_mode')}</h6>
-        <form className="px-4">
-          <div className="form-row justify-content-center">
-            <div className="form-group col-auto mb-0 d-flex align-items-center">
-              <IconWithTooltip id="iwt-sidebar-drawer" label="Drawer">
-                <SidebarDrawerIcon />
-              </IconWithTooltip>
-              <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
-                <input
-                  id="swSidebarMode"
-                  className="custom-control-input"
-                  type="checkbox"
-                  checked={!isPreferDrawerMode}
-                  onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked)}
-                />
-                <label className="custom-control-label" htmlFor="swSidebarMode"></label>
-              </div>
-              <IconWithTooltip id="iwt-sidebar-dock" label="Dock">
-                <SidebarDockIcon />
-              </IconWithTooltip>
-            </div>
-          </div>
-        </form>
-
-        {/* Sidebar Mode on Editor */}
-        <h6 className="dropdown-header">{t('personal_dropdown.sidebar_mode_editor')}</h6>
-        <form className="px-4">
-          <div className="form-row justify-content-center">
-            <div className="form-group col-auto mb-0 d-flex align-items-center">
-              <IconWithTooltip id="iwt-sidebar-editor-drawer" label="Drawer">
-                <SidebarDrawerIcon />
-              </IconWithTooltip>
-              <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
-                <input
-                  id="swSidebarModeOnEditor"
-                  className="custom-control-input"
-                  type="checkbox"
-                  checked={!isPreferDrawerModeOnEdit}
-                  onChange={e => preferDrawerModeOnEditSwitchModifiedHandler(!e.target.checked)}
-                />
-                <label className="custom-control-label" htmlFor="swSidebarModeOnEditor"></label>
-              </div>
-              <IconWithTooltip id="iwt-sidebar-editor-dock" label="Dock">
-                <SidebarDockIcon />
-              </IconWithTooltip>
-            </div>
-          </div>
-        </form>
-
-        <div className="dropdown-divider"></div>
-
-        {/* Color Mode */}
-        <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
-        <form className="px-4">
-          <div className="form-row">
-            <div className="form-group col-auto">
-              <div className="custom-control custom-checkbox">
-                <input
-                  id="cbFollowOs"
-                  className="custom-control-input"
-                  type="checkbox"
-                  checked={useOsSettings}
-                  onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
-                />
-                <label className="custom-control-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
-              </div>
-            </div>
-          </div>
-          <div className="form-row justify-content-center">
-            <div className="form-group col-auto mb-0 d-flex align-items-center">
-              <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-icon-container-muted' : ''}>
-                <SunIcon />
-              </IconWithTooltip>
-              <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
-                <input
-                  id="swUserPreference"
-                  className="custom-control-input"
-                  type="checkbox"
-                  checked={isDarkMode}
-                  disabled={useOsSettings}
-                  onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
-                />
-                <label className="custom-control-label" htmlFor="swUserPreference"></label>
-              </div>
-              <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-icon-container-muted' : ''}>
-                <MoonIcon />
-              </IconWithTooltip>
-            </div>
-          </div>
-        </form>
-
-        <div className="dropdown-divider"></div>
-
         <button type="button" className="dropdown-item" onClick={logoutHandler}><i className="icon-fw icon-power"></i>{ t('Sign out') }</button>
       </div>
 

+ 24 - 4
packages/app/src/stores/ui.tsx

@@ -8,17 +8,18 @@ import {
 } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
-
 import { IFocusable } from '~/client/interfaces/focusable';
+import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { Nullable } from '~/interfaces/common';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { UpdateDescCountData } from '~/interfaces/websocket';
 import loggerFactory from '~/utils/logger';
 
 import {
-  useCurrentPageId, useCurrentPagePath, useIsEditable, useIsTrashPage, useIsUserPage,
+  useCurrentPageId, useCurrentPagePath, useIsEditable, useIsTrashPage, useIsUserPage, useIsGuestUser,
   useIsNotCreatable, useIsSharedUser, useNotFoundTargetPathOrId, useIsForbidden, useIsIdenticalPath, useIsNotFoundPermalink,
 } from './context';
+import { localStorageMiddleware } from './middlewares/sync-to-storage';
 import { useStaticSWR } from './use-static-swr';
 
 const { isSharedPage } = pagePathUtils;
@@ -211,8 +212,27 @@ export const useIsDeviceSmallerThanLg = (): SWRResponse<boolean, Error> => {
   return useStaticSWR(key);
 };
 
-export const usePreferDrawerModeByUser = (initialData?: boolean): SWRResponse<boolean, Error> => {
-  return useStaticSWR('preferDrawerModeByUser', initialData, { fallbackData: false });
+type PreferDrawerModeByUserUtils = {
+  update: (preferDrawerMode: boolean) => void
+}
+
+export const usePreferDrawerModeByUser = (initialData?: boolean): SWRResponse<boolean, Error> & PreferDrawerModeByUserUtils => {
+  const { data: isGuestUser } = useIsGuestUser();
+  const { scheduleToPut } = useUserUISettings();
+
+  const swrResponse: SWRResponse<boolean, Error> = useStaticSWR('preferDrawerModeByUser', initialData, { use: isGuestUser ? [localStorageMiddleware] : [] });
+
+  return {
+    ...swrResponse,
+    data: swrResponse.data,
+    update: (preferDrawerMode: boolean) => {
+      swrResponse.mutate(preferDrawerMode);
+
+      if (!isGuestUser) {
+        scheduleToPut({ preferDrawerModeByUser: preferDrawerMode });
+      }
+    },
+  };
 };
 
 export const usePreferDrawerModeOnEditByUser = (initialData?: boolean): SWRResponse<boolean, Error> => {

+ 6 - 2
packages/app/src/styles/theme/_apply-colors.scss

@@ -79,10 +79,14 @@ pre:not(.hljs):not(.CodeMirror-line) {
 
 // Dropdown
 .grw-personal-dropdown {
-  .grw-icon-container svg {
+
+  .grw-sidebar-mode-icon svg {
+    fill: $secondary;
+  }
+  .grw-color-mode-icon svg {
     fill: $color-global;
   }
-  .grw-icon-container-muted svg {
+  .grw-color-mode-icon-muted svg {
     fill: $secondary;
   }
 }