PersonalDropdown.jsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import React, { useState, useCallback } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import { UncontrolledTooltip } from 'reactstrap';
  5. import { UserPicture } from '@growi/ui';
  6. import { scheduleToPutUserUISettings } from '~/client/services/user-ui-settings';
  7. import AppContainer from '~/client/services/AppContainer';
  8. import { withUnstatedContainers } from '../UnstatedUtils';
  9. import NavigationContainer from '~/client/services/NavigationContainer';
  10. import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
  11. import {
  12. isUserPreferenceExists,
  13. isDarkMode as isDarkModeByUtil,
  14. applyColorScheme,
  15. removeUserPreference,
  16. updateUserPreference,
  17. updateUserPreferenceWithOsSettings,
  18. } from '~/client/util/color-scheme';
  19. import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
  20. import SidebarDockIcon from '../Icons/SidebarDockIcon';
  21. import MoonIcon from '../Icons/MoonIcon';
  22. import SunIcon from '../Icons/SunIcon';
  23. const PersonalDropdown = (props) => {
  24. const { t, appContainer } = props;
  25. const user = appContainer.currentUser || {};
  26. const [useOsSettings, setOsSettings] = useState(!isUserPreferenceExists());
  27. const [isDarkMode, setIsDarkMode] = useState(isDarkModeByUtil());
  28. const { data: isPreferDrawerMode, mutate: mutatePreferDrawerMode } = usePreferDrawerModeByUser();
  29. const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
  30. const logoutHandler = () => {
  31. const { interceptorManager } = appContainer;
  32. const context = {
  33. user,
  34. currentPagePath: decodeURIComponent(window.location.pathname),
  35. };
  36. interceptorManager.process('logout', context);
  37. window.location.href = '/logout';
  38. };
  39. const preferDrawerModeSwitchModifiedHandler = useCallback((bool) => {
  40. mutatePreferDrawerMode(bool);
  41. scheduleToPutUserUISettings({ preferDrawerModeByUser: bool });
  42. }, [mutatePreferDrawerMode]);
  43. const preferDrawerModeOnEditSwitchModifiedHandler = useCallback((bool) => {
  44. mutatePreferDrawerModeOnEdit(bool);
  45. scheduleToPutUserUISettings({ preferDrawerModeOnEditByUser: bool });
  46. }, [mutatePreferDrawerModeOnEdit]);
  47. const followOsCheckboxModifiedHandler = (bool) => {
  48. if (bool) {
  49. removeUserPreference();
  50. }
  51. else {
  52. updateUserPreferenceWithOsSettings();
  53. }
  54. applyColorScheme();
  55. // update states
  56. setOsSettings(bool);
  57. setIsDarkMode(isDarkModeByUtil());
  58. };
  59. const userPreferenceSwitchModifiedHandler = (bool) => {
  60. updateUserPreference(bool);
  61. applyColorScheme();
  62. // update state
  63. setIsDarkMode(isDarkModeByUtil());
  64. };
  65. /* eslint-disable react/prop-types */
  66. const IconWithTooltip = ({
  67. id, label, children, additionalClasses,
  68. }) => (
  69. <>
  70. <div id={id} className={`px-2 grw-icon-container ${additionalClasses != null ? additionalClasses : ''}`}>{children}</div>
  71. <UncontrolledTooltip placement="bottom" fade={false} target={id}>{label}</UncontrolledTooltip>
  72. </>
  73. );
  74. /* eslint-enable react/prop-types */
  75. return (
  76. <>
  77. {/* Button */}
  78. {/* remove .dropdown-toggle for hide caret */}
  79. {/* See https://stackoverflow.com/a/44577512/13183572 */}
  80. <a className="px-md-2 nav-link waves-effect waves-light" data-toggle="dropdown">
  81. <UserPicture user={user} noLink noTooltip /><span className="d-none d-lg-inline-block">&nbsp;{user.name}</span>
  82. </a>
  83. {/* Menu */}
  84. <div className="dropdown-menu dropdown-menu-right">
  85. <div className="px-4 pt-3 pb-2 text-center">
  86. <UserPicture user={user} size="lg" noLink noTooltip />
  87. <h5 className="mt-2">
  88. {user.name}
  89. </h5>
  90. <div className="my-2">
  91. <i className="icon-user icon-fw"></i>{user.username}<br />
  92. <i className="icon-envelope icon-fw"></i><span className="grw-email-sm">{user.email}</span>
  93. </div>
  94. <div className="btn-group btn-block mt-2" role="group">
  95. <a className="btn btn-sm btn-outline-secondary col" href={`/user/${user.username}`}>
  96. <i className="icon-fw icon-home"></i>{ t('personal_dropdown.home') }
  97. </a>
  98. <a className="btn btn-sm btn-outline-secondary col" href="/me">
  99. <i className="icon-fw icon-wrench"></i>{ t('personal_dropdown.settings') }
  100. </a>
  101. </div>
  102. </div>
  103. <div className="dropdown-divider"></div>
  104. {/* Sidebar Mode */}
  105. <h6 className="dropdown-header">{t('personal_dropdown.sidebar_mode')}</h6>
  106. <form className="px-4">
  107. <div className="form-row justify-content-center">
  108. <div className="form-group col-auto mb-0 d-flex align-items-center">
  109. <IconWithTooltip id="iwt-sidebar-drawer" label="Drawer">
  110. <SidebarDrawerIcon />
  111. </IconWithTooltip>
  112. <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
  113. <input
  114. id="swSidebarMode"
  115. className="custom-control-input"
  116. type="checkbox"
  117. checked={!isPreferDrawerMode}
  118. onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked)}
  119. />
  120. <label className="custom-control-label" htmlFor="swSidebarMode"></label>
  121. </div>
  122. <IconWithTooltip id="iwt-sidebar-dock" label="Dock">
  123. <SidebarDockIcon />
  124. </IconWithTooltip>
  125. </div>
  126. </div>
  127. </form>
  128. {/* Sidebar Mode on Editor */}
  129. <h6 className="dropdown-header">{t('personal_dropdown.sidebar_mode_editor')}</h6>
  130. <form className="px-4">
  131. <div className="form-row justify-content-center">
  132. <div className="form-group col-auto mb-0 d-flex align-items-center">
  133. <IconWithTooltip id="iwt-sidebar-editor-drawer" label="Drawer">
  134. <SidebarDrawerIcon />
  135. </IconWithTooltip>
  136. <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
  137. <input
  138. id="swSidebarModeOnEditor"
  139. className="custom-control-input"
  140. type="checkbox"
  141. checked={!isPreferDrawerModeOnEdit}
  142. onChange={e => preferDrawerModeOnEditSwitchModifiedHandler(!e.target.checked)}
  143. />
  144. <label className="custom-control-label" htmlFor="swSidebarModeOnEditor"></label>
  145. </div>
  146. <IconWithTooltip id="iwt-sidebar-editor-dock" label="Dock">
  147. <SidebarDockIcon />
  148. </IconWithTooltip>
  149. </div>
  150. </div>
  151. </form>
  152. <div className="dropdown-divider"></div>
  153. {/* Color Mode */}
  154. <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
  155. <form className="px-4">
  156. <div className="form-row">
  157. <div className="form-group col-auto">
  158. <div className="custom-control custom-checkbox">
  159. <input
  160. id="cbFollowOs"
  161. className="custom-control-input"
  162. type="checkbox"
  163. checked={useOsSettings}
  164. onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
  165. />
  166. <label className="custom-control-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
  167. </div>
  168. </div>
  169. </div>
  170. <div className="form-row justify-content-center">
  171. <div className="form-group col-auto mb-0 d-flex align-items-center">
  172. <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-icon-container-muted' : ''}>
  173. <SunIcon />
  174. </IconWithTooltip>
  175. <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
  176. <input
  177. id="swUserPreference"
  178. className="custom-control-input"
  179. type="checkbox"
  180. checked={isDarkMode}
  181. disabled={useOsSettings}
  182. onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
  183. />
  184. <label className="custom-control-label" htmlFor="swUserPreference"></label>
  185. </div>
  186. <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-icon-container-muted' : ''}>
  187. <MoonIcon />
  188. </IconWithTooltip>
  189. </div>
  190. </div>
  191. </form>
  192. <div className="dropdown-divider"></div>
  193. <button type="button" className="dropdown-item" onClick={logoutHandler}><i className="icon-fw icon-power"></i>{ t('Sign out') }</button>
  194. </div>
  195. </>
  196. );
  197. };
  198. /**
  199. * Wrapper component for using unstated
  200. */
  201. const PersonalDropdownWrapper = withUnstatedContainers(PersonalDropdown, [AppContainer]);
  202. PersonalDropdown.propTypes = {
  203. t: PropTypes.func.isRequired, // i18next
  204. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  205. };
  206. export default withTranslation()(PersonalDropdownWrapper);