Use react-resizable-panels instead of the flexbox

Also Fix #255 due to the large layout restructuring.
This commit is contained in:
Enrico Ros
2023-12-27 05:21:07 -08:00
parent cdc2de5018
commit 8bf90e3622
5 changed files with 120 additions and 51 deletions
+10
View File
@@ -38,6 +38,7 @@
"react-dom": "^18.2.0",
"react-katex": "^3.0.1",
"react-markdown": "^9.0.1",
"react-resizable-panels": "^1.0.5",
"react-timeago": "^7.2.0",
"remark-gfm": "^4.0.0",
"superjson": "^2.2.1",
@@ -5874,6 +5875,15 @@
"react": ">=18"
}
},
"node_modules/react-resizable-panels": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-1.0.5.tgz",
"integrity": "sha512-OP0whNQCko+f4BgoptGaeIc7StBRyeMeJ+8r/7rXACBDf9W5EcMWuM32hfqPDMenS2HFy/eZVi/r8XqK+ZIEag==",
"peerDependencies": {
"react": "^16.14.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-ssr-prepass": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/react-ssr-prepass/-/react-ssr-prepass-1.5.0.tgz",
+1
View File
@@ -42,6 +42,7 @@
"react-dom": "^18.2.0",
"react-katex": "^3.0.1",
"react-markdown": "^9.0.1",
"react-resizable-panels": "^1.0.5",
"react-timeago": "^7.2.0",
"remark-gfm": "^4.0.0",
"superjson": "^2.2.1",
+48 -32
View File
@@ -1,7 +1,8 @@
import * as React from 'react';
import { Box } from '@mui/joy';
import ForkRightIcon from '@mui/icons-material/ForkRight';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { Box, useTheme } from '@mui/joy';
import { CmdRunBrowse } from '~/modules/browse/browse.client';
import { CmdRunReact } from '~/modules/aifn/react/react';
@@ -63,6 +64,8 @@ export function AppChat() {
const composerTextAreaRef = React.useRef<HTMLTextAreaElement>(null);
// external state
const theme = useTheme();
const { openLlmOptions } = useOptimaLayout();
const { chatLLM } = useChatLLM();
@@ -74,6 +77,7 @@ export function AppChat() {
openConversationInFocusedPane,
openConversationInSplitPane,
setFocusedPaneIndex,
removePaneIndex,
} = usePanesManager();
const {
@@ -97,10 +101,6 @@ export function AppChat() {
const chatPaneIDs = chatPanes.length > 0 ? chatPanes.map(pane => pane.conversationId) : [null];
const setActivePaneIndex = React.useCallback((idx: number) => {
setFocusedPaneIndex(idx);
}, [setFocusedPaneIndex]);
const setFocusedConversationId = React.useCallback((conversationId: DConversationId | null) => {
conversationId && openConversationInFocusedPane(conversationId);
}, [openConversationInFocusedPane]);
@@ -382,18 +382,28 @@ export function AppChat() {
return <>
<Box sx={{
flexGrow: 1,
display: 'flex', flexDirection: { xs: 'column', md: 'row' },
overflow: 'clip',
}}>
<PanelGroup direction='horizontal'>
{chatPaneIDs.map((_conversationId, idx) => (
<Box key={'chat-pane-' + idx} onClick={() => setActivePaneIndex(idx)} sx={{
flexGrow: 1, flexBasis: 1,
display: 'flex', flexDirection: 'column',
overflow: 'clip',
}}>
{chatPaneIDs.map((_conversationId, idx, panels) => <React.Fragment key={'chat-pane-' + _conversationId + '-' + panels.length}>
<Panel
id={'chat-pane-' + _conversationId}
order={idx}
collapsible
defaultSize={panels.length > 0 ? Math.round(100 / panels.length) : undefined}
minSize={20}
onClick={() => setFocusedPaneIndex(idx)}
onCollapse={() => setTimeout(() => removePaneIndex(idx), 50)}
style={{
// allows the content to be scrolled (all browsers)
overflowY: 'auto',
// border only for active pane (if two or more panes)
...(chatPaneIDs.length < 2 ? {}
: (_conversationId === focusedConversationId)
? { border: `2px solid ${theme.palette.primary.solidBg}` }
: { border: `2px solid ${theme.palette.background.level1}` }),
}}
>
<ChatMessageList
conversationId={_conversationId}
@@ -407,34 +417,40 @@ export function AppChat() {
onTextImagine={handleTextImagine}
onTextSpeak={handleTextSpeak}
sx={{
flexGrow: 1,
backgroundColor: themeBgApp,
overflowY: 'auto',
minHeight: 96,
// outline the current focused pane
...(chatPaneIDs.length < 2 ? {}
: (_conversationId === focusedConversationId)
? {
border: '2px solid',
borderColor: 'primary.solidBg',
} : {
padding: '2px',
}),
minHeight: '100%', // ensures filling of the blank space on newer chats
}}
/>
<Ephemerals
conversationId={_conversationId}
sx={{
// TODO: Fixme post panels?
// flexGrow: 0.1,
flexShrink: 0.5,
overflowY: 'auto',
minHeight: 64,
}} />
</Box>
))}
</Box>
</Panel>
{/* Panel Separators & Resizers */}
{idx < panels.length - 1 && (
<PanelResizeHandle>
<Box sx={{
backgroundColor: themeBgApp,
height: '100%',
width: '4px',
'&:hover': {
backgroundColor: 'primary.softActiveBg',
},
}} />
</PanelResizeHandle>
)}
</React.Fragment>)}
</PanelGroup>
<Composer
chatLLM={chatLLM}
+15 -16
View File
@@ -34,8 +34,7 @@ interface AppChatPanesStore {
openConversationInSplitPane: (conversationId: DConversationId) => void;
navigateHistoryInFocusedPane: (direction: 'back' | 'forward') => boolean;
setFocusedPaneIndex: (paneIndex: number) => void;
splitChatPane: (numberOfPanes: number) => void;
unsplitChatPane: (paneIndexToKeep: number) => void;
removePaneIndex: (paneIndex: number) => void;
onConversationsChanged: (conversationIds: DConversationId[]) => void;
}
@@ -169,22 +168,20 @@ const useAppChatPanesStore = create<AppChatPanesStore>()(persist(
};
}),
splitChatPane: (numberOfPanes: number) => {
const { chatPanes, chatPaneFocusIndex } = _get();
const focusedPane = (chatPaneFocusIndex !== null ? chatPanes[chatPaneFocusIndex] : null) ?? createPane();
removePaneIndex: (paneIndex: number) =>
_set(state => {
const { chatPanes, chatPaneFocusIndex } = state;
if (paneIndex < 0 || paneIndex >= chatPanes.length)
return state;
_set({
chatPanes: Array.from({ length: numberOfPanes }, () => ({ ...focusedPane })),
chatPaneFocusIndex: 0,
});
},
unsplitChatPane: (paneIndexToKeep: number) =>
_set(state => ({
chatPanes: [state.chatPanes[paneIndexToKeep] || createPane()],
chatPaneFocusIndex: 0,
})),
const newPanes = chatPanes.toSpliced(paneIndex, 1);
// when a pane is removed, focus the pane 0, or null if no panes remain
return {
chatPanes: newPanes,
chatPaneFocusIndex: newPanes.length ? 0 : null,
};
}),
/**
* This function is vital, as is invoked when the conversationId[] changes in the global chats store.
@@ -258,6 +255,7 @@ export function usePanesManager() {
onConversationsChanged,
openConversationInFocusedPane,
openConversationInSplitPane,
removePaneIndex,
setFocusedPaneIndex,
} = state;
const focusedConversationId = chatPaneFocusIndex !== null ? chatPanes[chatPaneFocusIndex]?.conversationId ?? null : null;
@@ -268,6 +266,7 @@ export function usePanesManager() {
onConversationsChanged,
openConversationInFocusedPane,
openConversationInSplitPane,
removePaneIndex,
setFocusedPaneIndex,
};
}, shallow);
+46 -3
View File
@@ -7,6 +7,7 @@ import { SettingsModal } from '../../../apps/settings-modal/SettingsModal';
import { ShortcutsModal } from '../../../apps/settings-modal/ShortcutsModal';
import { isPwa } from '~/common/util/pwaUtils';
import { useIsMobile } from '~/common/components/useMatchMedia';
import { useUIPreferencesStore } from '~/common/state/store-ui';
import { AppBar } from './AppBar';
@@ -14,6 +15,31 @@ import { NextRouterProgress } from './NextLoadProgress';
import { useOptimaLayout } from './useOptimaLayout';
/*function ResponsiveNavigation() {
return <>
<Drawer
open={false}
variant='solid'
anchor='left'
onClose={() => {
}}
sx={{
'& .MuiDrawer-paper': {
width: 256,
boxSizing: 'border-box',
},
}}
>
<Box sx={{ width: 256, height: '100%' }}>
<Box sx={{ p: 2, display: 'flex', flexDirection: 'column', height: '100%' }}>
<Box sx={{ flexGrow: 1 }} />
</Box>
</Box>
</Drawer>
</>;
}*/
/**
* Core layout of big-AGI, used by all the Primary applications therein.
*
@@ -28,22 +54,34 @@ import { useOptimaLayout } from './useOptimaLayout';
export function OptimaLayout(props: { suspendAutoModelsSetup?: boolean, children: React.ReactNode, }) {
// external state
const isMobile = useIsMobile();
let centerMode = useUIPreferencesStore(state => (isPwa() || isMobile) ? 'full' : state.centerMode);
const {
closePreferences, closeShortcuts,
openShortcuts,
showPreferencesTab, showShortcuts,
} = useOptimaLayout();
const centerMode = useUIPreferencesStore(state => isPwa() ? 'full' : state.centerMode);
return <>
{/*<Box sx={{*/}
{/* display: 'flex', flexDirection: 'row',*/}
{/* maxWidth: '100%', flexWrap: 'nowrap',*/}
{/* // overflowX: 'hidden',*/}
{/* background: 'lime',*/}
{/*}}>*/}
{/*<Box sx={{ background: 'rgba(100 0 0 / 0.5)' }}>a</Box>*/}
{/*<ResponsiveNavigation />*/}
<Container
disableGutters
maxWidth={centerMode === 'full' ? false : centerMode === 'narrow' ? 'md' : 'xl'}
sx={{
// minWidth: 0,
boxShadow: {
xs: 'none',
md: centerMode === 'narrow' ? 'md' : 'none',
@@ -60,12 +98,17 @@ export function OptimaLayout(props: { suspendAutoModelsSetup?: boolean, children
zIndex: 20,
}} />
{/* Children must make the assumption they're in a flex-col layout */}
{props.children}
</Box>
</Container>
{/*<Box sx={{ background: 'rgba(100 0 0 / 0.5)' }}>bb</Box>*/}
{/*</Box>*/}
{/* Overlay Settings */}
<SettingsModal open={!!showPreferencesTab} tabIndex={showPreferencesTab} onClose={closePreferences} onOpenShortcuts={openShortcuts} />