PersonalDropdown.jsx 8.1 KB

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