Update
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -57,6 +57,7 @@
|
|||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-day-picker": "^9.7.0",
|
"react-day-picker": "^9.7.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-error-boundary": "^6.0.0",
|
||||||
"react-hook-form": "^7.57.0",
|
"react-hook-form": "^7.57.0",
|
||||||
"react-konva": "^19.0.6",
|
"react-konva": "^19.0.6",
|
||||||
"react-resizable-panels": "^3.0.2",
|
"react-resizable-panels": "^3.0.2",
|
||||||
@@ -7208,6 +7209,18 @@
|
|||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-error-boundary": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.57.0",
|
"version": "7.57.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-day-picker": "^9.7.0",
|
"react-day-picker": "^9.7.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-error-boundary": "^6.0.0",
|
||||||
"react-hook-form": "^7.57.0",
|
"react-hook-form": "^7.57.0",
|
||||||
"react-konva": "^19.0.6",
|
"react-konva": "^19.0.6",
|
||||||
"react-resizable-panels": "^3.0.2",
|
"react-resizable-panels": "^3.0.2",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import '../css/app.css';
|
|||||||
import { createInertiaApp } from '@inertiajs/react';
|
import { createInertiaApp } from '@inertiajs/react';
|
||||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||||
import { createRoot } from 'react-dom/client';
|
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 { initializeTheme } from './hooks/use-appearance';
|
||||||
import { AxiosProvider } from './plugins/AxiosContext';
|
import { AxiosProvider } from './plugins/AxiosContext';
|
||||||
import { MittProvider } from './plugins/MittContext';
|
import { MittProvider } from './plugins/MittContext';
|
||||||
@@ -16,13 +18,26 @@ createInertiaApp({
|
|||||||
const root = createRoot(el);
|
const root = createRoot(el);
|
||||||
|
|
||||||
const app = (
|
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>
|
<MittProvider>
|
||||||
<AxiosProvider>
|
<AxiosProvider>
|
||||||
<App {...props} />
|
<App {...props} />
|
||||||
</AxiosProvider>
|
</AxiosProvider>
|
||||||
</MittProvider>
|
</MittProvider>
|
||||||
</>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|
||||||
root.render(app);
|
root.render(app);
|
||||||
@@ -32,5 +47,4 @@ createInertiaApp({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// This will set light / dark mode on load...
|
|
||||||
initializeTheme();
|
initializeTheme();
|
||||||
|
|||||||
71
resources/js/components/custom/detailed-error-feedback.jsx
Normal file
71
resources/js/components/custom/detailed-error-feedback.jsx
Normal 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;
|
||||||
@@ -294,9 +294,8 @@ const VideoPreview = ({
|
|||||||
const scaleX = node.scaleX();
|
const scaleX = node.scaleX();
|
||||||
const scaleY = node.scaleY();
|
const scaleY = node.scaleY();
|
||||||
|
|
||||||
let newWidth,
|
let newWidth, newHeight;
|
||||||
newHeight,
|
let updates = {};
|
||||||
updates = {};
|
|
||||||
|
|
||||||
if (element.type === 'text') {
|
if (element.type === 'text') {
|
||||||
// OPTION A: Convert scale change to fontSize change
|
// OPTION A: Convert scale change to fontSize change
|
||||||
@@ -310,9 +309,13 @@ const VideoPreview = ({
|
|||||||
node.scaleX(1);
|
node.scaleX(1);
|
||||||
node.scaleY(1);
|
node.scaleY(1);
|
||||||
|
|
||||||
// The width/height will be automatically calculated by Konva based on fontSize
|
// ✅ FIX: Always get current width/height for text elements too
|
||||||
// For text elements, we let Konva handle the natural dimensions
|
newWidth = node.width();
|
||||||
|
newHeight = node.height();
|
||||||
|
|
||||||
updates.fontSize = clampedFontSize;
|
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}`);
|
console.log(`Text transform: scale=${scale.toFixed(2)}, oldFontSize=${element.fontSize}, newFontSize=${clampedFontSize}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -342,10 +345,8 @@ const VideoPreview = ({
|
|||||||
// Convert center position to top-left for snapping
|
// Convert center position to top-left for snapping
|
||||||
const centerX = node.x();
|
const centerX = node.x();
|
||||||
const centerY = node.y();
|
const centerY = node.y();
|
||||||
const currentWidth = element.type === 'text' ? node.width() : newWidth;
|
topLeftX = centerX - newWidth / 2; // ✅ newWidth is now always defined
|
||||||
const currentHeight = element.type === 'text' ? node.height() : newHeight;
|
topLeftY = centerY - newHeight / 2; // ✅ newHeight is now always defined
|
||||||
topLeftX = centerX - currentWidth / 2;
|
|
||||||
topLeftY = centerY - currentHeight / 2;
|
|
||||||
} else {
|
} else {
|
||||||
// Use position directly for text
|
// Use position directly for text
|
||||||
topLeftX = node.x();
|
topLeftX = node.x();
|
||||||
@@ -355,15 +356,13 @@ const VideoPreview = ({
|
|||||||
// Check for position snapping during transform (but be less aggressive during rotation)
|
// Check for position snapping during transform (but be less aggressive during rotation)
|
||||||
const isRotating = Math.abs(rotation % 90) > 5; // Not close to perpendicular
|
const isRotating = Math.abs(rotation % 90) > 5; // Not close to perpendicular
|
||||||
if (!isRotating) {
|
if (!isRotating) {
|
||||||
const currentWidth = element.type === 'text' ? node.width() : newWidth;
|
const snapResult = calculateSnapAndGuides(elementId, topLeftX, topLeftY, newWidth, newHeight);
|
||||||
const currentHeight = element.type === 'text' ? node.height() : newHeight;
|
|
||||||
const snapResult = calculateSnapAndGuides(elementId, topLeftX, topLeftY, currentWidth, currentHeight);
|
|
||||||
|
|
||||||
if (Math.abs(snapResult.x - topLeftX) > 5 || Math.abs(snapResult.y - topLeftY) > 5) {
|
if (Math.abs(snapResult.x - topLeftX) > 5 || Math.abs(snapResult.y - topLeftY) > 5) {
|
||||||
if (usesCenterPositioning(element.type)) {
|
if (usesCenterPositioning(element.type)) {
|
||||||
// Convert back to center position
|
// Convert back to center position
|
||||||
const newCenterX = snapResult.x + currentWidth / 2;
|
const newCenterX = snapResult.x + newWidth / 2;
|
||||||
const newCenterY = snapResult.y + currentHeight / 2;
|
const newCenterY = snapResult.y + newHeight / 2;
|
||||||
node.x(newCenterX);
|
node.x(newCenterX);
|
||||||
node.y(newCenterY);
|
node.y(newCenterY);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user