PersonalDropdown.jsx 8.2 KB

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