mirror of https://github.com/buster-so/buster.git
add additional context provider for chats
This commit is contained in:
parent
be050b3521
commit
90d30c93af
|
@ -0,0 +1,5 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
import { appContentHeaderHeight } from '@/components/layout/AppContentHeader';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React from 'react';
|
||||
import { useChatSplitterContextSelector } from '../../ChatLayoutContext';
|
||||
import { ChatHeaderOptions } from './ChatHeaderOptions';
|
||||
import { ChatHeaderTitle } from './ChatHeaderTitle';
|
||||
import { useChatContextSelector } from '../../ChatContext';
|
||||
|
||||
export const ChatHeader: React.FC<{
|
||||
showScrollOverflow: boolean;
|
||||
}> = React.memo(({ showScrollOverflow }) => {
|
||||
const { cx, styles } = useStyles();
|
||||
const hasFile = useChatSplitterContextSelector((state) => state.hasFile);
|
||||
const hasFile = useChatContextSelector((state) => state.hasFile);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Dropdown, MenuProps } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useChatSplitterContextSelector } from '../../../ChatLayoutContext';
|
||||
import { HeaderOptionsRecord } from './config';
|
||||
import { useChatContextSelector } from '../../../ChatContext';
|
||||
|
||||
export const ChatContainerHeaderDropdown: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = React.memo(({ children }) => {
|
||||
const selectedFileType = useChatSplitterContextSelector((state) => state.selectedFileType);
|
||||
const selectedFileType = useChatContextSelector((state) => state.selectedFileType);
|
||||
|
||||
const menuItem: MenuProps['items'] = useMemo(() => {
|
||||
if (!selectedFileType || !(selectedFileType in HeaderOptionsRecord))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import type { FileType } from '@/api/buster_socket/chats';
|
||||
import { AppMaterialIcons } from '@/components/icons';
|
||||
import type { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
|
||||
import { MenuProps } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
|
||||
export const HeaderOptionsRecord: Record<AppChatMessageFileType, () => MenuProps['items']> = {
|
||||
export const HeaderOptionsRecord: Record<FileType, () => MenuProps['items']> = {
|
||||
dataset: () => [
|
||||
{
|
||||
label: 'Delete',
|
||||
|
@ -30,5 +30,7 @@ export const HeaderOptionsRecord: Record<AppChatMessageFileType, () => MenuProps
|
|||
key: 'delete',
|
||||
icon: <AppMaterialIcons icon="delete" />
|
||||
}
|
||||
]
|
||||
],
|
||||
term: () => [],
|
||||
value: () => []
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Text } from '@/components/text';
|
||||
import React from 'react';
|
||||
import { useChatSplitterContextSelector } from '../../ChatLayoutContext';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useChatContextSelector } from '../../ChatContext';
|
||||
|
||||
const animation = {
|
||||
initial: { opacity: 0 },
|
||||
|
@ -11,12 +11,12 @@ const animation = {
|
|||
};
|
||||
|
||||
export const ChatHeaderTitle: React.FC<{}> = React.memo(() => {
|
||||
const selectedFileTitle = useChatSplitterContextSelector((state) => state.selectedFileTitle);
|
||||
const chatTitle = useChatContextSelector((state) => state.chatTitle);
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div {...animation} key={selectedFileTitle} className="flex items-center">
|
||||
<Text>{selectedFileTitle}</Text>
|
||||
<motion.div {...animation} key={chatTitle} className="flex items-center">
|
||||
<Text>{chatTitle}</Text>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import React, { PropsWithChildren } from 'react';
|
||||
import {
|
||||
ContextSelector,
|
||||
createContext,
|
||||
useContextSelector
|
||||
} from '@fluentui/react-context-selector';
|
||||
import { useBusterChatIndividual } from '@/context/Chats';
|
||||
import type { SelectedFile } from '../interfaces';
|
||||
|
||||
export const useChatContext = ({
|
||||
chatId,
|
||||
defaultSelectedFile
|
||||
}: {
|
||||
chatId?: string;
|
||||
defaultSelectedFile?: SelectedFile;
|
||||
}) => {
|
||||
const selectedFileId = defaultSelectedFile?.id;
|
||||
const selectedFileType = defaultSelectedFile?.type;
|
||||
|
||||
//CHAT
|
||||
const { chat } = useBusterChatIndividual({
|
||||
chatId
|
||||
});
|
||||
const chatTitle = chat?.title;
|
||||
|
||||
//FILE
|
||||
const hasFile = !!defaultSelectedFile?.id;
|
||||
|
||||
return {
|
||||
hasFile,
|
||||
selectedFileId,
|
||||
chatTitle,
|
||||
selectedFileType
|
||||
};
|
||||
};
|
||||
|
||||
export const ChatContext = createContext<ReturnType<typeof useChatContext>>(
|
||||
{} as ReturnType<typeof useChatContext>
|
||||
);
|
||||
|
||||
export const ChatContextProvider = React.memo(
|
||||
({ value, children }: PropsWithChildren<{ value: ReturnType<typeof useChatContext> }>) => {
|
||||
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||
}
|
||||
);
|
||||
|
||||
ChatContextProvider.displayName = 'ChatContextProvider';
|
||||
|
||||
export const useChatContextSelector = <T,>(
|
||||
selector: ContextSelector<ReturnType<typeof useChatContext>, T>
|
||||
) => useContextSelector(ChatContext, selector);
|
|
@ -0,0 +1 @@
|
|||
export * from './ChatContext';
|
|
@ -7,7 +7,8 @@ import { FileContainer } from './FileContainer';
|
|||
import { ChatSplitterContextProvider } from './ChatLayoutContext';
|
||||
import { useChatLayout } from './ChatLayoutContext';
|
||||
import { SelectedFile } from './interfaces';
|
||||
import { useAutoSetLayout, useDefaultSplitterLayout } from './hooks';
|
||||
import { useDefaultSplitterLayout } from './hooks';
|
||||
import { ChatContextProvider, useChatContext } from './ChatContext/ChatContext';
|
||||
|
||||
export interface ChatSplitterProps {
|
||||
showChatCollapse?: boolean;
|
||||
|
@ -30,20 +31,25 @@ export const ChatLayout: React.FC<ChatSplitterProps> = React.memo(
|
|||
chatId
|
||||
});
|
||||
|
||||
const { hasFile, isPureChat, isPureFile } = useChatSplitterProps;
|
||||
const useChatContextValue = useChatContext({ chatId, defaultSelectedFile });
|
||||
|
||||
const { isPureChat, isPureFile } = useChatSplitterProps;
|
||||
const { hasFile } = useChatContextValue;
|
||||
|
||||
return (
|
||||
<ChatSplitterContextProvider useChatSplitterProps={useChatSplitterProps}>
|
||||
<AppSplitter
|
||||
ref={appSplitterRef}
|
||||
leftChildren={isPureFile ? null : <ChatContainer />}
|
||||
rightChildren={<FileContainer children={children} />}
|
||||
autoSaveId="chat-splitter"
|
||||
defaultLayout={defaultSplitterLayout}
|
||||
rightHidden={isPureChat}
|
||||
preserveSide="left"
|
||||
leftPanelMinSize={hasFile ? 225 : undefined}
|
||||
/>
|
||||
<ChatContextProvider value={useChatContextValue}>
|
||||
<AppSplitter
|
||||
ref={appSplitterRef}
|
||||
leftChildren={isPureFile ? null : <ChatContainer />}
|
||||
rightChildren={<FileContainer children={children} />}
|
||||
autoSaveId="chat-splitter"
|
||||
defaultLayout={defaultSplitterLayout}
|
||||
rightHidden={isPureChat}
|
||||
preserveSide="left"
|
||||
leftPanelMinSize={hasFile ? 225 : undefined}
|
||||
/>
|
||||
</ChatContextProvider>
|
||||
</ChatSplitterContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
createContext,
|
||||
useContextSelector
|
||||
} from '@fluentui/react-context-selector';
|
||||
import React, { PropsWithChildren, useMemo, useState, useTransition } from 'react';
|
||||
import React, { PropsWithChildren, useMemo, useTransition } from 'react';
|
||||
import type { SelectedFile } from '../interfaces';
|
||||
import type { ChatSplitterProps } from '../ChatLayout';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
|
@ -29,14 +29,6 @@ export const useChatLayout = ({
|
|||
const [isPending, startTransition] = useTransition();
|
||||
const onChangePage = useAppLayoutContextSelector((state) => state.onChangePage);
|
||||
const selectedLayout = defaultSelectedLayout;
|
||||
const selectedFileId = defaultSelectedFile?.id;
|
||||
const selectedFileType = defaultSelectedFile?.type;
|
||||
const hasFile = !!selectedFileId;
|
||||
|
||||
const selectedFileTitle: string = useMemo(() => {
|
||||
if (!selectedFileId) return '';
|
||||
return 'test';
|
||||
}, [selectedFileId]);
|
||||
|
||||
const animateOpenSplitter = useMemoizedFn((side: 'left' | 'right' | 'both') => {
|
||||
if (appSplitterRef.current) {
|
||||
|
@ -84,11 +76,7 @@ export const useChatLayout = ({
|
|||
});
|
||||
|
||||
return {
|
||||
selectedFileTitle,
|
||||
selectedFileType,
|
||||
selectedLayout,
|
||||
selectedFileId,
|
||||
hasFile,
|
||||
isPureFile,
|
||||
isPureChat,
|
||||
onSetSelectedFile,
|
||||
|
|
|
@ -6,7 +6,6 @@ import { CollapseFileButton } from './CollapseFileButton';
|
|||
|
||||
export const FileContainerHeader: React.FC = React.memo(() => {
|
||||
const { styles, cx } = useStyles();
|
||||
const selectedFileType = useChatSplitterContextSelector((state) => state.selectedFileType);
|
||||
const selectedLayout = useChatSplitterContextSelector((state) => state.selectedLayout);
|
||||
|
||||
const showCollapseButton = true;
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
import { useMemo } from 'react';
|
||||
import { SelectedFile } from '../interfaces';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
|
||||
import { ChatSplitterProps } from '../ChatLayout';
|
||||
import { FileType } from '@/api/buster_socket/chats';
|
||||
|
||||
export const useSelectedFileByParams = () => {
|
||||
const { metricId, collectionId, datasetId, dashboardId, chatId } = useParams() as {
|
||||
|
@ -16,10 +16,10 @@ export const useSelectedFileByParams = () => {
|
|||
};
|
||||
|
||||
const selectedFile: SelectedFile | undefined = useMemo(() => {
|
||||
if (metricId) return { id: metricId, type: AppChatMessageFileType.Metric };
|
||||
if (collectionId) return { id: collectionId, type: AppChatMessageFileType.Collection };
|
||||
if (datasetId) return { id: datasetId, type: AppChatMessageFileType.Dataset };
|
||||
if (dashboardId) return { id: dashboardId, type: AppChatMessageFileType.Dashboard };
|
||||
if (metricId) return { id: metricId, type: FileType.METRIC };
|
||||
if (collectionId) return { id: collectionId, type: FileType.COLLECTION };
|
||||
if (datasetId) return { id: datasetId, type: FileType.DATASET };
|
||||
if (dashboardId) return { id: dashboardId, type: FileType.DASHBOARD };
|
||||
}, [metricId, collectionId, datasetId, dashboardId, chatId]);
|
||||
|
||||
const selectedLayout: ChatSplitterProps['defaultSelectedLayout'] = useMemo(() => {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <div className="h-full w-full bg-orange-500">Dashboard swag</div>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <div className="h-full w-full bg-red-500">Dashboard swag</div>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { ChatLayout, useSelectedFileByParams } from '@chatLayout/index';
|
||||
import { AppChatMessageFileType } from '@/components/messages/AppChatMessageContainer';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const { selectedFile, selectedLayout, chatId } = useSelectedFileByParams();
|
||||
const router = useRouter();
|
||||
|
||||
useHotkeys('m', () => {
|
||||
const randomType: AppChatMessageFileType = (
|
||||
['dataset', 'collection', 'metric', 'dashboard'] as AppChatMessageFileType[]
|
||||
)[Math.floor(Math.random() * 4)];
|
||||
const isPureChat = Math.random() < 0.15;
|
||||
const isChat = Math.random() < 0.55;
|
||||
const randomChatId = Math.floor(Math.random() * 1000);
|
||||
const randomId = Math.floor(Math.random() * 1000000);
|
||||
|
||||
if (isPureChat) {
|
||||
router.push(`/test/splitter/chat/${randomChatId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const route = isChat
|
||||
? `/test/splitter/chat/${randomChatId}/${randomType}/${randomId}`
|
||||
: `/test/splitter/${randomType}/${randomId}`;
|
||||
router.push(route);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen">
|
||||
<ChatLayout
|
||||
chatId={chatId}
|
||||
defaultSelectedLayout={selectedLayout}
|
||||
defaultSelectedFile={selectedFile}>
|
||||
{children}
|
||||
</ChatLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
'use client';
|
||||
|
||||
export default function Page() {
|
||||
return <></>;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useRef, useState, useTransition } from 'react';
|
||||
import React, { useEffect, useRef, useTransition } from 'react';
|
||||
import {
|
||||
createContext,
|
||||
ContextSelector,
|
||||
|
@ -6,30 +6,18 @@ import {
|
|||
} from '@fluentui/react-context-selector';
|
||||
import { useBusterWebSocket } from '../BusterWebSocket';
|
||||
import type { BusterChatAsset, IBusterChat } from '@/api/buster_socket/chats';
|
||||
import { useMemoizedFn, useMount, useUnmount } from 'ahooks';
|
||||
import { useMemoizedFn, useUnmount } from 'ahooks';
|
||||
import type { FileType } from '@/api/buster_socket/chats';
|
||||
|
||||
export const useBusterChat = () => {
|
||||
const busterSocket = useBusterWebSocket();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const chatsRef = useRef<Record<string, IBusterChat>>({});
|
||||
const [seletedAssetId, setSeletedAssetId] = useState<Record<string, string | null>>({});
|
||||
|
||||
// GETTERS
|
||||
|
||||
const getSelectedAssetId = useCallback(
|
||||
(chatId: string) => {
|
||||
return seletedAssetId[chatId] || null;
|
||||
},
|
||||
[seletedAssetId]
|
||||
);
|
||||
|
||||
// SETTERS
|
||||
|
||||
const onSetSelectedAssetId = useMemoizedFn((chatId: string, assetId: string | null) => {
|
||||
setSeletedAssetId((prev) => ({ ...prev, [chatId]: assetId }));
|
||||
});
|
||||
|
||||
// LISTENERS
|
||||
|
||||
const _onGetChat = useMemoizedFn((chat: IBusterChat) => {
|
||||
|
@ -103,12 +91,10 @@ export const useBusterChat = () => {
|
|||
);
|
||||
|
||||
return {
|
||||
getSelectedAssetId,
|
||||
chats: chatsRef.current,
|
||||
unsubscribeFromChat,
|
||||
subscribeToChat,
|
||||
getChatAsset,
|
||||
onSetSelectedAssetId
|
||||
getChatAsset
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -128,25 +114,22 @@ export const useBusterChatContextSelector = <T,>(
|
|||
selector: ContextSelector<ReturnType<typeof useBusterChat>, T>
|
||||
) => useContextSelector(BusterChat, selector);
|
||||
|
||||
export const useBusterChatIndividual = ({ chatId }: { chatId: string }) => {
|
||||
export const useBusterChatIndividual = ({ chatId: chatIdProp }: { chatId?: string }) => {
|
||||
const chatId = chatIdProp || '';
|
||||
const chat = useBusterChatContextSelector((x) => x.chats[chatId]);
|
||||
const subscribeToChat = useBusterChatContextSelector((x) => x.subscribeToChat);
|
||||
const unsubscribeFromChat = useBusterChatContextSelector((x) => x.unsubscribeFromChat);
|
||||
const selectedAssetId = useBusterChatContextSelector((x) => x.getSelectedAssetId(chatId));
|
||||
const onSetSelectedAssetId = useBusterChatContextSelector((x) => x.onSetSelectedAssetId);
|
||||
|
||||
useMount(() => {
|
||||
subscribeToChat({ chatId });
|
||||
});
|
||||
useEffect(() => {
|
||||
if (chatId) subscribeToChat({ chatId });
|
||||
}, [chatId]);
|
||||
|
||||
useUnmount(() => {
|
||||
unsubscribeFromChat({ chatId });
|
||||
if (chatId) unsubscribeFromChat({ chatId });
|
||||
});
|
||||
|
||||
return {
|
||||
chat,
|
||||
selectedAssetId,
|
||||
onSetSelectedAssetId
|
||||
chat
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -237,4 +237,6 @@ export type BusterAppRoutesWithArgs = {
|
|||
chatId: string;
|
||||
valueId: string;
|
||||
};
|
||||
[BusterAppRoutes.APP_METRIC_ID]: { route: BusterAppRoutes.APP_METRIC_ID; metricId: string };
|
||||
[BusterAppRoutes.APP_VALUE_ID]: { route: BusterAppRoutes.APP_VALUE_ID; valueId: string };
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue