import React, { useEffect, useRef, useState, useCallback } from "react"; import { defineMessages, FormattedMessage, MessageDescriptor, useIntl, } from "react-intl"; import { Nav, Navbar, Button, Fade } from "react-bootstrap"; import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; import { LinkContainer } from "react-router-bootstrap"; import { Link, NavLink, useLocation, useHistory } from "react-router-dom"; import Mousetrap from "mousetrap"; import { SessionUtils } from "src/utils"; import Icon from "src/components/Shared/Icon"; import { ConfigurationContext } from "src/hooks/Config"; import { ManualStateContext } from "./Help/context"; import { SettingsButton } from "./SettingsButton"; import { faBars, faChartBar, faFilm, faHeart, faImage, faImages, faMapMarkerAlt, faPlayCircle, faQuestionCircle, faSignOutAlt, faTag, faTimes, faUser, faVideo, } from "@fortawesome/free-solid-svg-icons"; interface IMenuItem { name: string; message: MessageDescriptor; href: string; icon: IconDefinition; hotkey: string; userCreatable?: boolean; } const messages = defineMessages({ scenes: { id: "scenes", defaultMessage: "Scenes", }, images: { id: "images", defaultMessage: "Images", }, movies: { id: "movies", defaultMessage: "Movies", }, markers: { id: "markers", defaultMessage: "Markers", }, performers: { id: "performers", defaultMessage: "Performers", }, studios: { id: "studios", defaultMessage: "Studios", }, tags: { id: "tags", defaultMessage: "Tags", }, galleries: { id: "galleries", defaultMessage: "Galleries", }, sceneTagger: { id: "sceneTagger", defaultMessage: "Scene Tagger", }, donate: { id: "donate", defaultMessage: "Donate", }, statistics: { id: "statistics", defaultMessage: "Statistics", }, }); const allMenuItems: IMenuItem[] = [ { name: "scenes", message: messages.scenes, href: "/scenes", icon: faPlayCircle, hotkey: "g s", userCreatable: true, }, { name: "images", message: messages.images, href: "/images", icon: faImage, hotkey: "g i", }, { name: "movies", message: messages.movies, href: "/movies", icon: faFilm, hotkey: "g v", userCreatable: true, }, { name: "markers", message: messages.markers, href: "/scenes/markers", icon: faMapMarkerAlt, hotkey: "g k", }, { name: "galleries", message: messages.galleries, href: "/galleries", icon: faImages, hotkey: "g l", userCreatable: true, }, { name: "performers", message: messages.performers, href: "/performers", icon: faUser, hotkey: "g p", userCreatable: true, }, { name: "studios", message: messages.studios, href: "/studios", icon: faVideo, hotkey: "g u", userCreatable: true, }, { name: "tags", message: messages.tags, href: "/tags", icon: faTag, hotkey: "g t", userCreatable: true, }, ]; const newPathsList = allMenuItems .filter((item) => item.userCreatable) .map((item) => item.href); export const MainNavbar: React.FC = () => { const history = useHistory(); const location = useLocation(); const { configuration, loading } = React.useContext(ConfigurationContext); const { openManual } = React.useContext(ManualStateContext); // Show all menu items by default, unless config says otherwise const [menuItems, setMenuItems] = useState(allMenuItems); const [expanded, setExpanded] = useState(false); useEffect(() => { const iCfg = configuration?.interface; if (iCfg?.menuItems) { setMenuItems( allMenuItems.filter((menuItem) => iCfg.menuItems!.includes(menuItem.name) ) ); } }, [configuration]); // react-bootstrap typing bug // eslint-disable-next-line @typescript-eslint/no-explicit-any const navbarRef = useRef(); const intl = useIntl(); const maybeCollapse = useCallback( (event: Event) => { if ( navbarRef.current && event.target instanceof Node && !navbarRef.current.contains(event.target) ) { setExpanded(false); } }, [setExpanded] ); useEffect(() => { if (expanded) { document.addEventListener("click", maybeCollapse); document.addEventListener("touchstart", maybeCollapse); } return () => { document.removeEventListener("click", maybeCollapse); document.removeEventListener("touchstart", maybeCollapse); }; }, [expanded, maybeCollapse]); const goto = useCallback( (page: string) => { history.push(page); if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } }, [history] ); const pathname = location.pathname.replace(/\/$/, ""); let newPath = newPathsList.includes(pathname) ? `${pathname}/new` : null; if (newPath != null) { let queryParam = new URLSearchParams(location.search).get("q"); if (queryParam != null) { newPath += "?name=" + encodeURIComponent(queryParam); } } // set up hotkeys useEffect(() => { Mousetrap.bind("?", () => openManual()); Mousetrap.bind("g z", () => goto("/settings")); menuItems.forEach((item) => Mousetrap.bind(item.hotkey, () => goto(item.href)) ); if (newPath) { Mousetrap.bind("n", () => history.push(String(newPath))); } return () => { Mousetrap.unbind("?"); Mousetrap.unbind("g z"); menuItems.forEach((item) => Mousetrap.unbind(item.hotkey)); if (newPath) { Mousetrap.unbind("n"); } }; }); function maybeRenderLogout() { if (SessionUtils.isLoggedIn()) { return ( ); } } const handleDismiss = useCallback(() => setExpanded(false), [setExpanded]); function renderUtilityButtons() { return ( <> {maybeRenderLogout()} ); } return ( <> <> ); };