PersonalDropdown.jsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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. const PersonalDropdown = (props) => {
  18. const { t, appContainer, navigationContainer } = props;
  19. const user = appContainer.currentUser || {};
  20. const [useOsSettings, setOsSettings] = useState(!isUserPreferenceExists());
  21. const [isDarkMode, setIsDarkMode] = useState(isDarkModeByUtil());
  22. const logoutHandler = () => {
  23. const { interceptorManager } = appContainer;
  24. const context = {
  25. user,
  26. currentPagePath: decodeURIComponent(window.location.pathname),
  27. };
  28. interceptorManager.process('logout', context);
  29. window.location.href = '/logout';
  30. };
  31. const preferDrawerModeSwitchModifiedHandler = (bool) => {
  32. navigationContainer.setDrawerModePreference(bool);
  33. };
  34. const preferDrawerModeOnEditSwitchModifiedHandler = (bool) => {
  35. navigationContainer.setDrawerModePreferenceOnEdit(bool);
  36. };
  37. const followOsCheckboxModifiedHandler = (bool) => {
  38. if (bool) {
  39. removeUserPreference();
  40. }
  41. else {
  42. updateUserPreferenceWithOsSettings();
  43. }
  44. applyColorScheme();
  45. // update states
  46. setOsSettings(bool);
  47. setIsDarkMode(isDarkModeByUtil());
  48. };
  49. const userPreferenceSwitchModifiedHandler = (bool) => {
  50. updateUserPreference(bool);
  51. applyColorScheme();
  52. // update state
  53. setIsDarkMode(isDarkModeByUtil());
  54. };
  55. /*
  56. * render
  57. */
  58. const {
  59. preferDrawerModeByUser, preferDrawerModeOnEditByUser,
  60. } = navigationContainer.state;
  61. /* eslint-disable react/prop-types */
  62. const DrawerIcon = props => (
  63. <>
  64. <i id={props.id} className="icon-drawer px-2"></i>
  65. <UncontrolledTooltip placement="bottom" fade={false} target={props.id}>Drawer</UncontrolledTooltip>
  66. </>
  67. );
  68. const DockIcon = props => (
  69. <>
  70. <i id={props.id} className="ti-layout-sidebar-left px-2"></i>
  71. <UncontrolledTooltip placement="bottom" fade={false} target={props.id}>Dock</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" 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" 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. <h6 className="dropdown-header">{t('personal_dropdown.sidebar_mode')}</h6>
  105. <form className="px-4">
  106. <div className="form-row justify-content-center">
  107. <div className="form-group col-auto mb-0 d-flex align-items-center">
  108. <DrawerIcon id="icon-prefer-drawer" />
  109. <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
  110. <input
  111. id="swSidebarMode"
  112. className="custom-control-input"
  113. type="checkbox"
  114. checked={!preferDrawerModeByUser}
  115. onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked)}
  116. />
  117. <label className="custom-control-label" htmlFor="swSidebarMode"></label>
  118. </div>
  119. <DockIcon id="icon-prefer-dock" />
  120. </div>
  121. </div>
  122. </form>
  123. <h6 className="dropdown-header">{t('personal_dropdown.sidebar_mode_editor')}</h6>
  124. <form className="px-4">
  125. <div className="form-row justify-content-center">
  126. <div className="form-group col-auto mb-0 d-flex align-items-center">
  127. <DrawerIcon id="icon-prefer-drawer-on-edit" />
  128. <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
  129. <input
  130. id="swSidebarModeOnEditor"
  131. className="custom-control-input"
  132. type="checkbox"
  133. checked={!preferDrawerModeOnEditByUser}
  134. onChange={e => preferDrawerModeOnEditSwitchModifiedHandler(!e.target.checked)}
  135. />
  136. <label className="custom-control-label" htmlFor="swSidebarModeOnEditor"></label>
  137. </div>
  138. <DockIcon id="icon-prefer-dock-on-edit" />
  139. </div>
  140. </div>
  141. </form>
  142. <div className="dropdown-divider"></div>
  143. <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
  144. <form className="px-4">
  145. <div className="form-row">
  146. <div className="form-group col-auto">
  147. <div className="custom-control custom-checkbox">
  148. <input
  149. id="cbFollowOs"
  150. className="custom-control-input"
  151. type="checkbox"
  152. checked={useOsSettings}
  153. onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
  154. />
  155. <label className="custom-control-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
  156. </div>
  157. </div>
  158. </div>
  159. <div className="form-row justify-content-center">
  160. <div className="form-group col-auto mb-0 d-flex align-items-center">
  161. <span className={useOsSettings ? '' : 'text-muted'}>Light</span>
  162. <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
  163. <input
  164. id="swUserPreference"
  165. className="custom-control-input"
  166. type="checkbox"
  167. checked={isDarkMode}
  168. disabled={useOsSettings}
  169. onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
  170. />
  171. <label className="custom-control-label" htmlFor="swUserPreference"></label>
  172. </div>
  173. <span className={useOsSettings ? '' : 'text-muted'}>Dark</span>
  174. </div>
  175. </div>
  176. </form>
  177. <div className="dropdown-divider"></div>
  178. <button type="button" className="dropdown-item" onClick={logoutHandler}><i className="icon-fw icon-power"></i>{ t('Sign out') }</button>
  179. </div>
  180. </>
  181. );
  182. };
  183. /**
  184. * Wrapper component for using unstated
  185. */
  186. const PersonalDropdownWrapper = withUnstatedContainers(PersonalDropdown, [AppContainer, NavigationContainer]);
  187. PersonalDropdown.propTypes = {
  188. t: PropTypes.func.isRequired, // i18next
  189. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  190. navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
  191. };
  192. export default withTranslation()(PersonalDropdownWrapper);