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
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()
|