formatting
This commit is contained in:
parent
e4dd94f478
commit
6581fd70c2
@ -73,7 +73,11 @@ class Project_transition_settingsDBApi extends GenericDBApi {
|
||||
* @param {object} options - Query options
|
||||
* @returns {object|null} Settings record or null
|
||||
*/
|
||||
static async findByProjectAndEnvironment(projectId, environment, options = {}) {
|
||||
static async findByProjectAndEnvironment(
|
||||
projectId,
|
||||
environment,
|
||||
options = {},
|
||||
) {
|
||||
const transaction = options.transaction;
|
||||
|
||||
const record = await this.MODEL.findOne({
|
||||
|
||||
@ -52,12 +52,13 @@ class ProjectsDBApi extends GenericDBApi {
|
||||
// Note: transition_settings moved to project_transition_settings table
|
||||
return {
|
||||
id: data.id || undefined,
|
||||
name: 'name' in data ? (data.name || null) : undefined,
|
||||
slug: 'slug' in data ? (data.slug || null) : undefined,
|
||||
description: 'description' in data ? (data.description || null) : undefined,
|
||||
logo_url: 'logo_url' in data ? (data.logo_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,
|
||||
name: 'name' in data ? data.name || null : undefined,
|
||||
slug: 'slug' in data ? data.slug || null : undefined,
|
||||
description: 'description' in data ? data.description || null : undefined,
|
||||
logo_url: 'logo_url' in data ? data.logo_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,
|
||||
design_width: 'design_width' in data ? data.design_width : undefined,
|
||||
design_height: 'design_height' in data ? data.design_height : undefined,
|
||||
};
|
||||
|
||||
@ -130,13 +130,20 @@ module.exports = {
|
||||
try {
|
||||
settings = JSON.parse(settings);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -160,7 +167,9 @@ module.exports = {
|
||||
|
||||
if (records.length > 0) {
|
||||
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
|
||||
@ -192,16 +201,19 @@ module.exports = {
|
||||
overlayColor: setting.overlay_color,
|
||||
});
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
await queryInterface.sequelize.query(
|
||||
`
|
||||
UPDATE projects
|
||||
SET transition_settings = :settings::jsonb
|
||||
WHERE id = :projectId
|
||||
`, {
|
||||
replacements: {
|
||||
settings: jsonValue,
|
||||
projectId: setting.projectId,
|
||||
`,
|
||||
{
|
||||
replacements: {
|
||||
settings: jsonValue,
|
||||
projectId: setting.projectId,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Drop indexes and table
|
||||
@ -212,6 +224,8 @@ module.exports = {
|
||||
await queryInterface.dropTable('project_transition_settings');
|
||||
|
||||
// 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";',
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@ -114,7 +114,11 @@ const sanitizePublicRuntimeListResponse = (entityName) => {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -433,10 +433,7 @@ const downloadFile = async (req, res) => {
|
||||
res.setHeader('Content-Length', result.contentLength);
|
||||
|
||||
// Add caching headers for browser
|
||||
res.setHeader(
|
||||
'Cache-Control',
|
||||
`public, max-age=${config.s3CacheMaxAge}`,
|
||||
);
|
||||
res.setHeader('Cache-Control', `public, max-age=${config.s3CacheMaxAge}`);
|
||||
|
||||
if (useCache && typeof result.body.pipe === 'function') {
|
||||
// Stream to both response and cache file
|
||||
@ -839,13 +836,11 @@ const finalizeUploadSession = async (req, res) => {
|
||||
// Verify all chunks exist
|
||||
for (let i = 0; i < session.totalChunks; i++) {
|
||||
if (!sessionManager.chunkExists(sessionId, i)) {
|
||||
return res
|
||||
.status(400)
|
||||
.send(
|
||||
createErrorResponse(`Missing chunk ${i}`, 'MISSING_CHUNK', {
|
||||
missingChunk: i,
|
||||
}),
|
||||
);
|
||||
return res.status(400).send(
|
||||
createErrorResponse(`Missing chunk ${i}`, 'MISSING_CHUNK', {
|
||||
missingChunk: i,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -956,12 +951,16 @@ const getMimeTypeFromExtension = (filepath) => {
|
||||
*/
|
||||
const copyFile = async (sourceKey, destKey, options = {}) => {
|
||||
const provider = getFileStorageProvider();
|
||||
const contentType = options.contentType || getMimeTypeFromExtension(sourceKey);
|
||||
const contentType =
|
||||
options.contentType || getMimeTypeFromExtension(sourceKey);
|
||||
|
||||
if (provider === 's3') {
|
||||
const s3 = getS3Provider();
|
||||
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 };
|
||||
}
|
||||
|
||||
@ -969,7 +968,9 @@ const copyFile = async (sourceKey, destKey, options = {}) => {
|
||||
const local = getLocalProvider();
|
||||
await local.copy(sourceKey, destKey);
|
||||
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)
|
||||
@ -1029,13 +1030,20 @@ const copyFilesParallel = async (copies, options = {}) => {
|
||||
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(
|
||||
{ succeeded: succeeded.length, failed: failed.length, total: copies.length },
|
||||
{
|
||||
succeeded: succeeded.length,
|
||||
failed: failed.length,
|
||||
total: copies.length,
|
||||
},
|
||||
'Batch file copy completed',
|
||||
);
|
||||
|
||||
|
||||
@ -124,7 +124,11 @@ module.exports = class Project_transition_settingsService {
|
||||
/**
|
||||
* Find settings by project ID and environment
|
||||
*/
|
||||
static async findByProjectAndEnvironment(projectId, environment, currentUser) {
|
||||
static async findByProjectAndEnvironment(
|
||||
projectId,
|
||||
environment,
|
||||
currentUser,
|
||||
) {
|
||||
return Project_transition_settingsDBApi.findByProjectAndEnvironment(
|
||||
projectId,
|
||||
environment,
|
||||
|
||||
@ -293,10 +293,13 @@ class ProjectsService extends BaseProjectsService {
|
||||
'Starting parallel file copy for project clone',
|
||||
);
|
||||
|
||||
const { succeeded, failed } = await FileService.copyFilesParallel(copyOperations, {
|
||||
concurrency: 10,
|
||||
continueOnError: true,
|
||||
});
|
||||
const { succeeded, failed } = await FileService.copyFilesParallel(
|
||||
copyOperations,
|
||||
{
|
||||
concurrency: 10,
|
||||
continueOnError: true,
|
||||
},
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Phase D: Build assetPathMap from results
|
||||
@ -322,7 +325,9 @@ class ProjectsService extends BaseProjectsService {
|
||||
asset_type: sourceAsset.asset_type,
|
||||
type: sourceAsset.type || 'general',
|
||||
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,
|
||||
size_mb: sourceAsset.size_mb,
|
||||
width_px: sourceAsset.width_px,
|
||||
@ -345,7 +350,8 @@ class ProjectsService extends BaseProjectsService {
|
||||
if (sourceVariant.variant_type === 'reversed') continue; // Handled in Phase F
|
||||
|
||||
const variantStorageKey =
|
||||
assetPathMap.get(sourceVariant.storage_key) || sourceVariant.storage_key;
|
||||
assetPathMap.get(sourceVariant.storage_key) ||
|
||||
sourceVariant.storage_key;
|
||||
|
||||
await db.asset_variants.create(
|
||||
{
|
||||
@ -385,10 +391,13 @@ class ProjectsService extends BaseProjectsService {
|
||||
'Copying reversed videos for cloned assets',
|
||||
);
|
||||
|
||||
const reversedResults = await FileService.copyFilesParallel(reversedCopyOps, {
|
||||
concurrency: 10,
|
||||
continueOnError: true, // Many assets won't have reversed videos - that's OK
|
||||
});
|
||||
const reversedResults = await FileService.copyFilesParallel(
|
||||
reversedCopyOps,
|
||||
{
|
||||
concurrency: 10,
|
||||
continueOnError: true, // Many assets won't have reversed videos - that's OK
|
||||
},
|
||||
);
|
||||
|
||||
// Add successful reversed video copies to assetPathMap
|
||||
for (const { sourceKey, destKey } of reversedResults.succeeded) {
|
||||
@ -431,15 +440,18 @@ class ProjectsService extends BaseProjectsService {
|
||||
// Transform background URLs to new storage keys
|
||||
if (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) {
|
||||
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) {
|
||||
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(
|
||||
|
||||
@ -257,20 +257,21 @@ module.exports = class PublishService {
|
||||
transaction,
|
||||
) {
|
||||
// Get source content
|
||||
const [sourcePages, sourceAudioTracks, sourceTransitionSettings] = await Promise.all([
|
||||
db.tour_pages.findAll({
|
||||
where: { projectId, environment: fromEnv },
|
||||
transaction,
|
||||
}),
|
||||
db.project_audio_tracks.findAll({
|
||||
where: { projectId, environment: fromEnv },
|
||||
transaction,
|
||||
}),
|
||||
db.project_transition_settings.findOne({
|
||||
where: { projectId, environment: fromEnv },
|
||||
transaction,
|
||||
}),
|
||||
]);
|
||||
const [sourcePages, sourceAudioTracks, sourceTransitionSettings] =
|
||||
await Promise.all([
|
||||
db.tour_pages.findAll({
|
||||
where: { projectId, environment: fromEnv },
|
||||
transaction,
|
||||
}),
|
||||
db.project_audio_tracks.findAll({
|
||||
where: { projectId, environment: fromEnv },
|
||||
transaction,
|
||||
}),
|
||||
db.project_transition_settings.findOne({
|
||||
where: { projectId, environment: fromEnv },
|
||||
transaction,
|
||||
}),
|
||||
]);
|
||||
|
||||
// Clean up target environment (hard delete - paranoid models need force: true)
|
||||
await Promise.all([
|
||||
|
||||
@ -773,19 +773,20 @@ export function ElementEditorPanel({
|
||||
selectedElement.type === 'gallery'
|
||||
? selectedElement.gallerySlideTransitionDurationMs !==
|
||||
undefined &&
|
||||
selectedElement.gallerySlideTransitionDurationMs !== ''
|
||||
selectedElement.gallerySlideTransitionDurationMs !==
|
||||
''
|
||||
? String(
|
||||
selectedElement.gallerySlideTransitionDurationMs,
|
||||
)
|
||||
: ''
|
||||
: selectedElement.carouselSlideTransitionDurationMs !==
|
||||
undefined &&
|
||||
selectedElement.carouselSlideTransitionDurationMs !==
|
||||
''
|
||||
? String(
|
||||
selectedElement.carouselSlideTransitionDurationMs,
|
||||
)
|
||||
: '',
|
||||
undefined &&
|
||||
selectedElement.carouselSlideTransitionDurationMs !==
|
||||
''
|
||||
? String(
|
||||
selectedElement.carouselSlideTransitionDurationMs,
|
||||
)
|
||||
: '',
|
||||
slideTransitionEasing:
|
||||
selectedElement.type === 'gallery'
|
||||
? selectedElement.gallerySlideTransitionEasing || ''
|
||||
@ -848,7 +849,8 @@ export function ElementEditorPanel({
|
||||
} else if (prop === 'slideTransitionOverlayColor') {
|
||||
if (selectedElement.type === 'gallery') {
|
||||
updateSelectedElement({
|
||||
gallerySlideTransitionOverlayColor: value || undefined,
|
||||
gallerySlideTransitionOverlayColor:
|
||||
value || undefined,
|
||||
});
|
||||
} else if (selectedElement.type === 'carousel') {
|
||||
updateSelectedElement({
|
||||
|
||||
@ -270,7 +270,9 @@ const EffectsSettingsSectionCompact: React.FC<
|
||||
<select
|
||||
className='w-full rounded border border-gray-300 px-2 py-1 text-xs'
|
||||
value={values.slideTransitionType || ''}
|
||||
onChange={(e) => onChange('slideTransitionType', e.target.value)}
|
||||
onChange={(e) =>
|
||||
onChange('slideTransitionType', e.target.value)
|
||||
}
|
||||
>
|
||||
<option value=''>Use Default</option>
|
||||
<option value='fade'>Fade</option>
|
||||
|
||||
@ -14,7 +14,13 @@
|
||||
* - 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 type { CSSProperties } from 'react';
|
||||
import Icon from '@mdi/react';
|
||||
|
||||
@ -110,12 +110,15 @@ export function resolveSlideTransition(
|
||||
* Extract slide transition override from carousel element
|
||||
*/
|
||||
export function extractCarouselSlideOverride(
|
||||
element: {
|
||||
carouselSlideTransitionType?: SlideTransitionType | '';
|
||||
carouselSlideTransitionDurationMs?: number | '';
|
||||
carouselSlideTransitionEasing?: EasingFunction | '';
|
||||
carouselSlideTransitionOverlayColor?: string;
|
||||
} | null | undefined,
|
||||
element:
|
||||
| {
|
||||
carouselSlideTransitionType?: SlideTransitionType | '';
|
||||
carouselSlideTransitionDurationMs?: number | '';
|
||||
carouselSlideTransitionEasing?: EasingFunction | '';
|
||||
carouselSlideTransitionOverlayColor?: string;
|
||||
}
|
||||
| null
|
||||
| undefined,
|
||||
): SlideTransitionOverride | null {
|
||||
if (!element) return null;
|
||||
return {
|
||||
@ -130,12 +133,15 @@ export function extractCarouselSlideOverride(
|
||||
* Extract slide transition override from gallery element
|
||||
*/
|
||||
export function extractGallerySlideOverride(
|
||||
element: {
|
||||
gallerySlideTransitionType?: SlideTransitionType | '';
|
||||
gallerySlideTransitionDurationMs?: number | '';
|
||||
gallerySlideTransitionEasing?: EasingFunction | '';
|
||||
gallerySlideTransitionOverlayColor?: string;
|
||||
} | null | undefined,
|
||||
element:
|
||||
| {
|
||||
gallerySlideTransitionType?: SlideTransitionType | '';
|
||||
gallerySlideTransitionDurationMs?: number | '';
|
||||
gallerySlideTransitionEasing?: EasingFunction | '';
|
||||
gallerySlideTransitionOverlayColor?: string;
|
||||
}
|
||||
| null
|
||||
| undefined,
|
||||
): SlideTransitionOverride | null {
|
||||
if (!element) return null;
|
||||
return {
|
||||
|
||||
@ -347,7 +347,9 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
// Look up current element for gallery carousel (so it receives updates from element editor)
|
||||
const activeGalleryCarouselElement = useMemo(() => {
|
||||
if (!activeGalleryCarousel) return null;
|
||||
return elements.find((el) => el.id === activeGalleryCarousel.elementId) || null;
|
||||
return (
|
||||
elements.find((el) => el.id === activeGalleryCarousel.elementId) || null
|
||||
);
|
||||
}, [activeGalleryCarousel, elements]);
|
||||
|
||||
// Draggable panels using useDraggable hook
|
||||
@ -1303,7 +1305,10 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
const handleGalleryCardClick = useCallback(
|
||||
(element: CanvasElement, cardIndex: number) => {
|
||||
if (element.galleryCards && element.galleryCards.length > 0) {
|
||||
setActiveGalleryCarousel({ elementId: element.id, initialIndex: cardIndex });
|
||||
setActiveGalleryCarousel({
|
||||
elementId: element.id,
|
||||
initialIndex: cardIndex,
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user