Spaces:
Paused
Paused
dr-data commited on
Commit ·
8e395e9
1
Parent(s): dcd5e1d
Update DeepSite v2 for Hugging Face Spaces deployment
Browse files- .gitignore +22 -0
- app/api/test-direct/route.ts +0 -0
- app/api/test-minimal/route.ts +0 -0
- app/api/test-working/route.ts +0 -0
- assets/globals.css +195 -23
- components/editor/ask-ai/index.tsx +85 -7
- components/editor/preview/index-simplified.tsx +382 -0
- components/editor/preview/index.tsx +510 -578
.gitignore
CHANGED
|
@@ -39,3 +39,25 @@ yarn-error.log*
|
|
| 39 |
# typescript
|
| 40 |
*.tsbuildinfo
|
| 41 |
next-env.d.ts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
# typescript
|
| 40 |
*.tsbuildinfo
|
| 41 |
next-env.d.ts
|
| 42 |
+
|
| 43 |
+
# Documentation files
|
| 44 |
+
*.md
|
| 45 |
+
!README.md
|
| 46 |
+
CLAUDE.md
|
| 47 |
+
EMERGENCY_FIX_SUMMARY.md
|
| 48 |
+
GITHUB_REPO_SETUP.md
|
| 49 |
+
PREVIEW_*.md
|
| 50 |
+
SMOOTH_*.md
|
| 51 |
+
TRUE_*.md
|
| 52 |
+
TYPESCRIPT_*.md
|
| 53 |
+
ZERO_*.md
|
| 54 |
+
|
| 55 |
+
# Test files
|
| 56 |
+
test-*.js
|
| 57 |
+
test-*.mjs
|
| 58 |
+
test-*.html
|
| 59 |
+
debug-*.html
|
| 60 |
+
|
| 61 |
+
# Backup files
|
| 62 |
+
*.backup
|
| 63 |
+
*.complex-backup
|
app/api/test-direct/route.ts
ADDED
|
File without changes
|
app/api/test-minimal/route.ts
ADDED
|
File without changes
|
app/api/test-working/route.ts
ADDED
|
File without changes
|
assets/globals.css
CHANGED
|
@@ -138,40 +138,73 @@
|
|
| 138 |
@apply !bg-neutral-900;
|
| 139 |
}
|
| 140 |
|
| 141 |
-
/* ZERO-FLASH
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
/*
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
transform: translateZ(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
z-index: 10;
|
| 151 |
}
|
| 152 |
|
| 153 |
-
/*
|
| 154 |
-
#preview-iframe
|
| 155 |
-
|
|
|
|
| 156 |
}
|
| 157 |
|
| 158 |
-
/*
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
transform: translateZ(0);
|
| 164 |
}
|
| 165 |
|
| 166 |
-
/*
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
}
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
}
|
| 176 |
|
| 177 |
/* Ultra-smooth fade-in animation for new content with anti-flash */
|
|
@@ -245,3 +278,142 @@
|
|
| 245 |
.matched-line {
|
| 246 |
@apply bg-sky-500/30;
|
| 247 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
@apply !bg-neutral-900;
|
| 139 |
}
|
| 140 |
|
| 141 |
+
/* 🚀 BULLETPROOF ZERO-FLASH PREVIEW SYSTEM */
|
| 142 |
+
|
| 143 |
+
/* Main preview iframe with anti-flash protection */
|
| 144 |
+
#preview-iframe {
|
| 145 |
+
/* Bulletproof anti-flash transitions */
|
| 146 |
+
transition: opacity 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
| 147 |
+
|
| 148 |
+
/* Prevent any flash with guaranteed white background */
|
| 149 |
+
background-color: #ffffff !important;
|
| 150 |
+
background-image: none !important;
|
| 151 |
+
|
| 152 |
+
/* Hardware acceleration for ultra-smooth performance */
|
| 153 |
+
will-change: opacity;
|
| 154 |
transform: translateZ(0);
|
| 155 |
+
backface-visibility: hidden;
|
| 156 |
+
|
| 157 |
+
/* Ensure immediate visibility */
|
| 158 |
+
opacity: 1;
|
| 159 |
+
|
| 160 |
+
/* Smooth scrolling for content updates */
|
| 161 |
+
scroll-behavior: smooth;
|
| 162 |
+
|
| 163 |
+
/* Prevent layout shifts */
|
| 164 |
+
min-height: 100%;
|
| 165 |
+
|
| 166 |
+
/* Z-index management */
|
| 167 |
z-index: 10;
|
| 168 |
}
|
| 169 |
|
| 170 |
+
/* Loading state - fade in smoothly */
|
| 171 |
+
#preview-iframe.loading {
|
| 172 |
+
opacity: 0.7;
|
| 173 |
+
transition: opacity 150ms ease-out;
|
| 174 |
}
|
| 175 |
|
| 176 |
+
/* Enhanced container for smooth updates */
|
| 177 |
+
.smooth-preview-container {
|
| 178 |
+
/* Prevent layout jumps and overflow flashes */
|
| 179 |
+
overflow: hidden;
|
| 180 |
+
position: relative;
|
| 181 |
+
|
| 182 |
+
/* Ensure smooth transitions */
|
| 183 |
+
will-change: contents;
|
| 184 |
+
|
| 185 |
+
/* Hardware acceleration */
|
| 186 |
transform: translateZ(0);
|
| 187 |
}
|
| 188 |
|
| 189 |
+
/* Support for smooth element transitions within iframes */
|
| 190 |
+
.smooth-element-transition {
|
| 191 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 192 |
+
will-change: opacity, transform;
|
| 193 |
}
|
| 194 |
|
| 195 |
+
.smooth-content-update {
|
| 196 |
+
animation: smoothContentPulse 0.4s ease-in-out;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
@keyframes smoothContentPulse {
|
| 200 |
+
0%, 100% {
|
| 201 |
+
transform: scale(1);
|
| 202 |
+
opacity: 1;
|
| 203 |
+
}
|
| 204 |
+
50% {
|
| 205 |
+
transform: scale(1.01);
|
| 206 |
+
opacity: 0.95;
|
| 207 |
+
}
|
| 208 |
}
|
| 209 |
|
| 210 |
/* Ultra-smooth fade-in animation for new content with anti-flash */
|
|
|
|
| 278 |
.matched-line {
|
| 279 |
@apply bg-sky-500/30;
|
| 280 |
}
|
| 281 |
+
|
| 282 |
+
/* 🎨 ENHANCED IFRAME CONTENT TRANSITIONS */
|
| 283 |
+
|
| 284 |
+
/* Global styles that will be injected into iframe content */
|
| 285 |
+
.iframe-smooth-transitions * {
|
| 286 |
+
/* Smooth transitions for all content changes */
|
| 287 |
+
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1),
|
| 288 |
+
color 200ms cubic-bezier(0.4, 0, 0.2, 1),
|
| 289 |
+
background-color 200ms cubic-bezier(0.4, 0, 0.2, 1),
|
| 290 |
+
border-color 200ms cubic-bezier(0.4, 0, 0.2, 1),
|
| 291 |
+
transform 200ms cubic-bezier(0.4, 0, 0.2, 1) !important;
|
| 292 |
+
|
| 293 |
+
/* Hardware acceleration */
|
| 294 |
+
will-change: opacity, transform, background-color;
|
| 295 |
+
transform: translateZ(0);
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
/* Smooth element entrance animation */
|
| 299 |
+
.element-entering {
|
| 300 |
+
opacity: 0 !important;
|
| 301 |
+
transform: translateY(5px) scale(0.99) translateZ(0) !important;
|
| 302 |
+
transition: opacity 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
|
| 303 |
+
transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.element-entering.entered {
|
| 307 |
+
opacity: 1 !important;
|
| 308 |
+
transform: translateY(0) scale(1) translateZ(0) !important;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
/* Smooth element exit animation */
|
| 312 |
+
.element-leaving {
|
| 313 |
+
opacity: 1 !important;
|
| 314 |
+
transform: translateY(0) scale(1) translateZ(0) !important;
|
| 315 |
+
transition: opacity 250ms cubic-bezier(0.4, 0, 1, 1),
|
| 316 |
+
transform 250ms cubic-bezier(0.4, 0, 1, 1) !important;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.element-leaving.left {
|
| 320 |
+
opacity: 0 !important;
|
| 321 |
+
transform: translateY(-5px) scale(0.98) translateZ(0) !important;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
/* Content change highlight effect */
|
| 325 |
+
.content-changed {
|
| 326 |
+
animation: contentHighlight 400ms cubic-bezier(0.4, 0, 0.2, 1) !important;
|
| 327 |
+
transform: translateZ(0);
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
@keyframes contentHighlight {
|
| 331 |
+
0%, 100% {
|
| 332 |
+
background-color: transparent !important;
|
| 333 |
+
box-shadow: none !important;
|
| 334 |
+
}
|
| 335 |
+
30%, 70% {
|
| 336 |
+
background-color: rgba(59, 130, 246, 0.08) !important;
|
| 337 |
+
box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.1) !important;
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
/* Smooth morphing indicator */
|
| 342 |
+
.morphing-element {
|
| 343 |
+
position: relative;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.morphing-element::before {
|
| 347 |
+
content: '';
|
| 348 |
+
position: absolute;
|
| 349 |
+
top: -1px;
|
| 350 |
+
left: -1px;
|
| 351 |
+
right: -1px;
|
| 352 |
+
bottom: -1px;
|
| 353 |
+
background: linear-gradient(90deg,
|
| 354 |
+
transparent,
|
| 355 |
+
rgba(59, 130, 246, 0.1),
|
| 356 |
+
transparent
|
| 357 |
+
);
|
| 358 |
+
border-radius: 3px;
|
| 359 |
+
opacity: 0;
|
| 360 |
+
animation: morphingGlow 600ms ease-out;
|
| 361 |
+
pointer-events: none;
|
| 362 |
+
z-index: 1000;
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
@keyframes morphingGlow {
|
| 366 |
+
0% { opacity: 0; transform: translateX(-100%); }
|
| 367 |
+
50% { opacity: 1; transform: translateX(0); }
|
| 368 |
+
100% { opacity: 0; transform: translateX(100%); }
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
/* Ultra-smooth text content updates */
|
| 372 |
+
.text-morphing {
|
| 373 |
+
transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1) !important;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.text-morphing.changing {
|
| 377 |
+
opacity: 0.7;
|
| 378 |
+
transform: translateY(1px) translateZ(0);
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
/* ========================================
|
| 382 |
+
SMOOTH PREVIEW TRANSITIONS
|
| 383 |
+
======================================== */
|
| 384 |
+
|
| 385 |
+
/* Basic iframe transition for content changes */
|
| 386 |
+
iframe {
|
| 387 |
+
transition: opacity 0.3s ease-in-out, transform 0.2s ease-out;
|
| 388 |
+
will-change: opacity;
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
/* Smooth loading state */
|
| 392 |
+
.preview-updating {
|
| 393 |
+
opacity: 0.8;
|
| 394 |
+
transition: opacity 0.2s ease-in-out;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
/* Prevent flash during iframe content updates */
|
| 398 |
+
#preview-iframe-1,
|
| 399 |
+
#preview-iframe-2 {
|
| 400 |
+
background: white;
|
| 401 |
+
transition: opacity 0.2s ease-in-out;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
/* Loading indicator for preview updates */
|
| 405 |
+
.preview-loading-indicator {
|
| 406 |
+
transition: opacity 0.2s ease-in-out;
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
/* Hardware acceleration for smooth transitions */
|
| 410 |
+
.preview-container {
|
| 411 |
+
transform: translateZ(0);
|
| 412 |
+
will-change: transform;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
/* Prevent content jump during updates */
|
| 416 |
+
.preview-iframe-wrapper {
|
| 417 |
+
position: relative;
|
| 418 |
+
overflow: hidden;
|
| 419 |
+
}
|
components/editor/ask-ai/index.tsx
CHANGED
|
@@ -176,31 +176,109 @@ export function AskAI({
|
|
| 176 |
signal: abortController.signal,
|
| 177 |
});
|
| 178 |
if (request && request.body) {
|
| 179 |
-
//
|
| 180 |
if (!request.ok) {
|
| 181 |
console.error('❌ Request failed:', {
|
| 182 |
status: request.status,
|
| 183 |
statusText: request.statusText,
|
|
|
|
| 184 |
headers: Object.fromEntries(request.headers.entries())
|
| 185 |
});
|
| 186 |
|
| 187 |
try {
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
if (res.openLogin) {
|
| 192 |
setOpen(true);
|
| 193 |
} else if (res.openSelectProvider) {
|
| 194 |
setOpenProvider(true);
|
| 195 |
-
setProviderError(res.message);
|
| 196 |
} else if (res.openProModal) {
|
| 197 |
setOpenProModal(true);
|
| 198 |
} else {
|
| 199 |
-
|
|
|
|
|
|
|
| 200 |
}
|
| 201 |
} catch (parseError) {
|
| 202 |
-
console.error('❌
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
}
|
| 205 |
setisAiWorking(false);
|
| 206 |
return;
|
|
|
|
| 176 |
signal: abortController.signal,
|
| 177 |
});
|
| 178 |
if (request && request.body) {
|
| 179 |
+
// ROBUST ERROR HANDLING: Handle both JSON and HTML error responses
|
| 180 |
if (!request.ok) {
|
| 181 |
console.error('❌ Request failed:', {
|
| 182 |
status: request.status,
|
| 183 |
statusText: request.statusText,
|
| 184 |
+
url: request.url,
|
| 185 |
headers: Object.fromEntries(request.headers.entries())
|
| 186 |
});
|
| 187 |
|
| 188 |
try {
|
| 189 |
+
// Get the full response as text first
|
| 190 |
+
const responseText = await request.text();
|
| 191 |
+
console.log('📄 Raw error response:', {
|
| 192 |
+
length: responseText.length,
|
| 193 |
+
contentType: request.headers.get('content-type'),
|
| 194 |
+
preview: responseText.substring(0, 500) + (responseText.length > 500 ? '...' : ''),
|
| 195 |
+
isHtml: responseText.trim().toLowerCase().startsWith('<!doctype html') || responseText.trim().toLowerCase().startsWith('<html')
|
| 196 |
+
});
|
| 197 |
+
|
| 198 |
+
let res;
|
| 199 |
|
| 200 |
+
// Check if response is HTML (common for server errors)
|
| 201 |
+
if (responseText.trim().toLowerCase().startsWith('<!doctype html') ||
|
| 202 |
+
responseText.trim().toLowerCase().startsWith('<html') ||
|
| 203 |
+
responseText.includes('<title>') ||
|
| 204 |
+
responseText.includes('Internal Server Error')) {
|
| 205 |
+
|
| 206 |
+
console.error('❌ HTML error page received instead of JSON');
|
| 207 |
+
|
| 208 |
+
// Extract error info from HTML if possible
|
| 209 |
+
let errorMessage = 'Server error occurred';
|
| 210 |
+
const titleMatch = responseText.match(/<title[^>]*>([^<]+)<\/title>/i);
|
| 211 |
+
if (titleMatch && titleMatch[1]) {
|
| 212 |
+
errorMessage = titleMatch[1].trim();
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
// Look for common error patterns
|
| 216 |
+
if (responseText.includes('Internal Server Error')) {
|
| 217 |
+
errorMessage = 'Internal Server Error - Please try again';
|
| 218 |
+
} else if (responseText.includes('Service Unavailable')) {
|
| 219 |
+
errorMessage = 'Service temporarily unavailable';
|
| 220 |
+
} else if (responseText.includes('Bad Gateway')) {
|
| 221 |
+
errorMessage = 'Gateway error - Please try again';
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
res = {
|
| 225 |
+
ok: false,
|
| 226 |
+
error: errorMessage,
|
| 227 |
+
message: errorMessage,
|
| 228 |
+
isHtmlError: true
|
| 229 |
+
};
|
| 230 |
+
} else {
|
| 231 |
+
// Try to parse as JSON
|
| 232 |
+
try {
|
| 233 |
+
res = JSON.parse(responseText);
|
| 234 |
+
console.error('❌ Error response (JSON):', res);
|
| 235 |
+
} catch (jsonError) {
|
| 236 |
+
console.error('❌ Failed to parse JSON, treating as text:', {
|
| 237 |
+
jsonError: (jsonError as Error).message || String(jsonError),
|
| 238 |
+
responseLength: responseText.length
|
| 239 |
+
});
|
| 240 |
+
|
| 241 |
+
// Create error object from text response
|
| 242 |
+
let errorMessage = responseText.trim();
|
| 243 |
+
|
| 244 |
+
// Clean up common error patterns
|
| 245 |
+
if (errorMessage.length > 200) {
|
| 246 |
+
errorMessage = errorMessage.substring(0, 200) + '...';
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
if (!errorMessage) {
|
| 250 |
+
errorMessage = `HTTP ${request.status}: ${request.statusText}`;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
res = {
|
| 254 |
+
ok: false,
|
| 255 |
+
error: errorMessage,
|
| 256 |
+
message: errorMessage,
|
| 257 |
+
isTextError: true
|
| 258 |
+
};
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// Handle different error types
|
| 263 |
if (res.openLogin) {
|
| 264 |
setOpen(true);
|
| 265 |
} else if (res.openSelectProvider) {
|
| 266 |
setOpenProvider(true);
|
| 267 |
+
setProviderError(res.message || res.error || 'Provider error');
|
| 268 |
} else if (res.openProModal) {
|
| 269 |
setOpenProModal(true);
|
| 270 |
} else {
|
| 271 |
+
const errorMsg = res.message || res.error || 'Unknown error occurred';
|
| 272 |
+
console.error('❌ Showing error to user:', errorMsg);
|
| 273 |
+
toast.error(errorMsg);
|
| 274 |
}
|
| 275 |
} catch (parseError) {
|
| 276 |
+
console.error('❌ Complete failure to parse error response:', {
|
| 277 |
+
parseError: (parseError as Error).message || String(parseError),
|
| 278 |
+
status: request.status,
|
| 279 |
+
statusText: request.statusText
|
| 280 |
+
});
|
| 281 |
+
toast.error(`Request failed: ${request.status} ${request.statusText}`);
|
| 282 |
}
|
| 283 |
setisAiWorking(false);
|
| 284 |
return;
|
components/editor/preview/index-simplified.tsx
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { useUpdateEffect } from "react-use";
|
| 3 |
+
import { useMemo, useState, useRef, useEffect, forwardRef, useCallback } from "react";
|
| 4 |
+
import classNames from "classnames";
|
| 5 |
+
import { toast } from "sonner";
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils";
|
| 8 |
+
import { GridPattern } from "@/components/magic-ui/grid-pattern";
|
| 9 |
+
import { htmlTagToText } from "@/lib/html-tag-to-text";
|
| 10 |
+
|
| 11 |
+
export const Preview = forwardRef<
|
| 12 |
+
HTMLDivElement,
|
| 13 |
+
{
|
| 14 |
+
html: string;
|
| 15 |
+
isResizing: boolean;
|
| 16 |
+
isAiWorking: boolean;
|
| 17 |
+
device: "desktop" | "mobile";
|
| 18 |
+
currentTab: string;
|
| 19 |
+
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
| 20 |
+
isEditableModeEnabled?: boolean;
|
| 21 |
+
onClickElement?: (element: HTMLElement) => void;
|
| 22 |
+
}
|
| 23 |
+
>(({
|
| 24 |
+
html,
|
| 25 |
+
isResizing,
|
| 26 |
+
isAiWorking,
|
| 27 |
+
device,
|
| 28 |
+
currentTab,
|
| 29 |
+
iframeRef,
|
| 30 |
+
isEditableModeEnabled,
|
| 31 |
+
onClickElement,
|
| 32 |
+
}, ref) => {
|
| 33 |
+
const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(null);
|
| 34 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 35 |
+
const [displayHtml, setDisplayHtml] = useState(html);
|
| 36 |
+
const htmlUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
| 37 |
+
const prevHtmlRef = useRef(html);
|
| 38 |
+
const internalIframeRef = useRef<HTMLIFrameElement>(null);
|
| 39 |
+
|
| 40 |
+
// Use internal ref if external ref not provided
|
| 41 |
+
const currentIframeRef = iframeRef || internalIframeRef;
|
| 42 |
+
|
| 43 |
+
// Debug logging for initial state
|
| 44 |
+
useEffect(() => {
|
| 45 |
+
console.log('🚀 SIMPLIFIED Preview component mounted with HTML:', {
|
| 46 |
+
htmlLength: html.length,
|
| 47 |
+
htmlPreview: html.substring(0, 200) + '...',
|
| 48 |
+
displayHtmlLength: displayHtml.length
|
| 49 |
+
});
|
| 50 |
+
}, []);
|
| 51 |
+
|
| 52 |
+
// SIMPLIFIED: Reliable HTML update logic with optional smoothness
|
| 53 |
+
useEffect(() => {
|
| 54 |
+
console.log('🔄 SIMPLIFIED Preview update triggered:', {
|
| 55 |
+
htmlLength: html.length,
|
| 56 |
+
isAiWorking,
|
| 57 |
+
displayHtmlLength: displayHtml.length,
|
| 58 |
+
htmlChanged: html !== displayHtml,
|
| 59 |
+
htmlPreview: html.substring(0, 100) + '...'
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
// PRIORITY: Always ensure HTML updates work immediately
|
| 63 |
+
if (html !== displayHtml) {
|
| 64 |
+
console.log('📝 HTML changed! Updating display HTML immediately:', html.length, 'characters');
|
| 65 |
+
|
| 66 |
+
// Clear any pending timeout to prevent conflicts
|
| 67 |
+
if (htmlUpdateTimeoutRef.current) {
|
| 68 |
+
clearTimeout(htmlUpdateTimeoutRef.current);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// For AI working (streaming), add minimal smoothness
|
| 72 |
+
if (isAiWorking) {
|
| 73 |
+
console.log('🤖 AI working - adding minimal delay for smoothness');
|
| 74 |
+
setIsLoading(true);
|
| 75 |
+
|
| 76 |
+
htmlUpdateTimeoutRef.current = setTimeout(() => {
|
| 77 |
+
setDisplayHtml(html);
|
| 78 |
+
prevHtmlRef.current = html;
|
| 79 |
+
setIsLoading(false);
|
| 80 |
+
console.log('✅ Delayed update completed for AI streaming');
|
| 81 |
+
}, 100); // Very short delay for minimal smoothness
|
| 82 |
+
} else {
|
| 83 |
+
// Immediate update for manual changes
|
| 84 |
+
setDisplayHtml(html);
|
| 85 |
+
prevHtmlRef.current = html;
|
| 86 |
+
setIsLoading(false);
|
| 87 |
+
console.log('⚡ Immediate update completed for manual change');
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
console.log('✅ SIMPLIFIED Preview update completed, displayHtml length:', displayHtml.length);
|
| 92 |
+
}, [html, isAiWorking]);
|
| 93 |
+
|
| 94 |
+
// Add basic smooth transitions via CSS
|
| 95 |
+
useEffect(() => {
|
| 96 |
+
const iframe = currentIframeRef.current;
|
| 97 |
+
if (!iframe || !iframe.contentDocument) return;
|
| 98 |
+
|
| 99 |
+
const injectSmoothTransitions = () => {
|
| 100 |
+
const doc = iframe.contentDocument;
|
| 101 |
+
if (!doc) return;
|
| 102 |
+
|
| 103 |
+
let existingStyle = doc.getElementById('smooth-transitions');
|
| 104 |
+
if (existingStyle) return;
|
| 105 |
+
|
| 106 |
+
const style = doc.createElement('style');
|
| 107 |
+
style.id = 'smooth-transitions';
|
| 108 |
+
style.textContent = `
|
| 109 |
+
/* Smooth transitions for content changes */
|
| 110 |
+
* {
|
| 111 |
+
transition: opacity 0.15s ease, background-color 0.15s ease,
|
| 112 |
+
color 0.15s ease, border-color 0.15s ease !important;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
/* Prevent flashing during updates */
|
| 116 |
+
body {
|
| 117 |
+
transition: opacity 0.1s ease !important;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
/* Subtle animations for new content */
|
| 121 |
+
@keyframes contentFadeIn {
|
| 122 |
+
from { opacity: 0.8; }
|
| 123 |
+
to { opacity: 1; }
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
body.content-updating {
|
| 127 |
+
animation: contentFadeIn 0.2s ease-in-out;
|
| 128 |
+
}
|
| 129 |
+
`;
|
| 130 |
+
doc.head.appendChild(style);
|
| 131 |
+
console.log('✨ Smooth transitions injected into iframe');
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
// Inject on iframe load
|
| 135 |
+
iframe.addEventListener('load', injectSmoothTransitions);
|
| 136 |
+
|
| 137 |
+
// Also try to inject immediately if iframe is already loaded
|
| 138 |
+
if (iframe.contentDocument?.readyState === 'complete') {
|
| 139 |
+
injectSmoothTransitions();
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
return () => {
|
| 143 |
+
iframe.removeEventListener('load', injectSmoothTransitions);
|
| 144 |
+
};
|
| 145 |
+
}, [currentIframeRef]);
|
| 146 |
+
|
| 147 |
+
// Add content updating class for animations
|
| 148 |
+
useEffect(() => {
|
| 149 |
+
const iframe = currentIframeRef.current;
|
| 150 |
+
if (!iframe || !iframe.contentDocument) return;
|
| 151 |
+
|
| 152 |
+
const body = iframe.contentDocument.body;
|
| 153 |
+
if (!body) return;
|
| 154 |
+
|
| 155 |
+
body.classList.add('content-updating');
|
| 156 |
+
|
| 157 |
+
const timeout = setTimeout(() => {
|
| 158 |
+
body.classList.remove('content-updating');
|
| 159 |
+
}, 200);
|
| 160 |
+
|
| 161 |
+
return () => {
|
| 162 |
+
clearTimeout(timeout);
|
| 163 |
+
body.classList.remove('content-updating');
|
| 164 |
+
};
|
| 165 |
+
}, [displayHtml, currentIframeRef]);
|
| 166 |
+
|
| 167 |
+
// Cleanup timeout on unmount
|
| 168 |
+
useEffect(() => {
|
| 169 |
+
return () => {
|
| 170 |
+
if (htmlUpdateTimeoutRef.current) {
|
| 171 |
+
clearTimeout(htmlUpdateTimeoutRef.current);
|
| 172 |
+
}
|
| 173 |
+
};
|
| 174 |
+
}, []);
|
| 175 |
+
|
| 176 |
+
// Event handlers for editable mode
|
| 177 |
+
const handleMouseOver = (event: MouseEvent) => {
|
| 178 |
+
if (currentIframeRef?.current) {
|
| 179 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 180 |
+
if (iframeDocument) {
|
| 181 |
+
const targetElement = event.target as HTMLElement;
|
| 182 |
+
if (
|
| 183 |
+
hoveredElement !== targetElement &&
|
| 184 |
+
targetElement !== iframeDocument.body
|
| 185 |
+
) {
|
| 186 |
+
console.log("🎯 Edit mode: Element hovered", {
|
| 187 |
+
tagName: targetElement.tagName,
|
| 188 |
+
id: targetElement.id || 'no-id',
|
| 189 |
+
className: targetElement.className || 'no-class'
|
| 190 |
+
});
|
| 191 |
+
|
| 192 |
+
// Remove previous hover class
|
| 193 |
+
if (hoveredElement) {
|
| 194 |
+
hoveredElement.classList.remove("hovered-element");
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
setHoveredElement(targetElement);
|
| 198 |
+
targetElement.classList.add("hovered-element");
|
| 199 |
+
} else {
|
| 200 |
+
return setHoveredElement(null);
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
};
|
| 205 |
+
|
| 206 |
+
const handleMouseOut = () => {
|
| 207 |
+
setHoveredElement(null);
|
| 208 |
+
};
|
| 209 |
+
|
| 210 |
+
const handleClick = (event: MouseEvent) => {
|
| 211 |
+
console.log("🖱️ Edit mode: Click detected in iframe", {
|
| 212 |
+
target: event.target,
|
| 213 |
+
tagName: (event.target as HTMLElement)?.tagName,
|
| 214 |
+
isBody: event.target === currentIframeRef?.current?.contentDocument?.body,
|
| 215 |
+
hasOnClickElement: !!onClickElement
|
| 216 |
+
});
|
| 217 |
+
|
| 218 |
+
if (currentIframeRef?.current) {
|
| 219 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 220 |
+
if (iframeDocument) {
|
| 221 |
+
const targetElement = event.target as HTMLElement;
|
| 222 |
+
if (targetElement !== iframeDocument.body) {
|
| 223 |
+
console.log("✅ Edit mode: Valid element clicked, calling onClickElement", {
|
| 224 |
+
tagName: targetElement.tagName,
|
| 225 |
+
id: targetElement.id || 'no-id',
|
| 226 |
+
className: targetElement.className || 'no-class',
|
| 227 |
+
textContent: targetElement.textContent?.substring(0, 50) + '...'
|
| 228 |
+
});
|
| 229 |
+
|
| 230 |
+
// Prevent default behavior to avoid navigation
|
| 231 |
+
event.preventDefault();
|
| 232 |
+
event.stopPropagation();
|
| 233 |
+
|
| 234 |
+
onClickElement?.(targetElement);
|
| 235 |
+
} else {
|
| 236 |
+
console.log("⚠️ Edit mode: Body clicked, ignoring");
|
| 237 |
+
}
|
| 238 |
+
} else {
|
| 239 |
+
console.error("❌ Edit mode: No iframe document available on click");
|
| 240 |
+
}
|
| 241 |
+
} else {
|
| 242 |
+
console.error("❌ Edit mode: No iframe ref available on click");
|
| 243 |
+
}
|
| 244 |
+
};
|
| 245 |
+
|
| 246 |
+
// Setup event listeners for editable mode
|
| 247 |
+
useUpdateEffect(() => {
|
| 248 |
+
const cleanupListeners = () => {
|
| 249 |
+
if (currentIframeRef?.current?.contentDocument) {
|
| 250 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 251 |
+
iframeDocument.removeEventListener("mouseover", handleMouseOver);
|
| 252 |
+
iframeDocument.removeEventListener("mouseout", handleMouseOut);
|
| 253 |
+
iframeDocument.removeEventListener("click", handleClick);
|
| 254 |
+
console.log("🧹 Edit mode: Cleaned up iframe event listeners");
|
| 255 |
+
}
|
| 256 |
+
};
|
| 257 |
+
|
| 258 |
+
const setupListeners = () => {
|
| 259 |
+
try {
|
| 260 |
+
if (!currentIframeRef?.current) {
|
| 261 |
+
console.log("⚠️ Edit mode: No iframe ref available");
|
| 262 |
+
return;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 266 |
+
if (!iframeDocument) {
|
| 267 |
+
console.log("⚠️ Edit mode: No iframe content document available");
|
| 268 |
+
return;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
// Clean up existing listeners first
|
| 272 |
+
cleanupListeners();
|
| 273 |
+
|
| 274 |
+
if (isEditableModeEnabled) {
|
| 275 |
+
console.log("🎯 Edit mode: Setting up iframe event listeners");
|
| 276 |
+
iframeDocument.addEventListener("mouseover", handleMouseOver);
|
| 277 |
+
iframeDocument.addEventListener("mouseout", handleMouseOut);
|
| 278 |
+
iframeDocument.addEventListener("click", handleClick);
|
| 279 |
+
console.log("✅ Edit mode: Event listeners added successfully");
|
| 280 |
+
} else {
|
| 281 |
+
console.log("🔇 Edit mode: Disabled, no listeners added");
|
| 282 |
+
}
|
| 283 |
+
} catch (error) {
|
| 284 |
+
console.error("❌ Edit mode: Error setting up listeners:", error);
|
| 285 |
+
}
|
| 286 |
+
};
|
| 287 |
+
|
| 288 |
+
// Add a small delay to ensure iframe is fully loaded
|
| 289 |
+
const timeoutId = setTimeout(setupListeners, 100);
|
| 290 |
+
|
| 291 |
+
// Clean up when component unmounts or dependencies change
|
| 292 |
+
return () => {
|
| 293 |
+
clearTimeout(timeoutId);
|
| 294 |
+
cleanupListeners();
|
| 295 |
+
};
|
| 296 |
+
}, [currentIframeRef, isEditableModeEnabled]);
|
| 297 |
+
|
| 298 |
+
const selectedElement = useMemo(() => {
|
| 299 |
+
if (!isEditableModeEnabled) return null;
|
| 300 |
+
if (!hoveredElement) return null;
|
| 301 |
+
return hoveredElement;
|
| 302 |
+
}, [hoveredElement, isEditableModeEnabled]);
|
| 303 |
+
|
| 304 |
+
return (
|
| 305 |
+
<div
|
| 306 |
+
ref={ref}
|
| 307 |
+
className={classNames(
|
| 308 |
+
"bg-white overflow-hidden relative flex-1 h-full",
|
| 309 |
+
{
|
| 310 |
+
"cursor-wait": isLoading && isAiWorking,
|
| 311 |
+
}
|
| 312 |
+
)}
|
| 313 |
+
onClick={(e) => {
|
| 314 |
+
e.stopPropagation();
|
| 315 |
+
}}
|
| 316 |
+
>
|
| 317 |
+
<GridPattern
|
| 318 |
+
width={20}
|
| 319 |
+
height={20}
|
| 320 |
+
x={-1}
|
| 321 |
+
y={-1}
|
| 322 |
+
strokeDasharray={"4 2"}
|
| 323 |
+
className={cn(
|
| 324 |
+
"[mask-image:radial-gradient(300px_circle_at_center,white,transparent)] z-0 absolute inset-0 h-full w-full fill-neutral-100 stroke-neutral-100"
|
| 325 |
+
)}
|
| 326 |
+
/>
|
| 327 |
+
|
| 328 |
+
{/* Simplified loading overlay */}
|
| 329 |
+
{isLoading && isAiWorking && (
|
| 330 |
+
<div className="absolute inset-0 bg-black/5 backdrop-blur-[0.5px] transition-all duration-300 z-20 flex items-center justify-center">
|
| 331 |
+
<div className="bg-neutral-800/95 rounded-lg px-4 py-2 text-sm text-neutral-300 border border-neutral-700 shadow-lg">
|
| 332 |
+
<div className="flex items-center gap-2">
|
| 333 |
+
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
|
| 334 |
+
Updating preview...
|
| 335 |
+
</div>
|
| 336 |
+
</div>
|
| 337 |
+
</div>
|
| 338 |
+
)}
|
| 339 |
+
|
| 340 |
+
{/* Selected element indicator */}
|
| 341 |
+
{!isAiWorking && hoveredElement && selectedElement && (
|
| 342 |
+
<div className="absolute bottom-4 left-4 z-30">
|
| 343 |
+
<div className="bg-neutral-800/90 rounded-lg px-3 py-2 text-sm text-neutral-300 border border-neutral-700 shadow-lg">
|
| 344 |
+
<span className="font-medium">
|
| 345 |
+
{htmlTagToText(selectedElement.tagName.toLowerCase())}
|
| 346 |
+
</span>
|
| 347 |
+
{selectedElement.id && (
|
| 348 |
+
<span className="ml-2 text-neutral-400">#{selectedElement.id}</span>
|
| 349 |
+
)}
|
| 350 |
+
</div>
|
| 351 |
+
</div>
|
| 352 |
+
)}
|
| 353 |
+
|
| 354 |
+
{/* Simplified single iframe with CSS transitions */}
|
| 355 |
+
<iframe
|
| 356 |
+
id="preview-iframe"
|
| 357 |
+
ref={currentIframeRef}
|
| 358 |
+
title="output"
|
| 359 |
+
className={classNames(
|
| 360 |
+
"w-full select-none h-full transition-all duration-200 ease-out",
|
| 361 |
+
{
|
| 362 |
+
"pointer-events-none": isResizing || isAiWorking,
|
| 363 |
+
"opacity-95 scale-[0.999]": isLoading && isAiWorking,
|
| 364 |
+
"opacity-100 scale-100": !isLoading || !isAiWorking,
|
| 365 |
+
"bg-black": true,
|
| 366 |
+
"lg:max-w-md lg:mx-auto lg:!rounded-[42px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:h-[80dvh] lg:max-h-[996px]":
|
| 367 |
+
device === "mobile",
|
| 368 |
+
"lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:rounded-[24px]":
|
| 369 |
+
device === "desktop",
|
| 370 |
+
}
|
| 371 |
+
)}
|
| 372 |
+
srcDoc={displayHtml}
|
| 373 |
+
onLoad={() => {
|
| 374 |
+
console.log('🎯 SIMPLIFIED Preview iframe loaded with HTML length:', displayHtml.length);
|
| 375 |
+
setIsLoading(false);
|
| 376 |
+
}}
|
| 377 |
+
/>
|
| 378 |
+
</div>
|
| 379 |
+
);
|
| 380 |
+
});
|
| 381 |
+
|
| 382 |
+
Preview.displayName = "Preview";
|
components/editor/preview/index.tsx
CHANGED
|
@@ -1,579 +1,511 @@
|
|
| 1 |
"use client";
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
const activeIframe = iframe1Ref.current;
|
| 513 |
-
console.log("🎬 Primary iframe loaded:", {
|
| 514 |
-
isActive: activeIframeIndex === 0,
|
| 515 |
-
hasContentWindow: !!activeIframe?.contentWindow,
|
| 516 |
-
hasContentDocument: !!activeIframe?.contentDocument,
|
| 517 |
-
htmlLength: displayHtml.length,
|
| 518 |
-
srcDocPreview: displayHtml.substring(0, 100) + '...'
|
| 519 |
-
});
|
| 520 |
-
|
| 521 |
-
setIsLoading(false);
|
| 522 |
-
|
| 523 |
-
if (activeIframe?.contentWindow?.document?.body) {
|
| 524 |
-
activeIframe.contentWindow.document.body.scrollIntoView({
|
| 525 |
-
block: isAiWorking ? "end" : "start",
|
| 526 |
-
inline: "nearest",
|
| 527 |
-
behavior: "smooth",
|
| 528 |
-
});
|
| 529 |
-
}
|
| 530 |
-
}}
|
| 531 |
-
key={`primary-${displayHtml.length}`} // Force re-render when content changes
|
| 532 |
-
/>
|
| 533 |
-
|
| 534 |
-
{/* Secondary iframe for seamless swapping */}
|
| 535 |
-
<iframe
|
| 536 |
-
id="preview-iframe-2"
|
| 537 |
-
ref={iframe2Ref}
|
| 538 |
-
title="output-secondary"
|
| 539 |
-
className={classNames(
|
| 540 |
-
"absolute inset-0 w-full select-none h-full transition-all duration-300 ease-out",
|
| 541 |
-
{
|
| 542 |
-
"pointer-events-none": isResizing || isAiWorking,
|
| 543 |
-
"opacity-100": activeIframeIndex === 1,
|
| 544 |
-
"opacity-0": activeIframeIndex !== 1,
|
| 545 |
-
"bg-white": true,
|
| 546 |
-
"lg:max-w-md lg:mx-auto lg:!rounded-[42px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:h-[80dvh] lg:max-h-[996px]":
|
| 547 |
-
device === "mobile",
|
| 548 |
-
"lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:rounded-[24px]":
|
| 549 |
-
currentTab !== "preview" && device === "desktop",
|
| 550 |
-
}
|
| 551 |
-
)}
|
| 552 |
-
srcDoc={secondaryHtml || displayHtml}
|
| 553 |
-
onLoad={() => {
|
| 554 |
-
const activeIframe = iframe2Ref.current;
|
| 555 |
-
console.log("🎬 Secondary iframe loaded:", {
|
| 556 |
-
isActive: activeIframeIndex === 1,
|
| 557 |
-
hasContentWindow: !!activeIframe?.contentWindow,
|
| 558 |
-
hasContentDocument: !!activeIframe?.contentDocument,
|
| 559 |
-
htmlLength: secondaryHtml.length || displayHtml.length,
|
| 560 |
-
srcDocPreview: (secondaryHtml || displayHtml).substring(0, 100) + '...'
|
| 561 |
-
});
|
| 562 |
-
|
| 563 |
-
setIsLoading(false);
|
| 564 |
-
|
| 565 |
-
if (activeIframe?.contentWindow?.document?.body) {
|
| 566 |
-
activeIframe.contentWindow.document.body.scrollIntoView({
|
| 567 |
-
block: isAiWorking ? "end" : "start",
|
| 568 |
-
inline: "nearest",
|
| 569 |
-
behavior: "smooth",
|
| 570 |
-
});
|
| 571 |
-
}
|
| 572 |
-
}}
|
| 573 |
-
key={`secondary-${(secondaryHtml || displayHtml).length}`} // Force re-render when content changes
|
| 574 |
-
/>
|
| 575 |
-
</div>
|
| 576 |
-
);
|
| 577 |
-
});
|
| 578 |
-
|
| 579 |
-
Preview.displayName = "Preview";
|
|
|
|
| 1 |
"use client";
|
| 2 |
+
import { useUpdateEffect } from "react-use";
|
| 3 |
+
import { useMemo, useState, useRef, useEffect, forwardRef, useCallback } from "react";
|
| 4 |
+
import classNames from "classnames";
|
| 5 |
+
import { toast } from "sonner";
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils";
|
| 8 |
+
import { GridPattern } from "@/components/magic-ui/grid-pattern";
|
| 9 |
+
import { htmlTagToText } from "@/lib/html-tag-to-text";
|
| 10 |
+
|
| 11 |
+
export const Preview = forwardRef<
|
| 12 |
+
HTMLDivElement,
|
| 13 |
+
{
|
| 14 |
+
html: string;
|
| 15 |
+
isResizing: boolean;
|
| 16 |
+
isAiWorking: boolean;
|
| 17 |
+
device: "desktop" | "mobile";
|
| 18 |
+
currentTab: string;
|
| 19 |
+
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
| 20 |
+
isEditableModeEnabled?: boolean;
|
| 21 |
+
onClickElement?: (element: HTMLElement) => void;
|
| 22 |
+
}
|
| 23 |
+
>(({
|
| 24 |
+
html,
|
| 25 |
+
isResizing,
|
| 26 |
+
isAiWorking,
|
| 27 |
+
device,
|
| 28 |
+
currentTab,
|
| 29 |
+
iframeRef,
|
| 30 |
+
isEditableModeEnabled,
|
| 31 |
+
onClickElement,
|
| 32 |
+
}, ref) => {
|
| 33 |
+
const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(null);
|
| 34 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 35 |
+
const [displayHtml, setDisplayHtml] = useState(html);
|
| 36 |
+
const htmlUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
| 37 |
+
const prevHtmlRef = useRef(html);
|
| 38 |
+
const internalIframeRef = useRef<HTMLIFrameElement>(null);
|
| 39 |
+
|
| 40 |
+
// Add iframe key for force refresh when needed
|
| 41 |
+
const [iframeKey, setIframeKey] = useState(0);
|
| 42 |
+
|
| 43 |
+
// Use internal ref if external ref not provided
|
| 44 |
+
const currentIframeRef = iframeRef || internalIframeRef;
|
| 45 |
+
|
| 46 |
+
// Force refresh iframe if it seems stuck
|
| 47 |
+
const forceRefresh = useCallback(() => {
|
| 48 |
+
console.log('🔄 Force refreshing iframe');
|
| 49 |
+
setIframeKey(prev => prev + 1);
|
| 50 |
+
}, []);
|
| 51 |
+
|
| 52 |
+
// Monitor for stuck updates and force refresh if needed
|
| 53 |
+
useEffect(() => {
|
| 54 |
+
if (html !== displayHtml && !isAiWorking) {
|
| 55 |
+
const timeout = setTimeout(() => {
|
| 56 |
+
if (html !== displayHtml) {
|
| 57 |
+
console.log('⚠️ Preview seems stuck, force refreshing');
|
| 58 |
+
setDisplayHtml(html);
|
| 59 |
+
forceRefresh();
|
| 60 |
+
}
|
| 61 |
+
}, 2000);
|
| 62 |
+
|
| 63 |
+
return () => clearTimeout(timeout);
|
| 64 |
+
}
|
| 65 |
+
}, [html, displayHtml, isAiWorking, forceRefresh]);
|
| 66 |
+
|
| 67 |
+
// Debug logging for initial state and prop changes
|
| 68 |
+
useEffect(() => {
|
| 69 |
+
console.log('🚀 Preview component mounted/updated with HTML:', {
|
| 70 |
+
htmlLength: html.length,
|
| 71 |
+
displayHtmlLength: displayHtml.length,
|
| 72 |
+
htmlPreview: html.substring(0, 200) + '...',
|
| 73 |
+
isAiWorking,
|
| 74 |
+
device,
|
| 75 |
+
currentTab
|
| 76 |
+
});
|
| 77 |
+
}, [html, displayHtml, isAiWorking, device, currentTab]);
|
| 78 |
+
|
| 79 |
+
// CRITICAL: Reliable HTML update logic with debugging
|
| 80 |
+
useEffect(() => {
|
| 81 |
+
console.log('🔄 Preview update triggered:', {
|
| 82 |
+
htmlLength: html.length,
|
| 83 |
+
isAiWorking,
|
| 84 |
+
displayHtmlLength: displayHtml.length,
|
| 85 |
+
htmlChanged: html !== displayHtml,
|
| 86 |
+
prevHtmlLength: prevHtmlRef.current.length,
|
| 87 |
+
htmlPreview: html.substring(0, 100) + '...',
|
| 88 |
+
displayPreview: displayHtml.substring(0, 100) + '...'
|
| 89 |
+
});
|
| 90 |
+
|
| 91 |
+
// ALWAYS update when HTML prop changes - this is critical
|
| 92 |
+
if (html !== prevHtmlRef.current) {
|
| 93 |
+
console.log('📝 HTML prop changed! Forcing update:', {
|
| 94 |
+
from: prevHtmlRef.current.length,
|
| 95 |
+
to: html.length,
|
| 96 |
+
isAiWorking,
|
| 97 |
+
willUseDelay: isAiWorking
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
// Clear any pending timeout to prevent conflicts
|
| 101 |
+
if (htmlUpdateTimeoutRef.current) {
|
| 102 |
+
clearTimeout(htmlUpdateTimeoutRef.current);
|
| 103 |
+
console.log('⏰ Cleared pending timeout');
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// Update ref immediately
|
| 107 |
+
prevHtmlRef.current = html;
|
| 108 |
+
|
| 109 |
+
// For AI working (streaming), add minimal smoothness
|
| 110 |
+
if (isAiWorking && html.length > 0) {
|
| 111 |
+
console.log('🤖 AI working - adding minimal delay for smoothness');
|
| 112 |
+
setIsLoading(true);
|
| 113 |
+
|
| 114 |
+
htmlUpdateTimeoutRef.current = setTimeout(() => {
|
| 115 |
+
console.log('⚡ Applying delayed HTML update:', html.length);
|
| 116 |
+
setDisplayHtml(html);
|
| 117 |
+
setIsLoading(false);
|
| 118 |
+
}, 100); // Very short delay for minimal smoothness
|
| 119 |
+
} else {
|
| 120 |
+
// Immediate update for manual changes or when AI stops
|
| 121 |
+
console.log('⚡ Applying immediate HTML update:', html.length);
|
| 122 |
+
setDisplayHtml(html);
|
| 123 |
+
setIsLoading(false);
|
| 124 |
+
}
|
| 125 |
+
} else if (html !== displayHtml) {
|
| 126 |
+
// Edge case: displayHtml is out of sync
|
| 127 |
+
console.log('🔧 Fixing displayHtml sync issue');
|
| 128 |
+
setDisplayHtml(html);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
console.log('✅ Preview update completed');
|
| 132 |
+
}, [html, isAiWorking]);
|
| 133 |
+
|
| 134 |
+
// Enhanced smooth transitions via CSS injection
|
| 135 |
+
useEffect(() => {
|
| 136 |
+
const iframe = currentIframeRef.current;
|
| 137 |
+
if (!iframe) return;
|
| 138 |
+
|
| 139 |
+
const injectSmoothTransitions = () => {
|
| 140 |
+
const doc = iframe.contentDocument;
|
| 141 |
+
if (!doc) return;
|
| 142 |
+
|
| 143 |
+
let existingStyle = doc.getElementById('smooth-transitions');
|
| 144 |
+
if (existingStyle) {
|
| 145 |
+
console.log('🎨 Smooth transitions already injected, updating...');
|
| 146 |
+
existingStyle.remove();
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
const style = doc.createElement('style');
|
| 150 |
+
style.id = 'smooth-transitions';
|
| 151 |
+
style.textContent = `
|
| 152 |
+
/* Enhanced smooth transitions for zero-flash updates */
|
| 153 |
+
* {
|
| 154 |
+
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
| 155 |
+
background-color 0.2s ease,
|
| 156 |
+
color 0.2s ease,
|
| 157 |
+
border-color 0.2s ease,
|
| 158 |
+
transform 0.2s ease !important;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
/* Prevent flash during content updates */
|
| 162 |
+
body {
|
| 163 |
+
transition: opacity 0.15s ease !important;
|
| 164 |
+
will-change: opacity;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
/* Smooth content updates */
|
| 168 |
+
.content-updating {
|
| 169 |
+
animation: contentUpdate 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
@keyframes contentUpdate {
|
| 173 |
+
0% {
|
| 174 |
+
opacity: 0.9;
|
| 175 |
+
transform: translateY(1px);
|
| 176 |
+
}
|
| 177 |
+
100% {
|
| 178 |
+
opacity: 1;
|
| 179 |
+
transform: translateY(0);
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* New element entrance */
|
| 184 |
+
.element-entering {
|
| 185 |
+
animation: elementEnter 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
@keyframes elementEnter {
|
| 189 |
+
0% {
|
| 190 |
+
opacity: 0;
|
| 191 |
+
transform: translateY(8px) scale(0.98);
|
| 192 |
+
}
|
| 193 |
+
100% {
|
| 194 |
+
opacity: 1;
|
| 195 |
+
transform: translateY(0) scale(1);
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
/* Subtle glow for changed content */
|
| 200 |
+
.content-changed {
|
| 201 |
+
animation: contentGlow 0.6s ease-in-out;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
@keyframes contentGlow {
|
| 205 |
+
0%, 100% {
|
| 206 |
+
box-shadow: none;
|
| 207 |
+
background-color: transparent;
|
| 208 |
+
}
|
| 209 |
+
30% {
|
| 210 |
+
box-shadow: 0 0 20px rgba(59, 130, 246, 0.2);
|
| 211 |
+
background-color: rgba(59, 130, 246, 0.05);
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
/* Optimize rendering performance */
|
| 216 |
+
* {
|
| 217 |
+
backface-visibility: hidden;
|
| 218 |
+
-webkit-font-smoothing: antialiased;
|
| 219 |
+
}
|
| 220 |
+
`;
|
| 221 |
+
doc.head.appendChild(style);
|
| 222 |
+
console.log('✨ Enhanced smooth transitions injected into iframe');
|
| 223 |
+
};
|
| 224 |
+
|
| 225 |
+
// Inject on iframe load and when content changes
|
| 226 |
+
const handleLoad = () => {
|
| 227 |
+
setTimeout(injectSmoothTransitions, 10);
|
| 228 |
+
};
|
| 229 |
+
|
| 230 |
+
iframe.addEventListener('load', handleLoad);
|
| 231 |
+
|
| 232 |
+
// Also try to inject immediately if iframe is already loaded
|
| 233 |
+
if (iframe.contentDocument?.readyState === 'complete') {
|
| 234 |
+
injectSmoothTransitions();
|
| 235 |
+
} else {
|
| 236 |
+
// Try again after a short delay
|
| 237 |
+
setTimeout(() => {
|
| 238 |
+
if (iframe.contentDocument?.readyState === 'complete') {
|
| 239 |
+
injectSmoothTransitions();
|
| 240 |
+
}
|
| 241 |
+
}, 100);
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
return () => {
|
| 245 |
+
iframe.removeEventListener('load', handleLoad);
|
| 246 |
+
};
|
| 247 |
+
}, [currentIframeRef, iframeKey]);
|
| 248 |
+
|
| 249 |
+
// Enhanced content updating animations
|
| 250 |
+
useEffect(() => {
|
| 251 |
+
const iframe = currentIframeRef.current;
|
| 252 |
+
if (!iframe) return;
|
| 253 |
+
|
| 254 |
+
const addContentUpdateAnimation = () => {
|
| 255 |
+
const doc = iframe.contentDocument;
|
| 256 |
+
if (!doc || !doc.body) return;
|
| 257 |
+
|
| 258 |
+
const body = doc.body;
|
| 259 |
+
|
| 260 |
+
// Remove any existing animation classes
|
| 261 |
+
body.classList.remove('content-updating', 'content-changed');
|
| 262 |
+
|
| 263 |
+
// Add animation class
|
| 264 |
+
body.classList.add('content-updating');
|
| 265 |
+
|
| 266 |
+
console.log('🎬 Applied content update animation');
|
| 267 |
+
|
| 268 |
+
// Remove class after animation
|
| 269 |
+
const timeout = setTimeout(() => {
|
| 270 |
+
body.classList.remove('content-updating');
|
| 271 |
+
}, 300);
|
| 272 |
+
|
| 273 |
+
return () => {
|
| 274 |
+
clearTimeout(timeout);
|
| 275 |
+
body.classList.remove('content-updating', 'content-changed');
|
| 276 |
+
};
|
| 277 |
+
};
|
| 278 |
+
|
| 279 |
+
// Small delay to ensure iframe content is ready
|
| 280 |
+
const timeout = setTimeout(addContentUpdateAnimation, 50);
|
| 281 |
+
|
| 282 |
+
return () => {
|
| 283 |
+
clearTimeout(timeout);
|
| 284 |
+
};
|
| 285 |
+
}, [displayHtml, currentIframeRef]);
|
| 286 |
+
|
| 287 |
+
// Cleanup timeout on unmount
|
| 288 |
+
useEffect(() => {
|
| 289 |
+
return () => {
|
| 290 |
+
if (htmlUpdateTimeoutRef.current) {
|
| 291 |
+
clearTimeout(htmlUpdateTimeoutRef.current);
|
| 292 |
+
}
|
| 293 |
+
};
|
| 294 |
+
}, []);
|
| 295 |
+
|
| 296 |
+
// Event handlers for editable mode
|
| 297 |
+
const handleMouseOver = (event: MouseEvent) => {
|
| 298 |
+
if (currentIframeRef?.current) {
|
| 299 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 300 |
+
if (iframeDocument) {
|
| 301 |
+
const targetElement = event.target as HTMLElement;
|
| 302 |
+
if (
|
| 303 |
+
hoveredElement !== targetElement &&
|
| 304 |
+
targetElement !== iframeDocument.body
|
| 305 |
+
) {
|
| 306 |
+
console.log("🎯 Edit mode: Element hovered", {
|
| 307 |
+
tagName: targetElement.tagName,
|
| 308 |
+
id: targetElement.id || 'no-id',
|
| 309 |
+
className: targetElement.className || 'no-class'
|
| 310 |
+
});
|
| 311 |
+
|
| 312 |
+
// Remove previous hover class
|
| 313 |
+
if (hoveredElement) {
|
| 314 |
+
hoveredElement.classList.remove("hovered-element");
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
setHoveredElement(targetElement);
|
| 318 |
+
targetElement.classList.add("hovered-element");
|
| 319 |
+
} else {
|
| 320 |
+
return setHoveredElement(null);
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
+
};
|
| 325 |
+
|
| 326 |
+
const handleMouseOut = () => {
|
| 327 |
+
setHoveredElement(null);
|
| 328 |
+
};
|
| 329 |
+
|
| 330 |
+
const handleClick = (event: MouseEvent) => {
|
| 331 |
+
console.log("🖱️ Edit mode: Click detected in iframe", {
|
| 332 |
+
target: event.target,
|
| 333 |
+
tagName: (event.target as HTMLElement)?.tagName,
|
| 334 |
+
isBody: event.target === currentIframeRef?.current?.contentDocument?.body,
|
| 335 |
+
hasOnClickElement: !!onClickElement
|
| 336 |
+
});
|
| 337 |
+
|
| 338 |
+
if (currentIframeRef?.current) {
|
| 339 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 340 |
+
if (iframeDocument) {
|
| 341 |
+
const targetElement = event.target as HTMLElement;
|
| 342 |
+
if (targetElement !== iframeDocument.body) {
|
| 343 |
+
console.log("✅ Edit mode: Valid element clicked, calling onClickElement", {
|
| 344 |
+
tagName: targetElement.tagName,
|
| 345 |
+
id: targetElement.id || 'no-id',
|
| 346 |
+
className: targetElement.className || 'no-class',
|
| 347 |
+
textContent: targetElement.textContent?.substring(0, 50) + '...'
|
| 348 |
+
});
|
| 349 |
+
|
| 350 |
+
// Prevent default behavior to avoid navigation
|
| 351 |
+
event.preventDefault();
|
| 352 |
+
event.stopPropagation();
|
| 353 |
+
|
| 354 |
+
onClickElement?.(targetElement);
|
| 355 |
+
} else {
|
| 356 |
+
console.log("⚠️ Edit mode: Body clicked, ignoring");
|
| 357 |
+
}
|
| 358 |
+
} else {
|
| 359 |
+
console.error("❌ Edit mode: No iframe document available on click");
|
| 360 |
+
}
|
| 361 |
+
} else {
|
| 362 |
+
console.error("❌ Edit mode: No iframe ref available on click");
|
| 363 |
+
}
|
| 364 |
+
};
|
| 365 |
+
|
| 366 |
+
// Setup event listeners for editable mode
|
| 367 |
+
useUpdateEffect(() => {
|
| 368 |
+
const cleanupListeners = () => {
|
| 369 |
+
if (currentIframeRef?.current?.contentDocument) {
|
| 370 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 371 |
+
iframeDocument.removeEventListener("mouseover", handleMouseOver);
|
| 372 |
+
iframeDocument.removeEventListener("mouseout", handleMouseOut);
|
| 373 |
+
iframeDocument.removeEventListener("click", handleClick);
|
| 374 |
+
console.log("🧹 Edit mode: Cleaned up iframe event listeners");
|
| 375 |
+
}
|
| 376 |
+
};
|
| 377 |
+
|
| 378 |
+
const setupListeners = () => {
|
| 379 |
+
try {
|
| 380 |
+
if (!currentIframeRef?.current) {
|
| 381 |
+
console.log("⚠️ Edit mode: No iframe ref available");
|
| 382 |
+
return;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
const iframeDocument = currentIframeRef.current.contentDocument;
|
| 386 |
+
if (!iframeDocument) {
|
| 387 |
+
console.log("⚠️ Edit mode: No iframe content document available");
|
| 388 |
+
return;
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
// Clean up existing listeners first
|
| 392 |
+
cleanupListeners();
|
| 393 |
+
|
| 394 |
+
if (isEditableModeEnabled) {
|
| 395 |
+
console.log("🎯 Edit mode: Setting up iframe event listeners");
|
| 396 |
+
iframeDocument.addEventListener("mouseover", handleMouseOver);
|
| 397 |
+
iframeDocument.addEventListener("mouseout", handleMouseOut);
|
| 398 |
+
iframeDocument.addEventListener("click", handleClick);
|
| 399 |
+
console.log("✅ Edit mode: Event listeners added successfully");
|
| 400 |
+
} else {
|
| 401 |
+
console.log("🔇 Edit mode: Disabled, no listeners added");
|
| 402 |
+
}
|
| 403 |
+
} catch (error) {
|
| 404 |
+
console.error("❌ Edit mode: Error setting up listeners:", error);
|
| 405 |
+
}
|
| 406 |
+
};
|
| 407 |
+
|
| 408 |
+
// Add a small delay to ensure iframe is fully loaded
|
| 409 |
+
const timeoutId = setTimeout(setupListeners, 100);
|
| 410 |
+
|
| 411 |
+
// Clean up when component unmounts or dependencies change
|
| 412 |
+
return () => {
|
| 413 |
+
clearTimeout(timeoutId);
|
| 414 |
+
cleanupListeners();
|
| 415 |
+
};
|
| 416 |
+
}, [currentIframeRef, isEditableModeEnabled]);
|
| 417 |
+
|
| 418 |
+
const selectedElement = useMemo(() => {
|
| 419 |
+
if (!isEditableModeEnabled) return null;
|
| 420 |
+
if (!hoveredElement) return null;
|
| 421 |
+
return hoveredElement;
|
| 422 |
+
}, [hoveredElement, isEditableModeEnabled]);
|
| 423 |
+
|
| 424 |
+
return (
|
| 425 |
+
<div
|
| 426 |
+
ref={ref}
|
| 427 |
+
className={classNames(
|
| 428 |
+
"bg-white overflow-hidden relative flex-1 h-full",
|
| 429 |
+
{
|
| 430 |
+
"cursor-wait": isLoading && isAiWorking,
|
| 431 |
+
}
|
| 432 |
+
)}
|
| 433 |
+
onClick={(e) => {
|
| 434 |
+
e.stopPropagation();
|
| 435 |
+
}}
|
| 436 |
+
>
|
| 437 |
+
<GridPattern
|
| 438 |
+
width={20}
|
| 439 |
+
height={20}
|
| 440 |
+
x={-1}
|
| 441 |
+
y={-1}
|
| 442 |
+
strokeDasharray={"4 2"}
|
| 443 |
+
className={cn(
|
| 444 |
+
"[mask-image:radial-gradient(300px_circle_at_center,white,transparent)] z-0 absolute inset-0 h-full w-full fill-neutral-100 stroke-neutral-100"
|
| 445 |
+
)}
|
| 446 |
+
/>
|
| 447 |
+
|
| 448 |
+
{/* Simplified loading overlay */}
|
| 449 |
+
{isLoading && isAiWorking && (
|
| 450 |
+
<div className="absolute inset-0 bg-black/5 backdrop-blur-[0.5px] transition-all duration-300 z-20 flex items-center justify-center">
|
| 451 |
+
<div className="bg-neutral-800/95 rounded-lg px-4 py-2 text-sm text-neutral-300 border border-neutral-700 shadow-lg">
|
| 452 |
+
<div className="flex items-center gap-2">
|
| 453 |
+
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
|
| 454 |
+
Updating preview...
|
| 455 |
+
</div>
|
| 456 |
+
</div>
|
| 457 |
+
</div>
|
| 458 |
+
)}
|
| 459 |
+
|
| 460 |
+
{/* Selected element indicator */}
|
| 461 |
+
{!isAiWorking && hoveredElement && selectedElement && (
|
| 462 |
+
<div className="absolute bottom-4 left-4 z-30">
|
| 463 |
+
<div className="bg-neutral-800/90 rounded-lg px-3 py-2 text-sm text-neutral-300 border border-neutral-700 shadow-lg">
|
| 464 |
+
<span className="font-medium">
|
| 465 |
+
{htmlTagToText(selectedElement.tagName.toLowerCase())}
|
| 466 |
+
</span>
|
| 467 |
+
{selectedElement.id && (
|
| 468 |
+
<span className="ml-2 text-neutral-400">#{selectedElement.id}</span>
|
| 469 |
+
)}
|
| 470 |
+
</div>
|
| 471 |
+
</div>
|
| 472 |
+
)}
|
| 473 |
+
|
| 474 |
+
{/* Reliable iframe with force refresh capability */}
|
| 475 |
+
<iframe
|
| 476 |
+
key={iframeKey}
|
| 477 |
+
id="preview-iframe"
|
| 478 |
+
ref={currentIframeRef}
|
| 479 |
+
title="output"
|
| 480 |
+
className={classNames(
|
| 481 |
+
"w-full select-none h-full transition-all duration-200 ease-out",
|
| 482 |
+
{
|
| 483 |
+
"pointer-events-none": isResizing || isAiWorking,
|
| 484 |
+
"opacity-95 scale-[0.999]": isLoading && isAiWorking,
|
| 485 |
+
"opacity-100 scale-100": !isLoading || !isAiWorking,
|
| 486 |
+
"bg-black": true,
|
| 487 |
+
"lg:max-w-md lg:mx-auto lg:!rounded-[42px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:h-[80dvh] lg:max-h-[996px]":
|
| 488 |
+
device === "mobile",
|
| 489 |
+
"lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:rounded-[24px]":
|
| 490 |
+
device === "desktop",
|
| 491 |
+
}
|
| 492 |
+
)}
|
| 493 |
+
srcDoc={displayHtml}
|
| 494 |
+
onLoad={() => {
|
| 495 |
+
console.log('🎯 Preview iframe loaded:', {
|
| 496 |
+
displayHtmlLength: displayHtml.length,
|
| 497 |
+
iframeKey,
|
| 498 |
+
hasContent: displayHtml.length > 0
|
| 499 |
+
});
|
| 500 |
+
setIsLoading(false);
|
| 501 |
+
}}
|
| 502 |
+
onError={(e) => {
|
| 503 |
+
console.error('❌ Iframe loading error:', e);
|
| 504 |
+
setIsLoading(false);
|
| 505 |
+
}}
|
| 506 |
+
/>
|
| 507 |
+
</div>
|
| 508 |
+
);
|
| 509 |
+
});
|
| 510 |
+
|
| 511 |
+
Preview.displayName = "Preview";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|