added dimentions settings for gallery header image and cards previews
This commit is contained in:
parent
ca3b3725f9
commit
c235909c54
2
frontend/next-env.d.ts
vendored
2
frontend/next-env.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
/// <reference path="./.next/types/routes.d.ts" />
|
/// <reference path="./build/types/routes.d.ts" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -48,7 +48,9 @@ export function useProjectSelector({
|
|||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
'/projects?limit=100&page=0&sort=desc&field=updatedAt',
|
'/projects?limit=100&page=0&sort=desc&field=updatedAt',
|
||||||
);
|
);
|
||||||
const rows = Array.isArray(response?.data?.rows) ? response.data.rows : [];
|
const rows = Array.isArray(response?.data?.rows)
|
||||||
|
? response.data.rows
|
||||||
|
: [];
|
||||||
setProjects(rows);
|
setProjects(rows);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
|
|||||||
@ -91,7 +91,10 @@ const CanvasElement: React.FC<CanvasElementProps> = ({
|
|||||||
|
|
||||||
// Add transition for interactive effects (preview mode only)
|
// Add transition for interactive effects (preview mode only)
|
||||||
if (!isEditMode && hasAnyEffects(effectProperties)) {
|
if (!isEditMode && hasAnyEffects(effectProperties)) {
|
||||||
positionStyle = { ...positionStyle, ...buildTransitionStyle(effectProperties) };
|
positionStyle = {
|
||||||
|
...positionStyle,
|
||||||
|
...buildTransitionStyle(effectProperties),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add appear animation (ALWAYS - for WYSIWYG)
|
// Add appear animation (ALWAYS - for WYSIWYG)
|
||||||
|
|||||||
@ -367,7 +367,9 @@ export function ElementEditorPanel({
|
|||||||
}
|
}
|
||||||
navType={selectedElement.navType}
|
navType={selectedElement.navType}
|
||||||
navLabel={selectedElement.navLabel || ''}
|
navLabel={selectedElement.navLabel || ''}
|
||||||
navLabelFontFamily={selectedElement.navLabelFontFamily || ''}
|
navLabelFontFamily={
|
||||||
|
selectedElement.navLabelFontFamily || ''
|
||||||
|
}
|
||||||
navDisabled={selectedElement.navDisabled || false}
|
navDisabled={selectedElement.navDisabled || false}
|
||||||
iconUrl={selectedElement.iconUrl || ''}
|
iconUrl={selectedElement.iconUrl || ''}
|
||||||
targetPageSlug={selectedElement.targetPageSlug || ''}
|
targetPageSlug={selectedElement.targetPageSlug || ''}
|
||||||
@ -604,11 +606,20 @@ export function ElementEditorPanel({
|
|||||||
selectedElement.galleryHeaderBorderRadius || '',
|
selectedElement.galleryHeaderBorderRadius || '',
|
||||||
galleryHeaderBorder:
|
galleryHeaderBorder:
|
||||||
selectedElement.galleryHeaderBorder || '',
|
selectedElement.galleryHeaderBorder || '',
|
||||||
|
galleryHeaderWidth:
|
||||||
|
selectedElement.galleryHeaderWidth || '',
|
||||||
|
galleryHeaderHeight:
|
||||||
|
selectedElement.galleryHeaderHeight || '',
|
||||||
|
galleryHeaderMinHeight:
|
||||||
|
selectedElement.galleryHeaderMinHeight || '',
|
||||||
|
galleryHeaderMaxHeight:
|
||||||
|
selectedElement.galleryHeaderMaxHeight || '',
|
||||||
}}
|
}}
|
||||||
onChange={(prop, value) =>
|
onChange={(prop, value) =>
|
||||||
onUpdateElement({ [prop]: value || undefined })
|
onUpdateElement({ [prop]: value || undefined })
|
||||||
}
|
}
|
||||||
showFont
|
showFont
|
||||||
|
showDimensions
|
||||||
/>
|
/>
|
||||||
<GallerySectionStyleInputs
|
<GallerySectionStyleInputs
|
||||||
sectionLabel='Title'
|
sectionLabel='Title'
|
||||||
@ -695,6 +706,10 @@ export function ElementEditorPanel({
|
|||||||
selectedElement.galleryCardTitleFontWeight || '',
|
selectedElement.galleryCardTitleFontWeight || '',
|
||||||
galleryCardTitleShadow:
|
galleryCardTitleShadow:
|
||||||
selectedElement.galleryCardTitleShadow || '',
|
selectedElement.galleryCardTitleShadow || '',
|
||||||
|
galleryCardAspectRatio:
|
||||||
|
selectedElement.galleryCardAspectRatio || '',
|
||||||
|
galleryCardMinHeight:
|
||||||
|
selectedElement.galleryCardMinHeight || '',
|
||||||
}}
|
}}
|
||||||
onChange={(prop, value) =>
|
onChange={(prop, value) =>
|
||||||
onUpdateElement({ [prop]: value || undefined })
|
onUpdateElement({ [prop]: value || undefined })
|
||||||
@ -702,6 +717,7 @@ export function ElementEditorPanel({
|
|||||||
showGap
|
showGap
|
||||||
showColumns
|
showColumns
|
||||||
showTitleStyles
|
showTitleStyles
|
||||||
|
showAspectRatio
|
||||||
/>
|
/>
|
||||||
<p className='text-[11px] font-semibold text-gray-700 pt-2'>
|
<p className='text-[11px] font-semibold text-gray-700 pt-2'>
|
||||||
General Element Styles
|
General Element Styles
|
||||||
|
|||||||
@ -144,7 +144,11 @@ const CarouselSettingsSection: React.FC<CarouselSettingsSectionProps> = ({
|
|||||||
<select
|
<select
|
||||||
value={slide.imageUrl}
|
value={slide.imageUrl}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onUpdateSlide(slide.id, 'imageUrl', event.target.value)
|
onUpdateSlide(
|
||||||
|
slide.id,
|
||||||
|
'imageUrl',
|
||||||
|
event.target.value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value=''>Not selected</option>
|
<option value=''>Not selected</option>
|
||||||
|
|||||||
@ -89,7 +89,9 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
|||||||
placeholder='W (rem)'
|
placeholder='W (rem)'
|
||||||
value={prevWidth}
|
value={prevWidth}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onUpdateElement({ galleryCarouselPrevWidth: event.target.value })
|
onUpdateElement({
|
||||||
|
galleryCarouselPrevWidth: event.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@ -100,7 +102,9 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
|||||||
placeholder='H (rem)'
|
placeholder='H (rem)'
|
||||||
value={prevHeight}
|
value={prevHeight}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onUpdateElement({ galleryCarouselPrevHeight: event.target.value })
|
onUpdateElement({
|
||||||
|
galleryCarouselPrevHeight: event.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -136,7 +140,9 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
|||||||
placeholder='W (rem)'
|
placeholder='W (rem)'
|
||||||
value={nextWidth}
|
value={nextWidth}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onUpdateElement({ galleryCarouselNextWidth: event.target.value })
|
onUpdateElement({
|
||||||
|
galleryCarouselNextWidth: event.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@ -147,7 +153,9 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
|||||||
placeholder='H (rem)'
|
placeholder='H (rem)'
|
||||||
value={nextHeight}
|
value={nextHeight}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onUpdateElement({ galleryCarouselNextHeight: event.target.value })
|
onUpdateElement({
|
||||||
|
galleryCarouselNextHeight: event.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -183,7 +191,9 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
|||||||
placeholder='W (rem)'
|
placeholder='W (rem)'
|
||||||
value={backWidth}
|
value={backWidth}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onUpdateElement({ galleryCarouselBackWidth: event.target.value })
|
onUpdateElement({
|
||||||
|
galleryCarouselBackWidth: event.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@ -194,7 +204,9 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
|||||||
placeholder='H (rem)'
|
placeholder='H (rem)'
|
||||||
value={backHeight}
|
value={backHeight}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onUpdateElement({ galleryCarouselBackHeight: event.target.value })
|
onUpdateElement({
|
||||||
|
galleryCarouselBackHeight: event.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -210,7 +222,8 @@ const GalleryCarouselSettingsSectionCompact: React.FC<
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<p className='text-[10px] text-gray-500 mt-1'>
|
<p className='text-[10px] text-gray-500 mt-1'>
|
||||||
Set icon + dimensions for navigation-style buttons. Drag to reposition.
|
Set icon + dimensions for navigation-style buttons. Drag to
|
||||||
|
reposition.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,6 +18,8 @@ interface GallerySectionStyleInputsProps {
|
|||||||
showGap?: boolean;
|
showGap?: boolean;
|
||||||
showBlur?: boolean;
|
showBlur?: boolean;
|
||||||
showTitleStyles?: boolean;
|
showTitleStyles?: boolean;
|
||||||
|
showDimensions?: boolean;
|
||||||
|
showAspectRatio?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,6 +35,8 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
showGap = false,
|
showGap = false,
|
||||||
showBlur = false,
|
showBlur = false,
|
||||||
showTitleStyles = false,
|
showTitleStyles = false,
|
||||||
|
showDimensions = false,
|
||||||
|
showAspectRatio = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||||
@ -41,11 +45,15 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
<div className='grid grid-cols-2 gap-2'>
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
{/* Background Color */}
|
{/* Background Color */}
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>BG color</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
BG color
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values[`${prefix}BackgroundColor`] || ''}
|
value={values[`${prefix}BackgroundColor`] || ''}
|
||||||
onChange={(e) => onChange(`${prefix}BackgroundColor`, e.target.value)}
|
onChange={(e) =>
|
||||||
|
onChange(`${prefix}BackgroundColor`, e.target.value)
|
||||||
|
}
|
||||||
placeholder='rgba(0,0,0,0.6)'
|
placeholder='rgba(0,0,0,0.6)'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +61,9 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
{/* Text Color (not for wrapper) */}
|
{/* Text Color (not for wrapper) */}
|
||||||
{prefix !== 'galleryWrapper' && !showTitleStyles && (
|
{prefix !== 'galleryWrapper' && !showTitleStyles && (
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Text color</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Text color
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values[`${prefix}Color`] || ''}
|
value={values[`${prefix}Color`] || ''}
|
||||||
@ -65,7 +75,9 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
|
|
||||||
{/* Padding */}
|
{/* Padding */}
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Padding</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Padding
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values[`${prefix}Padding`] || ''}
|
value={values[`${prefix}Padding`] || ''}
|
||||||
@ -116,7 +128,9 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values[`${prefix}BackdropBlur`] || ''}
|
value={values[`${prefix}BackdropBlur`] || ''}
|
||||||
onChange={(e) => onChange(`${prefix}BackdropBlur`, e.target.value)}
|
onChange={(e) =>
|
||||||
|
onChange(`${prefix}BackdropBlur`, e.target.value)
|
||||||
|
}
|
||||||
placeholder='4px'
|
placeholder='4px'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -125,14 +139,18 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
{/* Grid Columns (optional) */}
|
{/* Grid Columns (optional) */}
|
||||||
{showColumns && (
|
{showColumns && (
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Columns</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Columns
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type='number'
|
type='number'
|
||||||
min='1'
|
min='1'
|
||||||
max='6'
|
max='6'
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values[`${prefix}Columns`] || ''}
|
value={values[`${prefix}Columns`] || ''}
|
||||||
onChange={(e) => onChange(`${prefix}Columns`, parseInt(e.target.value) || 3)}
|
onChange={(e) =>
|
||||||
|
onChange(`${prefix}Columns`, parseInt(e.target.value) || 3)
|
||||||
|
}
|
||||||
placeholder='3'
|
placeholder='3'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -141,7 +159,9 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
{/* Font Size (optional) */}
|
{/* Font Size (optional) */}
|
||||||
{showFont && (
|
{showFont && (
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Font size</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Font size
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values[`${prefix}FontSize`] || ''}
|
value={values[`${prefix}FontSize`] || ''}
|
||||||
@ -154,7 +174,9 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
{/* Font Weight (optional) */}
|
{/* Font Weight (optional) */}
|
||||||
{showFont && (
|
{showFont && (
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Font weight</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Font weight
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values[`${prefix}FontWeight`] || ''}
|
value={values[`${prefix}FontWeight`] || ''}
|
||||||
@ -184,54 +206,165 @@ const GallerySectionStyleInputs: React.FC<GallerySectionStyleInputsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Header Dimensions (header section only) */}
|
||||||
|
{showDimensions && (
|
||||||
|
<>
|
||||||
|
<p className='text-[10px] text-gray-500 pt-1'>Dimensions:</p>
|
||||||
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Width
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}Width`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}Width`, e.target.value)}
|
||||||
|
placeholder='100%, 300px'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Height
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}Height`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}Height`, e.target.value)}
|
||||||
|
placeholder='200px, auto'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Min Height
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}MinHeight`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}MinHeight`, e.target.value)}
|
||||||
|
placeholder='150px'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Max Height
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}MaxHeight`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}MaxHeight`, e.target.value)}
|
||||||
|
placeholder='400px, 50vh'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Card Aspect Ratio and Min Height (cards only) */}
|
||||||
|
{showAspectRatio && (
|
||||||
|
<>
|
||||||
|
<p className='text-[10px] text-gray-500 pt-1'>Card dimensions:</p>
|
||||||
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Aspect Ratio
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}AspectRatio`] || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChange(`${prefix}AspectRatio`, e.target.value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value=''>4:3 (Default)</option>
|
||||||
|
<option value='16/9'>16:9 (Widescreen)</option>
|
||||||
|
<option value='1/1'>1:1 (Square)</option>
|
||||||
|
<option value='3/2'>3:2</option>
|
||||||
|
<option value='auto'>Auto</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Min Height
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
value={values[`${prefix}MinHeight`] || ''}
|
||||||
|
onChange={(e) => onChange(`${prefix}MinHeight`, e.target.value)}
|
||||||
|
placeholder='150px'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Card Title Styles (cards only) */}
|
{/* Card Title Styles (cards only) */}
|
||||||
{showTitleStyles && (
|
{showTitleStyles && (
|
||||||
<>
|
<>
|
||||||
<p className='text-[10px] text-gray-500 pt-1'>Card title overlay:</p>
|
<p className='text-[10px] text-gray-500 pt-1'>Card title overlay:</p>
|
||||||
<div className='grid grid-cols-2 gap-2'>
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Title color</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Title color
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values.galleryCardTitleColor || ''}
|
value={values.galleryCardTitleColor || ''}
|
||||||
onChange={(e) => onChange('galleryCardTitleColor', e.target.value)}
|
onChange={(e) =>
|
||||||
|
onChange('galleryCardTitleColor', e.target.value)
|
||||||
|
}
|
||||||
placeholder='#ffffff'
|
placeholder='#ffffff'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Title BG</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Title BG
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values.galleryCardTitleBackgroundColor || ''}
|
value={values.galleryCardTitleBackgroundColor || ''}
|
||||||
onChange={(e) => onChange('galleryCardTitleBackgroundColor', e.target.value)}
|
onChange={(e) =>
|
||||||
|
onChange('galleryCardTitleBackgroundColor', e.target.value)
|
||||||
|
}
|
||||||
placeholder='transparent'
|
placeholder='transparent'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Title size</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Title size
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values.galleryCardTitleFontSize || ''}
|
value={values.galleryCardTitleFontSize || ''}
|
||||||
onChange={(e) => onChange('galleryCardTitleFontSize', e.target.value)}
|
onChange={(e) =>
|
||||||
|
onChange('galleryCardTitleFontSize', e.target.value)
|
||||||
|
}
|
||||||
placeholder='0.75rem'
|
placeholder='0.75rem'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Title weight</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Title weight
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values.galleryCardTitleFontWeight || ''}
|
value={values.galleryCardTitleFontWeight || ''}
|
||||||
onChange={(e) => onChange('galleryCardTitleFontWeight', e.target.value)}
|
onChange={(e) =>
|
||||||
|
onChange('galleryCardTitleFontWeight', e.target.value)
|
||||||
|
}
|
||||||
placeholder='700'
|
placeholder='700'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className='mb-1 block text-[10px] text-gray-600'>Title shadow</label>
|
<label className='mb-1 block text-[10px] text-gray-600'>
|
||||||
|
Title shadow
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={values.galleryCardTitleShadow || ''}
|
value={values.galleryCardTitleShadow || ''}
|
||||||
onChange={(e) => onChange('galleryCardTitleShadow', e.target.value)}
|
onChange={(e) =>
|
||||||
|
onChange('galleryCardTitleShadow', e.target.value)
|
||||||
|
}
|
||||||
placeholder='0 1px 3px rgba(0,0,0,0.5)'
|
placeholder='0 1px 3px rgba(0,0,0,0.5)'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -61,7 +61,9 @@ const GallerySettingsSectionCompact: React.FC<
|
|||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
{/* Header Settings */}
|
{/* Header Settings */}
|
||||||
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
<div className='rounded border border-gray-200 p-2 space-y-2'>
|
||||||
<p className='text-[11px] font-semibold text-gray-700'>Gallery header</p>
|
<p className='text-[11px] font-semibold text-gray-700'>
|
||||||
|
Gallery header
|
||||||
|
</p>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
|
|||||||
@ -133,7 +133,9 @@ const NavigationSettingsSectionCompact: React.FC<
|
|||||||
<select
|
<select
|
||||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||||
value={navLabelFontFamily}
|
value={navLabelFontFamily}
|
||||||
onChange={(event) => onChange('navLabelFontFamily', event.target.value)}
|
onChange={(event) =>
|
||||||
|
onChange('navLabelFontFamily', event.target.value)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<option value=''>Not set</option>
|
<option value=''>Not set</option>
|
||||||
{FONT_OPTIONS.map((font) => (
|
{FONT_OPTIONS.map((font) => (
|
||||||
|
|||||||
@ -327,7 +327,9 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
|
|||||||
mediaMuted: Boolean(settings.mediaMuted),
|
mediaMuted: Boolean(settings.mediaMuted),
|
||||||
carouselPrevIconUrl: String(settings.carouselPrevIconUrl || ''),
|
carouselPrevIconUrl: String(settings.carouselPrevIconUrl || ''),
|
||||||
carouselNextIconUrl: String(settings.carouselNextIconUrl || ''),
|
carouselNextIconUrl: String(settings.carouselNextIconUrl || ''),
|
||||||
carouselCaptionFontFamily: String(settings.carouselCaptionFontFamily || ''),
|
carouselCaptionFontFamily: String(
|
||||||
|
settings.carouselCaptionFontFamily || '',
|
||||||
|
),
|
||||||
galleryTitleFontFamily: String(settings.galleryTitleFontFamily || ''),
|
galleryTitleFontFamily: String(settings.galleryTitleFontFamily || ''),
|
||||||
galleryTextFontFamily: String(settings.galleryTextFontFamily || ''),
|
galleryTextFontFamily: String(settings.galleryTextFontFamily || ''),
|
||||||
galleryCards: Array.isArray(settings.galleryCards)
|
galleryCards: Array.isArray(settings.galleryCards)
|
||||||
@ -658,16 +660,19 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
|
|||||||
settings.descriptionText = state.descriptionText;
|
settings.descriptionText = state.descriptionText;
|
||||||
// Only include if explicitly set - allows CSS inheritance from wrapper
|
// Only include if explicitly set - allows CSS inheritance from wrapper
|
||||||
if (state.descriptionTitleFontSize.trim()) {
|
if (state.descriptionTitleFontSize.trim()) {
|
||||||
settings.descriptionTitleFontSize = state.descriptionTitleFontSize.trim();
|
settings.descriptionTitleFontSize =
|
||||||
|
state.descriptionTitleFontSize.trim();
|
||||||
}
|
}
|
||||||
if (state.descriptionTextFontSize.trim()) {
|
if (state.descriptionTextFontSize.trim()) {
|
||||||
settings.descriptionTextFontSize = state.descriptionTextFontSize.trim();
|
settings.descriptionTextFontSize = state.descriptionTextFontSize.trim();
|
||||||
}
|
}
|
||||||
if (state.descriptionTitleFontFamily.trim()) {
|
if (state.descriptionTitleFontFamily.trim()) {
|
||||||
settings.descriptionTitleFontFamily = state.descriptionTitleFontFamily.trim();
|
settings.descriptionTitleFontFamily =
|
||||||
|
state.descriptionTitleFontFamily.trim();
|
||||||
}
|
}
|
||||||
if (state.descriptionTextFontFamily.trim()) {
|
if (state.descriptionTextFontFamily.trim()) {
|
||||||
settings.descriptionTextFontFamily = state.descriptionTextFontFamily.trim();
|
settings.descriptionTextFontFamily =
|
||||||
|
state.descriptionTextFontFamily.trim();
|
||||||
}
|
}
|
||||||
if (state.descriptionTitleColor.trim()) {
|
if (state.descriptionTitleColor.trim()) {
|
||||||
settings.descriptionTitleColor = state.descriptionTitleColor.trim();
|
settings.descriptionTitleColor = state.descriptionTitleColor.trim();
|
||||||
@ -698,7 +703,8 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
|
|||||||
}));
|
}));
|
||||||
settings.carouselPrevIconUrl = state.carouselPrevIconUrl.trim();
|
settings.carouselPrevIconUrl = state.carouselPrevIconUrl.trim();
|
||||||
settings.carouselNextIconUrl = state.carouselNextIconUrl.trim();
|
settings.carouselNextIconUrl = state.carouselNextIconUrl.trim();
|
||||||
settings.carouselCaptionFontFamily = state.carouselCaptionFontFamily.trim();
|
settings.carouselCaptionFontFamily =
|
||||||
|
state.carouselCaptionFontFamily.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Media type settings
|
// Media type settings
|
||||||
|
|||||||
@ -392,23 +392,23 @@ export default function RuntimePresentation({
|
|||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{project?.name || 'Presentation'}</title>
|
<title>{project?.name || 'Presentation'}</title>
|
||||||
{faviconUrl && <link key="favicon" rel="icon" href={faviconUrl} />}
|
{faviconUrl && <link key='favicon' rel='icon' href={faviconUrl} />}
|
||||||
{ogImageUrl && (
|
{ogImageUrl && (
|
||||||
<>
|
<>
|
||||||
<meta key="og:image" property="og:image" content={ogImageUrl} />
|
<meta key='og:image' property='og:image' content={ogImageUrl} />
|
||||||
<meta
|
<meta
|
||||||
key="twitter:image:src"
|
key='twitter:image:src'
|
||||||
property="twitter:image:src"
|
property='twitter:image:src'
|
||||||
content={ogImageUrl}
|
content={ogImageUrl}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{project?.name && (
|
{project?.name && (
|
||||||
<>
|
<>
|
||||||
<meta key="og:title" property="og:title" content={project.name} />
|
<meta key='og:title' property='og:title' content={project.name} />
|
||||||
<meta
|
<meta
|
||||||
key="twitter:title"
|
key='twitter:title'
|
||||||
property="twitter:title"
|
property='twitter:title'
|
||||||
content={project.name}
|
content={project.name}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -416,13 +416,13 @@ export default function RuntimePresentation({
|
|||||||
{project?.description && (
|
{project?.description && (
|
||||||
<>
|
<>
|
||||||
<meta
|
<meta
|
||||||
key="og:description"
|
key='og:description'
|
||||||
property="og:description"
|
property='og:description'
|
||||||
content={project.description}
|
content={project.description}
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
key="twitter:description"
|
key='twitter:description'
|
||||||
property="twitter:description"
|
property='twitter:description'
|
||||||
content={project.description}
|
content={project.description}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -205,9 +205,17 @@ const GalleryCarouselOverlay: React.FC<GalleryCarouselOverlayProps> = ({
|
|||||||
);
|
);
|
||||||
// For prev/next, also update the other button's Y position
|
// For prev/next, also update the other button's Y position
|
||||||
if (draggingButton === 'prev') {
|
if (draggingButton === 'prev') {
|
||||||
onButtonPositionChange('next', currentPositions.nextX, currentPositions.prevY);
|
onButtonPositionChange(
|
||||||
|
'next',
|
||||||
|
currentPositions.nextX,
|
||||||
|
currentPositions.prevY,
|
||||||
|
);
|
||||||
} else if (draggingButton === 'next') {
|
} else if (draggingButton === 'next') {
|
||||||
onButtonPositionChange('prev', currentPositions.prevX, currentPositions.nextY);
|
onButtonPositionChange(
|
||||||
|
'prev',
|
||||||
|
currentPositions.prevX,
|
||||||
|
currentPositions.nextY,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDraggingButton(null);
|
setDraggingButton(null);
|
||||||
@ -377,7 +385,6 @@ const GalleryCarouselOverlay: React.FC<GalleryCarouselOverlayProps> = ({
|
|||||||
backWidth,
|
backWidth,
|
||||||
backHeight,
|
backHeight,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -95,13 +95,9 @@ const DescriptionElement: React.FC<DescriptionElementProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={className} style={style}>
|
<div className={className} style={style}>
|
||||||
<div className='p-4'>
|
<div className='p-4'>
|
||||||
<p style={titleStyle}>
|
<p style={titleStyle}>{element.descriptionTitle || ''}</p>
|
||||||
{element.descriptionTitle || ''}
|
|
||||||
</p>
|
|
||||||
{element.descriptionText && (
|
{element.descriptionText && (
|
||||||
<p style={textStyle}>
|
<p style={textStyle}>{element.descriptionText}</p>
|
||||||
{element.descriptionText}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -47,35 +47,63 @@ const GalleryElement: React.FC<GalleryElementProps> = ({
|
|||||||
const title = element.galleryTitle;
|
const title = element.galleryTitle;
|
||||||
|
|
||||||
// Build section styles from element properties
|
// Build section styles from element properties
|
||||||
const headerStyle = useMemo(() => buildGalleryHeaderStyle(element), [element]);
|
const headerStyle = useMemo(
|
||||||
|
() => buildGalleryHeaderStyle(element),
|
||||||
|
[element],
|
||||||
|
);
|
||||||
const titleStyle = useMemo(() => buildGalleryTitleStyle(element), [element]);
|
const titleStyle = useMemo(() => buildGalleryTitleStyle(element), [element]);
|
||||||
const spanStyle = useMemo(() => buildGallerySpanStyle(element), [element]);
|
const spanStyle = useMemo(() => buildGallerySpanStyle(element), [element]);
|
||||||
const spanGridStyle = useMemo(() => buildGallerySpanGridStyle(element), [element]);
|
const spanGridStyle = useMemo(
|
||||||
|
() => buildGallerySpanGridStyle(element),
|
||||||
|
[element],
|
||||||
|
);
|
||||||
const cardStyle = useMemo(() => buildGalleryCardStyle(element), [element]);
|
const cardStyle = useMemo(() => buildGalleryCardStyle(element), [element]);
|
||||||
const cardTitleStyle = useMemo(() => buildGalleryCardTitleStyle(element), [element]);
|
const cardTitleStyle = useMemo(
|
||||||
const cardGridStyle = useMemo(() => buildGalleryCardGridStyle(element), [element]);
|
() => buildGalleryCardTitleStyle(element),
|
||||||
|
[element],
|
||||||
|
);
|
||||||
|
const cardGridStyle = useMemo(
|
||||||
|
() => buildGalleryCardGridStyle(element),
|
||||||
|
[element],
|
||||||
|
);
|
||||||
|
|
||||||
// Build wrapper style from general element styles with gallery defaults
|
// Build wrapper style from general element styles with gallery defaults
|
||||||
const wrapperDefaults = GALLERY_SECTION_DEFAULTS.wrapper;
|
const wrapperDefaults = GALLERY_SECTION_DEFAULTS.wrapper;
|
||||||
const wrapperStyle: CSSProperties = useMemo(() => ({
|
const wrapperStyle: CSSProperties = useMemo(
|
||||||
display: 'flex',
|
() => ({
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
backgroundColor: style.backgroundColor || wrapperDefaults.backgroundColor,
|
flexDirection: 'column',
|
||||||
padding: style.padding || wrapperDefaults.padding,
|
backgroundColor: style.backgroundColor || wrapperDefaults.backgroundColor,
|
||||||
borderRadius: style.borderRadius || wrapperDefaults.borderRadius,
|
padding: style.padding || wrapperDefaults.padding,
|
||||||
border: style.border,
|
borderRadius: style.borderRadius || wrapperDefaults.borderRadius,
|
||||||
gap: style.gap || wrapperDefaults.gap,
|
border: style.border,
|
||||||
backdropFilter: wrapperDefaults.backdropFilter,
|
gap: style.gap || wrapperDefaults.gap,
|
||||||
WebkitBackdropFilter: wrapperDefaults.backdropFilter,
|
backdropFilter: wrapperDefaults.backdropFilter,
|
||||||
// Visual properties that should apply to the wrapper, not outer positioning div
|
WebkitBackdropFilter: wrapperDefaults.backdropFilter,
|
||||||
boxShadow: style.boxShadow,
|
// Visual properties that should apply to the wrapper, not outer positioning div
|
||||||
opacity: style.opacity,
|
boxShadow: style.boxShadow,
|
||||||
// Inheritable text styles - cascade to child sections
|
opacity: style.opacity,
|
||||||
color: style.color,
|
// Inheritable text styles - cascade to child sections
|
||||||
fontSize: style.fontSize,
|
color: style.color,
|
||||||
fontWeight: style.fontWeight,
|
fontSize: style.fontSize,
|
||||||
lineHeight: style.lineHeight,
|
fontWeight: style.fontWeight,
|
||||||
}), [style.backgroundColor, style.padding, style.borderRadius, style.border, style.gap, style.boxShadow, style.opacity, style.color, style.fontSize, style.fontWeight, style.lineHeight, wrapperDefaults]);
|
lineHeight: style.lineHeight,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
style.backgroundColor,
|
||||||
|
style.padding,
|
||||||
|
style.borderRadius,
|
||||||
|
style.border,
|
||||||
|
style.gap,
|
||||||
|
style.boxShadow,
|
||||||
|
style.opacity,
|
||||||
|
style.color,
|
||||||
|
style.fontSize,
|
||||||
|
style.fontWeight,
|
||||||
|
style.lineHeight,
|
||||||
|
wrapperDefaults,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
// Extract wrapper-related styles from outer style (they go to wrapper, not outer div)
|
// Extract wrapper-related styles from outer style (they go to wrapper, not outer div)
|
||||||
const {
|
const {
|
||||||
@ -95,19 +123,20 @@ const GalleryElement: React.FC<GalleryElementProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} style={outerStyle}>
|
<div className={className} style={outerStyle}>
|
||||||
<div
|
<div className='min-w-[200px]' style={wrapperStyle}>
|
||||||
className='min-w-[200px]'
|
|
||||||
style={wrapperStyle}
|
|
||||||
>
|
|
||||||
{/* Header: image takes priority, otherwise render text */}
|
{/* Header: image takes priority, otherwise render text */}
|
||||||
{/* Header styles (border, borderRadius, etc.) apply to both image and text modes */}
|
{/* Header styles (border, borderRadius, dimensions) apply to both image and text modes */}
|
||||||
{headerImageUrl ? (
|
{headerImageUrl ? (
|
||||||
<div style={{ ...headerStyle, padding: 0, overflow: 'hidden' }}>
|
<div style={{ ...headerStyle, padding: 0, overflow: 'hidden' }}>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src={resolve(headerImageUrl)}
|
src={resolve(headerImageUrl)}
|
||||||
alt=''
|
alt=''
|
||||||
className='w-full h-auto object-cover'
|
className='object-cover'
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: headerStyle.height || 'auto',
|
||||||
|
}}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -157,39 +186,53 @@ const GalleryElement: React.FC<GalleryElementProps> = ({
|
|||||||
{/* Gallery cards */}
|
{/* Gallery cards */}
|
||||||
{cards.length > 0 && (
|
{cards.length > 0 && (
|
||||||
<div style={cardGridStyle}>
|
<div style={cardGridStyle}>
|
||||||
{cards.map((card, index) => (
|
{cards.map((card, index) => {
|
||||||
<div
|
// Build card container style with aspect ratio and dimensions
|
||||||
key={card.id}
|
const cardContainerStyle: CSSProperties = {
|
||||||
className={`relative aspect-[4/3] min-w-[50px] min-h-[40px] overflow-hidden ${
|
...cardStyle,
|
||||||
onCardClick
|
position: 'relative',
|
||||||
? 'cursor-pointer hover:ring-2 hover:ring-white hover:ring-offset-1 hover:ring-offset-black/50 transition-all'
|
overflow: 'hidden',
|
||||||
: ''
|
minWidth: '50px',
|
||||||
}`}
|
// Use aspect-ratio from cardStyle, fallback to 4/3
|
||||||
style={cardStyle}
|
aspectRatio: cardStyle.aspectRatio || '4/3',
|
||||||
onClick={(e) => {
|
// Use minHeight from cardStyle, fallback to 40px
|
||||||
if (onCardClick) {
|
minHeight: cardStyle.minHeight || '40px',
|
||||||
e.stopPropagation();
|
};
|
||||||
onCardClick(index);
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={card.id}
|
||||||
|
className={
|
||||||
|
onCardClick
|
||||||
|
? 'cursor-pointer hover:ring-2 hover:ring-white hover:ring-offset-1 hover:ring-offset-black/50 transition-all'
|
||||||
|
: ''
|
||||||
}
|
}
|
||||||
}}
|
style={cardContainerStyle}
|
||||||
>
|
onClick={(e) => {
|
||||||
{card.imageUrl && (
|
if (onCardClick) {
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
e.stopPropagation();
|
||||||
<img
|
onCardClick(index);
|
||||||
src={resolve(card.imageUrl)}
|
}
|
||||||
alt={card.title || ''}
|
}}
|
||||||
className='absolute inset-0 w-full h-full object-cover'
|
>
|
||||||
style={{ borderRadius: cardStyle.borderRadius }}
|
{card.imageUrl && (
|
||||||
draggable={false}
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
/>
|
<img
|
||||||
)}
|
src={resolve(card.imageUrl)}
|
||||||
{card.title && (
|
alt={card.title || ''}
|
||||||
<div className='absolute inset-0 flex items-center justify-center'>
|
className='absolute inset-0 w-full h-full object-cover'
|
||||||
<span style={cardTitleStyle}>{card.title}</span>
|
style={{ borderRadius: cardContainerStyle.borderRadius }}
|
||||||
</div>
|
draggable={false}
|
||||||
)}
|
/>
|
||||||
</div>
|
)}
|
||||||
))}
|
{card.title && (
|
||||||
|
<div className='absolute inset-0 flex items-center justify-center'>
|
||||||
|
<span style={cardTitleStyle}>{card.title}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -69,12 +69,8 @@ const TooltipElement: React.FC<TooltipElementProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={className} style={style}>
|
<div className={className} style={style}>
|
||||||
<div className='p-3 max-w-[200px]'>
|
<div className='p-3 max-w-[200px]'>
|
||||||
<p style={titleFontStyle}>
|
<p style={titleFontStyle}>{element.tooltipTitle}</p>
|
||||||
{element.tooltipTitle}
|
<p style={{ opacity: 0.7, ...textFontStyle }}>{element.tooltipText}</p>
|
||||||
</p>
|
|
||||||
<p style={{ opacity: 0.7, ...textFontStyle }}>
|
|
||||||
{element.tooltipText}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -51,7 +51,8 @@ function validateAssetType(file, expectedType) {
|
|||||||
const hasExtensionMatch = extension && extensions.includes(extension);
|
const hasExtensionMatch = extension && extensions.includes(extension);
|
||||||
|
|
||||||
if (!hasMimeMatch && !hasExtensionMatch) {
|
if (!hasMimeMatch && !hasExtensionMatch) {
|
||||||
const typeLabel = expectedType.charAt(0).toUpperCase() + expectedType.slice(1);
|
const typeLabel =
|
||||||
|
expectedType.charAt(0).toUpperCase() + expectedType.slice(1);
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
error: `Invalid file type. Expected ${typeLabel} file but got "${mimeType || 'unknown'}" (${file.name})`,
|
error: `Invalid file type. Expected ${typeLabel} file but got "${mimeType || 'unknown'}" (${file.name})`,
|
||||||
@ -114,7 +115,9 @@ export default class FileUploader {
|
|||||||
if (schema.size && file.size > schema.size) {
|
if (schema.size && file.size > schema.size) {
|
||||||
const maxSizeMB = (schema.size / (1024 * 1024)).toFixed(1);
|
const maxSizeMB = (schema.size / (1024 * 1024)).toFixed(1);
|
||||||
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);
|
const fileSizeMB = (file.size / (1024 * 1024)).toFixed(1);
|
||||||
throw new Error(`File is too big. Maximum ${maxSizeMB}MB, got ${fileSizeMB}MB`);
|
throw new Error(
|
||||||
|
`File is too big. Maximum ${maxSizeMB}MB, got ${fileSizeMB}MB`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension validation
|
// Extension validation
|
||||||
|
|||||||
@ -363,8 +363,8 @@ export function useConstructorElements({
|
|||||||
update: (spanId: string, patch: Partial<GalleryInfoSpan>) => {
|
update: (spanId: string, patch: Partial<GalleryInfoSpan>) => {
|
||||||
if (!selectedElement || !isGalleryElementType(selectedElement.type))
|
if (!selectedElement || !isGalleryElementType(selectedElement.type))
|
||||||
return;
|
return;
|
||||||
const nextSpans = (selectedElement.galleryInfoSpans || []).map((span) =>
|
const nextSpans = (selectedElement.galleryInfoSpans || []).map(
|
||||||
span.id === spanId ? { ...span, ...patch } : span,
|
(span) => (span.id === spanId ? { ...span, ...patch } : span),
|
||||||
);
|
);
|
||||||
updateSelectedElement({ galleryInfoSpans: nextSpans });
|
updateSelectedElement({ galleryInfoSpans: nextSpans });
|
||||||
},
|
},
|
||||||
|
|||||||
@ -630,7 +630,11 @@ export function usePreloadOrchestrator(
|
|||||||
const checkObject = (obj: Record<string, unknown>) => {
|
const checkObject = (obj: Record<string, unknown>) => {
|
||||||
if (!obj || typeof obj !== 'object') return;
|
if (!obj || typeof obj !== 'object') return;
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (typeof value === 'string' && value && urlFields.includes(key)) {
|
if (
|
||||||
|
typeof value === 'string' &&
|
||||||
|
value &&
|
||||||
|
urlFields.includes(key)
|
||||||
|
) {
|
||||||
elementAssetUrls.push(value);
|
elementAssetUrls.push(value);
|
||||||
} else if (typeof value === 'object' && value !== null) {
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
checkObject(value as Record<string, unknown>);
|
checkObject(value as Record<string, unknown>);
|
||||||
|
|||||||
@ -30,7 +30,9 @@ interface ProjectAssetInput {
|
|||||||
* Extracts storage paths from URLs (handles both formats) and resolves them
|
* Extracts storage paths from URLs (handles both formats) and resolves them
|
||||||
* to presigned URLs for access.
|
* to presigned URLs for access.
|
||||||
*/
|
*/
|
||||||
export function useProjectAssets(project: ProjectAssetInput | null): ProjectAssets {
|
export function useProjectAssets(
|
||||||
|
project: ProjectAssetInput | null,
|
||||||
|
): ProjectAssets {
|
||||||
const [assets, setAssets] = useState<ProjectAssets>({
|
const [assets, setAssets] = useState<ProjectAssets>({
|
||||||
faviconUrl: null,
|
faviconUrl: null,
|
||||||
ogImageUrl: null,
|
ogImageUrl: null,
|
||||||
@ -89,7 +91,12 @@ export function useProjectAssets(project: ProjectAssetInput | null): ProjectAsse
|
|||||||
// Reset loading state and resolve
|
// Reset loading state and resolve
|
||||||
setAssets((prev) => ({ ...prev, isLoading: true }));
|
setAssets((prev) => ({ ...prev, isLoading: true }));
|
||||||
resolveAssets();
|
resolveAssets();
|
||||||
}, [project?.favicon_url, project?.og_image_url, project?.logo_url, resolveAssets]);
|
}, [
|
||||||
|
project?.favicon_url,
|
||||||
|
project?.og_image_url,
|
||||||
|
project?.logo_url,
|
||||||
|
resolveAssets,
|
||||||
|
]);
|
||||||
|
|
||||||
return assets;
|
return assets;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,8 +101,7 @@ export type EffectPropName = (typeof EFFECT_PROPS)[number];
|
|||||||
export function buildTransitionStyle(
|
export function buildTransitionStyle(
|
||||||
effects: Partial<ElementEffectProperties>,
|
effects: Partial<ElementEffectProperties>,
|
||||||
): CSSProperties {
|
): CSSProperties {
|
||||||
const duration =
|
const duration = normalizeDuration(effects.hoverTransitionDuration) || '0.2s';
|
||||||
normalizeDuration(effects.hoverTransitionDuration) || '0.2s';
|
|
||||||
return {
|
return {
|
||||||
transition: `all ${duration} ease`,
|
transition: `all ${duration} ease`,
|
||||||
};
|
};
|
||||||
@ -228,8 +227,7 @@ export function buildAppearAnimationStyle(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration =
|
const duration = normalizeDuration(effects.appearAnimationDuration) || '0.3s';
|
||||||
normalizeDuration(effects.appearAnimationDuration) || '0.3s';
|
|
||||||
const easing = effects.appearAnimationEasing || 'ease';
|
const easing = effects.appearAnimationEasing || 'ease';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -12,12 +12,20 @@ import { getFontByKey, getFontStyle } from './fonts';
|
|||||||
/**
|
/**
|
||||||
* Gallery section names for styling
|
* Gallery section names for styling
|
||||||
*/
|
*/
|
||||||
export type GallerySectionName = 'header' | 'title' | 'span' | 'card' | 'wrapper';
|
export type GallerySectionName =
|
||||||
|
| 'header'
|
||||||
|
| 'title'
|
||||||
|
| 'span'
|
||||||
|
| 'card'
|
||||||
|
| 'wrapper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default values for gallery sections to preserve current Tailwind appearance
|
* Default values for gallery sections to preserve current Tailwind appearance
|
||||||
*/
|
*/
|
||||||
export const GALLERY_SECTION_DEFAULTS: Record<GallerySectionName, CSSProperties> = {
|
export const GALLERY_SECTION_DEFAULTS: Record<
|
||||||
|
GallerySectionName,
|
||||||
|
CSSProperties
|
||||||
|
> = {
|
||||||
header: {
|
header: {
|
||||||
fontSize: '1.5rem', // text-2xl
|
fontSize: '1.5rem', // text-2xl
|
||||||
fontWeight: '700', // font-bold
|
fontWeight: '700', // font-bold
|
||||||
@ -104,10 +112,12 @@ const normalizeWithUnit = (value: unknown, unit: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Normalize rem values (fontSize, padding, borderRadius, gap) */
|
/** Normalize rem values (fontSize, padding, borderRadius, gap) */
|
||||||
const normalizeRemValue = (value: unknown): string => normalizeWithUnit(value, 'rem');
|
const normalizeRemValue = (value: unknown): string =>
|
||||||
|
normalizeWithUnit(value, 'rem');
|
||||||
|
|
||||||
/** Normalize pixel values (for properties that use px) */
|
/** Normalize pixel values (for properties that use px) */
|
||||||
const normalizePxValue = (value: unknown): string => normalizeWithUnit(value, 'px');
|
const normalizePxValue = (value: unknown): string =>
|
||||||
|
normalizeWithUnit(value, 'px');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply value with default fallback and optional unit normalization
|
* Apply value with default fallback and optional unit normalization
|
||||||
@ -155,11 +165,27 @@ export function buildGalleryHeaderStyle(
|
|||||||
applyIfSet(style, 'backgroundColor', element.galleryHeaderBackgroundColor);
|
applyIfSet(style, 'backgroundColor', element.galleryHeaderBackgroundColor);
|
||||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
applyIfSet(style, 'color', element.galleryHeaderColor);
|
applyIfSet(style, 'color', element.galleryHeaderColor);
|
||||||
applyIfSet(style, 'fontSize', element.galleryHeaderFontSize, normalizeRemValue);
|
applyIfSet(
|
||||||
|
style,
|
||||||
|
'fontSize',
|
||||||
|
element.galleryHeaderFontSize,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
applyIfSet(style, 'fontWeight', element.galleryHeaderFontWeight);
|
applyIfSet(style, 'fontWeight', element.galleryHeaderFontWeight);
|
||||||
// Non-inheritable properties: use defaults
|
// Non-inheritable properties: use defaults
|
||||||
applyWithDefault(style, 'padding', element.galleryHeaderPadding, defaults.padding, normalizeRemValue);
|
applyWithDefault(
|
||||||
applyIfSet(style, 'borderRadius', element.galleryHeaderBorderRadius, normalizeRemValue);
|
style,
|
||||||
|
'padding',
|
||||||
|
element.galleryHeaderPadding,
|
||||||
|
defaults.padding,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
|
applyIfSet(
|
||||||
|
style,
|
||||||
|
'borderRadius',
|
||||||
|
element.galleryHeaderBorderRadius,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
applyIfSet(style, 'border', element.galleryHeaderBorder); // Complex value, no normalization
|
applyIfSet(style, 'border', element.galleryHeaderBorder); // Complex value, no normalization
|
||||||
|
|
||||||
// Apply font family with font library resolution
|
// Apply font family with font library resolution
|
||||||
@ -173,6 +199,22 @@ export function buildGalleryHeaderStyle(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dimension properties - only apply if explicitly set (allows CSS defaults)
|
||||||
|
applyIfSet(style, 'width', element.galleryHeaderWidth);
|
||||||
|
applyIfSet(style, 'height', element.galleryHeaderHeight, normalizePxValue);
|
||||||
|
applyIfSet(
|
||||||
|
style,
|
||||||
|
'minHeight',
|
||||||
|
element.galleryHeaderMinHeight,
|
||||||
|
normalizePxValue,
|
||||||
|
);
|
||||||
|
applyIfSet(
|
||||||
|
style,
|
||||||
|
'maxHeight',
|
||||||
|
element.galleryHeaderMaxHeight,
|
||||||
|
normalizePxValue,
|
||||||
|
);
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,14 +227,36 @@ export function buildGalleryTitleStyle(
|
|||||||
const defaults = GALLERY_SECTION_DEFAULTS.title;
|
const defaults = GALLERY_SECTION_DEFAULTS.title;
|
||||||
const style: CSSProperties = {};
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
applyWithDefault(style, 'backgroundColor', element.galleryTitleBackgroundColor, defaults.backgroundColor);
|
applyWithDefault(
|
||||||
|
style,
|
||||||
|
'backgroundColor',
|
||||||
|
element.galleryTitleBackgroundColor,
|
||||||
|
defaults.backgroundColor,
|
||||||
|
);
|
||||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
applyIfSet(style, 'color', element.galleryTitleColor);
|
applyIfSet(style, 'color', element.galleryTitleColor);
|
||||||
applyIfSet(style, 'fontSize', element.galleryTitleFontSize, normalizeRemValue);
|
applyIfSet(
|
||||||
|
style,
|
||||||
|
'fontSize',
|
||||||
|
element.galleryTitleFontSize,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
applyIfSet(style, 'fontWeight', element.galleryTitleFontWeight);
|
applyIfSet(style, 'fontWeight', element.galleryTitleFontWeight);
|
||||||
// Non-inheritable properties: use defaults
|
// Non-inheritable properties: use defaults
|
||||||
applyWithDefault(style, 'padding', element.galleryTitlePadding, defaults.padding, normalizeRemValue);
|
applyWithDefault(
|
||||||
applyWithDefault(style, 'borderRadius', element.galleryTitleBorderRadius, defaults.borderRadius, normalizeRemValue);
|
style,
|
||||||
|
'padding',
|
||||||
|
element.galleryTitlePadding,
|
||||||
|
defaults.padding,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
|
applyWithDefault(
|
||||||
|
style,
|
||||||
|
'borderRadius',
|
||||||
|
element.galleryTitleBorderRadius,
|
||||||
|
defaults.borderRadius,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
applyIfSet(style, 'border', element.galleryTitleBorder); // Complex value, no normalization
|
applyIfSet(style, 'border', element.galleryTitleBorder); // Complex value, no normalization
|
||||||
|
|
||||||
// Apply font family with font library resolution
|
// Apply font family with font library resolution
|
||||||
@ -218,18 +282,36 @@ export function buildGallerySpanStyle(
|
|||||||
const defaults = GALLERY_SECTION_DEFAULTS.span;
|
const defaults = GALLERY_SECTION_DEFAULTS.span;
|
||||||
const style: CSSProperties = {};
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
applyWithDefault(style, 'backgroundColor', element.gallerySpanBackgroundColor, defaults.backgroundColor);
|
applyWithDefault(
|
||||||
|
style,
|
||||||
|
'backgroundColor',
|
||||||
|
element.gallerySpanBackgroundColor,
|
||||||
|
defaults.backgroundColor,
|
||||||
|
);
|
||||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
applyIfSet(style, 'color', element.gallerySpanColor);
|
applyIfSet(style, 'color', element.gallerySpanColor);
|
||||||
applyIfSet(style, 'fontSize', element.gallerySpanFontSize, normalizeRemValue);
|
applyIfSet(style, 'fontSize', element.gallerySpanFontSize, normalizeRemValue);
|
||||||
applyIfSet(style, 'fontWeight', element.gallerySpanFontWeight);
|
applyIfSet(style, 'fontWeight', element.gallerySpanFontWeight);
|
||||||
// Non-inheritable properties: use defaults
|
// Non-inheritable properties: use defaults
|
||||||
applyWithDefault(style, 'padding', element.gallerySpanPadding, defaults.padding, normalizeRemValue);
|
applyWithDefault(
|
||||||
applyWithDefault(style, 'borderRadius', element.gallerySpanBorderRadius, defaults.borderRadius, normalizeRemValue);
|
style,
|
||||||
|
'padding',
|
||||||
|
element.gallerySpanPadding,
|
||||||
|
defaults.padding,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
|
applyWithDefault(
|
||||||
|
style,
|
||||||
|
'borderRadius',
|
||||||
|
element.gallerySpanBorderRadius,
|
||||||
|
defaults.borderRadius,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
applyIfSet(style, 'border', element.gallerySpanBorder); // Complex value, no normalization
|
applyIfSet(style, 'border', element.gallerySpanBorder); // Complex value, no normalization
|
||||||
|
|
||||||
// Apply font family with font library resolution (fallback to galleryTextFontFamily for legacy support)
|
// Apply font family with font library resolution (fallback to galleryTextFontFamily for legacy support)
|
||||||
const fontKey = element.gallerySpanFontFamily || element.galleryTextFontFamily;
|
const fontKey =
|
||||||
|
element.gallerySpanFontFamily || element.galleryTextFontFamily;
|
||||||
if (fontKey) {
|
if (fontKey) {
|
||||||
const font = getFontByKey(fontKey);
|
const font = getFontByKey(fontKey);
|
||||||
if (font) {
|
if (font) {
|
||||||
@ -267,10 +349,32 @@ export function buildGalleryCardStyle(
|
|||||||
const defaults = GALLERY_SECTION_DEFAULTS.card;
|
const defaults = GALLERY_SECTION_DEFAULTS.card;
|
||||||
const style: CSSProperties = {};
|
const style: CSSProperties = {};
|
||||||
|
|
||||||
applyWithDefault(style, 'backgroundColor', element.galleryCardBackgroundColor, undefined);
|
applyWithDefault(
|
||||||
applyWithDefault(style, 'borderRadius', element.galleryCardBorderRadius, defaults.borderRadius, normalizeRemValue);
|
style,
|
||||||
|
'backgroundColor',
|
||||||
|
element.galleryCardBackgroundColor,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
applyWithDefault(
|
||||||
|
style,
|
||||||
|
'borderRadius',
|
||||||
|
element.galleryCardBorderRadius,
|
||||||
|
defaults.borderRadius,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
applyIfSet(style, 'border', element.galleryCardBorder); // Complex value, no normalization
|
applyIfSet(style, 'border', element.galleryCardBorder); // Complex value, no normalization
|
||||||
|
|
||||||
|
// Dimension properties - only apply if explicitly set
|
||||||
|
applyIfSet(style, 'width', element.galleryCardWidth);
|
||||||
|
applyIfSet(style, 'height', element.galleryCardHeight, normalizePxValue);
|
||||||
|
applyIfSet(
|
||||||
|
style,
|
||||||
|
'minHeight',
|
||||||
|
element.galleryCardMinHeight,
|
||||||
|
normalizePxValue,
|
||||||
|
);
|
||||||
|
applyIfSet(style, 'aspectRatio', element.galleryCardAspectRatio);
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +389,12 @@ export function buildGalleryCardTitleStyle(
|
|||||||
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
// Inheritable properties: only apply if explicitly set (allows CSS inheritance from wrapper)
|
||||||
// Note: card titles typically need white text for visibility over images
|
// Note: card titles typically need white text for visibility over images
|
||||||
applyIfSet(style, 'color', element.galleryCardTitleColor);
|
applyIfSet(style, 'color', element.galleryCardTitleColor);
|
||||||
applyIfSet(style, 'fontSize', element.galleryCardTitleFontSize, normalizeRemValue);
|
applyIfSet(
|
||||||
|
style,
|
||||||
|
'fontSize',
|
||||||
|
element.galleryCardTitleFontSize,
|
||||||
|
normalizeRemValue,
|
||||||
|
);
|
||||||
applyIfSet(style, 'fontWeight', element.galleryCardTitleFontWeight);
|
applyIfSet(style, 'fontWeight', element.galleryCardTitleFontWeight);
|
||||||
|
|
||||||
if (element.galleryCardTitleBackgroundColor) {
|
if (element.galleryCardTitleBackgroundColor) {
|
||||||
@ -298,7 +407,8 @@ export function buildGalleryCardTitleStyle(
|
|||||||
style.textShadow = shadow;
|
style.textShadow = shadow;
|
||||||
} else {
|
} else {
|
||||||
// Default drop-shadow-lg equivalent for visibility over images
|
// Default drop-shadow-lg equivalent for visibility over images
|
||||||
style.filter = 'drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))';
|
style.filter =
|
||||||
|
'drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply font family from galleryTextFontFamily for legacy support
|
// Apply font family from galleryTextFontFamily for legacy support
|
||||||
@ -359,6 +469,10 @@ export const GALLERY_SECTION_STYLE_PROPS = [
|
|||||||
'galleryHeaderPadding',
|
'galleryHeaderPadding',
|
||||||
'galleryHeaderBorderRadius',
|
'galleryHeaderBorderRadius',
|
||||||
'galleryHeaderBorder',
|
'galleryHeaderBorder',
|
||||||
|
'galleryHeaderWidth',
|
||||||
|
'galleryHeaderHeight',
|
||||||
|
'galleryHeaderMinHeight',
|
||||||
|
'galleryHeaderMaxHeight',
|
||||||
// Title
|
// Title
|
||||||
'galleryTitleBackgroundColor',
|
'galleryTitleBackgroundColor',
|
||||||
'galleryTitleColor',
|
'galleryTitleColor',
|
||||||
@ -390,6 +504,11 @@ export const GALLERY_SECTION_STYLE_PROPS = [
|
|||||||
'galleryCardTitleFontSize',
|
'galleryCardTitleFontSize',
|
||||||
'galleryCardTitleFontWeight',
|
'galleryCardTitleFontWeight',
|
||||||
'galleryCardTitleShadow',
|
'galleryCardTitleShadow',
|
||||||
|
'galleryCardWidth',
|
||||||
|
'galleryCardHeight',
|
||||||
|
'galleryCardMinHeight',
|
||||||
|
'galleryCardAspectRatio',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type GallerySectionStyleProp = (typeof GALLERY_SECTION_STYLE_PROPS)[number];
|
export type GallerySectionStyleProp =
|
||||||
|
(typeof GALLERY_SECTION_STYLE_PROPS)[number];
|
||||||
|
|||||||
@ -283,15 +283,31 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
|||||||
<meta property='og:url' content={url} />
|
<meta property='og:url' content={url} />
|
||||||
<meta property='og:site_name' content='https://flatlogic.com/' />
|
<meta property='og:site_name' content='https://flatlogic.com/' />
|
||||||
<meta key='og:title' property='og:title' content={title} />
|
<meta key='og:title' property='og:title' content={title} />
|
||||||
<meta key='og:description' property='og:description' content={description} />
|
<meta
|
||||||
|
key='og:description'
|
||||||
|
property='og:description'
|
||||||
|
content={description}
|
||||||
|
/>
|
||||||
<meta key='og:image' property='og:image' content={image} />
|
<meta key='og:image' property='og:image' content={image} />
|
||||||
<meta property='og:image:type' content='image/png' />
|
<meta property='og:image:type' content='image/png' />
|
||||||
<meta property='og:image:width' content={imageWidth} />
|
<meta property='og:image:width' content={imageWidth} />
|
||||||
<meta property='og:image:height' content={imageHeight} />
|
<meta property='og:image:height' content={imageHeight} />
|
||||||
<meta property='twitter:card' content='summary_large_image' />
|
<meta property='twitter:card' content='summary_large_image' />
|
||||||
<meta key='twitter:title' property='twitter:title' content={title} />
|
<meta
|
||||||
<meta key='twitter:description' property='twitter:description' content={description} />
|
key='twitter:title'
|
||||||
<meta key='twitter:image:src' property='twitter:image:src' content={image} />
|
property='twitter:title'
|
||||||
|
content={title}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
key='twitter:description'
|
||||||
|
property='twitter:description'
|
||||||
|
content={description}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
key='twitter:image:src'
|
||||||
|
property='twitter:image:src'
|
||||||
|
content={image}
|
||||||
|
/>
|
||||||
<meta property='twitter:image:width' content={imageWidth} />
|
<meta property='twitter:image:width' content={imageWidth} />
|
||||||
<meta property='twitter:image:height' content={imageHeight} />
|
<meta property='twitter:image:height' content={imageHeight} />
|
||||||
<link key='favicon' rel='icon' href='/favicon.svg' />
|
<link key='favicon' rel='icon' href='/favicon.svg' />
|
||||||
|
|||||||
@ -1164,7 +1164,9 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
|||||||
|
|
||||||
// Update the active carousel element to reflect the new positions
|
// Update the active carousel element to reflect the new positions
|
||||||
setActiveGalleryCarousel((prev) =>
|
setActiveGalleryCarousel((prev) =>
|
||||||
prev ? { ...prev, element: { ...prev.element, ...positionPatch } } : null,
|
prev
|
||||||
|
? { ...prev, element: { ...prev.element, ...positionPatch } }
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[activeGalleryCarousel, updateSelectedElement],
|
[activeGalleryCarousel, updateSelectedElement],
|
||||||
@ -1459,15 +1461,9 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
|||||||
initialIndex={activeGalleryCarousel.initialIndex}
|
initialIndex={activeGalleryCarousel.initialIndex}
|
||||||
onClose={() => setActiveGalleryCarousel(null)}
|
onClose={() => setActiveGalleryCarousel(null)}
|
||||||
resolveUrl={resolveUrlWithBlob}
|
resolveUrl={resolveUrlWithBlob}
|
||||||
prevIconUrl={
|
prevIconUrl={activeGalleryCarousel.element.galleryCarouselPrevIconUrl}
|
||||||
activeGalleryCarousel.element.galleryCarouselPrevIconUrl
|
nextIconUrl={activeGalleryCarousel.element.galleryCarouselNextIconUrl}
|
||||||
}
|
backIconUrl={activeGalleryCarousel.element.galleryCarouselBackIconUrl}
|
||||||
nextIconUrl={
|
|
||||||
activeGalleryCarousel.element.galleryCarouselNextIconUrl
|
|
||||||
}
|
|
||||||
backIconUrl={
|
|
||||||
activeGalleryCarousel.element.galleryCarouselBackIconUrl
|
|
||||||
}
|
|
||||||
backLabel={
|
backLabel={
|
||||||
activeGalleryCarousel.element.galleryCarouselBackLabel || 'BACK'
|
activeGalleryCarousel.element.galleryCarouselBackLabel || 'BACK'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -363,7 +363,9 @@ const ElementTypeDefaultDetailsPage = () => {
|
|||||||
<CarouselSettingsSection
|
<CarouselSettingsSection
|
||||||
carouselPrevIconUrl={form.state.carouselPrevIconUrl}
|
carouselPrevIconUrl={form.state.carouselPrevIconUrl}
|
||||||
carouselNextIconUrl={form.state.carouselNextIconUrl}
|
carouselNextIconUrl={form.state.carouselNextIconUrl}
|
||||||
carouselCaptionFontFamily={form.state.carouselCaptionFontFamily}
|
carouselCaptionFontFamily={
|
||||||
|
form.state.carouselCaptionFontFamily
|
||||||
|
}
|
||||||
carouselSlides={form.state.carouselSlides}
|
carouselSlides={form.state.carouselSlides}
|
||||||
onAddSlide={form.addCarouselSlide}
|
onAddSlide={form.addCarouselSlide}
|
||||||
onRemoveSlide={form.removeCarouselSlide}
|
onRemoveSlide={form.removeCarouselSlide}
|
||||||
|
|||||||
@ -548,7 +548,9 @@ const ProjectElementDefaultDetailsPage = () => {
|
|||||||
<CarouselSettingsSection
|
<CarouselSettingsSection
|
||||||
carouselPrevIconUrl={form.state.carouselPrevIconUrl}
|
carouselPrevIconUrl={form.state.carouselPrevIconUrl}
|
||||||
carouselNextIconUrl={form.state.carouselNextIconUrl}
|
carouselNextIconUrl={form.state.carouselNextIconUrl}
|
||||||
carouselCaptionFontFamily={form.state.carouselCaptionFontFamily}
|
carouselCaptionFontFamily={
|
||||||
|
form.state.carouselCaptionFontFamily
|
||||||
|
}
|
||||||
carouselSlides={form.state.carouselSlides}
|
carouselSlides={form.state.carouselSlides}
|
||||||
onAddSlide={form.addCarouselSlide}
|
onAddSlide={form.addCarouselSlide}
|
||||||
onRemoveSlide={form.removeCarouselSlide}
|
onRemoveSlide={form.removeCarouselSlide}
|
||||||
|
|||||||
@ -226,13 +226,18 @@ const EditProjectsPage = () => {
|
|||||||
: 'Select logo from Assets'}
|
: 'Select logo from Assets'}
|
||||||
</option>
|
</option>
|
||||||
{logoAssets.map((asset) => (
|
{logoAssets.map((asset) => (
|
||||||
<option key={asset.id} value={asset.storage_key || asset.cdn_url}>
|
<option
|
||||||
|
key={asset.id}
|
||||||
|
value={asset.storage_key || asset.cdn_url}
|
||||||
|
>
|
||||||
{(asset.name || '').replace(/^\[[^\]]+\]\s*/, '')}
|
{(asset.name || '').replace(/^\[[^\]]+\]\s*/, '')}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
{values.logo_url &&
|
{values.logo_url &&
|
||||||
!logoAssets.some(
|
!logoAssets.some(
|
||||||
(asset) => (asset.storage_key || asset.cdn_url) === values.logo_url,
|
(asset) =>
|
||||||
|
(asset.storage_key || asset.cdn_url) ===
|
||||||
|
values.logo_url,
|
||||||
) && (
|
) && (
|
||||||
<option value={values.logo_url}>
|
<option value={values.logo_url}>
|
||||||
{values.logo_url}
|
{values.logo_url}
|
||||||
@ -259,13 +264,18 @@ const EditProjectsPage = () => {
|
|||||||
: 'Select favicon from Assets logos'}
|
: 'Select favicon from Assets logos'}
|
||||||
</option>
|
</option>
|
||||||
{logoAssets.map((asset) => (
|
{logoAssets.map((asset) => (
|
||||||
<option key={asset.id} value={asset.storage_key || asset.cdn_url}>
|
<option
|
||||||
|
key={asset.id}
|
||||||
|
value={asset.storage_key || asset.cdn_url}
|
||||||
|
>
|
||||||
{(asset.name || '').replace(/^\[[^\]]+\]\s*/, '')}
|
{(asset.name || '').replace(/^\[[^\]]+\]\s*/, '')}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
{values.favicon_url &&
|
{values.favicon_url &&
|
||||||
!logoAssets.some(
|
!logoAssets.some(
|
||||||
(asset) => (asset.storage_key || asset.cdn_url) === values.favicon_url,
|
(asset) =>
|
||||||
|
(asset.storage_key || asset.cdn_url) ===
|
||||||
|
values.favicon_url,
|
||||||
) && (
|
) && (
|
||||||
<option value={values.favicon_url}>
|
<option value={values.favicon_url}>
|
||||||
{values.favicon_url}
|
{values.favicon_url}
|
||||||
@ -292,13 +302,18 @@ const EditProjectsPage = () => {
|
|||||||
: 'Select OG image from Assets logos'}
|
: 'Select OG image from Assets logos'}
|
||||||
</option>
|
</option>
|
||||||
{logoAssets.map((asset) => (
|
{logoAssets.map((asset) => (
|
||||||
<option key={asset.id} value={asset.storage_key || asset.cdn_url}>
|
<option
|
||||||
|
key={asset.id}
|
||||||
|
value={asset.storage_key || asset.cdn_url}
|
||||||
|
>
|
||||||
{(asset.name || '').replace(/^\[[^\]]+\]\s*/, '')}
|
{(asset.name || '').replace(/^\[[^\]]+\]\s*/, '')}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
{values.og_image_url &&
|
{values.og_image_url &&
|
||||||
!logoAssets.some(
|
!logoAssets.some(
|
||||||
(asset) => (asset.storage_key || asset.cdn_url) === values.og_image_url,
|
(asset) =>
|
||||||
|
(asset.storage_key || asset.cdn_url) ===
|
||||||
|
values.og_image_url,
|
||||||
) && (
|
) && (
|
||||||
<option value={values.og_image_url}>
|
<option value={values.og_image_url}>
|
||||||
{values.og_image_url}
|
{values.og_image_url}
|
||||||
|
|||||||
@ -110,6 +110,11 @@ export interface CanvasElement extends BaseCanvasElement {
|
|||||||
galleryHeaderPadding?: string;
|
galleryHeaderPadding?: string;
|
||||||
galleryHeaderBorderRadius?: string;
|
galleryHeaderBorderRadius?: string;
|
||||||
galleryHeaderBorder?: string;
|
galleryHeaderBorder?: string;
|
||||||
|
// Gallery Section Styles - Header Dimensions
|
||||||
|
galleryHeaderWidth?: string;
|
||||||
|
galleryHeaderHeight?: string;
|
||||||
|
galleryHeaderMinHeight?: string;
|
||||||
|
galleryHeaderMaxHeight?: string;
|
||||||
// Gallery Section Styles - Title
|
// Gallery Section Styles - Title
|
||||||
galleryTitleBackgroundColor?: string;
|
galleryTitleBackgroundColor?: string;
|
||||||
galleryTitleColor?: string;
|
galleryTitleColor?: string;
|
||||||
@ -140,6 +145,11 @@ export interface CanvasElement extends BaseCanvasElement {
|
|||||||
galleryCardTitleFontSize?: string;
|
galleryCardTitleFontSize?: string;
|
||||||
galleryCardTitleFontWeight?: string;
|
galleryCardTitleFontWeight?: string;
|
||||||
galleryCardTitleShadow?: string;
|
galleryCardTitleShadow?: string;
|
||||||
|
// Gallery Section Styles - Card Dimensions
|
||||||
|
galleryCardWidth?: string;
|
||||||
|
galleryCardHeight?: string;
|
||||||
|
galleryCardMinHeight?: string;
|
||||||
|
galleryCardAspectRatio?: string;
|
||||||
// Gallery Section Styles - Wrapper
|
// Gallery Section Styles - Wrapper
|
||||||
galleryWrapperBackgroundColor?: string;
|
galleryWrapperBackgroundColor?: string;
|
||||||
galleryWrapperPadding?: string;
|
galleryWrapperPadding?: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user