Basic constructor
This commit is contained in:
parent
8a20fdbd9e
commit
42684051c3
@ -86,6 +86,7 @@ export default function LayoutAuthenticated({
|
|||||||
|
|
||||||
|
|
||||||
const darkMode = useAppSelector((state) => state.style.darkMode)
|
const darkMode = useAppSelector((state) => state.style.darkMode)
|
||||||
|
const isConstructorFullscreen = router.pathname === '/constructor'
|
||||||
|
|
||||||
const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false)
|
const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false)
|
||||||
const [isAsideLgActive, setIsAsideLgActive] = useState(false)
|
const [isAsideLgActive, setIsAsideLgActive] = useState(false)
|
||||||
@ -106,43 +107,47 @@ export default function LayoutAuthenticated({
|
|||||||
}, [router.events, dispatch])
|
}, [router.events, dispatch])
|
||||||
|
|
||||||
|
|
||||||
const layoutAsidePadding = 'lg:pl-60'
|
const layoutAsidePadding = isConstructorFullscreen ? '' : 'lg:pl-60'
|
||||||
|
const layoutTopPadding = isConstructorFullscreen ? '' : 'pt-14'
|
||||||
|
const mobileAsideShift = !isConstructorFullscreen && isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${darkMode ? 'dark' : ''} overflow-hidden lg:overflow-visible`}>
|
<div className={`${darkMode ? 'dark' : ''} overflow-hidden lg:overflow-visible`}>
|
||||||
<div
|
<div
|
||||||
className={`${layoutAsidePadding} ${
|
className={`${layoutAsidePadding} ${mobileAsideShift} ${layoutTopPadding} min-h-screen w-screen transition-position lg:w-auto ${bgColor} dark:bg-dark-800 dark:text-slate-100`}
|
||||||
isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''
|
|
||||||
} pt-14 min-h-screen w-screen transition-position lg:w-auto ${bgColor} dark:bg-dark-800 dark:text-slate-100`}
|
|
||||||
>
|
>
|
||||||
<NavBar
|
{!isConstructorFullscreen && (
|
||||||
menu={menuNavBar}
|
<>
|
||||||
className={`${layoutAsidePadding} ${isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''}`}
|
<NavBar
|
||||||
>
|
menu={menuNavBar}
|
||||||
<NavBarItemPlain
|
className={`${layoutAsidePadding} ${isAsideMobileExpanded ? 'ml-60 lg:ml-0' : ''}`}
|
||||||
display="flex lg:hidden"
|
>
|
||||||
onClick={() => setIsAsideMobileExpanded(!isAsideMobileExpanded)}
|
<NavBarItemPlain
|
||||||
>
|
display="flex lg:hidden"
|
||||||
<BaseIcon path={isAsideMobileExpanded ? mdiBackburger : mdiForwardburger} size="24" />
|
onClick={() => setIsAsideMobileExpanded(!isAsideMobileExpanded)}
|
||||||
</NavBarItemPlain>
|
>
|
||||||
<NavBarItemPlain
|
<BaseIcon path={isAsideMobileExpanded ? mdiBackburger : mdiForwardburger} size="24" />
|
||||||
display="hidden"
|
</NavBarItemPlain>
|
||||||
onClick={() => setIsAsideLgActive(true)}
|
<NavBarItemPlain
|
||||||
>
|
display="hidden"
|
||||||
<BaseIcon path={mdiMenu} size="24" />
|
onClick={() => setIsAsideLgActive(true)}
|
||||||
</NavBarItemPlain>
|
>
|
||||||
<NavBarItemPlain useMargin>
|
<BaseIcon path={mdiMenu} size="24" />
|
||||||
<Search />
|
</NavBarItemPlain>
|
||||||
</NavBarItemPlain>
|
<NavBarItemPlain useMargin>
|
||||||
</NavBar>
|
<Search />
|
||||||
<AsideMenu
|
</NavBarItemPlain>
|
||||||
isAsideMobileExpanded={isAsideMobileExpanded}
|
</NavBar>
|
||||||
isAsideLgActive={isAsideLgActive}
|
<AsideMenu
|
||||||
menu={menuAside}
|
isAsideMobileExpanded={isAsideMobileExpanded}
|
||||||
onAsideLgClose={() => setIsAsideLgActive(false)}
|
isAsideLgActive={isAsideLgActive}
|
||||||
/>
|
menu={menuAside}
|
||||||
|
onAsideLgClose={() => setIsAsideLgActive(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
<FooterBar>Hand-crafted & Made with ❤️</FooterBar>
|
{!isConstructorFullscreen && <FooterBar>Hand-crafted & Made with ❤️</FooterBar>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { useRouter } from 'next/router';
|
|||||||
import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import BaseButton from '../components/BaseButton';
|
import BaseButton from '../components/BaseButton';
|
||||||
import BaseIcon from '../components/BaseIcon';
|
import BaseIcon from '../components/BaseIcon';
|
||||||
import { getPageTitle } from '../config';
|
import { baseURLApi, getPageTitle } from '../config';
|
||||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||||
|
|
||||||
type TourPage = {
|
type TourPage = {
|
||||||
@ -36,6 +36,7 @@ type ProjectAsset = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
asset_type?: 'image' | 'video' | 'audio' | 'file';
|
asset_type?: 'image' | 'video' | 'audio' | 'file';
|
||||||
cdn_url?: string | null;
|
cdn_url?: string | null;
|
||||||
|
storage_key?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AssetOption = {
|
type AssetOption = {
|
||||||
@ -134,7 +135,79 @@ const createLocalId = () => {
|
|||||||
|
|
||||||
const getAssetLabel = (asset: ProjectAsset) => {
|
const getAssetLabel = (asset: ProjectAsset) => {
|
||||||
const baseName = asset.name?.trim() || 'Untitled asset';
|
const baseName = asset.name?.trim() || 'Untitled asset';
|
||||||
return `${baseName}${asset.cdn_url ? ` · ${asset.cdn_url}` : ''}`;
|
const source = String(asset.storage_key || asset.cdn_url || '').trim();
|
||||||
|
return `${baseName}${source ? ` · ${source}` : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAssetSourceValue = (asset: ProjectAsset) => String(asset.storage_key || asset.cdn_url || '').trim();
|
||||||
|
|
||||||
|
const extractPrivateUrlFromDownloadPath = (value: string) => {
|
||||||
|
const normalized = String(value || '').trim();
|
||||||
|
if (!normalized) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(normalized, 'http://localhost');
|
||||||
|
const privateUrl = parsed.searchParams.get('privateUrl');
|
||||||
|
return String(privateUrl || '').trim();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse download URL:', error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractS3ObjectKey = (value: string) => {
|
||||||
|
const normalized = String(value || '').trim();
|
||||||
|
if (!normalized) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(normalized);
|
||||||
|
const hostname = String(parsed.hostname || '').toLowerCase();
|
||||||
|
if (!hostname.includes('amazonaws.com') && !hostname.includes('cloudfront.net')) return '';
|
||||||
|
const decodedPath = decodeURIComponent(String(parsed.pathname || '').replace(/^\/+/, ''));
|
||||||
|
if (!decodedPath) return '';
|
||||||
|
|
||||||
|
const pathParts = decodedPath.split('/').filter(Boolean);
|
||||||
|
if (pathParts.length <= 1) return decodedPath;
|
||||||
|
|
||||||
|
const firstPart = pathParts[0];
|
||||||
|
const isLikelyStoragePrefix = /^[a-f0-9]{24,64}$/i.test(firstPart);
|
||||||
|
if (isLikelyStoragePrefix) {
|
||||||
|
return pathParts.slice(1).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedPath;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse S3 asset URL:', error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveAssetPlaybackUrl = (value?: string) => {
|
||||||
|
const normalized = String(value || '').trim();
|
||||||
|
if (!normalized) return '';
|
||||||
|
|
||||||
|
if (normalized.startsWith('data:') || normalized.startsWith('blob:')) return normalized;
|
||||||
|
|
||||||
|
if (normalized.startsWith('/api/file/download')) return normalized;
|
||||||
|
|
||||||
|
if (normalized.startsWith('/file/download')) return `${baseURLApi}${normalized}`;
|
||||||
|
|
||||||
|
if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
|
||||||
|
const downloadPrivateUrl = extractPrivateUrlFromDownloadPath(normalized);
|
||||||
|
if (downloadPrivateUrl) {
|
||||||
|
return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(downloadPrivateUrl)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s3ObjectKey = extractS3ObjectKey(normalized);
|
||||||
|
if (s3ObjectKey) {
|
||||||
|
return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(s3ObjectKey)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedPrivateUrl = normalized.replace(/^\/+/, '');
|
||||||
|
return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(normalizedPrivateUrl)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isBackgroundImageAsset = (asset: ProjectAsset) => {
|
const isBackgroundImageAsset = (asset: ProjectAsset) => {
|
||||||
@ -295,6 +368,7 @@ const ConstructorPage = () => {
|
|||||||
const elementDragRef = useRef<DragElementState | null>(null);
|
const elementDragRef = useRef<DragElementState | null>(null);
|
||||||
const transitionVideoRef = useRef<HTMLVideoElement | null>(null);
|
const transitionVideoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
const reverseAnimationFrame = useRef<number | null>(null);
|
const reverseAnimationFrame = useRef<number | null>(null);
|
||||||
|
const didSetInitialCanvasFocus = useRef(false);
|
||||||
|
|
||||||
const activePage = useMemo(() => pages.find((item) => item.id === activePageId) || null, [activePageId, pages]);
|
const activePage = useMemo(() => pages.find((item) => item.id === activePageId) || null, [activePageId, pages]);
|
||||||
const pageNameById = useMemo(() => {
|
const pageNameById = useMemo(() => {
|
||||||
@ -311,35 +385,37 @@ const ConstructorPage = () => {
|
|||||||
const imageAssetOptions = useMemo(
|
const imageAssetOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
assets
|
assets
|
||||||
.filter((asset) => asset.asset_type === 'image' && asset.cdn_url)
|
.filter((asset) => asset.asset_type === 'image' && getAssetSourceValue(asset))
|
||||||
.map((asset) => ({ value: String(asset.cdn_url || ''), label: getAssetLabel(asset) })),
|
.map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
|
||||||
[assets],
|
[assets],
|
||||||
);
|
);
|
||||||
const backgroundImageAssetOptions = useMemo(
|
const backgroundImageAssetOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
assets
|
assets
|
||||||
.filter((asset) => asset.asset_type === 'image' && asset.cdn_url && isBackgroundImageAsset(asset))
|
.filter((asset) => asset.asset_type === 'image' && getAssetSourceValue(asset) && isBackgroundImageAsset(asset))
|
||||||
.map((asset) => ({ value: String(asset.cdn_url || ''), label: getAssetLabel(asset) })),
|
.map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
|
||||||
[assets],
|
[assets],
|
||||||
);
|
);
|
||||||
const videoAssetOptions = useMemo(
|
const videoAssetOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
assets
|
assets
|
||||||
.filter((asset) => asset.asset_type === 'video' && asset.cdn_url)
|
.filter((asset) => asset.asset_type === 'video' && getAssetSourceValue(asset))
|
||||||
.map((asset) => ({ value: String(asset.cdn_url || ''), label: getAssetLabel(asset) })),
|
.map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
|
||||||
[assets],
|
[assets],
|
||||||
);
|
);
|
||||||
const audioAssetOptions = useMemo(
|
const audioAssetOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
assets
|
assets
|
||||||
.filter((asset) => asset.asset_type === 'audio' && asset.cdn_url)
|
.filter((asset) => asset.asset_type === 'audio' && getAssetSourceValue(asset))
|
||||||
.map((asset) => ({ value: String(asset.cdn_url || ''), label: getAssetLabel(asset) })),
|
.map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) })),
|
||||||
[assets],
|
[assets],
|
||||||
);
|
);
|
||||||
const transitionVideoAssetOptions = useMemo(() => {
|
const transitionVideoAssetOptions = useMemo(() => {
|
||||||
const tagged = assets
|
const tagged = assets
|
||||||
.filter((asset) => asset.asset_type === 'video' && asset.cdn_url && /\[TRANSITION\]/i.test(String(asset.name || '')))
|
.filter(
|
||||||
.map((asset) => ({ value: String(asset.cdn_url || ''), label: getAssetLabel(asset) }));
|
(asset) => asset.asset_type === 'video' && getAssetSourceValue(asset) && /\[TRANSITION\]/i.test(String(asset.name || '')),
|
||||||
|
)
|
||||||
|
.map((asset) => ({ value: getAssetSourceValue(asset), label: getAssetLabel(asset) }));
|
||||||
|
|
||||||
if (tagged.length > 0) return tagged;
|
if (tagged.length > 0) return tagged;
|
||||||
|
|
||||||
@ -374,7 +450,7 @@ const ConstructorPage = () => {
|
|||||||
|
|
||||||
const defaultPageId = pageIdFromRoute || pageRows[0]?.id || '';
|
const defaultPageId = pageIdFromRoute || pageRows[0]?.id || '';
|
||||||
setActivePageId(defaultPageId);
|
setActivePageId(defaultPageId);
|
||||||
setIsMenuOpen(pageRows.length > 0);
|
setIsMenuOpen(false);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.response?.status === 401) {
|
if (error?.response?.status === 401) {
|
||||||
const message = 'Your session has expired. Please sign in again.';
|
const message = 'Your session has expired. Please sign in again.';
|
||||||
@ -440,6 +516,17 @@ const ConstructorPage = () => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!router.isReady || !isAuthReady || isLoading) return;
|
||||||
|
if (didSetInitialCanvasFocus.current) return;
|
||||||
|
if (!canvasRef.current) return;
|
||||||
|
|
||||||
|
didSetInitialCanvasFocus.current = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
canvasRef.current?.focus({ preventScroll: true });
|
||||||
|
});
|
||||||
|
}, [isAuthReady, isLoading, router.isReady]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activePage) {
|
if (!activePage) {
|
||||||
setElements([]);
|
setElements([]);
|
||||||
@ -498,7 +585,7 @@ const ConstructorPage = () => {
|
|||||||
setSelectedElementId((current) => {
|
setSelectedElementId((current) => {
|
||||||
if (!normalizedElements.length) return '';
|
if (!normalizedElements.length) return '';
|
||||||
if (normalizedElements.some((element) => element.id === current)) return current;
|
if (normalizedElements.some((element) => element.id === current)) return current;
|
||||||
return normalizedElements[0].id;
|
return '';
|
||||||
});
|
});
|
||||||
setBackgroundImageUrl(activePage.background_image_url || '');
|
setBackgroundImageUrl(activePage.background_image_url || '');
|
||||||
setBackgroundVideoUrl(activePage.background_video_url || '');
|
setBackgroundVideoUrl(activePage.background_video_url || '');
|
||||||
@ -890,7 +977,11 @@ const ConstructorPage = () => {
|
|||||||
<div key={card.id} className='h-12 overflow-hidden rounded bg-gray-100'>
|
<div key={card.id} className='h-12 overflow-hidden rounded bg-gray-100'>
|
||||||
{card.imageUrl ? (
|
{card.imageUrl ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img src={card.imageUrl} alt={card.title || 'Gallery card'} className='h-full w-full object-cover' />
|
<img
|
||||||
|
src={resolveAssetPlaybackUrl(card.imageUrl)}
|
||||||
|
alt={card.title || 'Gallery card'}
|
||||||
|
className='h-full w-full object-cover'
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex h-full items-center justify-center text-[9px] text-gray-400'>No image</div>
|
<div className='flex h-full items-center justify-center text-[9px] text-gray-400'>No image</div>
|
||||||
)}
|
)}
|
||||||
@ -909,7 +1000,11 @@ const ConstructorPage = () => {
|
|||||||
<div className='h-20 overflow-hidden rounded bg-gray-100'>
|
<div className='h-20 overflow-hidden rounded bg-gray-100'>
|
||||||
{firstSlide?.imageUrl ? (
|
{firstSlide?.imageUrl ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img src={firstSlide.imageUrl} alt={firstSlide.caption || 'Carousel slide'} className='h-full w-full object-cover' />
|
<img
|
||||||
|
src={resolveAssetPlaybackUrl(firstSlide.imageUrl)}
|
||||||
|
alt={firstSlide.caption || 'Carousel slide'}
|
||||||
|
className='h-full w-full object-cover'
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex h-full items-center justify-center text-[10px] text-gray-400'>No slide image</div>
|
<div className='flex h-full items-center justify-center text-[10px] text-gray-400'>No slide image</div>
|
||||||
)}
|
)}
|
||||||
@ -926,7 +1021,7 @@ const ConstructorPage = () => {
|
|||||||
<video
|
<video
|
||||||
key={`${element.id}_${element.mediaUrl || ''}_${String(Boolean(element.mediaAutoplay))}_${String(Boolean(element.mediaLoop))}_${String(Boolean(element.mediaMuted))}`}
|
key={`${element.id}_${element.mediaUrl || ''}_${String(Boolean(element.mediaAutoplay))}_${String(Boolean(element.mediaLoop))}_${String(Boolean(element.mediaMuted))}`}
|
||||||
className='h-24 w-full rounded bg-black object-cover'
|
className='h-24 w-full rounded bg-black object-cover'
|
||||||
src={element.mediaUrl || ''}
|
src={resolveAssetPlaybackUrl(element.mediaUrl)}
|
||||||
controls
|
controls
|
||||||
autoPlay={Boolean(element.mediaAutoplay)}
|
autoPlay={Boolean(element.mediaAutoplay)}
|
||||||
loop={Boolean(element.mediaLoop)}
|
loop={Boolean(element.mediaLoop)}
|
||||||
@ -944,7 +1039,7 @@ const ConstructorPage = () => {
|
|||||||
<audio
|
<audio
|
||||||
key={`${element.id}_${element.mediaUrl || ''}_${String(Boolean(element.mediaAutoplay))}_${String(Boolean(element.mediaLoop))}`}
|
key={`${element.id}_${element.mediaUrl || ''}_${String(Boolean(element.mediaAutoplay))}_${String(Boolean(element.mediaLoop))}`}
|
||||||
className='w-full'
|
className='w-full'
|
||||||
src={element.mediaUrl || ''}
|
src={resolveAssetPlaybackUrl(element.mediaUrl)}
|
||||||
controls
|
controls
|
||||||
autoPlay={Boolean(element.mediaAutoplay)}
|
autoPlay={Boolean(element.mediaAutoplay)}
|
||||||
loop={Boolean(element.mediaLoop)}
|
loop={Boolean(element.mediaLoop)}
|
||||||
@ -957,6 +1052,9 @@ const ConstructorPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const canvasBackgroundStyle: React.CSSProperties = {};
|
const canvasBackgroundStyle: React.CSSProperties = {};
|
||||||
|
const backgroundImageSrc = resolveAssetPlaybackUrl(backgroundImageUrl);
|
||||||
|
const backgroundVideoSrc = resolveAssetPlaybackUrl(backgroundVideoUrl);
|
||||||
|
const backgroundAudioSrc = resolveAssetPlaybackUrl(backgroundAudioUrl);
|
||||||
const backgroundImageSelectOptions = addFallbackAssetOption(
|
const backgroundImageSelectOptions = addFallbackAssetOption(
|
||||||
backgroundImageAssetOptions,
|
backgroundImageAssetOptions,
|
||||||
backgroundImageUrl,
|
backgroundImageUrl,
|
||||||
@ -974,8 +1072,8 @@ const ConstructorPage = () => {
|
|||||||
? 'Background audio'
|
? 'Background audio'
|
||||||
: selectedElement?.label || 'Element editor';
|
: selectedElement?.label || 'Element editor';
|
||||||
|
|
||||||
if (backgroundImageUrl) {
|
if (backgroundImageSrc) {
|
||||||
canvasBackgroundStyle.backgroundImage = `url("${backgroundImageUrl}")`;
|
canvasBackgroundStyle.backgroundImage = `url("${backgroundImageSrc}")`;
|
||||||
canvasBackgroundStyle.backgroundSize = 'cover';
|
canvasBackgroundStyle.backgroundSize = 'cover';
|
||||||
canvasBackgroundStyle.backgroundPosition = 'center';
|
canvasBackgroundStyle.backgroundPosition = 'center';
|
||||||
}
|
}
|
||||||
@ -1095,22 +1193,22 @@ const ConstructorPage = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref={canvasRef} className='absolute inset-0 bg-white overflow-hidden' style={canvasBackgroundStyle}>
|
<div ref={canvasRef} tabIndex={-1} className='absolute inset-0 bg-white overflow-hidden' style={canvasBackgroundStyle}>
|
||||||
{backgroundImageUrl ? (
|
{backgroundImageSrc ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img
|
<img
|
||||||
key={`bg_image_${backgroundImageUrl}`}
|
key={`bg_image_${backgroundImageSrc}`}
|
||||||
src={backgroundImageUrl}
|
src={backgroundImageSrc}
|
||||||
alt='Background'
|
alt='Background'
|
||||||
className='absolute inset-0 h-full w-full object-cover pointer-events-none select-none'
|
className='absolute inset-0 h-full w-full object-cover pointer-events-none select-none'
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{backgroundVideoUrl ? (
|
{backgroundVideoSrc ? (
|
||||||
<video
|
<video
|
||||||
key={`bg_video_${backgroundVideoUrl}`}
|
key={`bg_video_${backgroundVideoSrc}`}
|
||||||
className='absolute inset-0 w-full h-full object-cover'
|
className='absolute inset-0 w-full h-full object-cover'
|
||||||
src={backgroundVideoUrl}
|
src={backgroundVideoSrc}
|
||||||
autoPlay
|
autoPlay
|
||||||
loop
|
loop
|
||||||
muted
|
muted
|
||||||
@ -1118,7 +1216,7 @@ const ConstructorPage = () => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{backgroundAudioUrl ? <audio key={`bg_audio_${backgroundAudioUrl}`} src={backgroundAudioUrl} autoPlay loop hidden /> : null}
|
{backgroundAudioSrc ? <audio key={`bg_audio_${backgroundAudioSrc}`} src={backgroundAudioSrc} autoPlay loop hidden /> : null}
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className='absolute inset-0 flex items-center justify-center'>
|
<div className='absolute inset-0 flex items-center justify-center'>
|
||||||
@ -1681,7 +1779,9 @@ const ConstructorPage = () => {
|
|||||||
<div className='fixed inset-0 z-50 bg-black/95 flex items-center justify-center'>
|
<div className='fixed inset-0 z-50 bg-black/95 flex items-center justify-center'>
|
||||||
<video
|
<video
|
||||||
ref={transitionVideoRef}
|
ref={transitionVideoRef}
|
||||||
src={transitionPreview.reverseMode === 'separate' ? transitionPreview.reverseVideoUrl || '' : transitionPreview.videoUrl}
|
src={resolveAssetPlaybackUrl(
|
||||||
|
transitionPreview.reverseMode === 'separate' ? transitionPreview.reverseVideoUrl || '' : transitionPreview.videoUrl,
|
||||||
|
)}
|
||||||
className='h-full w-full object-cover'
|
className='h-full w-full object-cover'
|
||||||
muted
|
muted
|
||||||
playsInline
|
playsInline
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user