Files
filament/web/examples/serve.py
Powei Feng a73957a590 web: refactor examples and tutorials (#9833)
* Refactor web examples and tutorials

- Consolidate web/docs and web/samples into web/examples
- Remove literal programming blocks from Markdown tutorials
- Replace tutorial_template.html with a fully embedded template in serve.py
- Move serve.py to web/examples/serve.py and update dependency rules
- Update filament-js wrapper to expose _malloc, _free and set GEN_MIPMAPPABLE usage
- Update sample materials and WebGL asset generation within web/examples/CMakeLists.txt
- Add python venv instructions and usage details to web/README.md
- Remove obsolete examples, Pipfile, and demo templates

* Transfer web tutorials and samples to docs_src

- Introduce copy_web_docs.py api to deploy and rewrite web sample outputs into the docs output tree
- Hook copy_web_docs into docs_src/build/run.py natively
- Configure duplicates.json to map WebGL outputs to embedded .md docs
- Strip html skeleton out of examples in run.py so they map into markdown cleanly
- Update update-docs workflow to pre-build the WebGL target
- Remove obsolete remote docs path from src_raw
- Add explanatory comments to copy_web_docs.py and run.py
- Add index page with thumbnails for Web Tutorials and samples
2026-03-25 23:52:48 +00:00

162 lines
5.9 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (C) 2026 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import http.server
import socketserver
from pathlib import Path
try:
import mistletoe
from mistletoe.html_renderer import HTMLRenderer
except ImportError:
print("mistletoe is not installed. Please install it using `pip install mistletoe`.", file=sys.stderr)
sys.exit(1)
SCRIPT_DIR = Path(__file__).parent.resolve()
ROOT_DIR = SCRIPT_DIR.parent.parent
def find_examples_dir():
# search for filament.js in out/
out_dir = ROOT_DIR / 'out'
if out_dir.exists():
for root, dirs, files in os.walk(out_dir):
if 'filament.js' in files:
return root
return None
examples_dir = find_examples_dir()
if not examples_dir:
print("Could not find the built examples directory in out/. Please build the webgl target first.", file=sys.stderr)
# fallback to web/examples for testing, though it won't have the built assets
examples_dir = str(SCRIPT_DIR)
# Read template
HTML_TEMPLATE = """<!DOCTYPE html>
<html lang="en"><head>
<link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Tangerine:700|Inconsolata" rel="stylesheet">
<link href="../main.css" rel="stylesheet" type="text/css">
</head>
<body class="verbiage">
$BODY
</body>
</html>"""
class CustomHandler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=examples_dir, **kwargs)
def do_GET(self):
# Serve main index if root
if self.path == '/' or self.path == '/index.html':
self.serve_index()
return
# If it's a markdown file, render it
if self.path.endswith('.md'):
self.serve_markdown()
return
# Otherwise serve normally
super().do_GET()
def serve_index(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
# Generate a list of all samples and tutorials
html = ["<!DOCTYPE html><html><head><title>Filament Examples</title>"]
html.append("<style>body { font-family: sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; line-height: 1.6; } ")
html.append("h1 { border-bottom: 1px solid #eee; padding-bottom: 10px; } </style>")
html.append("</head><body><h1>Filament Examples and Tutorials</h1><ul>")
# Look for subdirectories in examples_dir
for item in sorted(os.listdir(examples_dir)):
item_path = os.path.join(examples_dir, item)
if os.path.isdir(item_path):
# check for .html or .md
entry_point = None
for file in os.listdir(item_path):
if file.endswith('.html') or file.endswith('.md'):
# Prefer markdown if multiple exist (tutorials have both usually)
if file.endswith('.md'):
entry_point = f"{item}/{file}"
break
entry_point = f"{item}/{file}"
if entry_point:
html.append(f"<li><a href='{entry_point}'>{item}</a></li>")
html.append("</ul></body></html>")
self.wfile.write("".join(html).encode('utf-8'))
def serve_markdown(self):
# path looks like /triangle/triangle.md
file_path = os.path.join(examples_dir, self.path.lstrip('/'))
if not os.path.exists(file_path):
self.send_error(404, "File not found")
return
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
with open(file_path, 'r') as f:
md_content = f.read()
import re
blocks = []
def repl(match):
blocks.append(match.group(0))
return f'<!-- BLOCK_{len(blocks)-1} -->'
md_content = re.sub(r'<script.*?</script>', repl, md_content, flags=re.DOTALL)
md_content = re.sub(r'<div class="demo_frame".*?</div>', repl, md_content, flags=re.DOTALL)
rendered_html = mistletoe.markdown(md_content, HTMLRenderer)
for i, block in enumerate(blocks):
rendered_html = rendered_html.replace(f'<!-- BLOCK_{i} -->', block)
# fix relative links, since we are serving at /dir/file.md but resources are at /dir/
# base url should just be the directory of the markdown
dir_name = os.path.dirname(self.path.lstrip('/'))
base_tag = f'<base href="/{dir_name}/" />'
# insert base tag into HEAD
if '<head>' in HTML_TEMPLATE:
final_html = HTML_TEMPLATE.replace('<head>', f'<head>\n{base_tag}')
else:
final_html = HTML_TEMPLATE
final_html = final_html.replace('$BODY', rendered_html)
self.wfile.write(final_html.encode('utf-8'))
if __name__ == '__main__':
port = 8000
print(f"Serving at http://localhost:{port}")
print(f"Serving directory: {examples_dir}")
with socketserver.TCPServer(("", port), CustomHandler) as httpd:
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nShutting down server.")