Phases 7-10: Complete remaining features and optimizations

Phase 7.2 - Debounce undo/redo history:
- Add 300ms debounce timer for rapid drag/transform changes
- Commit history on dragEnd/transformEnd events only
- Prevents history bloat during continuous interactions

Phase 8.3 - Template-aware export:
- Render template background layer first
- Apply slot crop regions for image elements
- Render template overlay layer last
- Support nonPrintable flag for guides/watermarks

Phase 9 - PWA icons:
- Add pwa-192x192.svg and pwa-512x512.svg icons
- Update vite.config.js manifest configuration

Phase 10.3 - Performance optimizations:
- Add React.memo to canvas components (ImageElement, TextElement, DesignCanvas)
- Add React.memo to panel components (LayersPanel, PropertiesPanel)
- Prevent unnecessary re-renders during canvas interactions

Phase 10.6 - Template documentation:
- Document template JSON schema in docs/template-schema.md
- Include element properties, slot definitions, and examples
- Describe background/overlay layer structure
This commit is contained in:
Khalid A
2026-04-21 21:50:33 -05:00
parent a02f020d4c
commit 4ca7910465
14 changed files with 465 additions and 93 deletions

View File

@@ -127,7 +127,7 @@ const EXPORT_SIZE = 4500;
app.post('/api/export', async (req, res) => {
try {
const { elements, designName = 'design' } = req.body;
const { elements, designName = 'design', template } = req.body;
if (!elements || !Array.isArray(elements)) {
return res.status(400).json({ error: 'Elements array is required' });
@@ -136,12 +136,33 @@ app.post('/api/export', async (req, res) => {
const canvas = createCanvas(EXPORT_SIZE, EXPORT_SIZE);
const ctx = canvas.getContext('2d');
// White background
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, EXPORT_SIZE, EXPORT_SIZE);
// Render template background layer first (if template active)
if (template && template.background) {
const bg = template.background;
if (bg.type === 'color') {
ctx.fillStyle = bg.color;
ctx.fillRect(0, 0, EXPORT_SIZE, EXPORT_SIZE);
} else if (bg.type === 'image' && bg.src) {
try {
const imgUrl = bg.src.startsWith('/')
? join(__dirname, bg.src.replace('/uploads', 'uploads'))
: bg.src;
const img = await loadImage(imgUrl);
ctx.drawImage(img, 0, 0, EXPORT_SIZE, EXPORT_SIZE);
} catch (imgError) {
console.error('Failed to load template background:', imgError);
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, EXPORT_SIZE, EXPORT_SIZE);
}
}
} else {
// Default white background
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, EXPORT_SIZE, EXPORT_SIZE);
}
// Render each element
for (const el of elements) {
// Helper function to render a single element
const renderElement = async (el) => {
ctx.save();
const x = (el.x || 0) * EXPORT_SCALE;
@@ -161,7 +182,18 @@ app.post('/api/export', async (req, res) => {
const img = await loadImage(imgUrl);
const width = (el.width || 100) * EXPORT_SCALE;
const height = (el.height || 100) * EXPORT_SCALE;
ctx.drawImage(img, x, y, width, height);
// Apply crop if slot crop region specified
if (el.crop) {
const { sx, sy, sWidth, sHeight } = el.crop;
ctx.drawImage(
img,
sx, sy, sWidth, sHeight, // source crop
x, y, width, height // destination
);
} else {
ctx.drawImage(img, x, y, width, height);
}
} catch (imgError) {
console.error('Failed to load image for export:', imgError);
}
@@ -175,6 +207,21 @@ app.post('/api/export', async (req, res) => {
}
ctx.restore();
};
// Render user elements
for (const el of elements) {
// Skip non-printable elements (guides, watermarks, template-only layers)
if (el.nonPrintable) continue;
await renderElement(el);
}
// Render template overlay layer last (if template active)
if (template && template.overlay) {
for (const overlayEl of template.overlay) {
if (overlayEl.nonPrintable) continue;
await renderElement(overlayEl);
}
}
// Save to file