Optima: rationalize Portal names

This commit is contained in:
Enrico Ros
2024-07-30 00:15:09 -07:00
parent 95788f5dcd
commit f0a0dfc72a
14 changed files with 138 additions and 114 deletions
+2 -2
View File
@@ -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' }}>
+2 -2
View File
@@ -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'
+3 -4
View File
@@ -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 -2
View File
@@ -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
+2 -2
View File
@@ -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 -2
View File
@@ -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,
+2 -2
View File
@@ -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();
+2 -2
View File
@@ -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}
+2 -2
View File
@@ -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;
}