You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
9.4 KiB

#!/usr/bin/env python3
"""
EasySmartInventory Generator
Genereert standalone HTML inventarisatieformulieren op basis van YAML configuratie
Gebruik:
python generator.py <config.yaml> [--output <output.html>] [--theme <modern|corporate|minimal>]
"""
import argparse
import json
import sys
from pathlib import Path
from html import escape
from yaml_parser import parse_yaml, validate_config, InventoryConfig, FieldConfig
from templates import get_theme_css, JAVASCRIPT
def generate_field_html(field: FieldConfig) -> str:
"""Genereer HTML voor een veld"""
required_attr = 'data-required="true"' if field.required else ''
required_mark = '<span class="required">*</span>' if field.required else ''
min_length_attr = f'data-min-length="{field.min_length}"' if field.min_length else ''
placeholder_attr = f'placeholder="{escape(field.placeholder)}"' if field.placeholder else ''
html = f'<div class="field-group">\n'
html += f' <label for="{field.id}">{escape(field.label)}{required_mark}</label>\n'
if field.type == "text":
html += f' <input type="text" id="{field.id}" {required_attr} {min_length_attr} {placeholder_attr}>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "number":
min_attr = f'min="{field.min}"' if field.min is not None else ''
max_attr = f'max="{field.max}"' if field.max is not None else ''
html += f' <input type="number" id="{field.id}" {required_attr} {min_attr} {max_attr} {placeholder_attr}>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "date":
html += f' <input type="date" id="{field.id}" {required_attr}>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "textarea":
html += f' <textarea id="{field.id}" rows="{field.rows}" {required_attr} {placeholder_attr}></textarea>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "dropdown":
html += f' <select id="{field.id}" {required_attr}>\n'
html += f' <option value="">-- Selecteer --</option>\n'
for option in field.options:
html += f' <option value="{escape(option)}">{escape(option)}</option>\n'
html += f' </select>\n'
html += f' <div class="error-message"></div>\n'
elif field.type == "multiselect":
html += f' <div class="multiselect-group" data-id="{field.id}">\n'
for option in field.options:
safe_value = escape(option)
html += f' <label class="multiselect-item">\n'
html += f' <input type="checkbox" name="{field.id}" value="{safe_value}">\n'
html += f' <span>{safe_value}</span>\n'
html += f' </label>\n'
html += f' </div>\n'
elif field.type == "boolean":
html += f' <div class="toggle-container">\n'
html += f' <label class="toggle">\n'
html += f' <input type="checkbox" id="{field.id}" class="boolean-field">\n'
html += f' <span class="toggle-slider"></span>\n'
html += f' </label>\n'
html += f' <span>Ja</span>\n'
html += f' </div>\n'
elif field.type == "photo":
html += f' <div class="photo-container" data-max-width="{field.max_width}" data-max-height="{field.max_height}">\n'
html += f' <input type="file" accept="image/*" capture="environment" style="display:none">\n'
html += f' <img id="{field.id}" class="photo-preview" src="" alt="Foto preview" style="display:none">\n'
html += f' <div class="photo-placeholder">📷 Klik om foto te maken of uploaden</div>\n'
html += f' <div class="photo-buttons" style="display:none">\n'
html += f' <button type="button" class="btn btn-secondary" onclick="removePhoto(\'{field.id}\')">Verwijderen</button>\n'
html += f' </div>\n'
html += f' </div>\n'
html += '</div>\n'
return html
def generate_html(config: InventoryConfig) -> str:
"""Genereer complete standalone HTML"""
# Config voor JavaScript
js_config = {
"name": config.name,
"version": config.version,
"autosave": {
"enabled": config.autosave.enabled,
"interval_seconds": config.autosave.interval_seconds,
"use_url_hash": config.autosave.use_url_hash,
"use_localstorage": config.autosave.use_localstorage,
},
"export": {
"csv": {
"enabled": config.export.csv.enabled,
"include_photo": config.export.csv.include_photo,
},
"mailto": {
"enabled": config.export.mailto.enabled,
"to": config.export.mailto.to,
"subject_prefix": config.export.mailto.subject_prefix,
"subject_fields": config.export.mailto.subject_fields,
"include_timestamp": config.export.mailto.include_timestamp,
},
},
}
# CSS genereren
css = get_theme_css(config.style.theme, config.style)
# Extra CSS voor photo container
css += """
.photo-container.has-photo .photo-placeholder { display: none; }
.photo-container.has-photo .photo-preview { display: block !important; }
.photo-container.has-photo .photo-buttons { display: flex !important; }
"""
# JavaScript met config
js = JAVASCRIPT.replace("{CONFIG_JSON}", json.dumps(js_config, ensure_ascii=False))
# Secties genereren
sections_html = ""
for section in config.sections:
sections_html += f'<section class="section">\n'
sections_html += f' <h2>{escape(section.name)}</h2>\n'
if section.description:
sections_html += f' <p class="section-desc">{escape(section.description)}</p>\n'
for field in section.fields:
sections_html += generate_field_html(field)
sections_html += '</section>\n'
# Logo HTML
logo_html = ""
if config.style.logo:
if config.style.logo.startswith("data:") or config.style.logo.startswith("http"):
logo_html = f'<img src="{config.style.logo}" alt="Logo" class="logo">'
else:
logo_html = f'<img src="{config.style.logo}" alt="Logo" class="logo">'
# Actions HTML
actions_html = '<div class="actions">\n'
if config.export.csv.enabled:
actions_html += ' <button type="button" class="btn btn-primary" onclick="exportCSV()">📥 Exporteer CSV</button>\n'
if config.export.mailto.enabled:
actions_html += ' <button type="button" class="btn btn-success" onclick="sendEmail(this)">📧 Verstuur per Email</button>\n'
actions_html += ' <button type="button" class="btn btn-danger" onclick="clearForm()">🗑️ Formulier Wissen</button>\n'
actions_html += '</div>\n'
# Complete HTML
html = f'''<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="EasySmartInventory v{config.version}">
<title>{escape(config.name)}</title>
<style>
{css}
</style>
</head>
<body>
<div class="container">
<header>
{logo_html}
<h1>{escape(config.name)}</h1>
<div class="version">Versie {config.version}</div>
</header>
<form id="inventory-form" onsubmit="return false;">
{sections_html}
{actions_html}
</form>
<div class="status-bar">
<span id="save-indicator" class="save-indicator">Gereed</span>
<span>EasySmartInventory v{config.version}</span>
</div>
</div>
<div id="toast" class="toast"></div>
<script>
{js}
</script>
</body>
</html>'''
return html
def main():
parser = argparse.ArgumentParser(
description="Genereer standalone HTML inventarisatieformulieren"
)
parser.add_argument("config", help="Pad naar YAML configuratiebestand")
parser.add_argument("-o", "--output", help="Output HTML bestand (default: <name>.html)")
parser.add_argument("-t", "--theme", choices=["modern", "corporate", "minimal"],
help="Override theme van YAML config")
args = parser.parse_args()
# Parse YAML
config_path = Path(args.config)
if not config_path.exists():
print(f"Error: Configuratiebestand niet gevonden: {config_path}")
sys.exit(1)
print(f"📖 Lezen configuratie: {config_path}")
config = parse_yaml(str(config_path))
# Valideer
errors = validate_config(config)
if errors:
print("❌ Validatie errors:")
for error in errors:
print(f" - {error}")
sys.exit(1)
# Override theme indien opgegeven
if args.theme:
config.style.theme = args.theme
# Output pad bepalen
if args.output:
output_path = Path(args.output)
else:
output_path = config_path.parent / f"{config.name}.html"
# Genereer HTML
print(f"⚙️ Genereren HTML met theme '{config.style.theme}'...")
html = generate_html(config)
# Schrijf bestand
output_path.write_text(html, encoding="utf-8")
print(f"✅ HTML gegenereerd: {output_path}")
print(f" Grootte: {len(html):,} bytes")
print(f" Secties: {len(config.sections)}")
total_fields = sum(len(s.fields) for s in config.sections)
print(f" Velden: {total_fields}")
if __name__ == "__main__":
main()

Powered by TurnKey Linux.