import React from "react"; import {Link, NavLink} from "react-router"; import {Dropdown, Icon as SIcon, Menu} from "semantic-ui-react"; import {Media, SettingsContext, StatusContext, ThemeContext} from "../contexts/contexts"; import {DarkModeToggle, HotspotStatusIcon, useLocalStorage} from "./Common"; import {ShareButton} from "./Share"; import {useCPUTemperature, useIOStats, useLoad, useMemoryStats, usePowerStats, useWROLMode} from "../hooks/customHooks"; import {useReorganizationStatus} from "../contexts/FileWorkerStatusContext"; import {SearchIconButton} from "./Search"; import {Icon, Popup} from "./Theme"; import {HELP_VIEWER_URI, NAME, semanticUIColorMap} from "./Vars"; import _ from "lodash"; function updateFavicon(colorName) { // Fall back to violet if invalid color const safeColor = colorName in semanticUIColorMap ? colorName : 'violet'; const faviconPath = `/favicon-${safeColor}.svg`; // Update all favicon links const iconLinks = document.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"]'); iconLinks.forEach(link => { link.href = faviconPath; }); // Also update the 32x32 and 16x16 specific icons if they exist const icon32 = document.querySelector('link[sizes="32x32"]'); const icon16 = document.querySelector('link[sizes="16x16"]'); if (icon32) icon32.href = faviconPath; if (icon16) icon16.href = faviconPath; // Update theme-color meta tag for iOS/Android status bar const hexColor = semanticUIColorMap[safeColor]; const themeColorMeta = document.querySelector('meta[name="theme-color"]'); if (themeColorMeta) themeColorMeta.content = hexColor; } const links = [ {text: 'Videos', to: '/videos', key: 'videos'}, {text: 'Archive', to: '/archive', key: 'archive'}, {text: 'Map', to: '/map', key: 'map'}, {text: 'Zim', to: '/zim', key: 'zim'}, {text: 'Files', to: '/files', key: 'files'}, { text: 'More', key: 'more', links: [ {text: 'Inventory', to: '/inventory', key: 'inventory'}, {to: '/more/otp', text: 'One Time Pad', end: true}, {to: '/more/vin', text: 'VIN Decoder', end: true}, {to: '/more/calculators', text: 'Calculators', end: true}, {to: '/more/statistics', text: 'Statistics', end: true}, ] }, ]; const help = {to: HELP_VIEWER_URI, text: 'Help', key: 'help', target: '_blank'}; const admin = {to: '/admin', text: 'Admin', key: 'admin'}; const rightLinks = [help, admin]; const collapsedLinks = links.concat([help, admin]); function DropdownLinks({link}) { return {link.links.map(l => )} } function MenuLink({link}) { // Wrapper around NavLink to handle Navlink/Dropdown change. if (link.links) { return } else { const end = link.end ? {end: true} : {end: undefined}; const target = link.target ? {target: link.target} : {}; return {link.text} } } function NavIconWrapper({children}) { if (children) { return
{children}
} else { // Do not use navbar space if children is empty. return } } function useNavColorSetting() { // Use localstorage to avoid flickering navbar color on startup. const {settings} = React.useContext(SettingsContext); const [navColor, setNavColor] = useLocalStorage('nav_color', 'violet'); React.useEffect(() => { if (!_.isEmpty(settings)) { setNavColor(settings.nav_color); } }, [settings.nav_color]); // Update favicon to match nav color React.useEffect(() => { if (navColor) { updateFavicon(navColor); } }, [navColor]); return navColor } export function NavBar() { const wrolModeEnabled = useWROLMode(); const {status} = React.useContext(StatusContext); const navColor = useNavColorSetting(); const wrolpiIcon = WROLPi Home Icon; const name = {NAME || wrolpiIcon}; const topNavText = wrolModeEnabled ? <>{name}  : name; const {i} = React.useContext(ThemeContext); const homeLink = {topNavText} ; // Red/Yellow colors will blend in to some navbar colors, swap the colors for high contrast. const conflictingColors = ['red', 'orange', 'yellow', 'olive', 'pink']; const [lowWarningColor, highWarningColor] = conflictingColors.includes(navColor) ? [null, 'black'] : ['yellow', 'red']; // Generic system load, the least important warning. const {minute_1, mediumLoad, highLoad} = useLoad(); let systemLoadIcon; if (mediumLoad || highLoad) { // System load is high, display a warning icon. const color = highLoad ? highWarningColor : lowWarningColor; const icon = ; systemLoadIcon = } // RAM consumption. const {percent: memoryPercent} = useMemoryStats(); let memoryIcon; if (memoryPercent > 80) { const color = memoryPercent > 90 ? highWarningColor : lowWarningColor; const icon = ; memoryIcon = } // Any disk is busy and processes are waiting. const {percentIOWait} = useIOStats(); let diskWaitIcon; if (percentIOWait >= 50) { // Processes are waiting on disk, display a warning icon. const color = percentIOWait > 75 ? highWarningColor : lowWarningColor; const icon = ; diskWaitIcon = } // CPU temperature. const {temperature, highTemperature, criticalTemperature} = useCPUTemperature(); let temperatureIcon; if (temperature && temperature >= highTemperature) { // CPU temperature is high, display a warning icon. const color = temperature >= criticalTemperature ? highWarningColor : lowWarningColor; const name = temperature >= criticalTemperature ? 'thermometer' : 'thermometer half'; const icon = const link = {icon}; temperatureIcon = ; } // Power issues, this is always displayed if detected. const {underVoltage, overCurrent} = usePowerStats(); let powerIcon; if (underVoltage || overCurrent) { const name = underVoltage ? 'power cord' : 'lightning'; const icon = ; const message = underVoltage ? 'Under-voltage detected! Your power supply is insufficient!' : 'Over-current detected! Your peripherals are using too much power!'; powerIcon = ; } // Display the temperature icon first because it can cause the system to throttle. The rest are in order of effects // that will slow down the system and the user should address. Generic load is last because it is probably not an // issue. const warningIcon = temperatureIcon || diskWaitIcon || memoryIcon || systemLoadIcon; const {isReorganizing, taskType, collectionId, collectionKind} = useReorganizationStatus(); let processingLink; if (status && status.flags) { if (status.flags.file_worker_busy) { // Smart navigation based on what the file worker is doing if (isReorganizing && collectionId && collectionKind) { // Single collection reorganization - navigate to that collection if (collectionKind === 'channel') { processingLink = `/videos/channel/${collectionId}/edit`; } else if (collectionKind === 'domain') { processingLink = `/archive/domain/${collectionId}/edit`; } else { processingLink = '/files'; } } else if (isReorganizing && taskType === 'batch_reorganize' && collectionKind) { // Batch reorganization - navigate to settings page for that kind if (collectionKind === 'channel') { processingLink = '/videos/settings'; } else if (collectionKind === 'domain') { processingLink = '/archive/settings'; } else { processingLink = '/files'; } } else { processingLink = '/files'; } } else if (status.flags.map_importing) { processingLink = '/map/manage'; } } const processingIcon = processingLink && ; let apiDownIcon; if (window.apiDown) { apiDownIcon = } /> } // Upgrade available notification - only show on native (non-Docker) installs and when WROL Mode is disabled let upgradeIcon; if (status?.update_available && !status?.dockerized && !wrolModeEnabled) { const commitsBehind = status.commits_behind || 0; const branch = status.git_branch || 'unknown'; const icon = ; upgradeIcon = ; } const icons = {apiDownIcon} {processingIcon} {upgradeIcon} {powerIcon} {warningIcon} ; return <> {homeLink} {icons} {collapsedLinks.map(i => )} {homeLink} {links.map(i => )} {icons} {rightLinks.map(i => )} }