PersonalDropdown.jsx 8.7 KB

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