Menu.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import * as React from "react";
  2. import { IconButton, IContextualMenuItem, INavLinkGroup, Nav, Pivot, PivotItem, Stack, useTheme } from "@fluentui/react";
  3. import { useConst } from "@fluentui/react-hooks";
  4. import nlsHPCC from "src/nlsHPCC";
  5. import { MainNav, routes } from "../routes";
  6. import { pushUrl } from "../util/history";
  7. import { useFavorite, useFavorites, useHistory } from "../hooks/favorite";
  8. import { useUserTheme } from "../hooks/theme";
  9. import { Breadcrumbs } from "./Breadcrumbs";
  10. // Top Level Nav ---
  11. const navLinkGroups: INavLinkGroup[] = [
  12. {
  13. links: [
  14. {
  15. name: nlsHPCC.Activities,
  16. url: "#/activities",
  17. icon: "Home",
  18. key: "activities"
  19. },
  20. {
  21. name: nlsHPCC.ECL,
  22. url: "#/workunits",
  23. icon: "SetAction",
  24. key: "workunits"
  25. },
  26. {
  27. name: nlsHPCC.Files,
  28. url: "#/files",
  29. icon: "PageData",
  30. key: "files"
  31. },
  32. {
  33. name: nlsHPCC.PublishedQueries,
  34. url: "#/queries",
  35. icon: "Globe",
  36. key: "queries"
  37. },
  38. {
  39. name: nlsHPCC.Operations,
  40. url: "#/topology",
  41. icon: "Admin",
  42. key: "topology"
  43. }
  44. ]
  45. }
  46. ];
  47. const navIdx: { [id: string]: MainNav[] } = {};
  48. function append(route, path) {
  49. if (!navIdx[path]) {
  50. navIdx[path] = [];
  51. }
  52. route.mainNav?.forEach(item => {
  53. navIdx[path].push(item);
  54. });
  55. }
  56. routes.forEach((route: any) => {
  57. if (Array.isArray(route.path)) {
  58. route.path.forEach(path => {
  59. append(route, path);
  60. });
  61. } else {
  62. append(route, route.path);
  63. }
  64. });
  65. function navSelectedKey(hashPath) {
  66. const rootPath = navIdx[`/${hashPath?.split("/")[1]}`];
  67. if (rootPath?.length) {
  68. return rootPath[0];
  69. }
  70. return null;
  71. }
  72. const FIXED_WIDTH = 38;
  73. interface MainNavigationProps {
  74. hashPath: string;
  75. }
  76. export const MainNavigation: React.FunctionComponent<MainNavigationProps> = ({
  77. hashPath
  78. }) => {
  79. const menu = useConst([...navLinkGroups]);
  80. const [theme, setTheme, isDark] = useUserTheme();
  81. const selKey = React.useMemo(() => {
  82. return navSelectedKey(hashPath);
  83. }, [hashPath]);
  84. return <Stack verticalAlign="space-between" styles={{ root: { width: `${FIXED_WIDTH}px`, height: "100%", position: "relative", backgroundColor: theme.palette.themeLighterAlt } }}>
  85. <Stack.Item>
  86. <Nav selectedKey={selKey} groups={menu} />
  87. </Stack.Item>
  88. <Stack.Item>
  89. <IconButton iconProps={{ iconName: isDark ? "Sunny" : "ClearNight" }} onClick={() => setTheme(isDark ? "light" : "dark")} />
  90. <IconButton iconProps={{ iconName: "Settings" }} onClick={() => { }} />
  91. </Stack.Item>
  92. </Stack>;
  93. };
  94. // Second Level Nav ---
  95. interface SubMenu {
  96. headerText: string;
  97. itemKey: string;
  98. }
  99. type SubMenuItems = { [nav: string]: SubMenu[] };
  100. const subMenuItems: SubMenuItems = {
  101. "activities": [
  102. { headerText: nlsHPCC.Activities, itemKey: "/activities" },
  103. { headerText: nlsHPCC.EventScheduler + " (L)", itemKey: "/events" }
  104. ],
  105. "workunits": [
  106. { headerText: nlsHPCC.Workunits, itemKey: "/workunits" },
  107. { headerText: nlsHPCC.Dashboard, itemKey: "/workunits/dashboard" },
  108. { headerText: nlsHPCC.Playground, itemKey: "/play" },
  109. ],
  110. "files": [
  111. { headerText: nlsHPCC.LogicalFiles, itemKey: "/files" },
  112. { headerText: nlsHPCC.LandingZones, itemKey: "/landingzone" },
  113. { headerText: nlsHPCC.Workunits, itemKey: "/dfuworkunits" },
  114. { headerText: nlsHPCC.XRef + " (L)", itemKey: "/xref" },
  115. ],
  116. "queries": [
  117. { headerText: nlsHPCC.Queries, itemKey: "/queries" },
  118. { headerText: nlsHPCC.PackageMaps, itemKey: "/packagemaps" }
  119. ],
  120. "topology": [
  121. { headerText: nlsHPCC.Topology + " (L)", itemKey: "/topology" },
  122. { headerText: nlsHPCC.DiskUsage + " (L)", itemKey: "/diskusage" },
  123. { headerText: nlsHPCC.TargetClusters + " (L)", itemKey: "/clusters" },
  124. { headerText: nlsHPCC.ClusterProcesses + " (L)", itemKey: "/processes" },
  125. { headerText: nlsHPCC.SystemServers + " (L)", itemKey: "/servers" },
  126. { headerText: nlsHPCC.Security + " (L)", itemKey: "/security" },
  127. { headerText: nlsHPCC.Monitoring + " (L)", itemKey: "/monitoring" },
  128. { headerText: nlsHPCC.DESDL + " (L)", itemKey: "/esdl" },
  129. ],
  130. };
  131. const subNavIdx: { [id: string]: string[] } = {};
  132. for (const key in subMenuItems) {
  133. const subNav = subMenuItems[key];
  134. subNav.forEach(item => {
  135. if (!subNavIdx[item.itemKey]) {
  136. subNavIdx[item.itemKey] = [];
  137. }
  138. subNavIdx[item.itemKey].push(key);
  139. });
  140. }
  141. function subNavSelectedKey(hashPath) {
  142. return !!subNavIdx[hashPath] ? hashPath : null;
  143. }
  144. const handleLinkClick = (item?: PivotItem) => {
  145. if (item?.props?.itemKey) {
  146. pushUrl(item.props.itemKey);
  147. }
  148. };
  149. interface SubNavigationProps {
  150. hashPath: string;
  151. }
  152. export const SubNavigation: React.FunctionComponent<SubNavigationProps> = ({
  153. hashPath,
  154. }) => {
  155. const theme = useTheme();
  156. const [favorites] = useFavorites();
  157. const [isFavorite, addFavorite, removeFavorite] = useFavorite(window.location.hash);
  158. const [history] = useHistory();
  159. const mainNav = React.useMemo(() => {
  160. return navSelectedKey(hashPath);
  161. }, [hashPath]);
  162. const subNav = React.useMemo(() => {
  163. return subNavSelectedKey(hashPath);
  164. }, [hashPath]);
  165. const altSubNav = React.useMemo(() => {
  166. const parts = hashPath.split("/");
  167. parts.shift();
  168. return parts.shift();
  169. }, [hashPath]);
  170. const favoriteMenu: IContextualMenuItem[] = React.useMemo(() => {
  171. const retVal: IContextualMenuItem[] = [];
  172. for (const key in favorites) {
  173. retVal.push({
  174. name: decodeURI(key),
  175. href: key,
  176. key,
  177. });
  178. }
  179. return retVal;
  180. }, [favorites]);
  181. return <div style={{ backgroundColor: theme.palette.themeLighter }}>
  182. <Stack horizontal horizontalAlign="space-between">
  183. <Stack.Item align="center" grow={1}>
  184. <Stack horizontal >
  185. <Stack.Item grow={0} >
  186. <Pivot selectedKey={subNav || altSubNav} onLinkClick={handleLinkClick} headersOnly={true} linkFormat="tabs" styles={{ root: { marginLeft: 4 }, text: { lineHeight: 20 }, link: { maxHeight: 20, marginRight: 4 }, linkContent: { maxHeight: 20 } }} >
  187. {subMenuItems[mainNav]?.map(row => <PivotItem headerText={row.headerText} itemKey={row.itemKey} key={row.itemKey} />)}
  188. </Pivot>
  189. </Stack.Item>
  190. {!subNav &&
  191. <Stack.Item grow={1}>
  192. <Breadcrumbs hashPath={hashPath} ignoreN={1} />
  193. </Stack.Item>
  194. }
  195. </Stack>
  196. </Stack.Item>
  197. <Stack.Item align="center" grow={0}>
  198. <IconButton title={nlsHPCC.Advanced} iconProps={{ iconName: "History" }} menuProps={{ items: history }} />
  199. <IconButton title={nlsHPCC.Advanced} iconProps={{ iconName: isFavorite ? "FavoriteStarFill" : "FavoriteStar" }} menuProps={{ items: favoriteMenu }} split onClick={() => {
  200. if (isFavorite) {
  201. removeFavorite();
  202. } else {
  203. addFavorite();
  204. }
  205. }} styles={{ splitButtonMenuButton: { backgroundColor: theme.palette.themeLighter, border: "none" } }} />
  206. </Stack.Item>
  207. </Stack>
  208. </div>;
  209. };