"""Regenerates docs/img/* from base.html with Playwright (demo data only). Setup: pip install playwright pillow && playwright install chromium Serve: mkdir /tmp/wt && cd /tmp/wt && ln -s web_template && ln -s /base.html base.html \ && python3 -m http.server 8100 Run: python3 scripts/capture-screenshots.py """ # flake8: noqa: E501 (demo data and canned output contain long literal lines) import asyncio import io from PIL import Image from playwright.async_api import async_playwright import pathlib OUT = str(pathlib.Path(__file__).resolve().parent.parent / "docs" / "img") URL = "http://127.0.0.1:8100/base.html" # serve so that /web_template/base.css and /base.html both resolve TOP = ( "document.activeElement&&document.activeElement.blur();" "document.documentElement.style.scrollBehavior='auto';window.scrollTo(0,0)" ) HIDE = "document.addEventListener('DOMContentLoaded',()=>{document.querySelectorAll('div').forEach(d=>{if(d.style.position==='fixed'&&d.style.bottom==='1rem'&&d.style.left==='1rem')d.style.display='none'})})" async def hdr(pg, text): h = pg.locator(".lt-section-header", has_text=text).first return h.locator("xpath=..") if await h.count() else None async def theme_run(theme): async with async_playwright() as p: b = await p.chromium.launch() ctx = await b.new_context(viewport={"width": 1440, "height": 900}) await ctx.add_init_script(HIDE) await ctx.add_init_script(f"try{{localStorage.setItem('lt_theme','{theme}')}}catch(e){{}}") pg = await ctx.new_page() await pg.goto(URL) await pg.wait_for_timeout(5000) await pg.evaluate(TOP) await pg.wait_for_timeout(500) await pg.screenshot( path=f"{OUT}/{theme}-dashboard.png", full_page=True, clip={"x": 0, "y": 0, "width": 1440, "height": 900} ) if theme == "dark": async def clipsec(text, name, extra=None, fixed=None): h = pg.locator(".lt-section-header", has_text=text).first if not await h.count(): print("missing", text) return await h.scroll_into_view_if_needed() hb = await h.bounding_box() sy = await pg.evaluate("window.scrollY") top = hb["y"] + sy if fixed: bot = top + fixed else: body = h.locator("xpath=following-sibling::*[1]") bb = await body.bounding_box() bot = bb["y"] + sy + bb["height"] await pg.screenshot( path=f"{OUT}/{name}.png", full_page=True, clip={"x": 0, "y": top - 4, "width": 1440, "height": bot - top + 8}, ) print("ok", name) await pg.evaluate(TOP) await clipsec("Ticket Queue", "ticket-queue") await clipsec("Component Reference", "components", fixed=520) await clipsec("Alert Banners", "alerts") await clipsec("SLA Banners", "sla-banners") await clipsec("Data Grid", "data-grid") await clipsec("Progress Bars", "progress-bars") await pg.evaluate(TOP) await pg.wait_for_timeout(300) kb = pg.get_by_text("Kanban", exact=False).first try: await kb.click(timeout=3000) await pg.wait_for_timeout(900) await pg.evaluate(TOP) await pg.wait_for_timeout(500) await pg.screenshot( path=f"{OUT}/kanban.png", full_page=True, clip={"x": 0, "y": 0, "width": 1440, "height": 900} ) print("ok kanban") except Exception as e: print("kanban fail", str(e)[:80]) await pg.evaluate(TOP) await pg.keyboard.press("Control+k") await pg.wait_for_timeout(500) await pg.keyboard.type("ti", delay=80) await pg.wait_for_timeout(500) await pg.screenshot( path=f"{OUT}/command-palette.png", clip={"x": 320, "y": 60, "width": 800, "height": 420} ) await b.close() async def gif(): frames = [] async with async_playwright() as p: b = await p.chromium.launch() ctx = await b.new_context(viewport={"width": 1280, "height": 720}) await ctx.add_init_script(HIDE) await ctx.add_init_script("try{localStorage.setItem('lt_theme','dark')}catch(e){}") pg = await ctx.new_page() await pg.goto(URL) await pg.wait_for_timeout(1200) async def snap(n=1, ms=0): if ms: await pg.wait_for_timeout(ms) im = Image.open(io.BytesIO(await pg.screenshot())).convert("RGB").resize((960, 540), Image.LANCZOS) for _ in range(n): frames.append(im) await snap(2, 300) # boot sequence await snap(2, 1200) await snap(1, 1500) await pg.evaluate(TOP) await pg.wait_for_timeout(600) await snap(3) await pg.keyboard.press("Control+k") await snap(2, 500) for ch in "new": await pg.keyboard.type(ch) await snap(1, 250) await snap(3, 300) await pg.keyboard.press("Enter") await snap(3, 600) # toast await pg.wait_for_timeout(1500) await pg.evaluate("lt.theme.set('light')") await snap(3, 600) await pg.evaluate("lt.theme.set('dark')") await snap(2, 600) l = await hdr(pg, "Component Reference") if l: await l.scroll_into_view_if_needed() await snap(3, 500) for y in (500, 1000, 1500): await pg.mouse.wheel(0, 500) await snap(2, 500) await b.close() pal = [f.quantize(colors=96, method=Image.MEDIANCUT, dither=Image.NONE) for f in frames] pal[0].save(f"{OUT}/demo.gif", save_all=True, append_images=pal[1:], duration=450, loop=0, optimize=True) print("gif frames", len(frames)) async def main(): await theme_run("dark") await theme_run("light") await gif() asyncio.run(main())