mirror of https://github.com/buster-so/buster.git
update timeouts for switching layouts
This commit is contained in:
parent
f417ada44a
commit
e2f5e64475
|
@ -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}
|
||||
|
|
|
@ -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 />
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 }) || '';
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue