mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Optima: rationalize Portal names
This commit is contained in:
@@ -8,7 +8,7 @@ import { BeamView } from '~/modules/beam/BeamView';
|
||||
import { createBeamVanillaStore } from '~/modules/beam/store-beam-vanilla';
|
||||
import { useModelsStore } from '~/modules/llms/store-llms';
|
||||
|
||||
import { OptimaPortalIn } from '~/common/layout/optima/portals/OptimaPortalIn';
|
||||
import { OptimaToolbarIn } from '~/common/layout/optima/portals/OptimaPortalsIn';
|
||||
import { createDConversation, DConversation } from '~/common/stores/chat/chat.conversation';
|
||||
import { createDMessageTextContent, DMessage } from '~/common/stores/chat/chat.message';
|
||||
import { useIsMobile } from '~/common/components/useMatchMedia';
|
||||
@@ -76,7 +76,7 @@ export function AppBeam() {
|
||||
|
||||
|
||||
return <>
|
||||
<OptimaPortalIn targetPortalId='optima-portal-toolbar'>{toolbarItems}</OptimaPortalIn>
|
||||
<OptimaToolbarIn>{toolbarItems}</OptimaToolbarIn>
|
||||
|
||||
<Box sx={{ flexGrow: 1, overflowY: 'auto', position: 'relative' }}>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useElevenLabsVoiceDropdown } from '~/modules/elevenlabs/useElevenLabsVo
|
||||
|
||||
import { AudioPlayer } from '~/common/util/audio/AudioPlayer';
|
||||
import { Link } from '~/common/components/Link';
|
||||
import { OptimaPortalIn } from '~/common/layout/optima/portals/OptimaPortalIn';
|
||||
import { OptimaToolbarIn } from '~/common/layout/optima/portals/OptimaPortalsIn';
|
||||
import { SpeechResult, useSpeechRecognition } from '~/common/components/useSpeechRecognition';
|
||||
import { conversationTitle } from '~/common/stores/chat/chat.conversation';
|
||||
import { createDMessageTextContent, DMessage, messageFragmentsReduceText, messageSingleTextOrThrow } from '~/common/stores/chat/chat.message';
|
||||
@@ -307,7 +307,7 @@ export function Telephone(props: {
|
||||
|
||||
|
||||
return <>
|
||||
<OptimaPortalIn targetPortalId='optima-portal-toolbar'>{chatLLMDropdown}</OptimaPortalIn>
|
||||
<OptimaToolbarIn>{chatLLMDropdown}</OptimaToolbarIn>
|
||||
|
||||
<Typography
|
||||
level='h1'
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { DConversation, DConversationId } from '~/common/stores/chat/chat.c
|
||||
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
import { ConversationsManager } from '~/common/chats/ConversationsManager';
|
||||
import { DMessageAttachmentFragment, DMessageContentFragment, duplicateDMessageFragments } from '~/common/stores/chat/chat.fragments';
|
||||
import { OptimaPortalIn } from '~/common/layout/optima/portals/OptimaPortalIn';
|
||||
import { OptimaDrawerIn, OptimaToolbarIn } from '~/common/layout/optima/portals/OptimaPortalsIn';
|
||||
import { PanelResizeInset } from '~/common/components/panes/GoodPanelResizeHandler';
|
||||
import { PreferencesTab, useOptimaLayout, usePluggableOptimaLayout } from '~/common/layout/optima/useOptimaLayout';
|
||||
import { ScrollToBottom } from '~/common/scroll-to-bottom/ScrollToBottom';
|
||||
@@ -460,9 +460,8 @@ export function AppChat() {
|
||||
usePluggableOptimaLayout(focusedMenuItems, 'AppChat');
|
||||
|
||||
return <>
|
||||
|
||||
<OptimaPortalIn targetPortalId='optima-portal-drawer'>{drawerContent}</OptimaPortalIn>
|
||||
<OptimaPortalIn targetPortalId='optima-portal-toolbar'>{focusedBarContent}</OptimaPortalIn>
|
||||
<OptimaDrawerIn>{drawerContent}</OptimaDrawerIn>
|
||||
<OptimaToolbarIn>{focusedBarContent}</OptimaToolbarIn>
|
||||
|
||||
<PanelGroup
|
||||
direction={isMobile ? 'vertical' : 'horizontal'}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
|
||||
import { useCapabilityTextToImage } from '~/modules/t2i/t2i.client';
|
||||
|
||||
import { OptimaPortalIn } from '~/common/layout/optima/portals/OptimaPortalIn';
|
||||
import { OptimaToolbarIn } from '~/common/layout/optima/portals/OptimaPortalsIn';
|
||||
import { useIsMobile } from '~/common/components/useMatchMedia';
|
||||
import { useProcessingQueue } from '~/common/logic/ProcessingQueue';
|
||||
|
||||
@@ -44,7 +44,7 @@ export function AppDraw() {
|
||||
const { drawSection, drawSectionDropdown } = useDrawSectionDropdown(queueState.items.length, queueCancelAll);
|
||||
|
||||
return <>
|
||||
<OptimaPortalIn targetPortalId='optima-portal-toolbar'>{drawSectionDropdown}</OptimaPortalIn>
|
||||
<OptimaToolbarIn>{drawSectionDropdown}</OptimaToolbarIn>
|
||||
|
||||
{drawSection === 'create' ? (
|
||||
<DrawCreate
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
import { GoodModal } from '~/common/components/GoodModal';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { LogoProgress } from '~/common/components/LogoProgress';
|
||||
import { OptimaPortalIn } from '~/common/layout/optima/portals/OptimaPortalIn';
|
||||
import { OptimaDrawerIn } from '~/common/layout/optima/portals/OptimaPortalsIn';
|
||||
import { addSnackbar } from '~/common/components/useSnackbarsStore';
|
||||
import { apiAsyncNode } from '~/common/util/trpc.client';
|
||||
import { capitalizeFirstLetter } from '~/common/util/textUtils';
|
||||
@@ -207,7 +207,7 @@ export function AppLinkChat(props: { chatLinkId: string | null }) {
|
||||
<title>{capitalizeFirstLetter(pageTitle)} · {Brand.Title.Base} 🚀</title>
|
||||
</Head>
|
||||
|
||||
<OptimaPortalIn targetPortalId='optima-portal-drawer'>{drawerContent}</OptimaPortalIn>
|
||||
<OptimaDrawerIn>{drawerContent}</OptimaDrawerIn>
|
||||
|
||||
{isListPage
|
||||
? <ListPlaceholder hasLinks={hasLinks} />
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
|
||||
import { Box, Container, ListDivider, Typography } from '@mui/joy';
|
||||
|
||||
import { OptimaPortalIn } from '~/common/layout/optima/portals/OptimaPortalIn';
|
||||
import { OptimaDrawerIn } from '~/common/layout/optima/portals/OptimaPortalsIn';
|
||||
|
||||
import { Creator } from './creator/Creator';
|
||||
import { CreatorDrawer } from './creator/CreatorDrawer';
|
||||
@@ -27,7 +27,7 @@ export function AppPersonas() {
|
||||
}, [selectedSimplePersonaId]);
|
||||
|
||||
return <>
|
||||
<OptimaPortalIn targetPortalId='optima-portal-drawer'>{drawerContent}</OptimaPortalIn>
|
||||
<OptimaDrawerIn>{drawerContent}</OptimaDrawerIn>
|
||||
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { checkVisibleNav, NavItemApp } from '~/common/app.nav';
|
||||
import { themeZIndexDesktopDrawer } from '~/common/app.theme';
|
||||
|
||||
import { useOptimaDrawers } from './useOptimaDrawers';
|
||||
import { useOptimaPortalOut } from './portals/useOptimaPortalOut';
|
||||
import { useOptimaPortalOutRef } from './portals/useOptimaPortalOutRef';
|
||||
|
||||
|
||||
// set to 0 to always keep the drawer mounted (smoother on/off)
|
||||
@@ -51,7 +51,7 @@ const DesktopDrawerTranslatingSheet = styled(Sheet)(({ theme }) => ({
|
||||
export function DesktopDrawer(props: { component: React.ElementType, currentApp?: NavItemApp }) {
|
||||
|
||||
// state
|
||||
const drawerPortalRef = useOptimaPortalOut('optima-portal-drawer', 'DesktopDrawer');
|
||||
const drawerPortalRef = useOptimaPortalOutRef('optima-portal-drawer', 'DesktopDrawer');
|
||||
|
||||
// external state
|
||||
const { isDrawerOpen, closeDrawer, openDrawer } = useOptimaDrawers();
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Box, Drawer } from '@mui/joy';
|
||||
import type { NavItemApp } from '~/common/app.nav';
|
||||
|
||||
import { useOptimaDrawers } from './useOptimaDrawers';
|
||||
import { useOptimaPortalOut } from './portals/useOptimaPortalOut';
|
||||
import { useOptimaPortalOutRef } from './portals/useOptimaPortalOutRef';
|
||||
|
||||
|
||||
function DrawerContentPortal() {
|
||||
const drawerPortalRef = useOptimaPortalOut('optima-portal-drawer', 'MobileDrawer');
|
||||
const drawerPortalRef = useOptimaPortalOutRef('optima-portal-drawer', 'MobileDrawer');
|
||||
return (
|
||||
<Box
|
||||
ref={drawerPortalRef}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { InvertedBar, InvertedBarCornerItem } from './components/InvertedBar';
|
||||
import { MobileNavListItem } from './MobileNavListItem';
|
||||
import { useOptimaDrawers } from './useOptimaDrawers';
|
||||
import { useOptimaLayout } from './useOptimaLayout';
|
||||
import { useOptimaPortalOut } from './portals/useOptimaPortalOut';
|
||||
import { useOptimaPortalOutRef } from './portals/useOptimaPortalOutRef';
|
||||
|
||||
|
||||
const PageBarItemsFallback = (props: { currentApp?: NavItemApp }) =>
|
||||
@@ -51,7 +51,7 @@ const centerItemsContainerSx: SxProps = {
|
||||
};
|
||||
|
||||
function CenterItemsPortal() {
|
||||
const portalToolbarRef = useOptimaPortalOut('optima-portal-toolbar', 'PageBar.CenterItemsContainer');
|
||||
const portalToolbarRef = useOptimaPortalOutRef('optima-portal-toolbar', 'PageBar.CenterItemsContainer');
|
||||
return (
|
||||
<Box ref={portalToolbarRef} sx={centerItemsContainerSx}>
|
||||
{/* TODO */}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { OptimaPortalId, usePortalElement } from './store-optima-portals';
|
||||
|
||||
|
||||
export function OptimaPortalIn(props: {
|
||||
targetPortalId: OptimaPortalId;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
|
||||
// react to the portal being made available
|
||||
const portalElement = usePortalElement(props.targetPortalId);
|
||||
if (!portalElement)
|
||||
return null;
|
||||
|
||||
return createPortal(props.children, portalElement);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { OptimaPortalId, useOptimaPortalsStore } from './store-optima-portals';
|
||||
|
||||
|
||||
export function OptimaDrawerIn(props: { children: React.ReactNode }) {
|
||||
const portalElement = _useOptimaPortalTargetElement('optima-portal-drawer');
|
||||
return portalElement ? createPortal(props.children, portalElement) : null;
|
||||
}
|
||||
|
||||
export function OptimaToolbarIn(props: { children: React.ReactNode }) {
|
||||
const portalElement = _useOptimaPortalTargetElement('optima-portal-toolbar');
|
||||
return portalElement ? createPortal(props.children, portalElement) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hook to get the target element for a portal.
|
||||
*/
|
||||
function _useOptimaPortalTargetElement(targetPortalId: OptimaPortalId) {
|
||||
// get the output element
|
||||
const targetPortalEl = useOptimaPortalsStore(state => state.portals[targetPortalId]?.element ?? null);
|
||||
|
||||
// increment/decrement input count
|
||||
React.useEffect(() => {
|
||||
const { incrementInputs, decrementInputs } = useOptimaPortalsStore.getState();
|
||||
incrementInputs(targetPortalId);
|
||||
return () => decrementInputs(targetPortalId);
|
||||
}, [targetPortalId]);
|
||||
|
||||
// return the output element
|
||||
return targetPortalEl;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { create } from 'zustand';
|
||||
|
||||
|
||||
@@ -7,51 +8,71 @@ const DEBUG_OPTIMA_PORTALS = false;
|
||||
|
||||
export type OptimaPortalId =
|
||||
| 'optima-portal-drawer'
|
||||
// | 'optima-portal-properties'
|
||||
| 'optima-portal-toolbar'
|
||||
;
|
||||
| 'optima-portal-toolbar';
|
||||
|
||||
interface PortalState {
|
||||
portalElements: Map<OptimaPortalId, HTMLElement>;
|
||||
|
||||
interface OptimaPortalState {
|
||||
portals: Record<OptimaPortalId, {
|
||||
element: HTMLElement | null;
|
||||
inputs: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface PortalActions {
|
||||
addPortal: (id: OptimaPortalId, element: HTMLElement) => void;
|
||||
removePortal: (id: OptimaPortalId) => void;
|
||||
interface OptimaPortalActions {
|
||||
|
||||
// store output elements
|
||||
setElement: (id: OptimaPortalId, element: HTMLElement | null) => void;
|
||||
|
||||
// reference counting
|
||||
incrementInputs: (id: OptimaPortalId) => void;
|
||||
decrementInputs: (id: OptimaPortalId) => void;
|
||||
|
||||
}
|
||||
|
||||
type PortalStore = PortalState & PortalActions;
|
||||
|
||||
|
||||
const useOptimaPortalsStore = create<PortalStore>((set, get) => ({
|
||||
export const useOptimaPortalsStore = create<OptimaPortalState & OptimaPortalActions>((_set) => ({
|
||||
|
||||
// init state
|
||||
portalElements: new Map(),
|
||||
portals: {
|
||||
'optima-portal-drawer': { element: null, inputs: 0 },
|
||||
'optima-portal-toolbar': { element: null, inputs: 0 },
|
||||
},
|
||||
|
||||
// actions
|
||||
addPortal: (id, element) => set((state) => {
|
||||
const newPortals = new Map(state.portalElements);
|
||||
newPortals.set(id, element);
|
||||
|
||||
setElement: (id, element) => _set((state) => {
|
||||
if (DEBUG_OPTIMA_PORTALS)
|
||||
console.log(' > store.addPortal', id, !!element);
|
||||
return { portalElements: newPortals };
|
||||
console.log(`${element ? 'Set' : 'Remove'} portal element`, id);
|
||||
return {
|
||||
portals: {
|
||||
...state.portals,
|
||||
[id]: { ...state.portals[id], element: element },
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
removePortal: (id) => set((state) => {
|
||||
const newPortals = new Map(state.portalElements);
|
||||
newPortals.delete(id);
|
||||
incrementInputs: (id) => _set((state) => {
|
||||
const newInputs = state.portals[id].inputs + 1;
|
||||
if (DEBUG_OPTIMA_PORTALS)
|
||||
console.log(' < store.removePortal', id);
|
||||
return { portalElements: newPortals };
|
||||
console.log(' + store.incrementInputs', id, newInputs);
|
||||
return {
|
||||
portals: {
|
||||
...state.portals,
|
||||
[id]: { ...state.portals[id], inputs: newInputs },
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
decrementInputs: (id) => _set((state) => {
|
||||
const newInputs = Math.max(0, state.portals[id].inputs - 1);
|
||||
if (DEBUG_OPTIMA_PORTALS)
|
||||
console.log(' - store.decrementInputs', id, newInputs);
|
||||
return {
|
||||
portals: {
|
||||
...state.portals,
|
||||
[id]: { ...state.portals[id], inputs: newInputs },
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
}));
|
||||
|
||||
|
||||
export function optimaPortalsActions(): PortalActions {
|
||||
return useOptimaPortalsStore.getState();
|
||||
}
|
||||
|
||||
export function usePortalElement(id: OptimaPortalId) {
|
||||
return useOptimaPortalsStore((state) => state.portalElements.get(id) || null);
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { OptimaPortalId, optimaPortalsActions } from './store-optima-portals';
|
||||
|
||||
|
||||
/**
|
||||
* Note: this hook assumes that the ref is created by when useLayoutEffect is called,
|
||||
* and will warn otherwise.
|
||||
*/
|
||||
export function useOptimaPortalOut(portalTargetId: OptimaPortalId, debugCallerName: string) {
|
||||
|
||||
// state
|
||||
const ref = React.useRef<HTMLElement>(null);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const { addPortal, removePortal } = optimaPortalsActions();
|
||||
if (!ref.current) {
|
||||
console.warn(`useOptimaPortalOut: ref.current is null for type ${portalTargetId} (called by ${debugCallerName})`);
|
||||
} else {
|
||||
addPortal(portalTargetId, ref.current);
|
||||
}
|
||||
return () => removePortal(portalTargetId);
|
||||
}, [debugCallerName, portalTargetId]);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
/*
|
||||
// This version will add the portal only when really getting the ref
|
||||
export function useOptimaPortalOut(portalTargetId: OptimaPortalId, debugCallerName: string) {
|
||||
|
||||
const setRef = React.useCallback((node: HTMLElement | null) => {
|
||||
const { addPortal, removePortal } = optimaPortalsActions();
|
||||
console.log('useOptimaPortalOut.setRef', portalTargetId, node);
|
||||
if (node) {
|
||||
console.log(' - useOptimaPortalOut call AddPortal', portalTargetId);
|
||||
addPortal(portalTargetId, node);
|
||||
} else {
|
||||
removePortal(portalTargetId);
|
||||
}
|
||||
}, [portalTargetId]);
|
||||
|
||||
return setRef;
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { OptimaPortalId, useOptimaPortalsStore } from './store-optima-portals';
|
||||
|
||||
|
||||
/**
|
||||
* Defines a React output portal for a given target id. Will return a ref that
|
||||
* must be attached to the target element.
|
||||
*
|
||||
* Note: this hook assumes that the ref is created by when useLayoutEffect is called,
|
||||
* and will warn otherwise.
|
||||
*
|
||||
* If the ref is created after the layout effect, the portal will not be added. In
|
||||
* that case, consider returning a Callback instead, with:
|
||||
* `const setRef = React.useCallback((node: HTMLElement | null) => { ... }, [portalTargetId]);`
|
||||
*/
|
||||
export function useOptimaPortalOutRef(portalTargetId: OptimaPortalId, debugCallerName: string) {
|
||||
|
||||
// state
|
||||
const ref = React.useRef<HTMLElement>(null);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const { setElement } = useOptimaPortalsStore.getState();
|
||||
if (!ref.current) {
|
||||
console.warn(`useOptimaPortalOut: ref.current is null for type ${portalTargetId} (called by ${debugCallerName})`);
|
||||
} else {
|
||||
setElement(portalTargetId, ref.current);
|
||||
}
|
||||
return () => setElement(portalTargetId, null);
|
||||
}, [debugCallerName, portalTargetId]);
|
||||
|
||||
return ref;
|
||||
}
|
||||
Reference in New Issue
Block a user