slideshare/src/utils/templateRenderer.ts
Michael Mainguy 1008bd4bca Implement slide preview and fix import standards project-wide
## New Feature: Full Screen Slide Preview
- Add SlidePreviewModal component for full screen slide preview in SlideEditor
- ESC key support and temporary hint for user guidance
- Proper aspect ratio handling with theme CSS inheritance
- Modal follows existing UI patterns for consistency

## Import Standards Compliance (31 files updated)
- Fix all imports to use explicit .tsx/.ts extensions per IMPORT_STANDARDS.md
- Eliminate barrel imports in App.tsx for better Vite tree shaking
- Add direct imports with explicit paths across entire codebase
- Preserve CSS imports and external library imports unchanged

## Code Architecture Improvements
- Add comprehensive CSS & Component Architecture Guidelines to CLAUDE.md
- Document modal patterns, aspect ratio handling, and CSS reuse principles
- Reference all coding standards files for consistent development workflow
- Prevent future CSS overcomplication and component duplication

## Performance Optimizations
- Enable Vite tree shaking with proper import structure
- Improve module resolution speed with explicit extensions
- Optimize build performance through direct imports

## Files Changed
- 31 TypeScript/React files with import fixes
- 2 new SlidePreviewModal files (component + CSS)
- Updated project documentation and coding guidelines
- Fixed aspect ratio CSS patterns across components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 06:52:56 -05:00

234 lines
7.9 KiB
TypeScript

import type { SlideLayout, SlotConfig } from '../types/theme.ts';
/**
* Creates a simple SVG pattern for image slots
*/
const createImageSVG = (slotName: string, width: number = 400, height: number = 300): string => {
const encodedSVG = encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<defs>
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#e2e8f0" stroke-width="1"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="#f8fafc"/>
<rect width="100%" height="100%" fill="url(#grid)"/>
<rect x="2" y="2" width="${width-4}" height="${height-4}" fill="none" stroke="#cbd5e1" stroke-width="2" stroke-dasharray="8,4"/>
<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle"
font-family="system-ui, sans-serif" font-size="16" font-weight="500" fill="#64748b">
${slotName}
</text>
</svg>
`);
return `data:image/svg+xml,${encodedSVG}`;
};
/**
* Sample content templates for more realistic previews
*/
const SAMPLE_CONTENT = {
title: [
'Quarterly Sales Review',
'Project Roadmap 2024',
'Market Analysis Report',
'New Product Launch',
'Team Performance Update'
],
subtitle: [
'Q4 Results and Future Outlook',
'Strategic Planning and Key Milestones',
'Customer Insights and Trends',
'Innovation and Growth Strategy',
'Achievements and Next Steps'
],
heading: [
'Key Findings',
'Next Steps',
'Executive Summary',
'Strategic Goals',
'Market Opportunities'
],
text: [
'This comprehensive analysis provides insights into market trends and customer behavior patterns that will shape our strategic decisions moving forward.',
'Our team has achieved significant milestones this quarter, demonstrating strong execution and commitment to delivering exceptional results.',
'The data shows promising growth opportunities in emerging markets, with particular strength in the technology and healthcare sectors.',
'Customer feedback indicates high satisfaction levels with our current offerings, while highlighting areas for continued innovation.',
'Looking ahead, we will focus on expanding our market presence while maintaining our commitment to quality and customer excellence.'
],
list: [
'• Increase market share by 15%\n• Launch 3 new products\n• Expand to 5 new regions',
'• Improve customer satisfaction\n• Reduce operational costs\n• Enhance digital capabilities',
'• Strengthen brand recognition\n• Optimize supply chain\n• Invest in talent development'
]
};
/**
* Generates sample data for a slot based on its configuration
*/
export const generateSampleDataForSlot = (slot: SlotConfig): string => {
const { id, type, defaultContent } = slot;
// Use default content if available
if (defaultContent && defaultContent.trim()) {
return defaultContent;
}
// Clean up slot name for display
const slotDisplayName = id.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
// Generate sample data based on slot type
switch (type) {
case 'image':
return createImageSVG(slotDisplayName);
case 'title': {
const titleSamples = SAMPLE_CONTENT.title;
return titleSamples[Math.floor(Math.random() * titleSamples.length)];
}
case 'subtitle': {
const subtitleSamples = SAMPLE_CONTENT.subtitle;
return subtitleSamples[Math.floor(Math.random() * subtitleSamples.length)];
}
case 'heading': {
const headingSamples = SAMPLE_CONTENT.heading;
return headingSamples[Math.floor(Math.random() * headingSamples.length)];
}
case 'text': {
const textSamples = SAMPLE_CONTENT.text;
return textSamples[Math.floor(Math.random() * textSamples.length)];
}
case 'video':
return createImageSVG(`${slotDisplayName} (Video)`, 640, 360);
case 'audio':
return `[${slotDisplayName} Audio]`;
case 'list': {
const listSamples = SAMPLE_CONTENT.list;
return listSamples[Math.floor(Math.random() * listSamples.length)];
}
case 'code':
return `// ${slotDisplayName}\nfunction ${id.replace(/-/g, '')}() {\n return "${slotDisplayName}";\n}`;
case 'table':
return `${slotDisplayName} Col 1 | ${slotDisplayName} Col 2\n--- | ---\nRow 1 Data | Row 1 Data\nRow 2 Data | Row 2 Data`;
default:
return slotDisplayName;
}
};
/**
* Generates a complete sample data object for all slots in a layout
*/
export const generateSampleDataForLayout = (layout: SlideLayout): Record<string, string> => {
const sampleData: Record<string, string> = {};
layout.slots.forEach(slot => {
sampleData[slot.id] = generateSampleDataForSlot(slot);
// Add related data for image slots
if (slot.type === 'image') {
sampleData[`${slot.id}Alt`] = `Sample ${slot.id} description`;
}
});
// Add common template variables that might be used
sampleData.footerText = 'Sample Footer Text';
sampleData.author = 'Sample Author';
sampleData.date = new Date().toLocaleDateString();
// Add layout metadata for templates that might use it
sampleData['layout-description'] = layout.description || `This is the ${layout.name} layout`;
sampleData['layout-name'] = layout.name;
return sampleData;
};
/**
* Renders an HTML template with sample data using simple string replacement
*/
export const renderTemplateWithSampleData = (
htmlTemplate: string,
layout: SlideLayout
): string => {
const sampleData = generateSampleDataForLayout(layout);
let rendered = htmlTemplate;
// Handle Handlebars-style templates: {{variable}}
Object.entries(sampleData).forEach(([key, value]) => {
const simpleRegex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
rendered = rendered.replace(simpleRegex, value);
});
// Handle conditional blocks: {{#variable}}...{{/variable}}
Object.entries(sampleData).forEach(([key, value]) => {
const conditionalRegex = new RegExp(`\\{\\{#${key}\\}\\}([\\s\\S]*?)\\{\\{/${key}\\}\\}`, 'g');
if (value && value.trim()) {
// Replace conditional block with content, substituting variables within
rendered = rendered.replace(conditionalRegex, (_match, content) => {
// Replace variables within the conditional block
let blockContent = content;
Object.entries(sampleData).forEach(([innerKey, innerValue]) => {
const innerRegex = new RegExp(`\\{\\{${innerKey}\\}\\}`, 'g');
blockContent = blockContent.replace(innerRegex, innerValue);
});
return blockContent;
});
} else {
// Remove conditional block if no data
rendered = rendered.replace(conditionalRegex, '');
}
});
// Clean up any remaining template variables
rendered = rendered.replace(/\{\{[^}]+\}\}/g, '');
return rendered;
};
/**
* Creates a preview-ready HTML document with theme CSS applied
*/
export const createPreviewDocument = (
renderedTemplate: string,
themeCssUrl: string
): string => {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="${themeCssUrl}">
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
overflow: hidden;
}
.preview-container {
width: 100vw;
height: 100vh;
transform: scale(0.25);
transform-origin: top left;
overflow: hidden;
}
</style>
</head>
<body>
<div class="preview-container">
${renderedTemplate}
</div>
</body>
</html>
`;
};