mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Rationalize Single Desktop Overflow Menu
This commit is contained in:
@@ -16,7 +16,6 @@ export const Release = {
|
||||
App: {
|
||||
versionCode: '2.0.0-open-rc3', // 1.92.0 sequentially...
|
||||
versionName: 'Big-AGI 2',
|
||||
releaseNotes: '',
|
||||
},
|
||||
|
||||
// Future compatibility
|
||||
@@ -65,5 +64,6 @@ export const Release = {
|
||||
|
||||
|
||||
export const BaseProduct = {
|
||||
ReleaseNotes: '',
|
||||
SupportForm: (_userId?: string) => 'https://github.com/enricoros/big-agi/issues',
|
||||
} as const;
|
||||
|
||||
@@ -1,35 +1,22 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, Dropdown, IconButton, ListDivider, ListItem, ListItemDecorator, Menu, MenuButton, MenuItem, Typography } from '@mui/joy';
|
||||
import { Box, IconButton, Typography } from '@mui/joy';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||
import FeedbackIcon from '@mui/icons-material/Feedback';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
|
||||
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
||||
|
||||
import { BuildInfoCard } from '../../../../apps/news/AppNews';
|
||||
import { blocksRenderHTMLIFrameCss } from '~/modules/blocks/code/code-renderers/RenderCodeHtmlIFrame';
|
||||
|
||||
import { BigAgiSquircleIcon } from '~/common/components/icons/big-agi/BigAgiSquircleIcon';
|
||||
import { Brand } from '~/common/app.config';
|
||||
import { GoodModal } from '~/common/components/modals/GoodModal';
|
||||
import { LayoutSidebarRight } from '~/common/components/icons/LayoutSidebarRight';
|
||||
import { Link } from '~/common/components/Link';
|
||||
import { Release } from '~/common/app.release';
|
||||
import { TooltipOutlined } from '~/common/components/TooltipOutlined';
|
||||
import { checkVisibleNav, NavItemApp } from '~/common/app.nav';
|
||||
import { navigateToIndex, ROUTE_INDEX } from '~/common/app.routes';
|
||||
import { useOverlayComponents } from '~/common/layout/overlays/useOverlayComponents';
|
||||
|
||||
import { InvertedBar, InvertedBarCornerItem } from '../InvertedBar';
|
||||
import { PopupPanel } from '../panel/PopupPanel';
|
||||
import { optimaOpenDrawer, optimaOpenPanel, optimaTogglePanel, useOptimaPanelOpen } from '../useOptima';
|
||||
import { scratchClipSupported, useScratchClipVisibility } from '../scratchclip/store-scratchclip';
|
||||
import { useOptimaPortalHasInputs } from '../portals/useOptimaPortalHasInputs';
|
||||
import { useOptimaPortalOutRef } from '../portals/useOptimaPortalOutRef';
|
||||
|
||||
@@ -95,51 +82,12 @@ export function OptimaBar(props: { component: React.ElementType, currentApp?: Na
|
||||
const appMenuAnchor = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
// external state
|
||||
/**
|
||||
* NOTE: shall we fall back to the 'standard' release notes when not available on the tenant?
|
||||
* - prob not because this could be a per-company deployment, and we don't know the tenant's release notes
|
||||
*/
|
||||
const releaseNotesUrl = Release.App.releaseNotes;
|
||||
const { showPromisedOverlay } = useOverlayComponents();
|
||||
const hasDrawerContent = useOptimaPortalHasInputs('optima-portal-drawer');
|
||||
const { panelAsPopup, panelHasContent, panelShownAsPanel, panelShownAsPopup } = useOptimaPanelOpen(props.isMobile, props.currentApp);
|
||||
const { isVisible: isScratchClipVisible, toggleVisibility: toggleScratchClipVisibility } = useScratchClipVisibility();
|
||||
|
||||
// derived state
|
||||
const navIsShown = checkVisibleNav(props.currentApp);
|
||||
|
||||
|
||||
// Handlers
|
||||
|
||||
const handleShowReleaseNotes = React.useCallback(async () => {
|
||||
if (!releaseNotesUrl) return;
|
||||
return await showPromisedOverlay('app-recent-changes', { rejectWithValue: false }, ({ onResolve, onUserReject }) =>
|
||||
<GoodModal
|
||||
open
|
||||
onClose={onUserReject}
|
||||
noTitleBar
|
||||
themedColor='neutral'
|
||||
unfilterBackdrop
|
||||
sx={{ minWidth: { xs: 400, sm: 580, md: 780, lg: 890 } }}
|
||||
>
|
||||
<iframe
|
||||
src={releaseNotesUrl}
|
||||
style={{ ...blocksRenderHTMLIFrameCss, height: '70svh' }}
|
||||
title='Release Notes Embed'
|
||||
loading='lazy' // do not load until visible in the viewport
|
||||
/>
|
||||
</GoodModal>,
|
||||
);
|
||||
}, [releaseNotesUrl, showPromisedOverlay]);
|
||||
|
||||
const handleShowTechnologies = React.useCallback(async () => {
|
||||
return await showPromisedOverlay<boolean>('app-recent-changes', { rejectWithValue: false }, ({ onUserReject }) =>
|
||||
<GoodModal open onClose={onUserReject} noTitleBar unfilterBackdrop>
|
||||
<BuildInfoCard noMargin />
|
||||
</GoodModal>,
|
||||
);
|
||||
}, [showPromisedOverlay]);
|
||||
|
||||
// [Desktop] optionally hide the Bar if the current app asks for it
|
||||
if (props.currentApp?.hideBar && !props.isMobile && !panelHasContent)
|
||||
return null;
|
||||
@@ -169,47 +117,7 @@ export function OptimaBar(props: { component: React.ElementType, currentApp?: Na
|
||||
{/* Pluggable Toolbar Items */}
|
||||
<CenterItemsPortal currentApp={props.currentApp} />
|
||||
|
||||
{/* (PREVIEW) Preview Menu */}
|
||||
{!props.isMobile && (
|
||||
<Dropdown>
|
||||
<MenuButton
|
||||
aria-label='Quick Tools Menu'
|
||||
slots={{ root: IconButton }}
|
||||
slotProps={{ root: { size: 'md' } }}
|
||||
>
|
||||
{/*<NotificationsNoneOutlinedIcon />*/}
|
||||
<LightbulbOutlinedIcon />
|
||||
{/*<FeedbackOutlinedIcon />*/}
|
||||
</MenuButton>
|
||||
|
||||
<Menu placement='bottom-end' sx={{ minWidth: 220 }}>
|
||||
<ListItem>
|
||||
<Typography level='body-xs' sx={{ textTransform: 'uppercase' }}>
|
||||
{Release.App.versionName}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
|
||||
{!!releaseNotesUrl && (
|
||||
<MenuItem onClick={handleShowReleaseNotes}>
|
||||
<ListItemDecorator><NewReleasesIcon /></ListItemDecorator>
|
||||
Release Notes
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={handleShowTechnologies}>
|
||||
{/*<ListItemDecorator><EventNoteOutlinedIcon /></ListItemDecorator>*/}
|
||||
<ListItemDecorator><EngineeringIcon /></ListItemDecorator>
|
||||
Build Info
|
||||
</MenuItem>
|
||||
|
||||
|
||||
{scratchClipSupported() && <MenuItem onClick={toggleScratchClipVisibility}>
|
||||
<ListItemDecorator><HistoryIcon /></ListItemDecorator>
|
||||
{isScratchClipVisible ? 'Hide ' : ''}Clipboard History
|
||||
</MenuItem>}
|
||||
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
)}
|
||||
{/* We used to have the Preview (lightbulb) menu here */}
|
||||
|
||||
{/* Panel Open: has content always on Mobile (the app menu) */}
|
||||
{panelHasContent && (
|
||||
|
||||
@@ -2,19 +2,31 @@ import * as React from 'react';
|
||||
import Router from 'next/router';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Divider, Dropdown, ListItemDecorator, Menu, MenuButton, MenuItem, Tooltip } from '@mui/joy';
|
||||
import { Divider, Dropdown, ListDivider, ListItem, ListItemButton, ListItemDecorator, Menu, MenuButton, MenuItem, Tooltip, Typography } from '@mui/joy';
|
||||
import CodeIcon from '@mui/icons-material/Code';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
|
||||
import { blocksRenderHTMLIFrameCss } from '~/modules/blocks/code/code-renderers/RenderCodeHtmlIFrame';
|
||||
|
||||
import { BuildInfoCard } from '../../../../apps/news/AppNews';
|
||||
|
||||
import { BaseProduct } from '~/common/app.release';
|
||||
import { BigAgiSquircleIcon } from '~/common/components/icons/big-agi/BigAgiSquircleIcon';
|
||||
import { FeatureBadge } from '~/common/components/FeatureBadge';
|
||||
import { GoodModal } from '~/common/components/modals/GoodModal';
|
||||
import { PhSquaresFour } from '~/common/components/icons/phosphor/PhSquaresFour';
|
||||
import { checkDivider, checkVisibileIcon, NavItemApp, navItems } from '~/common/app.nav';
|
||||
import { themeZIndexDesktopNav } from '~/common/app.theme';
|
||||
import { useHasLLMs } from '~/common/stores/llms/llms.hooks';
|
||||
import { useOverlayComponents } from '~/common/layout/overlays/useOverlayComponents';
|
||||
|
||||
import { BringTheLove } from './BringTheLove';
|
||||
import { DesktopNavGroupBox, DesktopNavIcon, navItemClasses } from './DesktopNavIcon';
|
||||
import { InvertedBar, InvertedBarCornerItem } from '../InvertedBar';
|
||||
import { optimaOpenModels, optimaOpenPreferences, optimaToggleDrawer, useOptimaDrawerOpen, useOptimaModals } from '../useOptima';
|
||||
import { scratchClipSupported, useScratchClipVisibility } from '../scratchclip/store-scratchclip';
|
||||
|
||||
|
||||
const desktopNavBarSx: SxProps = {
|
||||
@@ -34,14 +46,58 @@ const navItemsDividerSx: SxProps = {
|
||||
|
||||
export function DesktopNav(props: { component: React.ElementType, currentApp?: NavItemApp }) {
|
||||
|
||||
// state
|
||||
const [releaseNotesShown, setReleaseNotesShown] = React.useState(false);
|
||||
|
||||
/**
|
||||
* NOTE: shall we fall back to the 'standard' release notes when not available on the tenant?
|
||||
* - prob not because this could be a per-company deployment, and we don't know the tenant's release notes
|
||||
*/
|
||||
const releaseNotesUrl = BaseProduct.ReleaseNotes;
|
||||
|
||||
// external state
|
||||
const isDrawerOpen = useOptimaDrawerOpen();
|
||||
const { showPromisedOverlay } = useOverlayComponents();
|
||||
const { showModels, showPreferences } = useOptimaModals();
|
||||
const { isVisible: isScratchClipVisible, toggleVisibility: toggleScratchClipVisibility } = useScratchClipVisibility();
|
||||
|
||||
// derived state
|
||||
const noLLMs = !useHasLLMs();
|
||||
|
||||
|
||||
// handlers
|
||||
|
||||
const handleShowReleaseNotes = React.useCallback(async () => {
|
||||
if (!releaseNotesUrl) return;
|
||||
setReleaseNotesShown(true);
|
||||
return await showPromisedOverlay('app-recent-changes', { rejectWithValue: false }, ({ onUserReject }) =>
|
||||
<GoodModal
|
||||
open
|
||||
onClose={onUserReject}
|
||||
noTitleBar
|
||||
themedColor='neutral'
|
||||
unfilterBackdrop
|
||||
sx={{ minWidth: { xs: 400, sm: 580, md: 780, lg: 890 } }}
|
||||
>
|
||||
<iframe
|
||||
src={releaseNotesUrl}
|
||||
style={{ ...blocksRenderHTMLIFrameCss, height: '70svh' }}
|
||||
title='Release Notes Embed'
|
||||
loading='lazy' // do not load until visible in the viewport
|
||||
/>
|
||||
</GoodModal>,
|
||||
);
|
||||
}, [releaseNotesUrl, showPromisedOverlay]);
|
||||
|
||||
const handleShowTechnologies = React.useCallback(async () => {
|
||||
return await showPromisedOverlay<boolean>('app-recent-changes', { rejectWithValue: false }, ({ onUserReject }) =>
|
||||
<GoodModal open onClose={onUserReject} noTitleBar unfilterBackdrop>
|
||||
<BuildInfoCard noMargin />
|
||||
</GoodModal>,
|
||||
);
|
||||
}, [showPromisedOverlay]);
|
||||
|
||||
|
||||
// show/hide the pane when clicking on the logo
|
||||
const appUsesDrawer = !props.currentApp?.hideDrawer;
|
||||
const logoButtonTogglesPane = (appUsesDrawer && !isDrawerOpen) || isDrawerOpen;
|
||||
@@ -65,7 +121,7 @@ export function DesktopNav(props: { component: React.ElementType, currentApp?: N
|
||||
}
|
||||
});
|
||||
|
||||
// Application buttons (and group sepearator)
|
||||
// Application buttons (and group separator)
|
||||
const components: React.JSX.Element[] = visibleApps.map((app, appIdx) => {
|
||||
const isActive = app === props.currentApp;
|
||||
const isDrawerable = isActive && !app.hideDrawer;
|
||||
@@ -88,35 +144,85 @@ export function DesktopNav(props: { component: React.ElementType, currentApp?: N
|
||||
);
|
||||
});
|
||||
|
||||
// Overflow dropdown menu
|
||||
if (overflowApps.length) {
|
||||
components.push(
|
||||
<Dropdown key='n-app-overflow'>
|
||||
<Tooltip disableInteractive enterDelay={600} title='More Apps'>
|
||||
<MenuButton slots={{ root: DesktopNavIcon }} slotProps={{ root: { className: navItemClasses.typeApp } }}>
|
||||
<MoreHorizIcon />
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
variant='outlined'
|
||||
placement='right-start'
|
||||
popperOptions={{ modifiers: [{ name: 'offset', options: { offset: [0, -2] } }] }}
|
||||
sx={{ minWidth: 220 }}
|
||||
>
|
||||
{overflowApps.map((app, appIdx) =>
|
||||
<MenuItem key={'nav-app-extra-' + appIdx} onClick={() => Router.push(app.landingRoute || app.route)}>
|
||||
<ListItemDecorator>
|
||||
<app.icon />
|
||||
</ListItemDecorator>
|
||||
{app.name + (app.isDev ? ' [DEV]' : '')}
|
||||
</MenuItem>,
|
||||
)}
|
||||
</Menu>
|
||||
</Dropdown>,
|
||||
);
|
||||
}
|
||||
components.push(
|
||||
<Dropdown key='nav-quick-menu'>
|
||||
|
||||
<Tooltip disableInteractive enterDelay={600} title='Apps & Tools'>
|
||||
<MenuButton slots={{ root: DesktopNavIcon }} slotProps={{ root: { className: navItemClasses.typeApp } }}>
|
||||
<PhSquaresFour />
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
|
||||
<Menu
|
||||
variant="outlined"
|
||||
placement="right-start"
|
||||
popperOptions={{ modifiers: [{ name: 'offset', options: { offset: [0, -2] } }] }}
|
||||
sx={{ minWidth: 260 }}
|
||||
>
|
||||
|
||||
{/* APPS Section */}
|
||||
{overflowApps.length > 0 && (
|
||||
<>
|
||||
<ListItem>
|
||||
<Typography level="body-xs" sx={{ textTransform: 'uppercase', fontWeight: 600 }}>
|
||||
Apps
|
||||
</Typography>
|
||||
</ListItem>
|
||||
{overflowApps.map((app, appIdx) =>
|
||||
<MenuItem key={'nav-app-extra-' + appIdx} onClick={() => Router.push(app.landingRoute || app.route)}>
|
||||
<ListItemDecorator>
|
||||
<app.icon />
|
||||
</ListItemDecorator>
|
||||
{app.name + (app.isDev ? ' [DEV]' : '')}
|
||||
</MenuItem>,
|
||||
)}
|
||||
<ListDivider />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* QUICK TOOLS Section */}
|
||||
<ListItem>
|
||||
<Typography level="body-xs" sx={{ textTransform: 'uppercase', fontWeight: 600 }}>
|
||||
Quick Tools
|
||||
</Typography>
|
||||
</ListItem>
|
||||
<MenuItem disabled={!scratchClipSupported()} onClick={toggleScratchClipVisibility}>
|
||||
<ListItemDecorator><HistoryIcon /></ListItemDecorator>
|
||||
{isScratchClipVisible ? 'Hide ' : ''}Clipboard {scratchClipSupported() ? 'History' : '(not supported)'}
|
||||
</MenuItem>
|
||||
<ListDivider />
|
||||
|
||||
{/* SUPPORT Section */}
|
||||
<ListItem>
|
||||
<Typography level="body-xs" sx={{ textTransform: 'uppercase', fontWeight: 600 }}>
|
||||
Support
|
||||
</Typography>
|
||||
</ListItem>
|
||||
<ListItemButton component="a" href={BaseProduct.SupportForm()} target="_blank">
|
||||
<ListItemDecorator><LightbulbOutlinedIcon /></ListItemDecorator>
|
||||
I Have Feedback
|
||||
</ListItemButton>
|
||||
{!!releaseNotesUrl && (
|
||||
<MenuItem onClick={handleShowReleaseNotes}>
|
||||
<ListItemDecorator>
|
||||
<FeatureBadge featureKey='nav-quick-menu' active={releaseNotesShown}>
|
||||
<CodeIcon />
|
||||
</FeatureBadge>
|
||||
</ListItemDecorator>
|
||||
Release Notes
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={handleShowTechnologies}>
|
||||
{/*<ListItemDecorator><BuildCircleOutlinedIcon /></ListItemDecorator>*/}
|
||||
<ListItemDecorator />
|
||||
Build Info
|
||||
</MenuItem>
|
||||
|
||||
</Menu>
|
||||
</Dropdown>);
|
||||
|
||||
return components;
|
||||
}, [isDrawerOpen, props.currentApp]);
|
||||
}, [toggleScratchClipVisibility, isScratchClipVisible, releaseNotesUrl, handleShowReleaseNotes, releaseNotesShown, handleShowTechnologies, props.currentApp, isDrawerOpen]);
|
||||
|
||||
|
||||
// External link items
|
||||
|
||||
Reference in New Issue
Block a user