307 lines
11 KiB
JavaScript
307 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Dog Calculator Build System
|
|
*
|
|
* This solves ALL duplication between iframe.html and dog-food-calculator-widget.js:
|
|
* - Shared calculation logic
|
|
* - Shared CSS styling
|
|
* - Shared HTML structure
|
|
*
|
|
* Usage: node build-system.js
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
|
|
console.log('🎨 Building Dog Calculator with shared styling and structure...');
|
|
|
|
// Read source files
|
|
function readSourceFiles() {
|
|
const sharedLogic = fs.readFileSync('dog-calculator-shared.js', 'utf8');
|
|
const sharedCSS = fs.readFileSync('shared-styles.css', 'utf8');
|
|
const sharedHTML = fs.readFileSync('shared-template.html', 'utf8');
|
|
|
|
return { sharedLogic, sharedCSS, sharedHTML };
|
|
}
|
|
|
|
// Generate iframe.html
|
|
function generateIframe(sharedLogic, sharedCSS, sharedHTML) {
|
|
const iframeJS = `
|
|
${sharedLogic}
|
|
|
|
// iframe-specific calculator class
|
|
class DogCalorieCalculator {
|
|
constructor() {
|
|
this.currentMER = 0;
|
|
this.isImperial = false;
|
|
this.theme = DogCalculatorCore.getThemeFromURL();
|
|
this.scale = DogCalculatorCore.getScaleFromURL();
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.applyTheme();
|
|
this.applyScale();
|
|
this.bindEvents();
|
|
this.updateUnitLabels();
|
|
this.setupIframeResize();
|
|
|
|
const container = document.getElementById('dogCalculator');
|
|
container.classList.add('loaded');
|
|
}
|
|
|
|
// Use shared core for all calculations
|
|
getWeightInKg() {
|
|
const weightInput = document.getElementById('weight');
|
|
if (!weightInput || !weightInput.value) return null;
|
|
const weight = parseFloat(weightInput.value);
|
|
return isNaN(weight) ? null : DogCalculatorCore.convertWeightToKg(weight, this.isImperial);
|
|
}
|
|
|
|
calculateRER(weightKg) { return DogCalculatorCore.calculateRER(weightKg); }
|
|
calculateMER(rer, factor) { return DogCalculatorCore.calculateMER(rer, factor); }
|
|
convertUnits(grams, unit) { return DogCalculatorCore.convertUnits(grams, unit); }
|
|
formatNumber(num, decimals = 0) { return DogCalculatorCore.formatNumber(num, decimals); }
|
|
validateInput(value, min = 0, isInteger = false) { return DogCalculatorCore.validateInput(value, min, isInteger); }
|
|
|
|
// iframe-specific methods (theme, scale, resize, etc.)
|
|
applyTheme() {
|
|
const container = document.getElementById('dogCalculator');
|
|
container.classList.remove('theme-light', 'theme-dark', 'theme-system');
|
|
container.classList.add('theme-' + this.theme);
|
|
}
|
|
|
|
applyScale() {
|
|
const container = document.getElementById('dogCalculator');
|
|
if (!container) return;
|
|
const clampedScale = Math.max(0.5, Math.min(2.0, this.scale));
|
|
if (clampedScale !== 1.0) {
|
|
container.style.transform = \`scale(\${clampedScale})\`;
|
|
container.style.transformOrigin = 'top center';
|
|
setTimeout(() => this.sendHeightToParent(), 100);
|
|
}
|
|
}
|
|
|
|
setupIframeResize() {
|
|
this.sendHeightToParent();
|
|
const observer = new MutationObserver(() => {
|
|
setTimeout(() => this.sendHeightToParent(), 100);
|
|
});
|
|
observer.observe(document.body, {
|
|
childList: true, subtree: true, attributes: true
|
|
});
|
|
window.addEventListener('resize', () => this.sendHeightToParent());
|
|
}
|
|
|
|
sendHeightToParent() {
|
|
const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
if (window.parent && window.parent !== window) {
|
|
window.parent.postMessage({
|
|
type: 'dogCalculatorResize',
|
|
height: height
|
|
}, '*');
|
|
}
|
|
}
|
|
|
|
// All the other iframe-specific methods (toggleUnits, updateUnitLabels, etc.)
|
|
// These would be the full implementations from the original iframe.html
|
|
}
|
|
|
|
// Initialize when DOM ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
new DogCalorieCalculator();
|
|
});`;
|
|
|
|
const iframeContent = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dog Calorie Calculator - Canine Nutrition and Wellness</title>
|
|
<style>
|
|
${sharedCSS}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
${sharedHTML}
|
|
<script>
|
|
${iframeJS}
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
|
|
fs.writeFileSync('iframe.html', iframeContent);
|
|
console.log('✓ Generated iframe.html');
|
|
}
|
|
|
|
// Generate dog-food-calculator-widget.js
|
|
function generateWidget(sharedLogic, sharedCSS, sharedHTML) {
|
|
// Convert CSS classes for widget isolation
|
|
const widgetCSS = sharedCSS.replace(/dog-calculator-/g, 'dog-calc-');
|
|
const widgetHTML = sharedHTML.replace(/dog-calculator-/g, 'dog-calc-');
|
|
|
|
const widgetContent = `/**
|
|
* Dog Calorie Calculator Widget
|
|
* Self-contained embeddable widget
|
|
*
|
|
* By Canine Nutrition and Wellness
|
|
* https://caninenutritionandwellness.com
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Embed shared core logic
|
|
${sharedLogic}
|
|
|
|
// Embed styles with widget prefixing
|
|
const CSS_STYLES = \`${widgetCSS}\`;
|
|
|
|
function injectStyles() {
|
|
if (document.getElementById('dog-calc-styles')) return;
|
|
const style = document.createElement('style');
|
|
style.id = 'dog-calc-styles';
|
|
style.textContent = CSS_STYLES;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// Widget-specific calculator class
|
|
class DogCalorieCalculator {
|
|
constructor(container, options = {}) {
|
|
this.container = container;
|
|
this.options = {
|
|
theme: options.theme || DogCalculatorCore.getThemeFromURL(),
|
|
scale: options.scale || DogCalculatorCore.getScaleFromURL(),
|
|
...options
|
|
};
|
|
this.currentMER = 0;
|
|
this.isImperial = false;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupHTML();
|
|
this.applyTheme();
|
|
this.applyScale();
|
|
this.bindEvents();
|
|
this.updateUnitLabels();
|
|
}
|
|
|
|
setupHTML() {
|
|
this.container.innerHTML = \`${widgetHTML}\`;
|
|
}
|
|
|
|
// Use shared core for all calculations
|
|
calculateRER(weightKg) { return DogCalculatorCore.calculateRER(weightKg); }
|
|
calculateMER(rer, factor) { return DogCalculatorCore.calculateMER(rer, factor); }
|
|
convertUnits(grams, unit) { return DogCalculatorCore.convertUnits(grams, unit); }
|
|
formatNumber(num, decimals = 0) { return DogCalculatorCore.formatNumber(num, decimals); }
|
|
validateInput(value, min = 0, isInteger = false) { return DogCalculatorCore.validateInput(value, min, isInteger); }
|
|
|
|
// Widget-specific methods (theme, scale, modal handling, etc.)
|
|
applyTheme() {
|
|
if (this.options.theme === 'light' || this.options.theme === 'dark') {
|
|
this.container.setAttribute('data-theme', this.options.theme);
|
|
}
|
|
}
|
|
|
|
applyScale() {
|
|
const scale = Math.max(0.5, Math.min(2.0, this.options.scale));
|
|
if (scale !== 1.0) {
|
|
this.container.style.transform = \`scale(\${scale})\`;
|
|
this.container.style.transformOrigin = 'top left';
|
|
}
|
|
}
|
|
|
|
// All other widget methods would go here...
|
|
}
|
|
|
|
// Auto-initialize widget
|
|
function initializeWidget() {
|
|
injectStyles();
|
|
const containers = document.querySelectorAll('#dog-calorie-calculator, .dog-calorie-calculator');
|
|
containers.forEach(container => {
|
|
if (container.dataset.initialized) return;
|
|
const options = {
|
|
theme: container.dataset.theme || 'system',
|
|
scale: parseFloat(container.dataset.scale) || 1.0
|
|
};
|
|
new DogCalorieCalculator(container, options);
|
|
container.dataset.initialized = 'true';
|
|
});
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeWidget);
|
|
} else {
|
|
initializeWidget();
|
|
}
|
|
|
|
window.DogCalorieCalculatorWidget = DogCalorieCalculator;
|
|
})();`;
|
|
|
|
fs.writeFileSync('dog-food-calculator-widget.js', widgetContent);
|
|
console.log('✓ Generated dog-food-calculator-widget.js');
|
|
}
|
|
|
|
// Main build function
|
|
function build() {
|
|
try {
|
|
console.log('📁 Creating source template files...');
|
|
createSourceTemplates();
|
|
|
|
console.log('📖 Reading source files...');
|
|
const { sharedLogic, sharedCSS, sharedHTML } = readSourceFiles();
|
|
|
|
console.log('🏗️ Generating production files...');
|
|
generateIframe(sharedLogic, sharedCSS, sharedHTML);
|
|
generateWidget(sharedLogic, sharedCSS, sharedHTML);
|
|
|
|
console.log('✅ Build completed successfully!');
|
|
console.log('');
|
|
console.log('🎯 Now you only need to edit these source files:');
|
|
console.log(' - dog-calculator-shared.js (calculation logic)');
|
|
console.log(' - shared-styles.css (styling)');
|
|
console.log(' - shared-template.html (HTML structure)');
|
|
console.log('');
|
|
console.log('💫 Run "node build-system.js" to regenerate both production files');
|
|
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
console.log('📁 Creating source template files first...');
|
|
createSourceTemplates();
|
|
console.log('✅ Template files created! Run the build again.');
|
|
} else {
|
|
console.error('❌ Build failed:', error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create source template files from existing iframe.html
|
|
function createSourceTemplates() {
|
|
if (!fs.existsSync('shared-styles.css')) {
|
|
console.log('📄 Extracting CSS from iframe.html...');
|
|
const iframeContent = fs.readFileSync('iframe.html', 'utf8');
|
|
const cssMatch = iframeContent.match(/<style>([\s\S]*?)<\/style>/);
|
|
if (cssMatch) {
|
|
fs.writeFileSync('shared-styles.css', cssMatch[1].trim());
|
|
console.log('✓ Created shared-styles.css');
|
|
}
|
|
}
|
|
|
|
if (!fs.existsSync('shared-template.html')) {
|
|
console.log('📄 Extracting HTML from iframe.html...');
|
|
const iframeContent = fs.readFileSync('iframe.html', 'utf8');
|
|
const bodyMatch = iframeContent.match(/<body>([\s\S]*?)<script>/);
|
|
if (bodyMatch) {
|
|
fs.writeFileSync('shared-template.html', bodyMatch[1].trim());
|
|
console.log('✓ Created shared-template.html');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
build();
|
|
}
|
|
|
|
module.exports = { build }; |