Merge branch 'dev' of https://github.com/umami-software/umami into dev
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
Select,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBoardQuery, useMessages, useNavigation, useUpdateQuery } from '@/components/hooks';
|
||||
import { LinkSelect } from '@/components/input/LinkSelect';
|
||||
import { PixelSelect } from '@/components/input/PixelSelect';
|
||||
@@ -58,7 +57,7 @@ export function BoardEditForm({
|
||||
const { t, labels, messages, getErrorMessage } = useMessages();
|
||||
const { teamId: navigationTeamId } = useNavigation();
|
||||
const resolvedTeamId = teamId ?? navigationTeamId;
|
||||
const { data, isLoading } = useBoardQuery(boardId || '');
|
||||
const { data: board, isLoading } = useBoardQuery(boardId || '');
|
||||
const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(
|
||||
boardId ? `/boards/${boardId}` : '/boards',
|
||||
{
|
||||
@@ -66,20 +65,14 @@ export function BoardEditForm({
|
||||
teamId: resolvedTeamId,
|
||||
},
|
||||
);
|
||||
const [values, setValues] = useState<BoardFormValues>(getDefaultValues());
|
||||
const values = getDefaultValues(board);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setValues(getDefaultValues(data));
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const handleSubmit = async (data: BoardFormValues) => {
|
||||
const result = await mutateAsync({
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
type: values.type,
|
||||
parameters: setBoardEntity({}, values.type, values.entityId || undefined),
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
type: data.type,
|
||||
parameters: setBoardEntity(board?.parameters, data.type, data.entityId || undefined),
|
||||
});
|
||||
|
||||
toast(t(messages.saved));
|
||||
@@ -89,111 +82,100 @@ export function BoardEditForm({
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
const handleNameChange = (name: string) => {
|
||||
setValues(current => ({ ...current, name }));
|
||||
};
|
||||
|
||||
const handleDescriptionChange = (description: string) => {
|
||||
setValues(current => ({ ...current, description }));
|
||||
};
|
||||
|
||||
const handleTypeChange = (type: string) => {
|
||||
setValues(current => ({
|
||||
...current,
|
||||
type: type as BoardType,
|
||||
entityId: '',
|
||||
}));
|
||||
};
|
||||
|
||||
const handleEntityChange = (entityId: string) => {
|
||||
setValues(current => ({ ...current, entityId }));
|
||||
};
|
||||
|
||||
if (boardId && isLoading) {
|
||||
return <Loading placement="absolute" />;
|
||||
}
|
||||
|
||||
const entityLabel =
|
||||
values.type === BOARD_TYPES.pixel
|
||||
? t(labels.pixel)
|
||||
: values.type === BOARD_TYPES.link
|
||||
? t(labels.link)
|
||||
: t(labels.website);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)} values={values}>
|
||||
<FormField name="name" label={t(labels.name)} rules={{ required: t(labels.required) }}>
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
autoFocus={!boardId}
|
||||
value={values.name}
|
||||
placeholder={t(labels.untitled)}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField name="description" label={t(labels.description)}>
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
asTextArea
|
||||
resize="vertical"
|
||||
value={values.description}
|
||||
placeholder={t(labels.addDescription)}
|
||||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
name="type"
|
||||
label={t(labels.boardType)}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<Box width="100%" maxWidth="360px">
|
||||
<Select value={values.type} onChange={handleTypeChange}>
|
||||
<ListItem id={BOARD_TYPES.mixed}>{t(labels.open)}</ListItem>
|
||||
<ListItem id={BOARD_TYPES.website}>{t(labels.website)}</ListItem>
|
||||
<ListItem id={BOARD_TYPES.pixel}>{t(labels.pixel)}</ListItem>
|
||||
<ListItem id={BOARD_TYPES.link}>{t(labels.link)}</ListItem>
|
||||
</Select>
|
||||
</Box>
|
||||
</FormField>
|
||||
{requiresBoardEntity(values.type) && (
|
||||
<FormField
|
||||
name="entityId"
|
||||
label={entityLabel}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<Box width="100%" maxWidth="360px">
|
||||
{values.type === BOARD_TYPES.website ? (
|
||||
<WebsiteSelect
|
||||
websiteId={values.entityId}
|
||||
teamId={resolvedTeamId}
|
||||
onChange={handleEntityChange}
|
||||
/>
|
||||
) : values.type === BOARD_TYPES.pixel ? (
|
||||
<PixelSelect
|
||||
pixelId={values.entityId}
|
||||
teamId={resolvedTeamId}
|
||||
placeholder={t(labels.selectPixel)}
|
||||
onChange={handleEntityChange}
|
||||
/>
|
||||
) : (
|
||||
<LinkSelect
|
||||
linkId={values.entityId}
|
||||
teamId={resolvedTeamId}
|
||||
placeholder={t(labels.selectLink)}
|
||||
onChange={handleEntityChange}
|
||||
{({ watch, setValue }) => {
|
||||
const type = watch('type') as BoardType;
|
||||
const entityId = watch('entityId') as string;
|
||||
const entityLabel =
|
||||
type === BOARD_TYPES.pixel
|
||||
? t(labels.pixel)
|
||||
: type === BOARD_TYPES.link
|
||||
? t(labels.link)
|
||||
: t(labels.website);
|
||||
|
||||
const handleTypeChange = (value: string) => {
|
||||
setValue('type', value as BoardType, { shouldDirty: true });
|
||||
setValue('entityId', '', { shouldDirty: true });
|
||||
};
|
||||
|
||||
const handleEntityChange = (value: string) => {
|
||||
setValue('entityId', value, { shouldDirty: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField name="name" label={t(labels.name)} rules={{ required: t(labels.required) }}>
|
||||
<TextField autoComplete="off" autoFocus={!boardId} placeholder={t(labels.untitled)} />
|
||||
</FormField>
|
||||
<FormField name="description" label={t(labels.description)}>
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
asTextArea
|
||||
resize="vertical"
|
||||
placeholder={t(labels.addDescription)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
name="type"
|
||||
label={t(labels.boardType)}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<Box width="100%" maxWidth="360px">
|
||||
<Select value={type} onChange={handleTypeChange}>
|
||||
<ListItem id={BOARD_TYPES.mixed}>{t(labels.open)}</ListItem>
|
||||
<ListItem id={BOARD_TYPES.website}>{t(labels.website)}</ListItem>
|
||||
<ListItem id={BOARD_TYPES.pixel}>{t(labels.pixel)}</ListItem>
|
||||
<ListItem id={BOARD_TYPES.link}>{t(labels.link)}</ListItem>
|
||||
</Select>
|
||||
</Box>
|
||||
</FormField>
|
||||
{requiresBoardEntity(type) && (
|
||||
<FormField
|
||||
name="entityId"
|
||||
label={entityLabel}
|
||||
rules={{ required: t(labels.required) }}
|
||||
>
|
||||
<Box width="100%" maxWidth="360px">
|
||||
{type === BOARD_TYPES.website ? (
|
||||
<WebsiteSelect
|
||||
websiteId={entityId}
|
||||
teamId={resolvedTeamId}
|
||||
onChange={handleEntityChange}
|
||||
/>
|
||||
) : type === BOARD_TYPES.pixel ? (
|
||||
<PixelSelect
|
||||
pixelId={entityId}
|
||||
teamId={resolvedTeamId}
|
||||
placeholder={t(labels.selectPixel)}
|
||||
onChange={handleEntityChange}
|
||||
/>
|
||||
) : (
|
||||
<LinkSelect
|
||||
linkId={entityId}
|
||||
teamId={resolvedTeamId}
|
||||
placeholder={t(labels.selectLink)}
|
||||
onChange={handleEntityChange}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</FormField>
|
||||
)}
|
||||
</Box>
|
||||
</FormField>
|
||||
)}
|
||||
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
||||
{onClose && (
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
)}
|
||||
<FormSubmitButton isDisabled={isPending}>{t(labels.save)}</FormSubmitButton>
|
||||
</Row>
|
||||
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
||||
{onClose && (
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{t(labels.cancel)}
|
||||
</Button>
|
||||
)}
|
||||
<FormSubmitButton isDisabled={isPending}>{t(labels.save)}</FormSubmitButton>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ export function BoardComponentSelect({
|
||||
|
||||
const needsWebsite = selectedDef?.requiresWebsite !== false;
|
||||
const isOpenType = isOpenBoardType(boardType);
|
||||
const hasSelectedEntity = isOpenType ? !!selectedEntityId : !!boardEntityId;
|
||||
const resolvedEntityType = needsWebsite
|
||||
? isOpenType
|
||||
? selectedEntityType
|
||||
@@ -192,69 +193,6 @@ export function BoardComponentSelect({
|
||||
return (
|
||||
<Column gap="4">
|
||||
<Row gap="4" style={{ height: 600 }}>
|
||||
<Column gap="1" style={{ width: 280, flexShrink: 0, overflowY: 'auto' }}>
|
||||
{CATEGORIES.map(category => {
|
||||
const components = getComponentsByCategory(category.key).filter(def =>
|
||||
isBoardComponentSupported(def.type, activeEntityType) ||
|
||||
def.type === selectedDef?.type,
|
||||
);
|
||||
|
||||
if (!components.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Column key={category.key} gap="1" marginBottom="2">
|
||||
<Text weight="bold">{category.name}</Text>
|
||||
{components.map(def => (
|
||||
<Focusable key={def.type}>
|
||||
<Row
|
||||
alignItems="center"
|
||||
paddingX="3"
|
||||
paddingY="2"
|
||||
borderRadius
|
||||
backgroundColor={
|
||||
selectedDef?.type === def.type ? 'surface-sunken' : undefined
|
||||
}
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleSelectComponent(def)}
|
||||
>
|
||||
<Column>
|
||||
<Text
|
||||
size="sm"
|
||||
weight={selectedDef?.type === def.type ? 'bold' : undefined}
|
||||
>
|
||||
{def.name}
|
||||
</Text>
|
||||
<Text size="xs" color="muted">
|
||||
{def.description}
|
||||
</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
</Focusable>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
})}
|
||||
</Column>
|
||||
|
||||
<Column gap="3" flexGrow={1} style={{ minWidth: 0 }}>
|
||||
<Panel maxHeight="100%">
|
||||
{previewConfig && (!needsWebsite || resolvedEntityId) ? (
|
||||
<BoardComponentRenderer config={previewConfig} websiteId={resolvedEntityId} />
|
||||
) : (
|
||||
<Column alignItems="center" justifyContent="center" height="100%">
|
||||
<Text color="muted">
|
||||
{resolvedEntityId
|
||||
? t(messages.selectComponentPreview)
|
||||
: t(messages.selectBoardEntityFirst)}
|
||||
</Text>
|
||||
</Column>
|
||||
)}
|
||||
</Panel>
|
||||
</Column>
|
||||
|
||||
<Column gap="3" style={{ width: 320, flexShrink: 0, overflowY: 'auto' }}>
|
||||
<Text weight="bold">{t(labels.properties)}</Text>
|
||||
|
||||
@@ -367,6 +305,81 @@ export function BoardComponentSelect({
|
||||
</Column>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
<Column gap="3" style={{ width: 280, flexShrink: 0, minWidth: 0 }}>
|
||||
<Panel maxHeight="100%">
|
||||
{hasSelectedEntity ? (
|
||||
<Column gap="1" height="100%" style={{ overflowY: 'auto' }}>
|
||||
{CATEGORIES.map(category => {
|
||||
const components = getComponentsByCategory(category.key).filter(def =>
|
||||
isBoardComponentSupported(def.type, activeEntityType) ||
|
||||
def.type === selectedDef?.type,
|
||||
);
|
||||
|
||||
if (!components.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Column key={category.key} gap="1" marginBottom="2">
|
||||
<Text weight="bold">{category.name}</Text>
|
||||
{components.map(def => (
|
||||
<Focusable key={def.type}>
|
||||
<Row
|
||||
alignItems="center"
|
||||
paddingX="3"
|
||||
paddingY="2"
|
||||
borderRadius
|
||||
backgroundColor={
|
||||
selectedDef?.type === def.type ? 'surface-sunken' : undefined
|
||||
}
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleSelectComponent(def)}
|
||||
>
|
||||
<Column>
|
||||
<Text
|
||||
size="sm"
|
||||
weight={selectedDef?.type === def.type ? 'bold' : undefined}
|
||||
>
|
||||
{def.name}
|
||||
</Text>
|
||||
<Text size="xs" color="muted">
|
||||
{def.description}
|
||||
</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
</Focusable>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
})}
|
||||
</Column>
|
||||
) : (
|
||||
<Column alignItems="center" justifyContent="center" height="100%">
|
||||
<Text color="muted">{t(messages.selectBoardEntityFirst)}</Text>
|
||||
</Column>
|
||||
)}
|
||||
</Panel>
|
||||
</Column>
|
||||
|
||||
<Column gap="3" flexGrow={1} style={{ minWidth: 0 }}>
|
||||
<Panel maxHeight="100%">
|
||||
{hasSelectedEntity && previewConfig && (!needsWebsite || resolvedEntityId) ? (
|
||||
<BoardComponentRenderer config={previewConfig} websiteId={resolvedEntityId} />
|
||||
) : (
|
||||
<Column alignItems="center" justifyContent="center" height="100%">
|
||||
<Text color="muted">
|
||||
{!hasSelectedEntity
|
||||
? t(messages.selectBoardEntityFirst)
|
||||
: resolvedEntityId
|
||||
? t(messages.selectComponentPreview)
|
||||
: t(messages.selectBoardEntityFirst)}
|
||||
</Text>
|
||||
</Column>
|
||||
)}
|
||||
</Panel>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row justifyContent="flex-end" gap="2" paddingTop="4">
|
||||
|
||||
@@ -30,13 +30,13 @@ function BoardShareDialogContent({
|
||||
}) {
|
||||
const { t, labels, messages } = useMessages();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const showCreateForm = !hasShares || isCreating;
|
||||
const showCreateForm = isCreating;
|
||||
|
||||
return (
|
||||
<Column gap="4">
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<Heading>{t(labels.share)}</Heading>
|
||||
{hasShares && !isCreating && (
|
||||
{!isCreating && (
|
||||
<Button variant="primary" onPress={() => setIsCreating(true)}>
|
||||
<IconLabel icon={<Plus size={16} />} label={t(labels.add)} />
|
||||
</Button>
|
||||
@@ -46,17 +46,18 @@ function BoardShareDialogContent({
|
||||
<BoardShareCreateForm
|
||||
boardId={boardId}
|
||||
onSave={() => setIsCreating(false)}
|
||||
onCancel={hasShares ? () => setIsCreating(false) : undefined}
|
||||
onCancel={() => setIsCreating(false)}
|
||||
/>
|
||||
)}
|
||||
{hasShares ? (
|
||||
<>
|
||||
<Text>{t(messages.shareUrl)}</Text>
|
||||
<BoardSharesTable data={shares} />
|
||||
</>
|
||||
) : (
|
||||
!showCreateForm && <Text color="muted">{t(messages.noDataAvailable)}</Text>
|
||||
{hasShares && (
|
||||
<Text>{t(messages.shareUrl)}</Text>
|
||||
)}
|
||||
{!showCreateForm &&
|
||||
(hasShares ? (
|
||||
<BoardSharesTable data={shares} />
|
||||
) : (
|
||||
<Text color="muted">{t(messages.noDataAvailable)}</Text>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ export function BoardEditPage({ boardId }: { boardId: string }) {
|
||||
<Column
|
||||
margin="2"
|
||||
width="100%"
|
||||
style={{ minWidth: 'min(760px, 100%)', marginInline: 'auto' }}
|
||||
maxWidth="800px"
|
||||
style={{ marginInline: 'auto' }}
|
||||
>
|
||||
<>
|
||||
<Column marginTop="6">
|
||||
|
||||
@@ -30,13 +30,13 @@ function LinkShareFormContent({
|
||||
}) {
|
||||
const { t, labels, messages } = useMessages();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const showCreateForm = !hasShares || isCreating;
|
||||
const showCreateForm = isCreating;
|
||||
|
||||
return (
|
||||
<Column gap="4">
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<Heading>{t(labels.share)}</Heading>
|
||||
{hasShares && !isCreating && (
|
||||
{!isCreating && (
|
||||
<Button variant="primary" onPress={() => setIsCreating(true)}>
|
||||
<IconLabel icon={<Plus size={16} />} label={t(labels.add)} />
|
||||
</Button>
|
||||
@@ -46,17 +46,18 @@ function LinkShareFormContent({
|
||||
<SimpleShareCreateForm
|
||||
createPath={`/links/${linkId}/shares`}
|
||||
onSave={() => setIsCreating(false)}
|
||||
onCancel={hasShares ? () => setIsCreating(false) : undefined}
|
||||
onCancel={() => setIsCreating(false)}
|
||||
/>
|
||||
)}
|
||||
{hasShares ? (
|
||||
<>
|
||||
<Text>{t(messages.shareUrl)}</Text>
|
||||
<SimpleSharesTable data={shares} />
|
||||
</>
|
||||
) : (
|
||||
!showCreateForm && <Text color="muted">{t(messages.noDataAvailable)}</Text>
|
||||
{hasShares && (
|
||||
<Text>{t(messages.shareUrl)}</Text>
|
||||
)}
|
||||
{!showCreateForm &&
|
||||
(hasShares ? (
|
||||
<SimpleSharesTable data={shares} />
|
||||
) : (
|
||||
<Text color="muted">{t(messages.noDataAvailable)}</Text>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ArrowLeft, Link as LinkIcon } from '@/components/icons';
|
||||
export function LinkEditPage({ linkId }: { linkId: string }) {
|
||||
return (
|
||||
<LinkProvider linkId={linkId}>
|
||||
<Column margin="2">
|
||||
<Column margin="2" width="100%" maxWidth="800px" style={{ marginInline: 'auto' }}>
|
||||
<LinkEditHeader />
|
||||
<Column gap="6">
|
||||
<Panel>
|
||||
|
||||
@@ -30,13 +30,13 @@ function PixelShareFormContent({
|
||||
}) {
|
||||
const { t, labels, messages } = useMessages();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const showCreateForm = !hasShares || isCreating;
|
||||
const showCreateForm = isCreating;
|
||||
|
||||
return (
|
||||
<Column gap="4">
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<Heading>{t(labels.share)}</Heading>
|
||||
{hasShares && !isCreating && (
|
||||
{!isCreating && (
|
||||
<Button variant="primary" onPress={() => setIsCreating(true)}>
|
||||
<IconLabel icon={<Plus size={16} />} label={t(labels.add)} />
|
||||
</Button>
|
||||
@@ -46,17 +46,18 @@ function PixelShareFormContent({
|
||||
<SimpleShareCreateForm
|
||||
createPath={`/pixels/${pixelId}/shares`}
|
||||
onSave={() => setIsCreating(false)}
|
||||
onCancel={hasShares ? () => setIsCreating(false) : undefined}
|
||||
onCancel={() => setIsCreating(false)}
|
||||
/>
|
||||
)}
|
||||
{hasShares ? (
|
||||
<>
|
||||
<Text>{t(messages.shareUrl)}</Text>
|
||||
<SimpleSharesTable data={shares} />
|
||||
</>
|
||||
) : (
|
||||
!showCreateForm && <Text color="muted">{t(messages.noDataAvailable)}</Text>
|
||||
{hasShares && (
|
||||
<Text>{t(messages.shareUrl)}</Text>
|
||||
)}
|
||||
{!showCreateForm &&
|
||||
(hasShares ? (
|
||||
<SimpleSharesTable data={shares} />
|
||||
) : (
|
||||
<Text color="muted">{t(messages.noDataAvailable)}</Text>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ArrowLeft, Grid2x2 } from '@/components/icons';
|
||||
export function PixelEditPage({ pixelId }: { pixelId: string }) {
|
||||
return (
|
||||
<PixelProvider pixelId={pixelId}>
|
||||
<Column margin="2">
|
||||
<Column margin="2" width="100%" maxWidth="800px" style={{ marginInline: 'auto' }}>
|
||||
<PixelEditHeader />
|
||||
<Column gap="6">
|
||||
<Panel>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { WebsiteProvider } from '@/app/(main)/websites/WebsiteProvider';
|
||||
export function WebsiteSettingsPage({ websiteId }: { websiteId: string }) {
|
||||
return (
|
||||
<WebsiteProvider websiteId={websiteId}>
|
||||
<Column margin="2">
|
||||
<Column margin="2" width="100%" maxWidth="800px" style={{ marginInline: 'auto' }}>
|
||||
<WebsiteSettingsHeader />
|
||||
<WebsiteSettings websiteId={websiteId} />
|
||||
</Column>
|
||||
|
||||
@@ -2,6 +2,10 @@ import {
|
||||
BOARD_ENTITY_TYPES,
|
||||
isBoardComponentSupported,
|
||||
} from '../boards';
|
||||
import {
|
||||
BOARD_COMPONENT_COMPATIBILITY_MATRIX,
|
||||
getSupportedBoardComponentEntityTypes,
|
||||
} from '../boardComponentCompatibility';
|
||||
|
||||
test('isBoardComponentSupported allows events chart on website boards', () => {
|
||||
expect(isBoardComponentSupported('EventsChart', BOARD_ENTITY_TYPES.website)).toBe(true);
|
||||
@@ -12,6 +16,13 @@ test('isBoardComponentSupported rejects events chart on pixel and link boards',
|
||||
expect(isBoardComponentSupported('EventsChart', BOARD_ENTITY_TYPES.link)).toBe(false);
|
||||
});
|
||||
|
||||
test('board component compatibility matrix defines website-only components explicitly', () => {
|
||||
expect(BOARD_COMPONENT_COMPATIBILITY_MATRIX.EventsChart).toEqual([BOARD_ENTITY_TYPES.website]);
|
||||
expect(getSupportedBoardComponentEntityTypes('EventsChart')).toEqual([
|
||||
BOARD_ENTITY_TYPES.website,
|
||||
]);
|
||||
});
|
||||
|
||||
test('isBoardComponentSupported leaves other components available for all board entities', () => {
|
||||
expect(isBoardComponentSupported('WebsiteChart', BOARD_ENTITY_TYPES.pixel)).toBe(true);
|
||||
expect(isBoardComponentSupported('TextBlock', BOARD_ENTITY_TYPES.link)).toBe(true);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { BoardEntityType } from './boards';
|
||||
|
||||
export const BOARD_COMPONENT_COMPATIBILITY_MATRIX = {
|
||||
EventsChart: ['website'],
|
||||
} as const satisfies Partial<Record<string, readonly BoardEntityType[]>>;
|
||||
|
||||
export function getSupportedBoardComponentEntityTypes(componentType: string) {
|
||||
return BOARD_COMPONENT_COMPATIBILITY_MATRIX[componentType];
|
||||
}
|
||||
|
||||
export function isBoardComponentSupportedByEntityType(
|
||||
componentType: string,
|
||||
entityType?: BoardEntityType,
|
||||
) {
|
||||
const supportedEntityTypes = getSupportedBoardComponentEntityTypes(componentType);
|
||||
|
||||
if (!entityType || !supportedEntityTypes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return supportedEntityTypes.includes(entityType);
|
||||
}
|
||||
+2
-10
@@ -1,4 +1,5 @@
|
||||
import type { Board, BoardComponentConfig, BoardParameters } from './types';
|
||||
import { isBoardComponentSupportedByEntityType } from './boardComponentCompatibility';
|
||||
|
||||
export const BOARD_TYPES = {
|
||||
dashboard: 'dashboard',
|
||||
@@ -18,9 +19,6 @@ export type BoardType = (typeof BOARD_TYPES)[keyof typeof BOARD_TYPES];
|
||||
export type BoardEntityType = (typeof BOARD_ENTITY_TYPES)[keyof typeof BOARD_ENTITY_TYPES];
|
||||
|
||||
const boardTypes = new Set<string>(Object.values(BOARD_TYPES));
|
||||
const boardComponentEntityTypes: Partial<Record<string, readonly BoardEntityType[]>> = {
|
||||
EventsChart: [BOARD_ENTITY_TYPES.website],
|
||||
};
|
||||
|
||||
export function getLegacyBoardType(parameters?: BoardParameters): BoardType {
|
||||
if (parameters?.pixelId) {
|
||||
@@ -147,13 +145,7 @@ export function isBoardComponentSupported(
|
||||
componentType: string,
|
||||
entityType?: BoardEntityType,
|
||||
) {
|
||||
const supportedEntityTypes = boardComponentEntityTypes[componentType];
|
||||
|
||||
if (!entityType || !supportedEntityTypes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return supportedEntityTypes.includes(entityType);
|
||||
return isBoardComponentSupportedByEntityType(componentType, entityType);
|
||||
}
|
||||
|
||||
export function getFirstBoardComponentEntity(
|
||||
|
||||
Reference in New Issue
Block a user