This commit is contained in:
ct
2025-06-17 18:50:54 +08:00
parent e8bcb1d787
commit 0b0d1db35c
5 changed files with 115 additions and 17 deletions

View File

@@ -3,6 +3,8 @@ import '../css/app.css';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { createRoot } from 'react-dom/client';
import { ErrorBoundary } from 'react-error-boundary';
import DetailedErrorFallback from './components/custom/detailed-error-feedback'; // Import your component
import { initializeTheme } from './hooks/use-appearance';
import { AxiosProvider } from './plugins/AxiosContext';
import { MittProvider } from './plugins/MittContext';
@@ -16,13 +18,26 @@ createInertiaApp({
const root = createRoot(el);
const app = (
<>
<ErrorBoundary
FallbackComponent={DetailedErrorFallback}
onError={(error, errorInfo) => {
// Log to console for debugging
console.error('Error caught by boundary:', error, errorInfo);
// You could also send to an error reporting service here
// e.g., Sentry, LogRocket, etc.
}}
onReset={() => {
// Optional: Clear any error state in your app
console.log('Error boundary reset');
}}
>
<MittProvider>
<AxiosProvider>
<App {...props} />
</AxiosProvider>
</MittProvider>
</>
</ErrorBoundary>
);
root.render(app);
@@ -32,5 +47,4 @@ createInertiaApp({
},
});
// This will set light / dark mode on load...
initializeTheme();

View File

@@ -0,0 +1,71 @@
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { AlertCircle } from 'lucide-react';
function DetailedErrorFallback({ error, resetErrorBoundary }) {
return (
<div className="min-h-screen bg-gray-50 p-8">
<Alert variant="destructive" className="mx-auto max-w-4xl">
<AlertCircle className="h-5 w-5" />
<AlertTitle className="mb-2 text-xl font-bold">Runtime Error</AlertTitle>
<AlertDescription>
<div className="mt-4 space-y-4">
{/* Error Message */}
<div>
<h3 className="text-sm font-medium text-gray-500">Error Message:</h3>
<p className="mt-1 font-medium text-red-600">{error.message}</p>
</div>
{/* Component Stack */}
<div>
<h3 className="text-sm font-medium text-gray-500">Component Stack:</h3>
<pre className="mt-1 overflow-auto rounded-md bg-gray-900 p-4 text-sm text-white">
{error.componentStack || error.stack}
</pre>
</div>
{/* Additional Error Info */}
{error.fileName && (
<div>
<h3 className="text-sm font-medium text-gray-500">File:</h3>
<p className="mt-1 font-mono text-sm">{error.fileName}</p>
</div>
)}
{error.lineNumber && (
<div>
<h3 className="text-sm font-medium text-gray-500">Line:</h3>
<p className="mt-1 font-mono text-sm">{error.lineNumber}</p>
</div>
)}
{/* Error Properties */}
<div>
<h3 className="text-sm font-medium text-gray-500">Additional Details:</h3>
<pre className="mt-1 overflow-auto rounded-md bg-gray-100 p-4 text-sm">
{JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}
</pre>
</div>
{/* Actions */}
<div className="mt-6 flex gap-4">
<button
onClick={resetErrorBoundary}
className="rounded bg-red-600 px-4 py-2 text-white transition-colors hover:bg-red-700"
>
Try Again
</button>
<button
onClick={() => window.location.reload()}
className="rounded border border-red-600 px-4 py-2 text-red-600 transition-colors hover:bg-red-50"
>
Reload Page
</button>
</div>
</div>
</AlertDescription>
</Alert>
</div>
);
}
export default DetailedErrorFallback;

View File

@@ -294,9 +294,8 @@ const VideoPreview = ({
const scaleX = node.scaleX();
const scaleY = node.scaleY();
let newWidth,
newHeight,
updates = {};
let newWidth, newHeight;
let updates = {};
if (element.type === 'text') {
// OPTION A: Convert scale change to fontSize change
@@ -310,9 +309,13 @@ const VideoPreview = ({
node.scaleX(1);
node.scaleY(1);
// The width/height will be automatically calculated by Konva based on fontSize
// For text elements, we let Konva handle the natural dimensions
// ✅ FIX: Always get current width/height for text elements too
newWidth = node.width();
newHeight = node.height();
updates.fontSize = clampedFontSize;
updates.width = newWidth; // ✅ Always include width
updates.height = newHeight; // ✅ Always include height
console.log(`Text transform: scale=${scale.toFixed(2)}, oldFontSize=${element.fontSize}, newFontSize=${clampedFontSize}`);
} else {
@@ -342,10 +345,8 @@ const VideoPreview = ({
// Convert center position to top-left for snapping
const centerX = node.x();
const centerY = node.y();
const currentWidth = element.type === 'text' ? node.width() : newWidth;
const currentHeight = element.type === 'text' ? node.height() : newHeight;
topLeftX = centerX - currentWidth / 2;
topLeftY = centerY - currentHeight / 2;
topLeftX = centerX - newWidth / 2; // ✅ newWidth is now always defined
topLeftY = centerY - newHeight / 2; // ✅ newHeight is now always defined
} else {
// Use position directly for text
topLeftX = node.x();
@@ -355,15 +356,13 @@ const VideoPreview = ({
// Check for position snapping during transform (but be less aggressive during rotation)
const isRotating = Math.abs(rotation % 90) > 5; // Not close to perpendicular
if (!isRotating) {
const currentWidth = element.type === 'text' ? node.width() : newWidth;
const currentHeight = element.type === 'text' ? node.height() : newHeight;
const snapResult = calculateSnapAndGuides(elementId, topLeftX, topLeftY, currentWidth, currentHeight);
const snapResult = calculateSnapAndGuides(elementId, topLeftX, topLeftY, newWidth, newHeight);
if (Math.abs(snapResult.x - topLeftX) > 5 || Math.abs(snapResult.y - topLeftY) > 5) {
if (usesCenterPositioning(element.type)) {
// Convert back to center position
const newCenterX = snapResult.x + currentWidth / 2;
const newCenterY = snapResult.y + currentHeight / 2;
const newCenterX = snapResult.x + newWidth / 2;
const newCenterY = snapResult.y + newHeight / 2;
node.x(newCenterX);
node.y(newCenterY);
} else {