add additional components

This commit is contained in:
Nate Kelley 2025-01-30 15:33:06 -07:00
parent f234825029
commit 70d770f565
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
38 changed files with 701 additions and 15 deletions

View File

@ -1,6 +1,5 @@
import type { BusterDashboardMetric } from '../../../buster_rest/dashboards';
import type { DashboardConfig } from '../../dashboards';
import type { FileType } from '../config';
export type BusterDashboardAsset = {
id: string;

View File

@ -25,6 +25,14 @@ export type BusterMetricAsset = {
sent_by_name: string;
sent_by_avatar_url: string | null;
draft_session_id: string | null; //DO WE NEED THIS?
dashboards: {
id: string;
name: string;
}[];
collections: {
id: string;
name: string;
}[];
};
enum BusterVerificationStatus {

View File

@ -12,3 +12,12 @@ export type BusterChatAsset =
| BusterDatasetAsset
| BusterTermAsset
| BusterValueAsset;
export {
BusterCollectionAsset,
BusterDashboardAsset,
BusterDatasetAsset,
BusterMetricAsset,
BusterTermAsset,
BusterValueAsset
};

View File

@ -4,3 +4,4 @@ export * from './chatRequests';
export * from './chatResponses';
export * from './config';
export * from './chatMessageInterfaces';
export * from './chatAssetInterfaces';

View File

@ -0,0 +1,74 @@
import { AppMaterialIcons } from '@/components/icons';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { useCollectionsContextSelector } from '@/context/Collections';
import { useBusterThreadsContextSelector } from '@/context/Threads';
import { useMemoizedFn, useMount } from 'ahooks';
import { Button } from 'antd';
import React, { useState } from 'react';
import { SaveToCollectionsDropdown } from '../Dropdowns/SaveToCollectionsDropdown';
export const SaveMetricToCollectionButton: React.FC<{
metricIds: string[];
buttonType?: 'text' | 'default';
useText?: boolean;
}> = ({ metricIds, buttonType = 'text', useText = false }) => {
const { openInfoMessage } = useBusterNotifications();
const saveThreadToCollection = useBusterThreadsContextSelector(
(state) => state.saveThreadToCollection
);
const removeThreadFromCollection = useBusterThreadsContextSelector(
(state) => state.removeThreadFromCollection
);
const collectionsList = useCollectionsContextSelector((state) => state.collectionsList);
const getInitialCollections = useCollectionsContextSelector(
(state) => state.getInitialCollections
);
const [selectedCollections, setSelectedCollections] = useState<
Parameters<typeof SaveToCollectionsDropdown>[0]['selectedCollections']
>([]);
const onSaveToCollection = useMemoizedFn(async (collectionIds: string[]) => {
setSelectedCollections(collectionIds);
// const allSaves: Promise<void>[] = metricIds.map((metricId) => {
// return saveMetricToCollection({
// metricId,
// collectionIds
// });
// });
// await Promise.all(allSaves);
openInfoMessage('Metrics saved to collections');
});
const onRemoveFromCollection = useMemoizedFn(async (collectionId: string) => {
setSelectedCollections((prev) => prev.filter((id) => id !== collectionId));
// const allSelectedButLast = selectedRowKeys.slice(0, -1);
// const lastThreadId = selectedRowKeys[selectedRowKeys.length - 1];
// const allRemoves: Promise<void>[] = allSelectedButLast.map((threadId) => {
// return removeThreadFromCollection({ threadId, collectionId, ignoreFavoriteUpdates: true });
// });
// await removeThreadFromCollection({
// threadId: lastThreadId,
// collectionId,
// ignoreFavoriteUpdates: false
// });
// await Promise.all(allRemoves);
openInfoMessage('Metrics removed from collections');
});
useMount(() => {
if (!collectionsList.length) getInitialCollections();
});
return (
<SaveToCollectionsDropdown
onSaveToCollection={onSaveToCollection}
onRemoveFromCollection={onRemoveFromCollection}
selectedCollections={selectedCollections}>
<Button icon={<AppMaterialIcons icon="note_stack" />} type={buttonType}>
{useText ? 'Collections' : ''}
</Button>
</SaveToCollectionsDropdown>
);
};

View File

@ -0,0 +1,69 @@
import { useDashboardContextSelector } from '@/context/Dashboards';
import { useBusterThreadsContextSelector } from '@/context/Threads';
import { useMemoizedFn, useMount, useUnmount } from 'ahooks';
import React from 'react';
import { SaveToDashboardDropdown } from '../Dropdowns/SaveToDashboardDropdown';
import { Button } from 'antd';
import { AppMaterialIcons } from '@/components/icons';
import { BusterMetricAsset } from '@/api/buster_socket/chats';
const EMPTY_SELECTED_DASHBOARDS: BusterMetricAsset['dashboards'] = [];
export const SaveMetricToDashboardButton: React.FC<{
metricIds: string[];
disabled?: boolean;
selectedDashboards?: BusterMetricAsset['dashboards'];
}> = React.memo(
({ metricIds, disabled = false, selectedDashboards = EMPTY_SELECTED_DASHBOARDS }) => {
const saveThreadToDashboard = useBusterThreadsContextSelector(
(state) => state.saveThreadToDashboard
);
const removeThreadFromDashboard = useBusterThreadsContextSelector(
(state) => state.removeThreadFromDashboard
);
const initDashboardsList = useDashboardContextSelector((state) => state.initDashboardsList);
const unsubscribeFromDashboardsList = useDashboardContextSelector(
(state) => state.unsubscribeFromDashboardsList
);
const onSaveToDashboard = useMemoizedFn(async (dashboardIds: string[]) => {
console.warn('TODO: save metric to dashboard', dashboardIds);
// await saveThreadToDashboard({ threadId, dashboardIds });
});
const onRemoveFromDashboard = useMemoizedFn(async (dashboardId: string) => {
console.warn('TODO: remove metric from dashboard', dashboardId);
// return await removeThreadFromDashboard({ threadId, dashboardId, useConfirmModal: false });
});
const onClick = useMemoizedFn(() => {
initDashboardsList();
});
useMount(() => {
setTimeout(() => {
initDashboardsList();
}, 8000);
});
useUnmount(() => {
unsubscribeFromDashboardsList();
});
return (
<SaveToDashboardDropdown
selectedDashboards={selectedDashboards}
onSaveToDashboard={onSaveToDashboard}
onRemoveFromDashboard={onRemoveFromDashboard}>
<Button
type="text"
disabled={disabled}
icon={<AppMaterialIcons icon="dashboard_customize" />}
onClick={onClick}
/>
</SaveToDashboardDropdown>
);
}
);
SaveMetricToDashboardButton.displayName = 'SaveMetricToDashboardButton';

View File

@ -0,0 +1,10 @@
import { Button } from 'antd';
import React from 'react';
import { AppMaterialIcons } from '@/components/icons';
import { ShareMenu } from '../ShareMenu';
export const ShareMetricButton = React.memo(() => {
return <Button type="text" icon={<AppMaterialIcons icon="share_windows" />} />;
});
ShareMetricButton.displayName = 'ShareMetricButton';

View File

@ -0,0 +1,119 @@
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { useDashboardContextSelector } from '@/context/Dashboards';
import { useBusterThreadsContextSelector } from '@/context/Threads';
import { useMemoizedFn } from 'ahooks';
import React, { useEffect, useMemo } from 'react';
import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes';
import { Button } from 'antd';
import { AppDropdownSelect } from '@/components/dropdown';
import { AppTooltip } from '@/components/tooltip';
import { AppMaterialIcons } from '@/components/icons';
import type { BusterMetricAsset } from '@/api/buster_socket/chats';
import type { BusterDashboardListItem } from '@/api/buster_rest/dashboards';
export const SaveToDashboardDropdown: React.FC<{
children: React.ReactNode;
selectedDashboards: BusterMetricAsset['dashboards'];
onSaveToDashboard: (dashboardId: string[]) => Promise<void>;
onRemoveFromDashboard: (dashboardId: string) => void;
}> = ({ children, onRemoveFromDashboard, onSaveToDashboard, selectedDashboards }) => {
const onCreateNewDashboard = useDashboardContextSelector((x) => x.onCreateNewDashboard);
const creatingDashboard = useDashboardContextSelector((x) => x.creatingDashboard);
const initDashboardsList = useDashboardContextSelector((x) => x.initDashboardsList);
const dashboardsList = useDashboardContextSelector((state) => state.dashboardsList);
const saveThreadToDashboard = useBusterThreadsContextSelector(
(state) => state.saveThreadToDashboard
);
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
const [showDropdown, setShowDropdown] = React.useState(false);
const onClickItem = useMemoizedFn(async (dashboard: BusterDashboardListItem) => {
const isSelected = selectedDashboards.some((d) => d.id === dashboard.id);
if (isSelected) {
onRemoveFromDashboard(dashboard.id);
} else {
const allDashboardsAndSelected = selectedDashboards.map((d) => d.id).concat(dashboard.id);
await onSaveToDashboard(allDashboardsAndSelected);
}
});
const items = useMemo(
() =>
dashboardsList.map((dashboard) => {
return {
key: dashboard.id,
label: dashboard.name || 'New dashboard',
onClick: () => onClickItem(dashboard),
link: createBusterRoute({
route: BusterRoutes.APP_DASHBOARD_ID,
dashboardId: dashboard.id
})
};
}),
[dashboardsList]
);
const selectedItems = useMemo(() => {
return selectedDashboards.map((d) => d.id);
}, [selectedDashboards]);
const onClickNewDashboardButton = useMemoizedFn(async () => {
const res = await onCreateNewDashboard({
rerouteToDashboard: false
});
if (res?.id) {
await onSaveToDashboard([res.id]);
// await saveThreadToDashboard({
// threadId,
// dashboardIds: [res.id]
// });
}
if (res?.id) {
onChangePage({
route: BusterRoutes.APP_DASHBOARD_ID,
dashboardId: res.id
});
}
setShowDropdown(false);
});
const onOpenChange = useMemoizedFn((open: boolean) => {
setShowDropdown(open);
});
useEffect(() => {
if (showDropdown) {
initDashboardsList();
}
}, [showDropdown]);
return (
<>
<AppDropdownSelect
trigger={['click']}
headerContent={'Save to a dashboard'}
placement="bottomRight"
open={showDropdown}
onOpenChange={onOpenChange}
footerContent={
<Button
type="text"
className="!justify-start"
loading={creatingDashboard}
block
icon={<AppMaterialIcons icon="add" />}
onClick={onClickNewDashboardButton}>
New dashboard
</Button>
}
items={items}
selectedItems={selectedItems}>
<AppTooltip title={showDropdown ? '' : 'Save to dashboard'}>{children}</AppTooltip>
</AppDropdownSelect>
</>
);
};

View File

@ -1,5 +1,5 @@
import { AppMaterialIcons } from '@/components';
import { SaveToCollectionsDropdown } from '@appComponents/Buttons/SaveToCollectionsDropdown';
import { SaveToCollectionsDropdown } from '@/app/app/_components/Dropdowns/SaveToCollectionsDropdown';
import { useCollectionsContextSelector } from '@/context/Collections';
import { useBusterThreadsContextSelector } from '@/context/Threads';
import { IBusterThread } from '@/context/Threads/interfaces';

View File

@ -20,7 +20,6 @@ export const ChatInput: React.FC = React.memo(() => {
const onSubmit = useMemoizedFn(async () => {
if (disableSendButton) return;
console.log('submit');
});
const disableSubmit = !inputHasText(inputValue);

View File

@ -10,7 +10,6 @@ import { motion, AnimatePresence } from 'framer-motion';
import { itemAnimationConfig } from '../animationConfig';
import { useMemoizedFn } from 'ahooks';
import { StatusIndicator } from '../StatusIndicator';
import { useChatLayoutContextSelector } from '../../../../ChatLayoutContext';
import { useChatContextSelector } from '../../../../ChatContext';

View File

@ -28,7 +28,8 @@ export const ChatLayout: React.FC<ChatSplitterProps> = React.memo(
const useChatSplitterProps = useChatLayout({
appSplitterRef,
defaultSelectedLayout,
chatId
chatId,
defaultSelectedFile
});
const useChatContextValue = useChatContext({ chatId, defaultSelectedFile });

View File

@ -12,15 +12,18 @@ import { createChatAssetRoute, createFileRoute } from './helpers';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { DEFAULT_CHAT_OPTION } from './config';
import { useAutoSetLayout } from '../hooks';
import { useChatFileLayout } from './useChatFileLayout';
interface UseChatSplitterProps {
defaultSelectedLayout: ChatSplitterProps['defaultSelectedLayout'];
defaultSelectedFile: ChatSplitterProps['defaultSelectedFile'];
appSplitterRef: React.RefObject<AppSplitterRef>;
chatId: string | undefined;
}
export const useChatLayout = ({
defaultSelectedLayout,
defaultSelectedFile,
appSplitterRef,
chatId
}: UseChatSplitterProps) => {
@ -73,7 +76,13 @@ export const useChatLayout = ({
defaultSelectedLayout
});
const fileLayoutContext = useChatFileLayout({
selectedFileId: defaultSelectedFile?.id,
selectedFileType: defaultSelectedFile?.type
});
return {
...fileLayoutContext,
isPureFile,
isPureChat,
onSetSelectedFile,

View File

@ -0,0 +1,2 @@
export * from './useChatFileLayout';
export * from './interfaces';

View File

@ -0,0 +1,24 @@
export interface FileConfig {
selectedFileView: FileView;
fileViewConfig?: FileViewConfig;
}
export type FileViewConfig = Partial<Record<FileView, { secondaryView: FileViewSecondary }>>;
export type FileView =
| MetricFileView
| DashboardFileView
| TermFileView
| ValueFileView
| DatasetFileView
| CollectionFileView;
export type MetricFileView = 'chart' | 'results' | 'file';
export type DashboardFileView = 'dashboard' | 'file';
export type TermFileView = 'file';
export type ValueFileView = 'file';
export type DatasetFileView = 'file';
export type CollectionFileView = 'file' | 'results';
export type FileViewSecondary = null | MetricFileViewSecondary;
export type MetricFileViewSecondary = 'chart-edit';

View File

@ -0,0 +1,85 @@
import { FileType } from '@/api/buster_socket/chats';
import { useMemo, useState } from 'react';
import { FileConfig, FileView, FileViewConfig, FileViewSecondary } from './interfaces';
import { useMemoizedFn } from 'ahooks';
export const useChatFileLayout = ({
selectedFileId,
selectedFileType
}: {
selectedFileId: string | undefined;
selectedFileType: FileType | undefined;
}) => {
const [fileViews, setFileViews] = useState<Record<string, FileConfig>>({});
const onSetFileView = useMemoizedFn(
({
fileView,
fileId,
secondaryView
}: {
fileView?: FileView;
fileId?: string;
secondaryView?: FileViewSecondary;
}) => {
const id = fileId ?? selectedFileId;
if (!id) return;
setFileViews((prev) => {
const newFileConfig: FileConfig = { ...prev[id] };
const usedFileView =
fileView ??
newFileConfig.selectedFileView ??
defaultFileView[selectedFileType as FileType];
if (fileView !== undefined) {
newFileConfig.selectedFileView = fileView;
}
if (secondaryView !== undefined) {
newFileConfig.fileViewConfig = {
...newFileConfig.fileViewConfig,
[usedFileView]: {
...newFileConfig.fileViewConfig?.[usedFileView],
secondaryView
}
};
}
return { ...prev, [id]: newFileConfig };
});
}
);
const selectedFileView: FileView | undefined = useMemo(() => {
if (!selectedFileId) return undefined;
return (
fileViews[selectedFileId]?.selectedFileView || defaultFileView[selectedFileType as FileType]
);
}, [fileViews, selectedFileId]);
const selectedFileViewConfig: FileViewConfig | undefined = useMemo(() => {
if (!selectedFileId) return undefined;
return fileViews[selectedFileId]?.fileViewConfig;
}, [fileViews, selectedFileId]);
const selectedFileViewSecondary: FileViewSecondary | null = useMemo(() => {
if (!selectedFileId || !selectedFileViewConfig || !selectedFileView) return null;
return selectedFileViewConfig?.[selectedFileView]?.secondaryView ?? null;
}, [selectedFileViewConfig, selectedFileId, selectedFileView]);
return {
selectedFileType,
selectedFileView,
selectedFileViewSecondary,
onSetFileView
};
};
const defaultFileView: Record<FileType, FileView> = {
collection: 'results',
metric: 'chart',
value: 'results',
term: 'results',
dataset: 'results',
dashboard: 'results'
};

View File

@ -7,7 +7,7 @@ interface FileContainerProps {
export const FileContainer: React.FC<FileContainerProps> = React.memo(({ children }) => {
return (
<div className="flex min-w-[450px] flex-col">
<div className="flex min-w-[370px] flex-col">
<FileContainerHeader />
{children}
</div>

View File

@ -0,0 +1,10 @@
import React from 'react';
import { FileContainerButtonsProps } from './interfaces';
export const CollectionContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(
() => {
return <div>CollectionContainerHeaderButtons</div>;
}
);
CollectionContainerHeaderButtons.displayName = 'CollectionContainerHeaderButtons';

View File

@ -0,0 +1,10 @@
import React from 'react';
import { FileContainerSegmentProps } from './interfaces';
export const CollectionContainerHeaderSegment: React.FC<FileContainerSegmentProps> = React.memo(
() => {
return <div>Collection Container Header</div>;
}
);
CollectionContainerHeaderSegment.displayName = 'CollectionContainerHeaderSegment';

View File

@ -0,0 +1,10 @@
import React from 'react';
import { FileContainerButtonsProps } from './interfaces';
export const DashboardContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(
() => {
return <div>DashboardContainerHeaderButtons</div>;
}
);
DashboardContainerHeaderButtons.displayName = 'DashboardContainerHeaderButtons';

View File

@ -0,0 +1,26 @@
import React from 'react';
import type { FileContainerSegmentProps } from './interfaces';
import type { DashboardFileView, FileView } from '../../ChatLayoutContext/useChatFileLayout';
import { AppSegmented } from '@/components/segmented';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/es/segmented';
const segmentOptions: { label: string; value: DashboardFileView }[] = [
{ label: 'Dashboard', value: 'dashboard' },
{ label: 'File', value: 'file' }
];
export const DashboardContainerHeaderSegment: React.FC<FileContainerSegmentProps> = React.memo(
({ selectedFileView }) => {
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const onChange = useMemoizedFn((fileView: SegmentedValue) => {
onSetFileView({ fileView: fileView as FileView });
});
return <AppSegmented options={segmentOptions} value={selectedFileView} onChange={onChange} />;
}
);
DashboardContainerHeaderSegment.displayName = 'DashboardContainerHeaderSegment';

View File

@ -0,0 +1,8 @@
import React from 'react';
import { FileContainerButtonsProps } from './interfaces';
export const DatasetContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => {
return <div>DatasetContainerHeaderButtons</div>;
});
DatasetContainerHeaderButtons.displayName = 'DatasetContainerHeaderButtons';

View File

@ -0,0 +1,8 @@
import React from 'react';
import { FileContainerSegmentProps } from './interfaces';
export const DatasetContainerHeaderSegment: React.FC<FileContainerSegmentProps> = React.memo(() => {
return <div>Dataset Container Header</div>;
});
DatasetContainerHeaderSegment.displayName = 'DatasetContainerHeaderSegment';

View File

@ -2,22 +2,73 @@ import { appContentHeaderHeight } from '@/components/layout';
import { createStyles } from 'antd-style';
import React from 'react';
import { CollapseFileButton } from './CollapseFileButton';
import { FileType } from '@/api/buster_socket/chats';
import { FileContainerSegmentProps, FileContainerButtonsProps } from './interfaces';
import { MetricContainerHeaderButtons } from './MetricContainerHeaderButtons';
import { ValueContainerHeaderButtons } from './ValueContainerHeaderButtons';
import { TermContainerHeaderButtons } from './TermContainerHeaderButtons';
import { DatasetContainerHeaderButtons } from './DatasetContainerHeaderButtons';
import { DashboardContainerHeaderButtons } from './DashboardContainerHeaderButtons';
import { CollectionContainerHeaderButtons } from './CollectionContainerHeaderButtons';
import { CollectionContainerHeaderSegment } from './CollectionContainerHeaderSegment';
import { MetricContainerHeaderSegment } from './MetricContainerHeaderSegment';
import { ValueContainerHeaderSegment } from './ValueContainerHeaderSegment';
import { TermContainerHeaderSegment } from './TermContainerHeaderSegment';
import { DatasetContainerHeaderSegment } from './DatasetContainerHeaderSegment';
import { DashboardContainerHeaderSegment } from './DashboardContainerHeaderSegment';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
export const FileContainerHeader: React.FC = React.memo(() => {
const { styles, cx } = useStyles();
const selectedFileType = useChatLayoutContextSelector((x) => x.selectedFileType);
const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView);
const showCollapseButton = true;
const isCollapseOpen = true; //I could get the defaultSelectedLayout from the context and check if it is 'both'?
const SelectedFileSegment = React.useMemo(
() => (selectedFileType ? SelectedFileSegmentRecord[selectedFileType] : () => <></>),
[selectedFileType]
);
const SelectedFileButtons = React.useMemo(
() => (selectedFileType ? SelectedFileButtonsRecord[selectedFileType] : () => <></>),
[selectedFileType]
);
return (
<div className={cx(styles.container, 'flex w-full items-center px-3')}>
<CollapseFileButton showCollapseButton={showCollapseButton} isOpen={isCollapseOpen} />
<div
className={cx(styles.container, 'flex w-full items-center justify-between space-x-3.5 px-3')}>
<div className="flex items-center gap-1.5">
<CollapseFileButton showCollapseButton={showCollapseButton} isOpen={isCollapseOpen} />
{selectedFileView && <SelectedFileSegment selectedFileView={selectedFileView} />}
</div>
<SelectedFileButtons selectedFileView={selectedFileView} />
</div>
);
});
FileContainerHeader.displayName = 'FileContainerHeader';
const SelectedFileButtonsRecord: Record<FileType, React.FC<FileContainerButtonsProps>> = {
metric: MetricContainerHeaderButtons,
value: ValueContainerHeaderButtons,
term: TermContainerHeaderButtons,
dataset: DatasetContainerHeaderButtons,
dashboard: DashboardContainerHeaderButtons,
collection: CollectionContainerHeaderButtons
};
const SelectedFileSegmentRecord: Record<FileType, React.FC<FileContainerSegmentProps>> = {
metric: MetricContainerHeaderSegment,
value: ValueContainerHeaderSegment,
term: TermContainerHeaderSegment,
dataset: DatasetContainerHeaderSegment,
dashboard: DashboardContainerHeaderSegment,
collection: CollectionContainerHeaderSegment
};
const useStyles = createStyles(({ css, token }) => ({
container: css`
min-height: ${appContentHeaderHeight}px;

View File

@ -0,0 +1,57 @@
import React from 'react';
import { FileContainerButtonsProps } from './interfaces';
import { Button, ButtonProps } from 'antd';
import { AppMaterialIcons, AppTooltip } from '@/components';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useMemoizedFn } from 'ahooks';
import { SaveMetricToCollectionButton } from '@appComponents/Buttons/SaveMetricToCollectionButton';
import { SaveMetricToDashboardButton } from '@appComponents/Buttons/SaveMetricToDashboardButton';
import { useChatContextSelector } from '../../ChatContext';
export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => {
return (
<div className="flex items-center gap-1">
<EditChartButton />
<SaveToCollectionButtonLocal />
<SaveToDashboardButton />
</div>
);
});
MetricContainerHeaderButtons.displayName = 'MetricContainerHeaderButtons';
const EditChartButton = React.memo(() => {
const selectedFileViewSecondary = useChatLayoutContextSelector(
(x) => x.selectedFileViewSecondary
);
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const isSelectedView = selectedFileViewSecondary === 'chart-edit';
const buttonVariant: ButtonProps['variant'] = isSelectedView ? 'filled' : 'text';
const onClickButton = useMemoizedFn(() => {
const secondaryView = isSelectedView ? null : 'chart-edit';
onSetFileView({ secondaryView });
});
return (
<AppTooltip title="Edit chart">
<Button
color="default"
variant={buttonVariant}
icon={<AppMaterialIcons icon="chart_edit" />}
onClick={onClickButton}
/>
</AppTooltip>
);
});
EditChartButton.displayName = 'EditChartButton';
const SaveToCollectionButtonLocal = React.memo(() => {
const selectedFileId = useChatContextSelector((x) => x.selectedFileId)!;
return <SaveMetricToCollectionButton metricIds={[selectedFileId]} />;
});
SaveMetricToCollectionButton.displayName = 'SaveToCollectionButton';
const SaveToDashboardButton = React.memo(() => {
const selectedFileId = useChatContextSelector((x) => x.selectedFileId)!;
return <SaveMetricToDashboardButton metricIds={[selectedFileId]} />;
});
SaveMetricToDashboardButton.displayName = 'SaveToDashboardButton';

View File

@ -0,0 +1,27 @@
import React from 'react';
import { FileContainerSegmentProps } from './interfaces';
import { AppSegmented } from '@/components/segmented';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import type { FileView, MetricFileView } from '../../ChatLayoutContext/useChatFileLayout';
import { SegmentedValue } from 'antd/es/segmented';
import { useMemoizedFn } from 'ahooks';
const segmentOptions: { label: string; value: MetricFileView }[] = [
{ label: 'Chart', value: 'chart' },
{ label: 'Results', value: 'results' },
{ label: 'File', value: 'file' }
];
export const MetricContainerHeaderSegment: React.FC<FileContainerSegmentProps> = React.memo(
({ selectedFileView }) => {
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const onChange = useMemoizedFn((fileView: SegmentedValue) => {
onSetFileView({ fileView: fileView as FileView });
});
return <AppSegmented options={segmentOptions} value={selectedFileView} onChange={onChange} />;
}
);
MetricContainerHeaderSegment.displayName = 'MetricContainerHeaderSegment';

View File

@ -0,0 +1,8 @@
import React from 'react';
import { FileContainerButtonsProps } from './interfaces';
export const TermContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => {
return <div>TermContainerHeaderButtons</div>;
});
TermContainerHeaderButtons.displayName = 'TermContainerHeaderButtons';

View File

@ -0,0 +1,8 @@
import React from 'react';
import { FileContainerSegmentProps } from './interfaces';
export const TermContainerHeaderSegment: React.FC<FileContainerSegmentProps> = React.memo(() => {
return <div>Term Container Header</div>;
});
TermContainerHeaderSegment.displayName = 'TermContainerHeaderSegment';

View File

@ -0,0 +1,8 @@
import React from 'react';
import { FileContainerButtonsProps } from './interfaces';
export const ValueContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => {
return <div>ValueContainerHeaderButtons</div>;
});
ValueContainerHeaderButtons.displayName = 'ValueContainerHeaderButtons';

View File

@ -0,0 +1,8 @@
import React from 'react';
import { FileContainerSegmentProps } from './interfaces';
export const ValueContainerHeaderSegment: React.FC<FileContainerSegmentProps> = React.memo(() => {
return <div>Value Container Header</div>;
});
ValueContainerHeaderSegment.displayName = 'ValueContainerHeaderSegment';

View File

@ -0,0 +1,9 @@
import { FileView } from '../../ChatLayoutContext/useChatFileLayout';
export interface FileContainerSegmentProps {
selectedFileView: FileView | undefined;
}
export interface FileContainerButtonsProps {
selectedFileView: FileView | undefined;
}

View File

@ -6,7 +6,7 @@ import { useUserConfigContextSelector } from '@/context/Users';
import { useCollectionsContextSelector } from '@/context/Collections';
import { useMemoizedFn, useMount } from 'ahooks';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { SaveToCollectionsDropdown } from '@appComponents/Buttons/SaveToCollectionsDropdown';
import { SaveToCollectionsDropdown } from '@/app/app/_components/Dropdowns/SaveToCollectionsDropdown';
import { useDashboardContextSelector } from '@/context/Dashboards';
export const DashboardSelectedOptionPopup: React.FC<{

View File

@ -8,7 +8,7 @@ import { useBusterThreadsContextSelector } from '@/context/Threads';
import { useUserConfigContextSelector } from '@/context/Users';
import { useCollectionsContextSelector } from '@/context/Collections';
import { useMemoizedFn, useMount } from 'ahooks';
import { SaveToCollectionsDropdown } from '@appComponents/Buttons/SaveToCollectionsDropdown';
import { SaveToCollectionsDropdown } from '@/app/app/_components/Dropdowns/SaveToCollectionsDropdown';
import { useBusterNotifications } from '@/context/BusterNotifications';
export const ThreadSelectedOptionPopup: React.FC<{

View File

@ -5,7 +5,7 @@ import { BusterResizeableGridRow } from './interfaces';
import { BusterResizeColumns } from './BusterResizeColumns';
import classNames from 'classnames';
import { BusterNewItemDropzone } from './_BusterBusterNewItemDropzone';
import { MAX_HEIGHT_OF_ITEM, MIN_ROW_HEIGHT, TOP_SASH_ID, NEW_ROW_ID } from './config';
import { MIN_ROW_HEIGHT, TOP_SASH_ID, NEW_ROW_ID, MAX_ROW_HEIGHT } from './config';
import { createStyles } from 'antd-style';
import clamp from 'lodash/clamp';
import { useDebounceFn, useMemoizedFn, useUpdateLayoutEffect } from 'ahooks';
@ -192,7 +192,7 @@ const ResizeRowHandle: React.FC<{
function onMouseMove(mouseMoveEvent: MouseEvent) {
const newSize = sizes[index!] + (mouseMoveEvent.pageY - startPosition);
const clampedSize = clamp(newSize, MIN_ROW_HEIGHT, MAX_HEIGHT_OF_ITEM);
const clampedSize = clamp(newSize, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT);
onResize(index!, clampedSize);
}
function onMouseUp() {

View File

@ -2,9 +2,8 @@ export const NUMBER_OF_COLUMNS = 12;
export const MIN_NUMBER_OF_COLUMNS = 3;
export const MAX_NUMBER_OF_COLUMNS = 12;
export const MAX_NUMBER_OF_ITEMS = 4;
export const MIN_ROW_HEIGHT = 320;
export const MAX_HEIGHT_OF_ITEM = 550;
export const MAX_ROW_HEIGHT = 550;
export const HEIGHT_OF_DROPZONE = 100;
export const SASH_SIZE = 12;

View File

@ -7,6 +7,7 @@ import { BarChartSortAscIcon } from './customIcons/BarChartSortAscIcon';
import { LineChartAreaChartIcon } from './customIcons/LineChart_AreaChart';
import { LineChartDotLineIcon } from './customIcons/LineChart_DotLine';
import { KeepIcon } from './customIcons/KeepIcon';
import { EditChartIcon } from './customIcons/EditChart';
//https://react-material-symbols.vercel.app/?path=/docs/outlined--docs
@ -24,7 +25,8 @@ const CustomIcons: Record<
bar_sort_none: BarChartSortNoneIcon,
line_chart_area: LineChartAreaChartIcon,
line_chart_dot_line: LineChartDotLineIcon,
keep: KeepIcon
keep: KeepIcon,
chart_edit: EditChartIcon
};
type AppMaterialIconProps = Omit<MaterialSymbolProps, 'icon'> & {

View File

@ -0,0 +1,29 @@
import React from 'react';
export const EditChartIcon: React.FC<{
'data-value'?: string;
color?: string;
}> = ({ 'data-value': dataValue, color = 'currentColor' }) => {
return (
<svg
{...(dataValue ? { 'data-value': dataValue } : {})}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill={color}>
<path
d="M8.75 15.3752V14.0746C8.75 13.9694 8.77083 13.8717 8.8125 13.7814C8.85417 13.6912 8.90972 13.6113 8.97917 13.5419L13.125 9.41686C13.2258 9.31395 13.3378 9.23957 13.461 9.19374C13.5842 9.14804 13.7067 9.1252 13.8283 9.1252C13.961 9.1252 14.0881 9.1495 14.2098 9.19811C14.3315 9.24672 14.4421 9.31964 14.5417 9.41686L15.4583 10.3544C15.5499 10.4552 15.6213 10.5672 15.6727 10.6904C15.7242 10.8136 15.75 10.936 15.75 11.0577C15.75 11.1794 15.7272 11.3038 15.6815 11.431C15.6356 11.5581 15.5613 11.6714 15.4583 11.771L11.3333 15.896C11.2639 15.9655 11.184 16.021 11.0938 16.0627C11.0035 16.1044 10.9058 16.1252 10.8006 16.1252H9.5C9.2875 16.1252 9.10938 16.0533 8.96562 15.9096C8.82187 15.7658 8.75 15.5877 8.75 15.3752ZM9.75 15.1252H10.6875L13.0833 12.7294L12.625 12.2502L12.1667 11.7919L9.75 14.1877V15.1252ZM12.625 12.2502L12.1667 11.7919L13.0833 12.7294L12.625 12.2502Z"
fill={color}
/>
<path
d="M1 2.5C1 2.0875 1.14688 1.73437 1.44063 1.44062C1.73438 1.14687 2.0875 1 2.5 1H13.5C13.9125 1 14.2656 1.14687 14.5594 1.44062C14.8531 1.73437 15 2.0875 15 2.5V7.25781C15 7.50781 14.9219 7.69531 14.7656 7.82031C14.6094 7.94531 14.4375 8.00781 14.25 8.00781C14.0625 8.00781 13.8906 7.94378 13.7344 7.81573C13.5781 7.68767 13.5 7.5017 13.5 7.25781V2.5H2.5V13.5H6.5C6.74319 13.5 6.92729 13.5781 7.05229 13.7344C7.17729 13.8906 7.23979 14.0625 7.23979 14.25C7.23979 14.4375 7.17729 14.6094 7.05229 14.7656C6.92729 14.9219 6.73979 15 6.48979 15H2.5C2.0875 15 1.73438 14.8531 1.44063 14.5594C1.14688 14.2656 1 13.9125 1 13.5V2.5Z"
fill={color}
/>
<path
d="M4.75437 6C4.54312 6 4.36458 6.07187 4.21875 6.21562C4.07292 6.35938 4 6.5375 4 6.75V11.25C4 11.4625 4.07146 11.6406 4.21438 11.7844C4.35729 11.9281 4.53438 12 4.74563 12C4.95688 12 5.13542 11.9281 5.28125 11.7844C5.42708 11.6406 5.5 11.4625 5.5 11.25V6.75C5.5 6.5375 5.42854 6.35938 5.28562 6.21562C5.14271 6.07187 4.96563 6 4.75437 6ZM8.00437 4C7.79313 4 7.61458 4.07187 7.46875 4.21562C7.32292 4.35937 7.25 4.5375 7.25 4.75V11.25C7.25 11.4625 7.32146 11.6406 7.46438 11.7844C7.60729 11.9281 7.78438 12 7.99563 12C8.20688 12 8.38542 11.9281 8.53125 11.7844C8.67708 11.6406 8.75 11.4625 8.75 11.25V4.75C8.75 4.5375 8.67854 4.35937 8.53562 4.21562C8.39271 4.07187 8.21562 4 8.00437 4ZM11.2544 8C11.0431 8 10.8646 8.07188 10.7188 8.21563C10.5729 8.35938 10.5 8.5375 10.5 8.75V10C10.5 10 11.8542 8.76875 12 8.625C12 8.5 11.9285 8.35938 11.7856 8.21563C11.6427 8.07188 11.4656 8 11.2544 8Z"
fill={color}
/>
</svg>
);
};