constructor (UI elements basic)

This commit is contained in:
Flatlogic Bot 2026-03-18 06:43:39 +00:00
parent b4fe0dde81
commit 1b5c13c8ae

View File

@ -61,8 +61,11 @@ type CanvasElement = {
label: string;
xPercent: number;
yPercent: number;
iconUrl?: string;
galleryCards?: GalleryCard[];
carouselSlides?: CarouselSlide[];
carouselPrevIconUrl?: string;
carouselNextIconUrl?: string;
tooltipTitle?: string;
tooltipText?: string;
descriptionTitle?: string;
@ -258,12 +261,15 @@ const createDefaultElement = (type: CanvasElementType, index: number): CanvasEle
return {
...base,
carouselSlides: [{ id: createLocalId(), imageUrl: '', caption: 'Slide 1' }],
carouselPrevIconUrl: '',
carouselNextIconUrl: '',
};
}
if (type === 'tooltip') {
return {
...base,
iconUrl: '',
tooltipTitle: 'Tooltip title',
tooltipText: 'Tooltip text',
};
@ -272,6 +278,7 @@ const createDefaultElement = (type: CanvasElementType, index: number): CanvasEle
if (type === 'description') {
return {
...base,
iconUrl: '',
descriptionTitle: 'Description title',
descriptionText: 'Description text',
};
@ -281,6 +288,7 @@ const createDefaultElement = (type: CanvasElementType, index: number): CanvasEle
return {
...base,
navLabel: type === 'navigation_next' ? 'Forward' : 'Back',
iconUrl: '',
transitionReverseMode: 'auto_reverse',
transitionDurationSec: 0.7,
};
@ -429,6 +437,13 @@ const ConstructorPage = () => {
return videoAssetOptions;
}, [assets, videoAssetOptions]);
const iconAssetOptions = useMemo(
() =>
assets
.filter((asset) => asset.type === 'icon' && asset.asset_type === 'image' && getAssetSourceValue(asset))
.map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
[assets],
);
useEffect(() => {
if (newTransitionVideoUrl) return;
@ -570,6 +585,9 @@ const ConstructorPage = () => {
caption: String(slide?.caption || `Slide ${index + 1}`),
}))
: undefined,
iconUrl: typeof item.iconUrl === 'string' ? item.iconUrl : '',
carouselPrevIconUrl: typeof item.carouselPrevIconUrl === 'string' ? item.carouselPrevIconUrl : '',
carouselNextIconUrl: typeof item.carouselNextIconUrl === 'string' ? item.carouselNextIconUrl : '',
tooltipTitle: typeof item.tooltipTitle === 'string' ? item.tooltipTitle : '',
tooltipText: typeof item.tooltipText === 'string' ? item.tooltipText : '',
descriptionTitle: typeof item.descriptionTitle === 'string' ? item.descriptionTitle : '',
@ -951,7 +969,13 @@ const ConstructorPage = () => {
const targetPageName = element.targetPageId ? pageNameById[element.targetPageId] : '';
return (
<div className='flex flex-col items-start gap-1'>
<span>{element.navLabel || (element.type === 'navigation_next' ? 'Forward' : 'Back')}</span>
<div className='flex items-center gap-2'>
{element.iconUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={resolveAssetPlaybackUrl(element.iconUrl)} alt='Navigation icon' className='h-4 w-4 object-contain' />
) : null}
<span>{element.navLabel || (element.type === 'navigation_next' ? 'Forward' : 'Back')}</span>
</div>
{targetPageName ? <span className='text-[10px] text-gray-500'>To: {targetPageName}</span> : null}
</div>
);
@ -960,6 +984,10 @@ const ConstructorPage = () => {
if (element.type === 'tooltip') {
return (
<div className='max-w-[200px] text-left'>
{element.iconUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={resolveAssetPlaybackUrl(element.iconUrl)} alt='Tooltip icon' className='mb-1 h-5 w-5 object-contain' />
) : null}
<p className='text-[11px] font-bold'>{element.tooltipTitle || 'Tooltip title'}</p>
<p className='text-[10px] text-gray-600 line-clamp-3'>{element.tooltipText || 'Tooltip text'}</p>
</div>
@ -969,6 +997,10 @@ const ConstructorPage = () => {
if (element.type === 'description') {
return (
<div className='max-w-[220px] text-left'>
{element.iconUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={resolveAssetPlaybackUrl(element.iconUrl)} alt='Description icon' className='mb-1 h-5 w-5 object-contain' />
) : null}
<p className='text-[11px] font-bold'>{element.descriptionTitle || 'Description title'}</p>
<p className='text-[10px] text-gray-600 line-clamp-4'>{element.descriptionText || 'Description text'}</p>
</div>
@ -1018,6 +1050,32 @@ const ConstructorPage = () => {
)}
</div>
<p className='mt-1 text-[10px] text-gray-600 line-clamp-1'>{firstSlide?.caption || 'No caption'}</p>
{(element.carouselPrevIconUrl || element.carouselNextIconUrl) && (
<div className='mt-1 flex items-center justify-between text-[9px] text-gray-500'>
<span className='flex items-center gap-1'>
{element.carouselPrevIconUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolveAssetPlaybackUrl(element.carouselPrevIconUrl)}
alt='Previous icon'
className='h-3 w-3 object-contain'
/>
) : null}
Prev
</span>
<span className='flex items-center gap-1'>
Next
{element.carouselNextIconUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolveAssetPlaybackUrl(element.carouselNextIconUrl)}
alt='Next icon'
className='h-3 w-3 object-contain'
/>
) : null}
</span>
</div>
)}
</div>
);
}
@ -1419,6 +1477,25 @@ const ConstructorPage = () => {
onChange={(event) => updateSelectedElement({ navLabel: event.target.value })}
/>
</div>
<div>
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>Icon</label>
<select
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
value={selectedElement.iconUrl || ''}
onChange={(event) => updateSelectedElement({ iconUrl: event.target.value })}
>
<option value=''>Not selected</option>
{addFallbackAssetOption(
iconAssetOptions,
selectedElement.iconUrl,
`Current icon · ${selectedElement.iconUrl || ''}`,
).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div>
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>Target page</label>
<select
@ -1511,6 +1588,25 @@ const ConstructorPage = () => {
{selectedElement && selectedElement.type === 'tooltip' && (
<div className='space-y-2'>
<div>
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>Icon</label>
<select
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
value={selectedElement.iconUrl || ''}
onChange={(event) => updateSelectedElement({ iconUrl: event.target.value })}
>
<option value=''>Not selected</option>
{addFallbackAssetOption(
iconAssetOptions,
selectedElement.iconUrl,
`Current icon · ${selectedElement.iconUrl || ''}`,
).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div>
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>Tooltip title</label>
<input
@ -1533,6 +1629,25 @@ const ConstructorPage = () => {
{selectedElement && selectedElement.type === 'description' && (
<div className='space-y-2'>
<div>
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>Icon</label>
<select
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
value={selectedElement.iconUrl || ''}
onChange={(event) => updateSelectedElement({ iconUrl: event.target.value })}
>
<option value=''>Not selected</option>
{addFallbackAssetOption(
iconAssetOptions,
selectedElement.iconUrl,
`Current icon · ${selectedElement.iconUrl || ''}`,
).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div>
<label className='mb-1 block text-[11px] font-semibold text-gray-600'>Description title</label>
<input
@ -1653,6 +1768,41 @@ const ConstructorPage = () => {
{selectedElement && selectedElement.type === 'carousel' && (
<div className='space-y-2'>
<div className='rounded border border-gray-200 p-2 space-y-2'>
<p className='text-[11px] font-semibold text-gray-700'>Navigation icons</p>
<select
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
value={selectedElement.carouselPrevIconUrl || ''}
onChange={(event) => updateSelectedElement({ carouselPrevIconUrl: event.target.value })}
>
<option value=''>Previous icon</option>
{addFallbackAssetOption(
iconAssetOptions,
selectedElement.carouselPrevIconUrl,
`Current prev icon · ${selectedElement.carouselPrevIconUrl || ''}`,
).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<select
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
value={selectedElement.carouselNextIconUrl || ''}
onChange={(event) => updateSelectedElement({ carouselNextIconUrl: event.target.value })}
>
<option value=''>Next icon</option>
{addFallbackAssetOption(
iconAssetOptions,
selectedElement.carouselNextIconUrl,
`Current next icon · ${selectedElement.carouselNextIconUrl || ''}`,
).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div className='flex items-center justify-between'>
<p className='text-[11px] font-semibold text-gray-600'>Carousel slides</p>
<button type='button' className='text-xs text-blue-700 hover:underline' onClick={addCarouselSlide}>