AdminNavigation.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import React, { useCallback } from 'react';
  2. import { pathUtils } from '@growi/core/dist/utils';
  3. import { useTranslation } from 'next-i18next';
  4. import Link from 'next/link';
  5. import urljoin from 'url-join';
  6. import { useGrowiCloudUri, useGrowiAppIdForGrowiCloud } from '~/stores-universal/context';
  7. import styles from './AdminNavigation.module.scss';
  8. const moduleClass = styles['admin-navigation'];
  9. // eslint-disable-next-line react/prop-types
  10. const MenuLabel = ({ menu }: { menu: string }) => {
  11. const { t } = useTranslation(['admin', 'commons']);
  12. switch (menu) {
  13. /* eslint-disable no-multi-spaces, max-len */
  14. case 'app': return <><span className="material-symbols-outlined me-1">settings</span>{ t('headers.app_settings', { ns: 'commons' }) }</>;
  15. case 'security': return <><span className="material-symbols-outlined me-1">shield</span>{ t('security_settings.security_settings') }</>;
  16. case 'markdown': return <><span className="material-symbols-outlined me-1">note</span>{ t('markdown_settings.markdown_settings') }</>;
  17. case 'customize': return <><span className="material-symbols-outlined me-1">construction</span>{ t('customize_settings.customize_settings') }</>;
  18. case 'importer': return <><span className="material-symbols-outlined me-1">cloud_upload</span>{ t('importer_management.import_data') }</>;
  19. case 'export': return <><span className="material-symbols-outlined me-1">cloud_download</span>{ t('export_management.export_archive_data') }</>;
  20. case 'data-transfer': return <><span className="material-symbols-outlined me-1">flight</span>{ t('g2g_data_transfer.data_transfer', { ns: 'commons' })}</>;
  21. case 'notification': return <><span className="material-symbols-outlined me-1">notifications</span>{ t('external_notification.external_notification')}</>;
  22. case 'slack-integration': return <><span className="material-symbols-outlined me-1">shuffle</span>{ t('slack_integration.slack_integration') }</>;
  23. case 'slack-integration-legacy': return <><span className="material-symbols-outlined me-1">shuffle</span>{ t('slack_integration_legacy.slack_integration_legacy')}</>;
  24. case 'users': return <><span className="material-symbols-outlined me-1">person</span>{ t('user_management.user_management') }</>;
  25. case 'user-groups': return <><span className="material-symbols-outlined me-1">group</span>{ t('user_group_management.user_group_management') }</>;
  26. case 'audit-log': return <><span className="material-symbols-outlined me-1">feed</span>{ t('audit_log_management.audit_log')}</>;
  27. case 'plugins': return <><span className="material-symbols-outlined me-1">extension</span>{ t('plugins.plugins')}</>;
  28. case 'search': return <><span className="material-symbols-outlined me-1">search</span>{ t('full_text_search_management.full_text_search_management') }</>;
  29. case 'cloud': return <><span className="material-symbols-outlined me-1">share</span>{ t('cloud_setting_management.to_cloud_settings')} </>;
  30. default: return <><span className="material-symbols-outlined me-1">home</span>{ t('wiki_management_homepage') }</>;
  31. /* eslint-enable no-multi-spaces, max-len */
  32. }
  33. };
  34. type MenuLinkProps = {
  35. menu: string,
  36. isListGroupItems: boolean,
  37. isRoot?: boolean,
  38. isActive?: boolean,
  39. }
  40. const MenuLink = ({
  41. menu, isRoot, isListGroupItems, isActive,
  42. }: MenuLinkProps) => {
  43. const pageTransitionClassName = isListGroupItems
  44. ? 'list-group-item list-group-item-action rounded border-0'
  45. : 'dropdown-item px-3 py-2';
  46. const href = isRoot ? '/admin' : urljoin('/admin', menu);
  47. return (
  48. <Link
  49. href={href}
  50. className={`${pageTransitionClassName} ${isActive ? 'active' : ''}`}
  51. >
  52. <MenuLabel menu={menu} />
  53. </Link>
  54. );
  55. };
  56. export const AdminNavigation = (): JSX.Element => {
  57. const pathname = window.location.pathname;
  58. const { data: growiCloudUri } = useGrowiCloudUri();
  59. const { data: growiAppIdForGrowiCloud } = useGrowiAppIdForGrowiCloud();
  60. const isActiveMenu = useCallback((path: string | string[]) => {
  61. const paths = Array.isArray(path) ? path : [path];
  62. return paths.some((path) => {
  63. const basisPath = pathUtils.normalizePath(urljoin('/admin', path));
  64. const basisParentPath = pathUtils.addTrailingSlash(basisPath);
  65. return (
  66. pathname === basisPath
  67. || pathname.startsWith(basisParentPath)
  68. );
  69. });
  70. }, [pathname]);
  71. const getListGroupItemOrDropdownItemList = useCallback((isListGroupItems: boolean) => {
  72. return (
  73. <>
  74. {/* eslint-disable no-multi-spaces */}
  75. <MenuLink menu="home" isListGroupItems={isListGroupItems} isActive={pathname === '/admin'} isRoot />
  76. <MenuLink menu="app" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/app')} />
  77. <MenuLink menu="security" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/security')} />
  78. <MenuLink menu="markdown" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/markdown')} />
  79. <MenuLink menu="customize" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/customize')} />
  80. <MenuLink menu="importer" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/importer')} />
  81. <MenuLink menu="export" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/export')} />
  82. <MenuLink menu="data-transfer" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/data-transfer')} />
  83. <MenuLink menu="notification" isListGroupItems={isListGroupItems} isActive={isActiveMenu(['/notification', '/global-notification'])} />
  84. <MenuLink menu="slack-integration" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/slack-integration')} />
  85. <MenuLink menu="slack-integration-legacy" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/slack-integration-legacy')} />
  86. <MenuLink menu="users" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/users')} />
  87. <MenuLink menu="user-groups" isListGroupItems={isListGroupItems} isActive={isActiveMenu(['/user-groups', 'user-group-detail'])} />
  88. <MenuLink menu="audit-log" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/audit-log')} />
  89. <MenuLink menu="plugins" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/plugins')} />
  90. <MenuLink menu="search" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/search')} />
  91. {growiCloudUri != null && growiAppIdForGrowiCloud != null
  92. && (
  93. <a
  94. href={`${growiCloudUri}/my/apps/${growiAppIdForGrowiCloud}`}
  95. className="list-group-item list-group-item-action border-0 round-corner"
  96. >
  97. <MenuLabel menu="cloud" />
  98. </a>
  99. )
  100. }
  101. {/* eslint-enable no-multi-spaces */}
  102. </>
  103. );
  104. }, [growiAppIdForGrowiCloud, growiCloudUri, isActiveMenu, pathname]);
  105. return (
  106. <React.Fragment>
  107. {/* List group */}
  108. <div className={`list-group ${moduleClass} sticky-top d-none d-lg-block`}>
  109. {getListGroupItemOrDropdownItemList(true)}
  110. </div>
  111. {/* Dropdown */}
  112. <div className="dropdown d-block d-lg-none mb-5">
  113. <button
  114. className="btn btn-outline-primary btn-lg dropdown-toggle col-12 text-end"
  115. type="button"
  116. id="dropdown-admin-navigation"
  117. data-display="static"
  118. data-bs-toggle="dropdown"
  119. aria-haspopup="true"
  120. aria-expanded="false"
  121. >
  122. <span className="float-start">
  123. {/* eslint-disable no-multi-spaces */}
  124. {pathname === '/admin' && <MenuLabel menu="home" />}
  125. {isActiveMenu('/app') && <MenuLabel menu="app" />}
  126. {isActiveMenu('/security') && <MenuLabel menu="security" />}
  127. {isActiveMenu('/markdown') && <MenuLabel menu="markdown" />}
  128. {isActiveMenu('/customize') && <MenuLabel menu="customize" />}
  129. {isActiveMenu('/importer') && <MenuLabel menu="importer" />}
  130. {isActiveMenu('/export') && <MenuLabel menu="export" />}
  131. {(isActiveMenu(['/notification', '/global-notification']))
  132. && <MenuLabel menu="notification" />}
  133. {isActiveMenu('/slack-integration') && <MenuLabel menu="slack-integration" />}
  134. {isActiveMenu('/users') && <MenuLabel menu="users" />}
  135. {isActiveMenu(['/user-groups', 'user-group-detail'])
  136. && <MenuLabel menu="user-groups" />}
  137. {isActiveMenu('/search') && <MenuLabel menu="search" />}
  138. {isActiveMenu('/audit-log') && <MenuLabel menu="audit-log" />}
  139. {isActiveMenu('/plugins') && <MenuLabel menu="plugins" />}
  140. {isActiveMenu('/data-transfer') && <MenuLabel menu="data-transfer" />}
  141. {/* eslint-enable no-multi-spaces */}
  142. </span>
  143. </button>
  144. <div className="dropdown-menu" aria-labelledby="dropdown-admin-navigation">
  145. {getListGroupItemOrDropdownItemList(false)}
  146. </div>
  147. </div>
  148. </React.Fragment>
  149. );
  150. };