docs: скриншоты панели (9 разделов) + tools/shots_ui.py для пересъёмки; README — раздел «Скриншоты»
This commit is contained in:
78
tools/shots_ui.py
Normal file
78
tools/shots_ui.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Пересъёмка скриншотов панели в docs/screenshots (нужен playwright + chromium).
|
||||
|
||||
python -m omni serve --port 8077 # в одном окне
|
||||
python tools/shots_ui.py # в другом
|
||||
|
||||
Каждый маршрут снимается только после того, как пропали скелетоны,
|
||||
иначе в кадр попадает «Загрузка находок...». Ресайз до 1600px по ширине —
|
||||
картинки для README, а не для печати.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import pathlib
|
||||
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
ROUTES = ["feed", "abuse", "sources", "tg", "watch", "digest", "overview", "runs", "keys"]
|
||||
SKELETONS = (
|
||||
"() => document.querySelectorAll("
|
||||
"'[class*=skeleton],[class*=placeholder],[class*=spinner]').length"
|
||||
)
|
||||
|
||||
|
||||
def resize(path: pathlib.Path, width: int = 1600) -> tuple[int, int]:
|
||||
from PIL import Image
|
||||
|
||||
im = Image.open(path)
|
||||
if im.width > width:
|
||||
im = im.resize((width, round(im.height * width / im.width)), Image.LANCZOS)
|
||||
im.save(path, "PNG", optimize=True)
|
||||
return im.size
|
||||
|
||||
|
||||
async def shoot(base: str, out: pathlib.Path, only: list[str], scale: int) -> None:
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(
|
||||
headless=True,
|
||||
args=["--no-sandbox", "--disable-gpu", "--hide-scrollbars",
|
||||
"--force-color-profile=srgb"],
|
||||
)
|
||||
ctx = await browser.new_context(
|
||||
viewport={"width": 1600, "height": 1000},
|
||||
device_scale_factor=scale,
|
||||
locale="ru-RU",
|
||||
)
|
||||
page = await ctx.new_page()
|
||||
for route in only:
|
||||
await page.goto(f"{base}/#/{route}", wait_until="networkidle", timeout=90000)
|
||||
clean = False
|
||||
for _ in range(30):
|
||||
await page.wait_for_timeout(1000)
|
||||
if await page.evaluate(SKELETONS) == 0:
|
||||
await page.wait_for_timeout(1500)
|
||||
if await page.evaluate(SKELETONS) == 0:
|
||||
clean = True
|
||||
break
|
||||
target = out / f"{route}.png"
|
||||
await page.screenshot(path=str(target))
|
||||
size = resize(target)
|
||||
print(f"{route:9s} clean={clean} -> {size} {target.stat().st_size} B")
|
||||
await browser.close()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--base", default="http://127.0.0.1:8077")
|
||||
ap.add_argument("--out", default="docs/screenshots")
|
||||
ap.add_argument("--only", default="", help="через запятую: feed,abuse")
|
||||
ap.add_argument("--scale", type=int, default=2, help="device scale factor")
|
||||
a = ap.parse_args()
|
||||
only = [r.strip() for r in a.only.split(",") if r.strip()] or ROUTES
|
||||
asyncio.run(shoot(a.base, pathlib.Path(a.out), only, a.scale))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user