tsmoothe transitions improvement
This commit is contained in:
parent
54aec6d861
commit
fa41bd6ee1
@ -21,7 +21,7 @@ import BaseButton from './BaseButton';
|
|||||||
import CardBox from './CardBox';
|
import CardBox from './CardBox';
|
||||||
import { OfflineToggle } from './Offline/OfflineToggle';
|
import { OfflineToggle } from './Offline/OfflineToggle';
|
||||||
import LayoutGuest from '../layouts/Guest';
|
import LayoutGuest from '../layouts/Guest';
|
||||||
import { getPageTitle } from '../config';
|
import { getPageTitle, baseURLApi } from '../config';
|
||||||
import { PRELOAD_CONFIG } from '../config/preload.config';
|
import { PRELOAD_CONFIG } from '../config/preload.config';
|
||||||
import { usePreloadOrchestrator } from '../hooks/usePreloadOrchestrator';
|
import { usePreloadOrchestrator } from '../hooks/usePreloadOrchestrator';
|
||||||
import { useTransitionPlayback } from '../hooks/useTransitionPlayback';
|
import { useTransitionPlayback } from '../hooks/useTransitionPlayback';
|
||||||
@ -31,7 +31,6 @@ import {
|
|||||||
markPresignedUrlFailed,
|
markPresignedUrlFailed,
|
||||||
isRelativeStoragePath,
|
isRelativeStoragePath,
|
||||||
} from '../lib/assetUrl';
|
} from '../lib/assetUrl';
|
||||||
import { baseURLApi } from '../config';
|
|
||||||
import { buildElementStyle } from '../lib/elementStyles';
|
import { buildElementStyle } from '../lib/elementStyles';
|
||||||
import type {
|
import type {
|
||||||
RuntimeProject,
|
RuntimeProject,
|
||||||
@ -214,6 +213,9 @@ export default function RuntimePresentation({
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
const [isBackgroundReady, setIsBackgroundReady] = useState(true);
|
||||||
|
const [pendingTransitionComplete, setPendingTransitionComplete] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const transitionVideoRef = useRef<HTMLVideoElement>(null);
|
const transitionVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
@ -289,7 +291,7 @@ export default function RuntimePresentation({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Integrate useTransitionPlayback hook for smooth transitions (matches Constructor pattern)
|
// Integrate useTransitionPlayback hook for smooth transitions (matches Constructor pattern)
|
||||||
const { isBuffering } = useTransitionPlayback({
|
const { isBuffering, phase: transitionPhase } = useTransitionPlayback({
|
||||||
videoRef: transitionVideoRef,
|
videoRef: transitionVideoRef,
|
||||||
transition: transitionPreview
|
transition: transitionPreview
|
||||||
? {
|
? {
|
||||||
@ -300,17 +302,23 @@ export default function RuntimePresentation({
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
onComplete: (targetPageId) => {
|
onComplete: (targetPageId) => {
|
||||||
|
const video = transitionVideoRef.current;
|
||||||
if (targetPageId) {
|
if (targetPageId) {
|
||||||
const targetPage = pages.find((p) => p.id === targetPageId);
|
const targetPage = pages.find((p) => p.id === targetPageId);
|
||||||
waitForPageImages(targetPage || null).then(() => {
|
waitForPageImages(targetPage || null).then(() => {
|
||||||
|
// Mark background as not ready - new image will need to load
|
||||||
|
setIsBackgroundReady(false);
|
||||||
setSelectedPageId(targetPageId);
|
setSelectedPageId(targetPageId);
|
||||||
setPageHistory((prev) => [...prev, targetPageId]);
|
setPageHistory((prev) => [...prev, targetPageId]);
|
||||||
requestAnimationFrame(() => {
|
// Signal that transition is complete and waiting for background
|
||||||
requestAnimationFrame(() => {
|
setPendingTransitionComplete(true);
|
||||||
setTransitionPreview(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// No target page - clean up and remove overlay
|
||||||
|
video?.removeAttribute('src');
|
||||||
|
video?.load();
|
||||||
|
setTransitionPreview(null);
|
||||||
|
setPendingTransitionComplete(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
@ -359,6 +367,21 @@ export default function RuntimePresentation({
|
|||||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Remove transition overlay when background is ready
|
||||||
|
useEffect(() => {
|
||||||
|
if (pendingTransitionComplete && isBackgroundReady) {
|
||||||
|
const video = transitionVideoRef.current;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
video?.removeAttribute('src');
|
||||||
|
video?.load();
|
||||||
|
setTransitionPreview(null);
|
||||||
|
setPendingTransitionComplete(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [pendingTransitionComplete, isBackgroundReady]);
|
||||||
|
|
||||||
// Load presentation data
|
// Load presentation data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
@ -471,6 +494,17 @@ export default function RuntimePresentation({
|
|||||||
}
|
}
|
||||||
}, [selectedPage]);
|
}, [selectedPage]);
|
||||||
|
|
||||||
|
// Handle background ready state for pages without images or with videos
|
||||||
|
useEffect(() => {
|
||||||
|
// If no background image, or if there's a video (video takes over), mark as ready
|
||||||
|
if (
|
||||||
|
!selectedPage?.background_image_url ||
|
||||||
|
selectedPage?.background_video_url
|
||||||
|
) {
|
||||||
|
setIsBackgroundReady(true);
|
||||||
|
}
|
||||||
|
}, [selectedPage?.background_image_url, selectedPage?.background_video_url]);
|
||||||
|
|
||||||
const navigateToPage = useCallback(
|
const navigateToPage = useCallback(
|
||||||
async (
|
async (
|
||||||
targetPageId: string,
|
targetPageId: string,
|
||||||
@ -488,8 +522,10 @@ export default function RuntimePresentation({
|
|||||||
isReverse: isBack,
|
isReverse: isBack,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Direct navigation - wait for images first
|
// Direct navigation - wait for images first, then switch
|
||||||
await waitForPageImages(targetPage);
|
await waitForPageImages(targetPage);
|
||||||
|
// Mark background as loading (Image onLoad will set it back to true)
|
||||||
|
setIsBackgroundReady(false);
|
||||||
setSelectedPageId(targetPageId);
|
setSelectedPageId(targetPageId);
|
||||||
setPageHistory((prev) => [...prev, targetPageId]);
|
setPageHistory((prev) => [...prev, targetPageId]);
|
||||||
}
|
}
|
||||||
@ -756,6 +792,24 @@ export default function RuntimePresentation({
|
|||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Background image element - ensures proper loading for waitForPageImages() */}
|
||||||
|
{backgroundImageUrl && !backgroundVideoUrl && (
|
||||||
|
<div className='absolute inset-0 pointer-events-none'>
|
||||||
|
<Image
|
||||||
|
key={backgroundImageUrl}
|
||||||
|
src={backgroundImageUrl}
|
||||||
|
alt=''
|
||||||
|
fill
|
||||||
|
sizes='100vw'
|
||||||
|
className='object-cover'
|
||||||
|
priority
|
||||||
|
unoptimized
|
||||||
|
onLoad={() => setIsBackgroundReady(true)}
|
||||||
|
onError={() => setIsBackgroundReady(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Background video */}
|
{/* Background video */}
|
||||||
{backgroundVideoUrl && (
|
{backgroundVideoUrl && (
|
||||||
<video
|
<video
|
||||||
@ -828,12 +882,15 @@ export default function RuntimePresentation({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Transition overlay - uses useTransitionPlayback hook for smooth transitions */}
|
{/* Transition overlay - uses useTransitionPlayback hook for smooth transitions */}
|
||||||
|
{/* Opacity is 0 during 'preparing' phase to show old page while video loads */}
|
||||||
{transitionPreview && (
|
{transitionPreview && (
|
||||||
<div className='fixed inset-0 z-50 overflow-hidden pointer-events-none'>
|
<div className='fixed inset-0 z-50 overflow-hidden pointer-events-none'>
|
||||||
<video
|
<video
|
||||||
ref={transitionVideoRef}
|
ref={transitionVideoRef}
|
||||||
className='absolute inset-0 h-full w-full object-cover transition-opacity duration-300 ease-linear'
|
className='absolute inset-0 h-full w-full object-cover transition-opacity duration-300 ease-linear'
|
||||||
style={{ opacity: isBuffering ? 0 : 1 }}
|
style={{
|
||||||
|
opacity: transitionPhase === 'preparing' || isBuffering ? 0 : 1,
|
||||||
|
}}
|
||||||
muted
|
muted
|
||||||
playsInline
|
playsInline
|
||||||
preload='auto'
|
preload='auto'
|
||||||
|
|||||||
@ -515,10 +515,16 @@ export function usePreloadOrchestrator(
|
|||||||
const storagePaths: string[] = [];
|
const storagePaths: string[] = [];
|
||||||
const currentPage = pages.find((p) => p.id === currentPageId);
|
const currentPage = pages.find((p) => p.id === currentPageId);
|
||||||
|
|
||||||
if (currentPage?.background_image_url && isRelativeStoragePath(currentPage.background_image_url)) {
|
if (
|
||||||
|
currentPage?.background_image_url &&
|
||||||
|
isRelativeStoragePath(currentPage.background_image_url)
|
||||||
|
) {
|
||||||
storagePaths.push(currentPage.background_image_url);
|
storagePaths.push(currentPage.background_image_url);
|
||||||
}
|
}
|
||||||
if (currentPage?.background_video_url && isRelativeStoragePath(currentPage.background_video_url)) {
|
if (
|
||||||
|
currentPage?.background_video_url &&
|
||||||
|
isRelativeStoragePath(currentPage.background_video_url)
|
||||||
|
) {
|
||||||
storagePaths.push(currentPage.background_video_url);
|
storagePaths.push(currentPage.background_video_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,7 +538,10 @@ export function usePreloadOrchestrator(
|
|||||||
const neighbors = neighborGraph.getNeighbors(currentPageId, 1);
|
const neighbors = neighborGraph.getNeighbors(currentPageId, 1);
|
||||||
neighbors.forEach(({ pageId }) => {
|
neighbors.forEach(({ pageId }) => {
|
||||||
const page = pages.find((p) => p.id === pageId);
|
const page = pages.find((p) => p.id === pageId);
|
||||||
if (page?.background_image_url && isRelativeStoragePath(page.background_image_url)) {
|
if (
|
||||||
|
page?.background_image_url &&
|
||||||
|
isRelativeStoragePath(page.background_image_url)
|
||||||
|
) {
|
||||||
storagePaths.push(page.background_image_url);
|
storagePaths.push(page.background_image_url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -548,7 +557,9 @@ export function usePreloadOrchestrator(
|
|||||||
addToQueue({
|
addToQueue({
|
||||||
id: `bg-img-${currentPageId}`,
|
id: `bg-img-${currentPageId}`,
|
||||||
url: resolvedUrl,
|
url: resolvedUrl,
|
||||||
storageKey: isRelativeStoragePath(storageKey) ? storageKey : undefined,
|
storageKey: isRelativeStoragePath(storageKey)
|
||||||
|
? storageKey
|
||||||
|
: undefined,
|
||||||
priority: PRELOAD_CONFIG.priority.currentPage + 200,
|
priority: PRELOAD_CONFIG.priority.currentPage + 200,
|
||||||
assetType: 'image',
|
assetType: 'image',
|
||||||
pageId: currentPageId,
|
pageId: currentPageId,
|
||||||
@ -562,7 +573,9 @@ export function usePreloadOrchestrator(
|
|||||||
addToQueue({
|
addToQueue({
|
||||||
id: `bg-vid-${currentPageId}`,
|
id: `bg-vid-${currentPageId}`,
|
||||||
url: resolvedUrl,
|
url: resolvedUrl,
|
||||||
storageKey: isRelativeStoragePath(storageKey) ? storageKey : undefined,
|
storageKey: isRelativeStoragePath(storageKey)
|
||||||
|
? storageKey
|
||||||
|
: undefined,
|
||||||
priority: PRELOAD_CONFIG.priority.currentPage + 150,
|
priority: PRELOAD_CONFIG.priority.currentPage + 150,
|
||||||
assetType: 'video',
|
assetType: 'video',
|
||||||
pageId: currentPageId,
|
pageId: currentPageId,
|
||||||
@ -578,7 +591,9 @@ export function usePreloadOrchestrator(
|
|||||||
addToQueue({
|
addToQueue({
|
||||||
id: generateJobId(),
|
id: generateJobId(),
|
||||||
url: resolvedUrl,
|
url: resolvedUrl,
|
||||||
storageKey: isRelativeStoragePath(storageKey) ? storageKey : undefined,
|
storageKey: isRelativeStoragePath(storageKey)
|
||||||
|
? storageKey
|
||||||
|
: undefined,
|
||||||
priority: asset.priority,
|
priority: asset.priority,
|
||||||
assetType: asset.assetType,
|
assetType: asset.assetType,
|
||||||
pageId: asset.pageId,
|
pageId: asset.pageId,
|
||||||
@ -598,7 +613,9 @@ export function usePreloadOrchestrator(
|
|||||||
addToQueue({
|
addToQueue({
|
||||||
id: `bg-img-${pageId}`,
|
id: `bg-img-${pageId}`,
|
||||||
url: resolvedUrl,
|
url: resolvedUrl,
|
||||||
storageKey: isRelativeStoragePath(storageKey) ? storageKey : undefined,
|
storageKey: isRelativeStoragePath(storageKey)
|
||||||
|
? storageKey
|
||||||
|
: undefined,
|
||||||
priority: PRELOAD_CONFIG.priority.neighborBase,
|
priority: PRELOAD_CONFIG.priority.neighborBase,
|
||||||
assetType: 'image',
|
assetType: 'image',
|
||||||
pageId,
|
pageId,
|
||||||
@ -620,9 +637,12 @@ export function usePreloadOrchestrator(
|
|||||||
addAssetsToQueue();
|
addAssetsToQueue();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('[PRELOAD] Failed to fetch presigned URLs, falling back to proxy', {
|
logger.error(
|
||||||
error: error?.message,
|
'[PRELOAD] Failed to fetch presigned URLs, falling back to proxy',
|
||||||
});
|
{
|
||||||
|
error: error?.message,
|
||||||
|
},
|
||||||
|
);
|
||||||
// Fallback: add to queue without presigned URLs (will use backend proxy)
|
// Fallback: add to queue without presigned URLs (will use backend proxy)
|
||||||
addAssetsToQueue();
|
addAssetsToQueue();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -113,7 +113,10 @@ function isPresignedUrl(url: string): boolean {
|
|||||||
* Convert a presigned URL back to proxy URL
|
* Convert a presigned URL back to proxy URL
|
||||||
* Extracts the storage key from the S3 path and builds a proxy URL
|
* Extracts the storage key from the S3 path and builds a proxy URL
|
||||||
*/
|
*/
|
||||||
function getProxyUrlFallback(presignedUrl: string, originalStorageKey?: string): string | null {
|
function getProxyUrlFallback(
|
||||||
|
presignedUrl: string,
|
||||||
|
originalStorageKey?: string,
|
||||||
|
): string | null {
|
||||||
// If we have the original storage key, use it directly
|
// If we have the original storage key, use it directly
|
||||||
if (originalStorageKey && isRelativeStoragePath(originalStorageKey)) {
|
if (originalStorageKey && isRelativeStoragePath(originalStorageKey)) {
|
||||||
const normalizedPath = originalStorageKey.replace(/^\/+/, '');
|
const normalizedPath = originalStorageKey.replace(/^\/+/, '');
|
||||||
@ -615,7 +618,11 @@ export function useTransitionPlayback(
|
|||||||
|
|
||||||
// Check if this is a presigned URL failure (likely CORS)
|
// Check if this is a presigned URL failure (likely CORS)
|
||||||
const currentUrl = currentPlayableUrlRef.current;
|
const currentUrl = currentPlayableUrlRef.current;
|
||||||
if (currentUrl && isPresignedUrl(currentUrl) && !didTryFallbackRef.current) {
|
if (
|
||||||
|
currentUrl &&
|
||||||
|
isPresignedUrl(currentUrl) &&
|
||||||
|
!didTryFallbackRef.current
|
||||||
|
) {
|
||||||
logger.info('Presigned URL failed, trying proxy fallback', {
|
logger.info('Presigned URL failed, trying proxy fallback', {
|
||||||
url: currentUrl.slice(0, 80),
|
url: currentUrl.slice(0, 80),
|
||||||
});
|
});
|
||||||
@ -628,7 +635,10 @@ export function useTransitionPlayback(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get proxy fallback URL
|
// Get proxy fallback URL
|
||||||
const fallbackUrl = getProxyUrlFallback(currentUrl, currentTransition.videoUrl);
|
const fallbackUrl = getProxyUrlFallback(
|
||||||
|
currentUrl,
|
||||||
|
currentTransition.videoUrl,
|
||||||
|
);
|
||||||
if (fallbackUrl) {
|
if (fallbackUrl) {
|
||||||
didTryFallbackRef.current = true;
|
didTryFallbackRef.current = true;
|
||||||
video.pause();
|
video.pause();
|
||||||
@ -684,7 +694,17 @@ export function useTransitionPlayback(
|
|||||||
clearTimers();
|
clearTimers();
|
||||||
stopReverseRef.current?.();
|
stopReverseRef.current?.();
|
||||||
};
|
};
|
||||||
}, [sourceUrl, videoRef, playbackStartMs, durationBufferMs, hardTimeoutMs, clearTimers, revokeBlobUrl, finishPlayback, handleError]);
|
}, [
|
||||||
|
sourceUrl,
|
||||||
|
videoRef,
|
||||||
|
playbackStartMs,
|
||||||
|
durationBufferMs,
|
||||||
|
hardTimeoutMs,
|
||||||
|
clearTimers,
|
||||||
|
revokeBlobUrl,
|
||||||
|
finishPlayback,
|
||||||
|
handleError,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!transition) {
|
if (!transition) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import { baseURLApi } from '../config';
|
import { baseURLApi } from '../config';
|
||||||
|
import { logger } from './logger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a URL is a presigned S3 URL
|
* Check if a URL is a presigned S3 URL
|
||||||
@ -27,12 +28,12 @@ export const setupPresignedUrlInterceptor = (): void => {
|
|||||||
|
|
||||||
// Check if this is a presigned S3 URL failure (likely CORS)
|
// Check if this is a presigned S3 URL failure (likely CORS)
|
||||||
if (isPresignedS3Url(url) && !presignedUrlsDisabled) {
|
if (isPresignedS3Url(url) && !presignedUrlsDisabled) {
|
||||||
console.info('[assetUrl] Presigned URL request failed, disabling presigned URLs');
|
logger.info('Presigned URL request failed, disabling presigned URLs');
|
||||||
disablePresignedUrls();
|
disablePresignedUrls();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ export const disablePresignedUrls = (): void => {
|
|||||||
if (!presignedUrlsDisabled) {
|
if (!presignedUrlsDisabled) {
|
||||||
presignedUrlsDisabled = true;
|
presignedUrlsDisabled = true;
|
||||||
presignedUrlCache.clear();
|
presignedUrlCache.clear();
|
||||||
console.info('[assetUrl] Presigned URLs disabled - all requests will use proxy');
|
logger.info('Presigned URLs disabled - all requests will use proxy');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -127,7 +128,9 @@ const processBatch = async (): Promise<void> => {
|
|||||||
* Queue a URL for batch presigning and return a promise that resolves when the batch completes.
|
* Queue a URL for batch presigning and return a promise that resolves when the batch completes.
|
||||||
* Used by the preloader to efficiently fetch presigned URLs for multiple assets.
|
* Used by the preloader to efficiently fetch presigned URLs for multiple assets.
|
||||||
*/
|
*/
|
||||||
export const queuePresignedUrl = (storageKey: string): Promise<string | null> => {
|
export const queuePresignedUrl = (
|
||||||
|
storageKey: string,
|
||||||
|
): Promise<string | null> => {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
const cached = presignedUrlCache.get(storageKey);
|
const cached = presignedUrlCache.get(storageKey);
|
||||||
if (cached && cached.expiresAt > Date.now() + CACHE_BUFFER_MS) {
|
if (cached && cached.expiresAt > Date.now() + CACHE_BUFFER_MS) {
|
||||||
@ -158,7 +161,9 @@ export const queuePresignedUrl = (storageKey: string): Promise<string | null> =>
|
|||||||
* More efficient than calling queuePresignedUrl multiple times.
|
* More efficient than calling queuePresignedUrl multiple times.
|
||||||
* Returns empty object if presigned URLs are disabled.
|
* Returns empty object if presigned URLs are disabled.
|
||||||
*/
|
*/
|
||||||
export const queuePresignedUrls = (storageKeys: string[]): Promise<Record<string, string>> => {
|
export const queuePresignedUrls = (
|
||||||
|
storageKeys: string[],
|
||||||
|
): Promise<Record<string, string>> => {
|
||||||
// Skip if presigned URLs are disabled
|
// Skip if presigned URLs are disabled
|
||||||
if (presignedUrlsDisabled) {
|
if (presignedUrlsDisabled) {
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
@ -259,7 +264,7 @@ export const arePresignedUrlsDisabled = (): boolean => {
|
|||||||
export const markPresignedUrlsVerified = (): void => {
|
export const markPresignedUrlsVerified = (): void => {
|
||||||
if (!presignedUrlsDisabled && !presignedUrlsVerified) {
|
if (!presignedUrlsDisabled && !presignedUrlsVerified) {
|
||||||
presignedUrlsVerified = true;
|
presignedUrlsVerified = true;
|
||||||
console.info('[assetUrl] Presigned URLs verified - enabling direct S3 access');
|
logger.info('Presigned URLs verified - enabling direct S3 access');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -64,9 +64,7 @@ export const decodeImages = async (
|
|||||||
* @example
|
* @example
|
||||||
* const imageUrls = extractPageImageUrls(page);
|
* const imageUrls = extractPageImageUrls(page);
|
||||||
*/
|
*/
|
||||||
export const extractPageImageUrls = (
|
export const extractPageImageUrls = (page: PageWithImages | null): string[] => {
|
||||||
page: PageWithImages | null,
|
|
||||||
): string[] => {
|
|
||||||
if (!page) return [];
|
if (!page) return [];
|
||||||
|
|
||||||
const imageUrls: string[] = [];
|
const imageUrls: string[] = [];
|
||||||
|
|||||||
@ -69,7 +69,10 @@ axios.interceptors.response.use(
|
|||||||
|
|
||||||
// Detect presigned S3 URL failures (CORS not configured)
|
// Detect presigned S3 URL failures (CORS not configured)
|
||||||
// Network errors (status 0) or CORS errors typically indicate S3 CORS issues
|
// Network errors (status 0) or CORS errors typically indicate S3 CORS issues
|
||||||
if (isPresignedS3Url(requestUrl) && (!status || status === 0 || error.message?.includes('Network Error'))) {
|
if (
|
||||||
|
isPresignedS3Url(requestUrl) &&
|
||||||
|
(!status || status === 0 || error.message?.includes('Network Error'))
|
||||||
|
) {
|
||||||
logger.info('[axios] Presigned URL failed, disabling presigned URLs', {
|
logger.info('[axios] Presigned URL failed, disabling presigned URLs', {
|
||||||
url: requestUrl.slice(0, 80),
|
url: requestUrl.slice(0, 80),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -427,9 +427,13 @@ const mergeElementWithDefaults = (
|
|||||||
const elementValue = elementRecord[prop];
|
const elementValue = elementRecord[prop];
|
||||||
const defaultValue = defaultsRecord[prop];
|
const defaultValue = defaultsRecord[prop];
|
||||||
const elementIsEmpty =
|
const elementIsEmpty =
|
||||||
elementValue === '' || elementValue === undefined || elementValue === null;
|
elementValue === '' ||
|
||||||
|
elementValue === undefined ||
|
||||||
|
elementValue === null;
|
||||||
const defaultHasValue =
|
const defaultHasValue =
|
||||||
defaultValue !== undefined && defaultValue !== null && defaultValue !== '';
|
defaultValue !== undefined &&
|
||||||
|
defaultValue !== null &&
|
||||||
|
defaultValue !== '';
|
||||||
if (elementIsEmpty && defaultHasValue) {
|
if (elementIsEmpty && defaultHasValue) {
|
||||||
mergedRecord[prop] = defaultValue;
|
mergedRecord[prop] = defaultValue;
|
||||||
}
|
}
|
||||||
@ -1087,10 +1091,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
|||||||
(item: Record<string, unknown>) => {
|
(item: Record<string, unknown>) => {
|
||||||
const extracted: Record<string, unknown> = {};
|
const extracted: Record<string, unknown> = {};
|
||||||
nestedUrlFields.forEach((urlField) => {
|
nestedUrlFields.forEach((urlField) => {
|
||||||
if (
|
if (item[urlField] !== undefined && item[urlField] !== '') {
|
||||||
item[urlField] !== undefined &&
|
|
||||||
item[urlField] !== ''
|
|
||||||
) {
|
|
||||||
extracted[urlField] = item[urlField];
|
extracted[urlField] = item[urlField];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -232,8 +232,7 @@ const RuntimePageView = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBack =
|
const isBack = linkDirection === 'back' || isBackNavigation(targetPageId);
|
||||||
linkDirection === 'back' || isBackNavigation(targetPageId);
|
|
||||||
const transitionName =
|
const transitionName =
|
||||||
transition?.name || transition?.slug || 'Transition';
|
transition?.name || transition?.slug || 'Transition';
|
||||||
const canUseReverseVideo =
|
const canUseReverseVideo =
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user