import React, { useEffect, useRef, useState, useCallback, useMemo, } 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/session"; import { Icon } from "src/components/Shared/Icon"; import { ConfigurationContext } from "src/hooks/Config"; import { ManualStateContext } from "./Help/context"; import { SettingsButton } from "./SettingsButton"; import { faBars, faChartColumn, faFilm, faHeart, faImage, faImages, faMapMarkerAlt, faPlayCircle, faQuestionCircle, faSignOutAlt, faTag, faTimes, faUser, faVideo, } from "@fortawesome/free-solid-svg-icons"; import { baseURL } from "src/core/createClient"; import { PatchComponent } from "src/patch"; 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", }, groups: { id: "groups", defaultMessage: "Groups", }, 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", }, { name: "images", message: messages.images, href: "/images", icon: faImage, hotkey: "g i", }, { name: "groups", message: messages.groups, href: "/groups", 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); const MainNavbarMenuItems = PatchComponent( "MainNavBar.MenuItems", (props: React.PropsWithChildren<{}>) => { return ; } ); const MainNavbarUtilityItems = PatchComponent( "MainNavBar.UtilityItems", (props: React.PropsWithChildren<{}>) => { return <>{props.children}; } ); export const MainNavbar: React.FC = () => { const history = useHistory(); const location = useLocation(); const { configuration, loading } = React.useContext(ConfigurationContext); const { openManual } = React.useContext(ManualStateContext); const [expanded, setExpanded] = useState(false); // Show all menu items by default, unless config says otherwise const menuItems = useMemo(() => { let cfgMenuItems = configuration?.interface.menuItems; if (!cfgMenuItems) { return allMenuItems; } // translate old movies menu item to groups cfgMenuItems = cfgMenuItems.map((item) => { if (item === "movies") { return "groups"; } return item; }); return allMenuItems.filter((menuItem) => cfgMenuItems!.includes(menuItem.name) ); }, [configuration]); // react-bootstrap typing bug const navbarRef = useRef(null); 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) { newPath += "?q=" + 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 ( <> <> {menuItems.map(({ href, icon, message }) => ( ))} ); };