103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Build a single self-contained HTML file from the source files.
|
|
|
|
Usage:
|
|
python3 build.py [output.html]
|
|
|
|
If output is not given, defaults to 'background-standalone.html'.
|
|
|
|
What gets inlined:
|
|
- config.js, i18n.js (plain <script> swap)
|
|
- vendor/suncalc.js, vendor/clouds3d.js (plain <script> swap)
|
|
- vendor/stars.json (window.__starsData)
|
|
- every theme CSS referenced by window.THEMES
|
|
(window.__inlineThemeCss,
|
|
so theme switching works
|
|
without any files)
|
|
|
|
The result needs NO sibling files. Note that runtime DATA is still
|
|
fetched when the page loads: basemap/elevation tiles (via the nginx
|
|
CORS proxies of the Docker image), Overpass road + place data and the
|
|
Open-Meteo weather API. Serve the standalone file from the same nginx
|
|
(or a warm IndexedDB cache = fully offline) for the full experience;
|
|
opened via file:// it still runs sky, clouds and the whole UI, just
|
|
without the textured ground.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def read(path):
|
|
with open(os.path.join(ROOT, path), 'r', encoding='utf-8') as f:
|
|
return f.read()
|
|
|
|
|
|
def guard(s):
|
|
# a literal </script> inside an inline block would end it early
|
|
return s.replace('</', '<\\/')
|
|
|
|
|
|
def inline_script(html, tag, body):
|
|
old = '<script src="%s"></script>' % tag
|
|
new = '<script>\n%s\n</script>' % body
|
|
if old not in html:
|
|
raise SystemExit('build.py: expected tag not found: ' + tag)
|
|
return html.replace(old, new, 1)
|
|
|
|
|
|
def parse_themes(config_src):
|
|
m = re.search(r'window\.THEMES\s*=\s*\{(.*?)\}', config_src, re.S)
|
|
if not m:
|
|
raise SystemExit('build.py: window.THEMES not found in config.js')
|
|
return dict(re.findall(r"'([^']+)'\s*:\s*'([^']+\.css)'", m.group(1)))
|
|
|
|
|
|
def main():
|
|
out = sys.argv[1] if len(sys.argv) > 1 else 'background-standalone.html'
|
|
html = read('background.html')
|
|
|
|
config_src = read('config.js')
|
|
themes = parse_themes(config_src)
|
|
|
|
# embedded data preamble (before clouds3d's own scripts run)
|
|
stars = json.loads(read('vendor/stars.json'))
|
|
theme_css = {name: read(fname) for name, fname in themes.items()}
|
|
embed = (
|
|
'<script>\n'
|
|
'window.__starsData = %s;\n'
|
|
'window.__inlineThemeCss = %s;\n'
|
|
'</script>\n'
|
|
% (json.dumps(stars, separators=(',', ':')),
|
|
json.dumps(theme_css))
|
|
)
|
|
anchor = '<script src="vendor/suncalc.js"></script>'
|
|
html = html.replace(anchor, embed + anchor, 1)
|
|
|
|
html = inline_script(html, 'config.js', read('config.js'))
|
|
html = inline_script(html, 'i18n.js', read('i18n.js'))
|
|
html = inline_script(html, 'vendor/suncalc.js', read('vendor/suncalc.js'))
|
|
html = inline_script(html, 'vendor/plane3d.js', read('vendor/plane3d.js'))
|
|
html = inline_script(html, 'vendor/clouds3d.js', read('vendor/clouds3d.js'))
|
|
|
|
# the default <link id="theme-css" href="ash.css"> stays but is
|
|
# disabled by the inline-theme loader; keep a guarded fallback anyway
|
|
html = html.replace('<link id="theme-css"', '<!-- standalone: themes embedded -->\n <link id="theme-css"', 1)
|
|
|
|
with open(os.path.join(ROOT, out), 'w', encoding='utf-8') as f:
|
|
f.write(html)
|
|
|
|
print('Wrote %s (%d bytes)' % (out, len(html)))
|
|
print('Inlined: config.js, i18n.js, suncalc.js, clouds3d.js,')
|
|
print(' stars.json (%d entries), themes: %s'
|
|
% (len(stars), ', '.join(themes)))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|