update timeouts for switching layouts

This commit is contained in:
Nate Kelley 2025-04-01 12:34:26 -06:00
parent f417ada44a
commit e2f5e64475
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
7 changed files with 225 additions and 38 deletions

View File

@ -20,7 +20,7 @@ import {
DropdownMenuLink
} from './DropdownBase';
import { CircleSpinnerLoader } from '../loaders/CircleSpinnerLoader';
import { useMemoizedFn, useMount } from '@/hooks';
import { useMemoizedFn } from '@/hooks';
import { cn } from '@/lib/classMerge';
import { Input } from '../inputs/Input';
import { useDebounceSearch } from '@/hooks';
@ -159,6 +159,8 @@ export const DropdownContent = <T,>({
debounceTime: 50
});
console.log(filteredItems, searchText);
const hasShownItem = useMemo(() => {
return filteredItems.length > 0 && filteredItems.some((item) => (item as DropdownItem).value);
}, [filteredItems]);
@ -264,13 +266,13 @@ export const DropdownContent = <T,>({
return (
<DropdownItemSelector
item={item as DropdownItems<T>[number]}
key={dropdownItemKey(item, hotkeyIndex)}
item={item}
index={hotkeyIndex}
selectType={selectType}
onSelect={onSelect}
onSelectItem={onSelectItem}
closeOnSelect={closeOnSelect}
key={dropdownItemKey(item, hotkeyIndex)}
showIndex={showIndex}
/>
);
@ -419,7 +421,7 @@ const DropdownItem = <T,>({
}
//I do not think this selected check is stable... look into refactoring
if (selectType === 'single' || selected) {
if (selectType === 'single') {
return (
<DropdownMenuCheckboxItemSingle
checked={selected}

View File

@ -2,9 +2,10 @@
import type { Meta, StoryObj } from '@storybook/react';
import { SelectMultiple } from './SelectMultiple';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { type SelectItem } from './Select';
import { fn } from '@storybook/test';
import { faker } from '@faker-js/faker';
const meta = {
title: 'UI/Select/SelectMultiple',
@ -113,19 +114,40 @@ export const CustomWidth: Story = {
)
};
const WithHundredItemsWithHooks = () => {
const [value, setValue] = useState<string[]>([]);
const items = useMemo(
() =>
Array.from({ length: 100 }, (_, index) => ({
value: index.toString(),
label: faker.company.name()
})),
[]
);
const handleSelect = (selectedValues: string[]) => {
setValue(selectedValues);
};
return (
<div className="w-[300px]">
<SelectMultiple
items={items}
onChange={handleSelect}
placeholder="Select multiple options..."
value={value}
/>
</div>
);
};
export const WithHundredItems: Story = {
args: {
items: Array.from({ length: 100 }, (_, index) => ({
value: index.toString(),
label: `Option ${index + 1}`
})),
items: [],
value: [],
onChange: fn(),
placeholder: 'Select multiple options...'
},
render: (args) => (
<div className="w-[300px]">
<SelectMultiple {...args} />
</div>
)
render: () => <WithHundredItemsWithHooks />
};

View File

@ -16,6 +16,7 @@ interface SelectMultipleProps extends VariantProps<typeof selectVariants> {
placeholder?: string;
value: string[];
disabled?: boolean;
useSearch?: boolean;
}
export const SelectMultiple: React.FC<SelectMultipleProps> = React.memo(
@ -27,7 +28,8 @@ export const SelectMultiple: React.FC<SelectMultipleProps> = React.memo(
size = 'default',
variant = 'default',
value,
disabled
disabled,
useSearch = true
}) => {
const selectedRecord = useMemo(() => {
return itemsProp.reduce<Record<string, boolean>>((acc, item) => {
@ -72,6 +74,7 @@ export const SelectMultiple: React.FC<SelectMultipleProps> = React.memo(
<Dropdown
items={items}
onSelect={handleSelect}
menuHeader={useSearch ? 'Search...' : undefined}
selectType="multiple"
align="start"
modal={false}

View File

@ -1,8 +1,13 @@
import type { FileType, AllFileTypes } from '@/api/asset_interfaces';
import { BusterRoutes, createBusterRoute } from '@/routes';
import type { FileView } from './useLayoutConfig';
import type {
DashboardFileViewSecondary,
FileView,
FileViewSecondary,
MetricFileViewSecondary
} from './useLayoutConfig';
const chatRouteRecord: Record<AllFileTypes, (chatId: string, assetId: string) => string> = {
const chatRouteRecord: Record<AllFileTypes, (chatId: string, assetId: string) => string | null> = {
collection: (chatId, assetId) =>
createBusterRoute({
route: BusterRoutes.APP_CHAT_ID_COLLECTION_ID,
@ -48,18 +53,57 @@ const chatRouteRecord: Record<AllFileTypes, (chatId: string, assetId: string) =>
empty: () => ''
};
const assetRouteRecord: Record<AllFileTypes, (assetId: string) => string | null> = {
collection: (assetId) =>
createBusterRoute({
route: BusterRoutes.APP_COLLECTIONS_ID,
collectionId: assetId
}),
dataset: (assetId) =>
createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID,
datasetId: assetId
}),
metric: (assetId) =>
createBusterRoute({
route: BusterRoutes.APP_METRIC_ID,
metricId: assetId
}),
dashboard: (assetId) =>
createBusterRoute({
route: BusterRoutes.APP_DASHBOARD_ID,
dashboardId: assetId
}),
term: (assetId) =>
createBusterRoute({
route: BusterRoutes.APP_TERMS_ID,
termId: assetId
}),
value: (assetId) =>
createBusterRoute({
route: BusterRoutes.APP_VALUE_ID,
valueId: assetId
}),
reasoning: () => null,
empty: () => null
};
export const createChatAssetRoute = ({
chatId,
assetId,
type
}: {
chatId: string;
chatId: string | undefined;
assetId: string;
type: FileType;
}) => {
const routeBuilder = chatRouteRecord[type];
if (!routeBuilder) return null;
return routeBuilder(chatId, assetId);
if (chatId) return routeBuilder(chatId, assetId);
const assetRouteBuilder = assetRouteRecord[type];
if (!assetRouteBuilder) return null;
return assetRouteBuilder(assetId);
};
const routeToFileView: Partial<Record<BusterRoutes, FileView>> = {
@ -86,3 +130,88 @@ export const DEFAULT_FILE_VIEW: Record<FileType, FileView> = {
// term: 'results',
// dataset: 'results',
};
export const assetParamsToRoute = ({
chatId,
assetId,
type,
secondaryView: secondaryViewProp
}: {
chatId: string | undefined;
assetId: string;
type: FileType;
secondaryView?: FileViewSecondary;
}) => {
if (type === 'metric') {
const secondaryView = secondaryViewProp as MetricFileViewSecondary | undefined;
if (chatId) {
switch (secondaryView) {
case 'chart-edit':
return createBusterRoute({
route: BusterRoutes.APP_CHAT_ID_METRIC_ID_CHART,
chatId,
metricId: assetId
});
case 'sql-edit':
return createBusterRoute({
route: BusterRoutes.APP_CHAT_ID_METRIC_ID_RESULTS,
chatId,
metricId: assetId
});
case 'version-history':
return createBusterRoute({
route: BusterRoutes.APP_CHAT_ID_METRIC_ID_CHART,
chatId,
metricId: assetId
});
default:
const test: never | undefined = secondaryView;
return '';
}
}
switch (secondaryView) {
case 'chart-edit':
return createBusterRoute({
route: BusterRoutes.APP_METRIC_ID_CHART,
metricId: assetId
});
case 'sql-edit':
return createBusterRoute({
route: BusterRoutes.APP_METRIC_ID_RESULTS,
metricId: assetId
});
case 'version-history':
return createBusterRoute({
route: BusterRoutes.APP_METRIC_ID_CHART,
metricId: assetId
});
default:
const test: never | undefined = secondaryView;
return '';
}
}
if (type === 'dashboard') {
const secondaryView = secondaryViewProp as DashboardFileViewSecondary | undefined;
if (chatId) {
switch (secondaryView) {
case 'version-history':
return createBusterRoute({
route: BusterRoutes.APP_CHAT_ID_DASHBOARD_ID,
chatId,
dashboardId: assetId
});
}
}
return createBusterRoute({
route: BusterRoutes.APP_DASHBOARD_ID,
dashboardId: assetId
});
}
console.warn('Asset params to route has not been implemented for this file type', type);
return createChatAssetRoute({ chatId, assetId, type }) || '';
};

View File

@ -88,7 +88,7 @@ export const useLayoutConfig = ({
if (secondaryView) {
animateOpenSplitter('right');
await timeout(250); //wait for splitter to close before opening secondary view
await timeout(chatId ? 250 : 0); //wait for splitter to close before opening secondary view
} else if (chatId) {
animateOpenSplitter('both');
}

View File

@ -6,12 +6,13 @@ import { useTransition } from 'react';
export const useCloseVersionHistory = () => {
const [isPending, startTransition] = useTransition();
const chatId = useChatLayoutContextSelector((x) => x.chatId);
const onChangeQueryParams = useAppLayoutContextSelector((x) => x.onChangeQueryParams);
const closeSecondaryView = useChatLayoutContextSelector((x) => x.closeSecondaryView);
const removeVersionHistoryQueryParams = useMemoizedFn(async () => {
closeSecondaryView();
await timeout(250);
await timeout(chatId ? 250 : 0);
startTransition(() => {
onChangeQueryParams({ metric_version_number: null, dashboard_version_number: null });
});

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { FileContainerButtonsProps } from '../interfaces';
import { MetricFileViewSecondary, useChatLayoutContextSelector } from '../../../ChatLayoutContext';
import { useMemoizedFn } from '@/hooks';
@ -14,6 +14,12 @@ import { SquareChartPen, SquareCode } from '@/components/ui/icons';
import { useGetMetric } from '@/api/buster_rest/metrics';
import { ThreeDotMenuButton } from './MetricThreeDotMenu';
import { canEdit, getIsEffectiveOwner } from '@/lib/share';
import Link from 'next/link';
import { BusterRoutes, createBusterRoute } from '@/routes';
import {
assetParamsToRoute,
createChatAssetRoute
} from '@/layouts/ChatLayout/ChatLayoutContext/helpers';
export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> = React.memo(() => {
const selectedLayout = useChatLayoutContextSelector((x) => x.selectedLayout);
@ -32,8 +38,8 @@ export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> =
return (
<FileButtonContainer>
{isEditor && <EditChartButton />}
{isEffectiveOwner && <EditSQLButton />}
{isEditor && <EditChartButton metricId={metricId} />}
{isEffectiveOwner && <EditSQLButton metricId={metricId} />}
<SaveToCollectionButton metricId={metricId} />
<SaveToDashboardButton metricId={metricId} />
{isEffectiveOwner && <ShareMetricButton metricId={metricId} />}
@ -47,50 +53,74 @@ export const MetricContainerHeaderButtons: React.FC<FileContainerButtonsProps> =
MetricContainerHeaderButtons.displayName = 'MetricContainerHeaderButtons';
const EditChartButton = React.memo(() => {
const EditChartButton = React.memo(({ metricId }: { metricId: string }) => {
const selectedFileViewSecondary = useChatLayoutContextSelector(
(x) => x.selectedFileViewSecondary
);
const chatId = useChatIndividualContextSelector((x) => x.chatId);
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const editableSecondaryView: MetricFileViewSecondary = 'chart-edit';
const isSelectedView = selectedFileViewSecondary === editableSecondaryView;
const href = useMemo(() => {
return assetParamsToRoute({
chatId,
assetId: metricId,
type: 'metric',
secondaryView: 'chart-edit'
});
}, [chatId, metricId]);
const onClickButton = useMemoizedFn(() => {
const secondaryView = isSelectedView ? null : editableSecondaryView;
onSetFileView({ secondaryView, fileView: 'chart' });
});
return (
<SelectableButton
tooltipText="Edit chart"
icon={<SquareChartPen />}
onClick={onClickButton}
selected={isSelectedView}
/>
<Link href={href}>
<SelectableButton
tooltipText="Edit chart"
icon={<SquareChartPen />}
onClick={onClickButton}
selected={isSelectedView}
/>
</Link>
);
});
EditChartButton.displayName = 'EditChartButton';
const EditSQLButton = React.memo(() => {
const EditSQLButton = React.memo(({ metricId }: { metricId: string }) => {
const selectedFileViewSecondary = useChatLayoutContextSelector(
(x) => x.selectedFileViewSecondary
);
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const chatId = useChatIndividualContextSelector((x) => x.chatId);
const editableSecondaryView: MetricFileViewSecondary = 'sql-edit';
const isSelectedView = selectedFileViewSecondary === editableSecondaryView;
const href = useMemo(() => {
return assetParamsToRoute({
chatId,
assetId: metricId,
type: 'metric',
secondaryView: 'sql-edit'
});
}, [chatId, metricId]);
const onClickButton = useMemoizedFn(() => {
const secondaryView = isSelectedView ? null : editableSecondaryView;
onSetFileView({ secondaryView, fileView: 'results' });
});
return (
<SelectableButton
tooltipText="SQL editor"
icon={<SquareCode />}
onClick={onClickButton}
selected={isSelectedView}
/>
<Link href={href}>
<SelectableButton
tooltipText="SQL editor"
icon={<SquareCode />}
onClick={onClickButton}
selected={isSelectedView}
/>
</Link>
);
});
EditSQLButton.displayName = 'EditSQLButton';