729 lines
20 KiB
TypeScript
729 lines
20 KiB
TypeScript
/**
|
|
* Element Defaults
|
|
*
|
|
* Single source of truth for UI element default values, creation, and merging.
|
|
* Used by constructor, runtime, element-type-defaults, and project-element-defaults pages.
|
|
*/
|
|
|
|
import type {
|
|
CanvasElement,
|
|
CanvasElementType,
|
|
GalleryCard,
|
|
GalleryInfoSpan,
|
|
CarouselSlide,
|
|
NavigationButtonKind,
|
|
} from '../types/constructor';
|
|
import { ELEMENT_STYLE_PROPS } from './elementStyles';
|
|
import { GALLERY_SECTION_STYLE_PROPS } from './gallerySectionStyles';
|
|
|
|
/**
|
|
* Effect properties that can be configured in element defaults.
|
|
* Used for appear animations, hover effects, focus effects, and active effects.
|
|
*/
|
|
export const ELEMENT_EFFECT_PROPS = [
|
|
'appearAnimation',
|
|
'appearAnimationDuration',
|
|
'appearAnimationEasing',
|
|
'hoverScale',
|
|
'hoverOpacity',
|
|
'hoverBackgroundColor',
|
|
'hoverColor',
|
|
'hoverBoxShadow',
|
|
'hoverTransitionDuration',
|
|
'focusScale',
|
|
'focusOpacity',
|
|
'focusOutline',
|
|
'focusBoxShadow',
|
|
'activeScale',
|
|
'activeOpacity',
|
|
'activeBackgroundColor',
|
|
] as const;
|
|
|
|
/**
|
|
* Generate a local unique ID for elements
|
|
*/
|
|
export const createLocalId = (): string => {
|
|
if (typeof window !== 'undefined' && window.crypto?.randomUUID) {
|
|
return window.crypto.randomUUID();
|
|
}
|
|
return `element_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
};
|
|
|
|
/**
|
|
* Clamp a number between min and max
|
|
*/
|
|
export const clamp = (value: number, min: number, max: number): number =>
|
|
Math.min(Math.max(value, min), max);
|
|
|
|
/**
|
|
* Normalize appearDelaySec value
|
|
*/
|
|
export const normalizeAppearDelaySec = (value: unknown): number => {
|
|
const parsed = Number(value);
|
|
if (!Number.isFinite(parsed) || parsed < 0) return 0;
|
|
return parsed;
|
|
};
|
|
|
|
/**
|
|
* Normalize appearDurationSec value (null means infinite)
|
|
*/
|
|
export const normalizeAppearDurationSec = (value: unknown): number | null => {
|
|
if (value === null || value === undefined || value === '') return null;
|
|
const parsed = Number(value);
|
|
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
return parsed;
|
|
};
|
|
|
|
/**
|
|
* Labels for each element type
|
|
*/
|
|
export const ELEMENT_TYPE_LABELS: Record<CanvasElementType, string> = {
|
|
navigation_next: 'Navigation: Forward',
|
|
navigation_prev: 'Navigation: Back',
|
|
spot: 'Hotspot',
|
|
description: 'Description',
|
|
tooltip: 'Tooltip',
|
|
gallery: 'Gallery',
|
|
carousel: 'Carousel',
|
|
logo: 'Logo',
|
|
video_player: 'Video Player',
|
|
audio_player: 'Audio Player',
|
|
popup: 'Popup',
|
|
};
|
|
|
|
/**
|
|
* Get navigation button label based on type
|
|
*/
|
|
export const getNavigationButtonLabel = (
|
|
type: 'navigation_next' | 'navigation_prev',
|
|
): string => (type === 'navigation_next' ? 'Forward' : 'Back');
|
|
|
|
/**
|
|
* Get navigation button kind based on type
|
|
*/
|
|
export const getNavigationButtonKind = (
|
|
type: 'navigation_next' | 'navigation_prev',
|
|
): NavigationButtonKind => (type === 'navigation_prev' ? 'back' : 'forward');
|
|
|
|
/**
|
|
* Get navigation type from kind
|
|
*/
|
|
export const getNavigationTypeFromKind = (
|
|
kind: NavigationButtonKind,
|
|
): 'navigation_next' | 'navigation_prev' =>
|
|
kind === 'back' ? 'navigation_prev' : 'navigation_next';
|
|
|
|
/**
|
|
* Type-specific default values for elements.
|
|
* These are the base defaults used when creating new elements.
|
|
*/
|
|
export const TYPE_SPECIFIC_DEFAULTS: Partial<
|
|
Record<CanvasElementType, Partial<CanvasElement>>
|
|
> = {
|
|
navigation_next: {
|
|
navLabel: 'Forward',
|
|
navType: 'forward',
|
|
navDisabled: false,
|
|
iconUrl: '',
|
|
transitionReverseMode: 'auto_reverse',
|
|
},
|
|
navigation_prev: {
|
|
navLabel: 'Back',
|
|
navType: 'back',
|
|
navDisabled: false,
|
|
iconUrl: '',
|
|
transitionReverseMode: 'auto_reverse',
|
|
},
|
|
tooltip: {
|
|
iconUrl: '',
|
|
tooltipTitle: 'Tooltip title',
|
|
tooltipText: 'Tooltip text',
|
|
},
|
|
description: {
|
|
iconUrl: '',
|
|
descriptionTitle: 'TITLE',
|
|
descriptionText: '',
|
|
descriptionTitleFontSize: '24px',
|
|
descriptionTextFontSize: '16px',
|
|
descriptionTitleFontFamily: 'inherit',
|
|
descriptionTextFontFamily: 'inherit',
|
|
descriptionTitleColor: '#ffffff',
|
|
descriptionTextColor: '#ffffff',
|
|
},
|
|
gallery: {
|
|
galleryCards: [],
|
|
},
|
|
carousel: {
|
|
carouselSlides: [],
|
|
carouselPrevIconUrl: '',
|
|
carouselNextIconUrl: '',
|
|
},
|
|
video_player: {
|
|
mediaUrl: '',
|
|
mediaAutoplay: true,
|
|
mediaLoop: true,
|
|
mediaMuted: true,
|
|
},
|
|
audio_player: {
|
|
mediaUrl: '',
|
|
mediaAutoplay: true,
|
|
mediaLoop: true,
|
|
mediaMuted: false,
|
|
},
|
|
logo: {
|
|
iconUrl: '',
|
|
},
|
|
spot: {
|
|
iconUrl: '',
|
|
},
|
|
popup: {},
|
|
};
|
|
|
|
/**
|
|
* Create a default gallery card
|
|
*/
|
|
export const createDefaultGalleryCard = (index: number): GalleryCard => ({
|
|
id: createLocalId(),
|
|
imageUrl: '',
|
|
title: `Card ${index + 1}`,
|
|
description: '',
|
|
});
|
|
|
|
/**
|
|
* Create a default carousel slide
|
|
*/
|
|
export const createDefaultCarouselSlide = (index: number): CarouselSlide => ({
|
|
id: createLocalId(),
|
|
imageUrl: '',
|
|
caption: `Slide ${index + 1}`,
|
|
});
|
|
|
|
/**
|
|
* Create a new element with default values.
|
|
* The index is used to offset position for multiple elements.
|
|
*/
|
|
export const createDefaultElement = (
|
|
type: CanvasElementType,
|
|
index = 0,
|
|
): CanvasElement => {
|
|
const base: CanvasElement = {
|
|
id: createLocalId(),
|
|
type,
|
|
label: ELEMENT_TYPE_LABELS[type] || type,
|
|
xPercent: clamp(45 + index * 3, 10, 85), // Center horizontally
|
|
yPercent: clamp(45 + index * 4, 15, 80), // Center vertically
|
|
appearDelaySec: 0,
|
|
appearDurationSec: null,
|
|
};
|
|
|
|
const typeDefaults = TYPE_SPECIFIC_DEFAULTS[type] || {};
|
|
|
|
// Handle gallery with initial card
|
|
if (isGalleryElementType(type)) {
|
|
return {
|
|
...base,
|
|
...typeDefaults,
|
|
galleryCards: [createDefaultGalleryCard(0)],
|
|
};
|
|
}
|
|
|
|
// Handle carousel with initial slide
|
|
if (isCarouselElementType(type)) {
|
|
return {
|
|
...base,
|
|
...typeDefaults,
|
|
carouselSlides: [createDefaultCarouselSlide(0)],
|
|
};
|
|
}
|
|
|
|
return {
|
|
...base,
|
|
...typeDefaults,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Normalize a gallery card from unknown input
|
|
*/
|
|
export const normalizeGalleryCard = (
|
|
card: Record<string, unknown>,
|
|
): GalleryCard => ({
|
|
id: String(card?.id || createLocalId()),
|
|
imageUrl: String(card?.imageUrl ?? ''),
|
|
title: String(card?.title ?? ''),
|
|
description: String(card?.description ?? ''),
|
|
});
|
|
|
|
/**
|
|
* Normalize a gallery info span from unknown input
|
|
*/
|
|
export const normalizeGalleryInfoSpan = (
|
|
span: Record<string, unknown>,
|
|
): GalleryInfoSpan => ({
|
|
id: String(span?.id || createLocalId()),
|
|
text: String(span?.text ?? ''),
|
|
iconUrl: span?.iconUrl ? String(span.iconUrl) : undefined,
|
|
});
|
|
|
|
/**
|
|
* Normalize a carousel slide from unknown input
|
|
*/
|
|
export const normalizeCarouselSlide = (
|
|
slide: Record<string, unknown>,
|
|
): CarouselSlide => ({
|
|
id: String(slide?.id || createLocalId()),
|
|
imageUrl: String(slide?.imageUrl ?? ''),
|
|
caption: String(slide?.caption ?? ''),
|
|
});
|
|
|
|
/**
|
|
* Merge an element with project/global defaults.
|
|
* Used when loading elements from the database or creating new elements.
|
|
*
|
|
* @param element - The element to merge
|
|
* @param defaults - Project or global defaults to apply
|
|
* @param options.preferElementValues - If true, element values take precedence over defaults
|
|
*/
|
|
export const mergeElementWithDefaults = (
|
|
element: CanvasElement,
|
|
defaults?: Partial<CanvasElement>,
|
|
options?: { preferElementValues?: boolean },
|
|
): CanvasElement => {
|
|
if (!defaults) return element;
|
|
|
|
const preferElementValues = Boolean(options?.preferElementValues);
|
|
const base = preferElementValues ? defaults : element;
|
|
const override = preferElementValues ? element : defaults;
|
|
|
|
const merged: CanvasElement = {
|
|
...base,
|
|
...override,
|
|
id: element.id,
|
|
type: element.type,
|
|
label: element.label || defaults.label || element.type,
|
|
xPercent: element.xPercent ?? defaults.xPercent ?? 50,
|
|
yPercent: element.yPercent ?? defaults.yPercent ?? 50,
|
|
};
|
|
|
|
// For style properties, use defaults if element has empty/null/undefined value
|
|
// This ensures DB defaults are applied when element has no explicit value
|
|
const elementRecord = element as unknown as Record<string, unknown>;
|
|
const defaultsRecord = defaults as unknown as Record<string, unknown>;
|
|
const mergedRecord = merged as unknown as Record<string, unknown>;
|
|
|
|
ELEMENT_STYLE_PROPS.forEach((prop) => {
|
|
const elementValue = elementRecord[prop];
|
|
const defaultValue = defaultsRecord[prop];
|
|
const elementIsEmpty =
|
|
elementValue === '' ||
|
|
elementValue === undefined ||
|
|
elementValue === null;
|
|
const defaultHasValue =
|
|
defaultValue !== undefined &&
|
|
defaultValue !== null &&
|
|
defaultValue !== '';
|
|
if (elementIsEmpty && defaultHasValue) {
|
|
mergedRecord[prop] = defaultValue;
|
|
}
|
|
});
|
|
|
|
// For effect properties, use defaults if element has empty/null/undefined value
|
|
// This ensures effect defaults (animations, hover, focus, active) cascade to elements
|
|
ELEMENT_EFFECT_PROPS.forEach((prop) => {
|
|
const elementValue = elementRecord[prop];
|
|
const defaultValue = defaultsRecord[prop];
|
|
const elementIsEmpty =
|
|
elementValue === '' ||
|
|
elementValue === undefined ||
|
|
elementValue === null;
|
|
const defaultHasValue =
|
|
defaultValue !== undefined &&
|
|
defaultValue !== null &&
|
|
defaultValue !== '';
|
|
if (elementIsEmpty && defaultHasValue) {
|
|
mergedRecord[prop] = defaultValue;
|
|
}
|
|
});
|
|
|
|
// Normalize position values
|
|
merged.xPercent = clamp(Number(merged.xPercent ?? element.xPercent), 0, 100);
|
|
merged.yPercent = clamp(Number(merged.yPercent ?? element.yPercent), 0, 100);
|
|
merged.appearDelaySec = normalizeAppearDelaySec(merged.appearDelaySec);
|
|
merged.appearDurationSec = normalizeAppearDurationSec(
|
|
merged.appearDurationSec,
|
|
);
|
|
|
|
// Handle gallery cards array
|
|
if (isGalleryElementType(merged.type)) {
|
|
const cards = preferElementValues
|
|
? Array.isArray(element.galleryCards)
|
|
? element.galleryCards
|
|
: defaults.galleryCards || []
|
|
: Array.isArray(defaults.galleryCards)
|
|
? defaults.galleryCards
|
|
: element.galleryCards || [];
|
|
merged.galleryCards = cards.map((card) =>
|
|
normalizeGalleryCard(card as unknown as Record<string, unknown>),
|
|
);
|
|
|
|
// Handle gallery info spans array
|
|
const spans = preferElementValues
|
|
? Array.isArray(element.galleryInfoSpans)
|
|
? element.galleryInfoSpans
|
|
: defaults.galleryInfoSpans || []
|
|
: Array.isArray(defaults.galleryInfoSpans)
|
|
? defaults.galleryInfoSpans
|
|
: element.galleryInfoSpans || [];
|
|
merged.galleryInfoSpans = spans.map((span) =>
|
|
normalizeGalleryInfoSpan(span as unknown as Record<string, unknown>),
|
|
);
|
|
}
|
|
|
|
// Handle carousel slides array
|
|
if (isCarouselElementType(merged.type)) {
|
|
const slides = preferElementValues
|
|
? Array.isArray(element.carouselSlides)
|
|
? element.carouselSlides
|
|
: defaults.carouselSlides || []
|
|
: Array.isArray(defaults.carouselSlides)
|
|
? defaults.carouselSlides
|
|
: element.carouselSlides || [];
|
|
merged.carouselSlides = slides.map((slide) =>
|
|
normalizeCarouselSlide(slide as unknown as Record<string, unknown>),
|
|
);
|
|
}
|
|
|
|
return merged;
|
|
};
|
|
|
|
/**
|
|
* Parse element settings from JSON string or object.
|
|
* Used when loading element defaults from the database.
|
|
*/
|
|
export const parseElementSettings = (
|
|
settingsValue?: string | Record<string, unknown>,
|
|
): Partial<CanvasElement> => {
|
|
if (!settingsValue) return {};
|
|
|
|
let settings: Record<string, unknown> = {};
|
|
|
|
if (typeof settingsValue === 'string') {
|
|
try {
|
|
settings = JSON.parse(settingsValue);
|
|
} catch {
|
|
return {};
|
|
}
|
|
} else {
|
|
settings = settingsValue;
|
|
}
|
|
|
|
// Parse gallery cards if present
|
|
if (Array.isArray(settings.galleryCards)) {
|
|
settings.galleryCards = settings.galleryCards.map((card) =>
|
|
normalizeGalleryCard(card as Record<string, unknown>),
|
|
);
|
|
}
|
|
|
|
// Parse gallery info spans if present
|
|
if (Array.isArray(settings.galleryInfoSpans)) {
|
|
settings.galleryInfoSpans = settings.galleryInfoSpans.map((span) =>
|
|
normalizeGalleryInfoSpan(span as Record<string, unknown>),
|
|
);
|
|
}
|
|
|
|
// Parse carousel slides if present
|
|
if (Array.isArray(settings.carouselSlides)) {
|
|
settings.carouselSlides = settings.carouselSlides.map((slide) =>
|
|
normalizeCarouselSlide(slide as Record<string, unknown>),
|
|
);
|
|
}
|
|
|
|
return settings as Partial<CanvasElement>;
|
|
};
|
|
|
|
/**
|
|
* Check if a value is empty (null, undefined, or empty string)
|
|
*/
|
|
const isEmpty = (value: unknown): boolean =>
|
|
value === null || value === undefined || value === '';
|
|
|
|
/**
|
|
* Add value to settings if not empty
|
|
*/
|
|
const addIfNotEmpty = (
|
|
settings: Record<string, unknown>,
|
|
key: string,
|
|
value: unknown,
|
|
): void => {
|
|
if (!isEmpty(value)) {
|
|
settings[key] = value;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Build settings JSON object from element properties.
|
|
* Used when saving element defaults to the database.
|
|
*/
|
|
export const buildElementSettings = (
|
|
element: Partial<CanvasElement>,
|
|
elementType: CanvasElementType | string,
|
|
): Record<string, unknown> => {
|
|
const settings: Record<string, unknown> = {};
|
|
|
|
// Common properties
|
|
addIfNotEmpty(settings, 'label', element.label);
|
|
if (element.xPercent !== undefined) settings.xPercent = element.xPercent;
|
|
if (element.yPercent !== undefined) settings.yPercent = element.yPercent;
|
|
if (element.appearDelaySec !== undefined) {
|
|
settings.appearDelaySec = normalizeAppearDelaySec(element.appearDelaySec);
|
|
}
|
|
if (element.appearDurationSec !== undefined) {
|
|
settings.appearDurationSec = normalizeAppearDurationSec(
|
|
element.appearDurationSec,
|
|
);
|
|
}
|
|
|
|
// Style properties
|
|
ELEMENT_STYLE_PROPS.forEach((prop) => {
|
|
const value = (element as Record<string, unknown>)[prop];
|
|
addIfNotEmpty(settings, prop, value);
|
|
});
|
|
|
|
// Effect properties (using shared constant)
|
|
ELEMENT_EFFECT_PROPS.forEach((prop) => {
|
|
const value = (element as Record<string, unknown>)[prop];
|
|
addIfNotEmpty(settings, prop, value);
|
|
});
|
|
|
|
// Navigation type settings
|
|
if (isNavigationElementType(elementType)) {
|
|
addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
|
|
addIfNotEmpty(settings, 'navLabel', element.navLabel);
|
|
addIfNotEmpty(settings, 'navType', element.navType);
|
|
if (element.navDisabled !== undefined) {
|
|
settings.navDisabled = element.navDisabled;
|
|
}
|
|
addIfNotEmpty(settings, 'targetPageId', element.targetPageId);
|
|
addIfNotEmpty(settings, 'targetPageSlug', element.targetPageSlug);
|
|
addIfNotEmpty(settings, 'transitionVideoUrl', element.transitionVideoUrl);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'transitionReverseMode',
|
|
element.transitionReverseMode,
|
|
);
|
|
addIfNotEmpty(settings, 'reverseVideoUrl', element.reverseVideoUrl);
|
|
}
|
|
|
|
// Tooltip type settings
|
|
if (isTooltipElementType(elementType)) {
|
|
addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
|
|
addIfNotEmpty(settings, 'tooltipTitle', element.tooltipTitle);
|
|
addIfNotEmpty(settings, 'tooltipText', element.tooltipText);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'tooltipTitleFontFamily',
|
|
element.tooltipTitleFontFamily,
|
|
);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'tooltipTextFontFamily',
|
|
element.tooltipTextFontFamily,
|
|
);
|
|
}
|
|
|
|
// Description type settings
|
|
if (isDescriptionElementType(elementType)) {
|
|
addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
|
|
addIfNotEmpty(settings, 'descriptionTitle', element.descriptionTitle);
|
|
addIfNotEmpty(settings, 'descriptionText', element.descriptionText);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'descriptionTitleFontSize',
|
|
element.descriptionTitleFontSize,
|
|
);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'descriptionTextFontSize',
|
|
element.descriptionTextFontSize,
|
|
);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'descriptionTitleFontFamily',
|
|
element.descriptionTitleFontFamily,
|
|
);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'descriptionTextFontFamily',
|
|
element.descriptionTextFontFamily,
|
|
);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'descriptionTitleColor',
|
|
element.descriptionTitleColor,
|
|
);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'descriptionTextColor',
|
|
element.descriptionTextColor,
|
|
);
|
|
}
|
|
|
|
// Gallery type settings
|
|
if (isGalleryElementType(elementType)) {
|
|
if (Array.isArray(element.galleryCards)) {
|
|
settings.galleryCards = element.galleryCards.map((card) => ({
|
|
id: String(card.id || createLocalId()),
|
|
imageUrl: card.imageUrl ?? '',
|
|
title: card.title ?? '',
|
|
description: card.description ?? '',
|
|
}));
|
|
}
|
|
if (Array.isArray(element.galleryInfoSpans)) {
|
|
settings.galleryInfoSpans = element.galleryInfoSpans.map((span) => ({
|
|
id: String(span.id || createLocalId()),
|
|
text: span.text ?? '',
|
|
iconUrl: span.iconUrl ?? undefined,
|
|
}));
|
|
}
|
|
addIfNotEmpty(
|
|
settings,
|
|
'galleryTitleFontFamily',
|
|
element.galleryTitleFontFamily,
|
|
);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'galleryTextFontFamily',
|
|
element.galleryTextFontFamily,
|
|
);
|
|
addIfNotEmpty(settings, 'galleryColumns', element.galleryColumns);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'galleryHeaderImageUrl',
|
|
element.galleryHeaderImageUrl,
|
|
);
|
|
addIfNotEmpty(settings, 'galleryHeaderText', element.galleryHeaderText);
|
|
addIfNotEmpty(settings, 'galleryTitle', element.galleryTitle);
|
|
// Gallery section style properties
|
|
GALLERY_SECTION_STYLE_PROPS.forEach((prop) => {
|
|
const value = (element as Record<string, unknown>)[prop];
|
|
addIfNotEmpty(settings, prop, value);
|
|
});
|
|
}
|
|
|
|
// Carousel type settings
|
|
if (isCarouselElementType(elementType)) {
|
|
if (Array.isArray(element.carouselSlides)) {
|
|
settings.carouselSlides = element.carouselSlides.map((slide) => ({
|
|
id: String(slide.id || createLocalId()),
|
|
imageUrl: slide.imageUrl ?? '',
|
|
caption: slide.caption ?? '',
|
|
}));
|
|
}
|
|
addIfNotEmpty(settings, 'carouselPrevIconUrl', element.carouselPrevIconUrl);
|
|
addIfNotEmpty(settings, 'carouselNextIconUrl', element.carouselNextIconUrl);
|
|
addIfNotEmpty(
|
|
settings,
|
|
'carouselCaptionFontFamily',
|
|
element.carouselCaptionFontFamily,
|
|
);
|
|
}
|
|
|
|
// Media type settings
|
|
if (isMediaElementType(elementType)) {
|
|
addIfNotEmpty(settings, 'mediaUrl', element.mediaUrl);
|
|
if (element.mediaAutoplay !== undefined) {
|
|
settings.mediaAutoplay = element.mediaAutoplay;
|
|
}
|
|
if (element.mediaLoop !== undefined) {
|
|
settings.mediaLoop = element.mediaLoop;
|
|
}
|
|
if (element.mediaMuted !== undefined) {
|
|
settings.mediaMuted = element.mediaMuted;
|
|
}
|
|
}
|
|
|
|
// Logo/spot type settings
|
|
if (isLogoElementType(elementType) || isSpotElementType(elementType)) {
|
|
addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
|
|
}
|
|
|
|
return settings;
|
|
};
|
|
|
|
/**
|
|
* Type detection helpers for element types.
|
|
* Single source of truth used across the application.
|
|
*/
|
|
|
|
/**
|
|
* Check if a type is a navigation element type
|
|
*/
|
|
export const isNavigationElementType = (
|
|
type: string,
|
|
): type is 'navigation_next' | 'navigation_prev' =>
|
|
type === 'navigation_next' || type === 'navigation_prev';
|
|
|
|
/**
|
|
* Check if a type is a tooltip element type
|
|
*/
|
|
export const isTooltipElementType = (type: string): type is 'tooltip' =>
|
|
type === 'tooltip';
|
|
|
|
/**
|
|
* Check if a type is a description element type
|
|
*/
|
|
export const isDescriptionElementType = (type: string): type is 'description' =>
|
|
type === 'description';
|
|
|
|
/**
|
|
* Check if a type is a gallery element type
|
|
*/
|
|
export const isGalleryElementType = (type: string): type is 'gallery' =>
|
|
type === 'gallery';
|
|
|
|
/**
|
|
* Check if a type is a carousel element type
|
|
*/
|
|
export const isCarouselElementType = (type: string): type is 'carousel' =>
|
|
type === 'carousel';
|
|
|
|
/**
|
|
* Check if a type is a media (video/audio player) element type
|
|
*/
|
|
export const isMediaElementType = (
|
|
type: string,
|
|
): type is 'video_player' | 'audio_player' =>
|
|
type === 'video_player' || type === 'audio_player';
|
|
|
|
/**
|
|
* Check if a type is a video player element type
|
|
*/
|
|
export const isVideoPlayerElementType = (
|
|
type: string,
|
|
): type is 'video_player' => type === 'video_player';
|
|
|
|
/**
|
|
* Check if a type is an audio player element type
|
|
*/
|
|
export const isAudioPlayerElementType = (
|
|
type: string,
|
|
): type is 'audio_player' => type === 'audio_player';
|
|
|
|
/**
|
|
* Check if a type is a logo element type
|
|
*/
|
|
export const isLogoElementType = (type: string): type is 'logo' =>
|
|
type === 'logo';
|
|
|
|
/**
|
|
* Check if a type is a spot (hotspot) element type
|
|
*/
|
|
export const isSpotElementType = (type: string): type is 'spot' =>
|
|
type === 'spot';
|
|
|
|
/**
|
|
* Check if a type is a popup element type
|
|
*/
|
|
export const isPopupElementType = (type: string): type is 'popup' =>
|
|
type === 'popup';
|