formatting

This commit is contained in:
Dmitri 2026-05-04 17:30:32 +02:00
parent e4dd94f478
commit 6581fd70c2
13 changed files with 156 additions and 87 deletions

View File

@ -73,7 +73,11 @@ class Project_transition_settingsDBApi extends GenericDBApi {
* @param {object} options - Query options * @param {object} options - Query options
* @returns {object|null} Settings record or null * @returns {object|null} Settings record or null
*/ */
static async findByProjectAndEnvironment(projectId, environment, options = {}) { static async findByProjectAndEnvironment(
projectId,
environment,
options = {},
) {
const transaction = options.transaction; const transaction = options.transaction;
const record = await this.MODEL.findOne({ const record = await this.MODEL.findOne({

View File

@ -52,12 +52,13 @@ class ProjectsDBApi extends GenericDBApi {
// Note: transition_settings moved to project_transition_settings table // Note: transition_settings moved to project_transition_settings table
return { return {
id: data.id || undefined, id: data.id || undefined,
name: 'name' in data ? (data.name || null) : undefined, name: 'name' in data ? data.name || null : undefined,
slug: 'slug' in data ? (data.slug || null) : undefined, slug: 'slug' in data ? data.slug || null : undefined,
description: 'description' in data ? (data.description || null) : undefined, description: 'description' in data ? data.description || null : undefined,
logo_url: 'logo_url' in data ? (data.logo_url || null) : undefined, logo_url: 'logo_url' in data ? data.logo_url || null : undefined,
favicon_url: 'favicon_url' in data ? (data.favicon_url || null) : undefined, favicon_url: 'favicon_url' in data ? data.favicon_url || null : undefined,
og_image_url: 'og_image_url' in data ? (data.og_image_url || null) : undefined, og_image_url:
'og_image_url' in data ? data.og_image_url || null : undefined,
design_width: 'design_width' in data ? data.design_width : undefined, design_width: 'design_width' in data ? data.design_width : undefined,
design_height: 'design_height' in data ? data.design_height : undefined, design_height: 'design_height' in data ? data.design_height : undefined,
}; };

View File

@ -130,13 +130,20 @@ module.exports = {
try { try {
settings = JSON.parse(settings); settings = JSON.parse(settings);
} catch (e) { } catch (e) {
console.warn(`Failed to parse transition_settings for project ${project.id}:`, e); console.warn(
`Failed to parse transition_settings for project ${project.id}:`,
e,
);
continue; continue;
} }
} }
// Skip if settings is null, empty object, or has no actual values // Skip if settings is null, empty object, or has no actual values
if (!settings || typeof settings !== 'object' || Object.keys(settings).length === 0) { if (
!settings ||
typeof settings !== 'object' ||
Object.keys(settings).length === 0
) {
continue; continue;
} }
@ -160,7 +167,9 @@ module.exports = {
if (records.length > 0) { if (records.length > 0) {
await queryInterface.bulkInsert('project_transition_settings', records); await queryInterface.bulkInsert('project_transition_settings', records);
console.log(`Migrated ${records.length} project transition settings to 'dev' environment`); console.log(
`Migrated ${records.length} project transition settings to 'dev' environment`,
);
} }
// Step 3: Drop the transition_settings column from projects table // Step 3: Drop the transition_settings column from projects table
@ -192,16 +201,19 @@ module.exports = {
overlayColor: setting.overlay_color, overlayColor: setting.overlay_color,
}); });
await queryInterface.sequelize.query(` await queryInterface.sequelize.query(
`
UPDATE projects UPDATE projects
SET transition_settings = :settings::jsonb SET transition_settings = :settings::jsonb
WHERE id = :projectId WHERE id = :projectId
`, { `,
replacements: { {
settings: jsonValue, replacements: {
projectId: setting.projectId, settings: jsonValue,
projectId: setting.projectId,
},
}, },
}); );
} }
// Step 3: Drop indexes and table // Step 3: Drop indexes and table
@ -212,6 +224,8 @@ module.exports = {
await queryInterface.dropTable('project_transition_settings'); await queryInterface.dropTable('project_transition_settings');
// Drop the ENUM type // Drop the ENUM type
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_project_transition_settings_environment";'); await queryInterface.sequelize.query(
'DROP TYPE IF EXISTS "enum_project_transition_settings_environment";',
);
}, },
}; };

View File

@ -114,7 +114,11 @@ const sanitizePublicRuntimeListResponse = (entityName) => {
pattern instanceof RegExp ? pattern.test(req.path) : req.path === pattern, pattern instanceof RegExp ? pattern.test(req.path) : req.path === pattern,
); );
if (!isPublicRuntimeReadRequest(req) || !pathMatches || fields.length === 0) { if (
!isPublicRuntimeReadRequest(req) ||
!pathMatches ||
fields.length === 0
) {
return next(); return next();
} }

View File

@ -433,10 +433,7 @@ const downloadFile = async (req, res) => {
res.setHeader('Content-Length', result.contentLength); res.setHeader('Content-Length', result.contentLength);
// Add caching headers for browser // Add caching headers for browser
res.setHeader( res.setHeader('Cache-Control', `public, max-age=${config.s3CacheMaxAge}`);
'Cache-Control',
`public, max-age=${config.s3CacheMaxAge}`,
);
if (useCache && typeof result.body.pipe === 'function') { if (useCache && typeof result.body.pipe === 'function') {
// Stream to both response and cache file // Stream to both response and cache file
@ -839,13 +836,11 @@ const finalizeUploadSession = async (req, res) => {
// Verify all chunks exist // Verify all chunks exist
for (let i = 0; i < session.totalChunks; i++) { for (let i = 0; i < session.totalChunks; i++) {
if (!sessionManager.chunkExists(sessionId, i)) { if (!sessionManager.chunkExists(sessionId, i)) {
return res return res.status(400).send(
.status(400) createErrorResponse(`Missing chunk ${i}`, 'MISSING_CHUNK', {
.send( missingChunk: i,
createErrorResponse(`Missing chunk ${i}`, 'MISSING_CHUNK', { }),
missingChunk: i, );
}),
);
} }
} }
@ -956,12 +951,16 @@ const getMimeTypeFromExtension = (filepath) => {
*/ */
const copyFile = async (sourceKey, destKey, options = {}) => { const copyFile = async (sourceKey, destKey, options = {}) => {
const provider = getFileStorageProvider(); const provider = getFileStorageProvider();
const contentType = options.contentType || getMimeTypeFromExtension(sourceKey); const contentType =
options.contentType || getMimeTypeFromExtension(sourceKey);
if (provider === 's3') { if (provider === 's3') {
const s3 = getS3Provider(); const s3 = getS3Provider();
const result = await s3.copy(sourceKey, destKey, { contentType }); const result = await s3.copy(sourceKey, destKey, { contentType });
logger.debug({ sourceKey, destKey, provider: 's3' }, 'File copied (server-side)'); logger.debug(
{ sourceKey, destKey, provider: 's3' },
'File copied (server-side)',
);
return { url: result.url }; return { url: result.url };
} }
@ -969,7 +968,9 @@ const copyFile = async (sourceKey, destKey, options = {}) => {
const local = getLocalProvider(); const local = getLocalProvider();
await local.copy(sourceKey, destKey); await local.copy(sourceKey, destKey);
logger.debug({ sourceKey, destKey, provider: 'local' }, 'File copied'); logger.debug({ sourceKey, destKey, provider: 'local' }, 'File copied');
return { url: `/api/file/download?privateUrl=${encodeURIComponent(destKey)}` }; return {
url: `/api/file/download?privateUrl=${encodeURIComponent(destKey)}`,
};
} }
// GCloud fallback: download + upload (no native copy implemented) // GCloud fallback: download + upload (no native copy implemented)
@ -1029,13 +1030,20 @@ const copyFilesParallel = async (copies, options = {}) => {
throw new Error(`Copy failed for ${copy.sourceKey}: ${errorMsg}`); throw new Error(`Copy failed for ${copy.sourceKey}: ${errorMsg}`);
} }
logger.warn({ sourceKey: copy.sourceKey, error: errorMsg }, 'File copy failed'); logger.warn(
{ sourceKey: copy.sourceKey, error: errorMsg },
'File copy failed',
);
} }
} }
} }
logger.info( logger.info(
{ succeeded: succeeded.length, failed: failed.length, total: copies.length }, {
succeeded: succeeded.length,
failed: failed.length,
total: copies.length,
},
'Batch file copy completed', 'Batch file copy completed',
); );

View File

@ -124,7 +124,11 @@ module.exports = class Project_transition_settingsService {
/** /**
* Find settings by project ID and environment * Find settings by project ID and environment
*/ */
static async findByProjectAndEnvironment(projectId, environment, currentUser) { static async findByProjectAndEnvironment(
projectId,
environment,
currentUser,
) {
return Project_transition_settingsDBApi.findByProjectAndEnvironment( return Project_transition_settingsDBApi.findByProjectAndEnvironment(
projectId, projectId,
environment, environment,

View File

@ -293,10 +293,13 @@ class ProjectsService extends BaseProjectsService {
'Starting parallel file copy for project clone', 'Starting parallel file copy for project clone',
); );
const { succeeded, failed } = await FileService.copyFilesParallel(copyOperations, { const { succeeded, failed } = await FileService.copyFilesParallel(
concurrency: 10, copyOperations,
continueOnError: true, {
}); concurrency: 10,
continueOnError: true,
},
);
// ============================================ // ============================================
// Phase D: Build assetPathMap from results // Phase D: Build assetPathMap from results
@ -322,7 +325,9 @@ class ProjectsService extends BaseProjectsService {
asset_type: sourceAsset.asset_type, asset_type: sourceAsset.asset_type,
type: sourceAsset.type || 'general', type: sourceAsset.type || 'general',
cdn_url: '', // Will be populated on first presigned URL request cdn_url: '', // Will be populated on first presigned URL request
storage_key: assetPathMap.get(sourceAsset.storage_key) || sourceAsset.storage_key, storage_key:
assetPathMap.get(sourceAsset.storage_key) ||
sourceAsset.storage_key,
mime_type: sourceAsset.mime_type, mime_type: sourceAsset.mime_type,
size_mb: sourceAsset.size_mb, size_mb: sourceAsset.size_mb,
width_px: sourceAsset.width_px, width_px: sourceAsset.width_px,
@ -345,7 +350,8 @@ class ProjectsService extends BaseProjectsService {
if (sourceVariant.variant_type === 'reversed') continue; // Handled in Phase F if (sourceVariant.variant_type === 'reversed') continue; // Handled in Phase F
const variantStorageKey = const variantStorageKey =
assetPathMap.get(sourceVariant.storage_key) || sourceVariant.storage_key; assetPathMap.get(sourceVariant.storage_key) ||
sourceVariant.storage_key;
await db.asset_variants.create( await db.asset_variants.create(
{ {
@ -385,10 +391,13 @@ class ProjectsService extends BaseProjectsService {
'Copying reversed videos for cloned assets', 'Copying reversed videos for cloned assets',
); );
const reversedResults = await FileService.copyFilesParallel(reversedCopyOps, { const reversedResults = await FileService.copyFilesParallel(
concurrency: 10, reversedCopyOps,
continueOnError: true, // Many assets won't have reversed videos - that's OK {
}); concurrency: 10,
continueOnError: true, // Many assets won't have reversed videos - that's OK
},
);
// Add successful reversed video copies to assetPathMap // Add successful reversed video copies to assetPathMap
for (const { sourceKey, destKey } of reversedResults.succeeded) { for (const { sourceKey, destKey } of reversedResults.succeeded) {
@ -431,15 +440,18 @@ class ProjectsService extends BaseProjectsService {
// Transform background URLs to new storage keys // Transform background URLs to new storage keys
if (pageData.background_image_url) { if (pageData.background_image_url) {
pageData.background_image_url = pageData.background_image_url =
assetPathMap.get(pageData.background_image_url) || pageData.background_image_url; assetPathMap.get(pageData.background_image_url) ||
pageData.background_image_url;
} }
if (pageData.background_video_url) { if (pageData.background_video_url) {
pageData.background_video_url = pageData.background_video_url =
assetPathMap.get(pageData.background_video_url) || pageData.background_video_url; assetPathMap.get(pageData.background_video_url) ||
pageData.background_video_url;
} }
if (pageData.background_audio_url) { if (pageData.background_audio_url) {
pageData.background_audio_url = pageData.background_audio_url =
assetPathMap.get(pageData.background_audio_url) || pageData.background_audio_url; assetPathMap.get(pageData.background_audio_url) ||
pageData.background_audio_url;
} }
await db.tour_pages.create( await db.tour_pages.create(

View File

@ -257,20 +257,21 @@ module.exports = class PublishService {
transaction, transaction,
) { ) {
// Get source content // Get source content
const [sourcePages, sourceAudioTracks, sourceTransitionSettings] = await Promise.all([ const [sourcePages, sourceAudioTracks, sourceTransitionSettings] =
db.tour_pages.findAll({ await Promise.all([
where: { projectId, environment: fromEnv }, db.tour_pages.findAll({
transaction, where: { projectId, environment: fromEnv },
}), transaction,
db.project_audio_tracks.findAll({ }),
where: { projectId, environment: fromEnv }, db.project_audio_tracks.findAll({
transaction, where: { projectId, environment: fromEnv },
}), transaction,
db.project_transition_settings.findOne({ }),
where: { projectId, environment: fromEnv }, db.project_transition_settings.findOne({
transaction, where: { projectId, environment: fromEnv },
}), transaction,
]); }),
]);
// Clean up target environment (hard delete - paranoid models need force: true) // Clean up target environment (hard delete - paranoid models need force: true)
await Promise.all([ await Promise.all([

View File

@ -773,19 +773,20 @@ export function ElementEditorPanel({
selectedElement.type === 'gallery' selectedElement.type === 'gallery'
? selectedElement.gallerySlideTransitionDurationMs !== ? selectedElement.gallerySlideTransitionDurationMs !==
undefined && undefined &&
selectedElement.gallerySlideTransitionDurationMs !== '' selectedElement.gallerySlideTransitionDurationMs !==
''
? String( ? String(
selectedElement.gallerySlideTransitionDurationMs, selectedElement.gallerySlideTransitionDurationMs,
) )
: '' : ''
: selectedElement.carouselSlideTransitionDurationMs !== : selectedElement.carouselSlideTransitionDurationMs !==
undefined && undefined &&
selectedElement.carouselSlideTransitionDurationMs !== selectedElement.carouselSlideTransitionDurationMs !==
'' ''
? String( ? String(
selectedElement.carouselSlideTransitionDurationMs, selectedElement.carouselSlideTransitionDurationMs,
) )
: '', : '',
slideTransitionEasing: slideTransitionEasing:
selectedElement.type === 'gallery' selectedElement.type === 'gallery'
? selectedElement.gallerySlideTransitionEasing || '' ? selectedElement.gallerySlideTransitionEasing || ''
@ -848,7 +849,8 @@ export function ElementEditorPanel({
} else if (prop === 'slideTransitionOverlayColor') { } else if (prop === 'slideTransitionOverlayColor') {
if (selectedElement.type === 'gallery') { if (selectedElement.type === 'gallery') {
updateSelectedElement({ updateSelectedElement({
gallerySlideTransitionOverlayColor: value || undefined, gallerySlideTransitionOverlayColor:
value || undefined,
}); });
} else if (selectedElement.type === 'carousel') { } else if (selectedElement.type === 'carousel') {
updateSelectedElement({ updateSelectedElement({

View File

@ -270,7 +270,9 @@ const EffectsSettingsSectionCompact: 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={values.slideTransitionType || ''} value={values.slideTransitionType || ''}
onChange={(e) => onChange('slideTransitionType', e.target.value)} onChange={(e) =>
onChange('slideTransitionType', e.target.value)
}
> >
<option value=''>Use Default</option> <option value=''>Use Default</option>
<option value='fade'>Fade</option> <option value='fade'>Fade</option>

View File

@ -14,7 +14,13 @@
* - Navigation-style rendering when custom icons with dimensions are set * - Navigation-style rendering when custom icons with dimensions are set
*/ */
import React, { useMemo, useCallback, useEffect, useRef, useState } from 'react'; import React, {
useMemo,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import Icon from '@mdi/react'; import Icon from '@mdi/react';

View File

@ -110,12 +110,15 @@ export function resolveSlideTransition(
* Extract slide transition override from carousel element * Extract slide transition override from carousel element
*/ */
export function extractCarouselSlideOverride( export function extractCarouselSlideOverride(
element: { element:
carouselSlideTransitionType?: SlideTransitionType | ''; | {
carouselSlideTransitionDurationMs?: number | ''; carouselSlideTransitionType?: SlideTransitionType | '';
carouselSlideTransitionEasing?: EasingFunction | ''; carouselSlideTransitionDurationMs?: number | '';
carouselSlideTransitionOverlayColor?: string; carouselSlideTransitionEasing?: EasingFunction | '';
} | null | undefined, carouselSlideTransitionOverlayColor?: string;
}
| null
| undefined,
): SlideTransitionOverride | null { ): SlideTransitionOverride | null {
if (!element) return null; if (!element) return null;
return { return {
@ -130,12 +133,15 @@ export function extractCarouselSlideOverride(
* Extract slide transition override from gallery element * Extract slide transition override from gallery element
*/ */
export function extractGallerySlideOverride( export function extractGallerySlideOverride(
element: { element:
gallerySlideTransitionType?: SlideTransitionType | ''; | {
gallerySlideTransitionDurationMs?: number | ''; gallerySlideTransitionType?: SlideTransitionType | '';
gallerySlideTransitionEasing?: EasingFunction | ''; gallerySlideTransitionDurationMs?: number | '';
gallerySlideTransitionOverlayColor?: string; gallerySlideTransitionEasing?: EasingFunction | '';
} | null | undefined, gallerySlideTransitionOverlayColor?: string;
}
| null
| undefined,
): SlideTransitionOverride | null { ): SlideTransitionOverride | null {
if (!element) return null; if (!element) return null;
return { return {

View File

@ -347,7 +347,9 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
// Look up current element for gallery carousel (so it receives updates from element editor) // Look up current element for gallery carousel (so it receives updates from element editor)
const activeGalleryCarouselElement = useMemo(() => { const activeGalleryCarouselElement = useMemo(() => {
if (!activeGalleryCarousel) return null; if (!activeGalleryCarousel) return null;
return elements.find((el) => el.id === activeGalleryCarousel.elementId) || null; return (
elements.find((el) => el.id === activeGalleryCarousel.elementId) || null
);
}, [activeGalleryCarousel, elements]); }, [activeGalleryCarousel, elements]);
// Draggable panels using useDraggable hook // Draggable panels using useDraggable hook
@ -1303,7 +1305,10 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
const handleGalleryCardClick = useCallback( const handleGalleryCardClick = useCallback(
(element: CanvasElement, cardIndex: number) => { (element: CanvasElement, cardIndex: number) => {
if (element.galleryCards && element.galleryCards.length > 0) { if (element.galleryCards && element.galleryCards.length > 0) {
setActiveGalleryCarousel({ elementId: element.id, initialIndex: cardIndex }); setActiveGalleryCarousel({
elementId: element.id,
initialIndex: cardIndex,
});
} }
}, },
[], [],