docs: refresh docs and screenshots, drop em dashes, add scripts/screenshots.sh
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0b9a16e8a0
commit
13f380291f
24 files changed
+213
-26
No files matched your search
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regera as capturas de tela de docs/img a partir da interface real do Guará.
|
||||
#
|
||||
# Dados: uma imagem FAT32 sintética com 72 fotos JPEG apagadas (EXIF falso de câmera, data e
|
||||
# GPS), gerada por scripts/make-photo-fixture.py e encontrada por uma varredura de verdade. A
|
||||
# lista de dispositivos do computador é substituída por um nome genérico. Nenhum dado real.
|
||||
#
|
||||
# scripts/screenshots.sh # grava em docs/img
|
||||
# OUT=/tmp/img scripts/screenshots.sh # grava em outro diretório
|
||||
#
|
||||
# Gera explorer-{dark,light}-{pt,en}, folders-light-{pt,en} e settings-language-{pt,en}.
|
||||
# Variáveis: OUT, GUARA_BIN (padrão target/release/guara; sempre recompilado se não for dado,
|
||||
# porque a interface vai embutida no binário), CHROME, PUPPETEER_CORE (padrões: Chrome do cache
|
||||
# do puppeteer e puppeteer-core em ~/.claude/tools/node_modules).
|
||||
# Requer: python3, ImageMagick (convert), mkfs.vfat e mtools (mmd, mcopy, mdel), node e Chrome.
|
||||
# Usa /tmp/test-photos.img (a imagem aparece com esse caminho na barra lateral das capturas).
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/.." && pwd)
|
||||
out=${OUT:-$root/docs/img}
|
||||
image=/tmp/test-photos.img
|
||||
|
||||
if [ -z "${GUARA_BIN:-}" ]; then
|
||||
(cd "$root" && cargo build --release -p guara-cli)
|
||||
GUARA_BIN=$root/target/release/guara
|
||||
fi
|
||||
mkdir -p "$out"
|
||||
python3 "$root/scripts/make-photo-fixture.py" "$image" 72
|
||||
trap 'rm -f "$image"' EXIT
|
||||
|
||||
node "$root/scripts/screenshots/shots.mjs" "$GUARA_BIN" "$image" "$out"
|
||||
echo "capturas gravadas em $out"
|
||||
@@ -0,0 +1,129 @@
|
||||
// Captura a interface do Guará (explorer, vista de pastas e configurações) nos temas
|
||||
// claro/escuro e nos idiomas pt/en. Chamado por scripts/screenshots.sh:
|
||||
// node shots.mjs <binário guara> <imagem de teste> <dir-saída>
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
import { homedir } from "node:os";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const [bin, image, out] = process.argv.slice(2);
|
||||
const core = process.env.PUPPETEER_CORE || `${homedir()}/.claude/tools/node_modules/puppeteer-core/lib/esm/puppeteer/puppeteer-core.js`;
|
||||
const { default: puppeteer } = await import(core);
|
||||
const chrome = process.env.CHROME || execFileSync("bash", ["-c", `ls -1d "$HOME"/.cache/puppeteer/chrome/*/chrome-linux64/chrome | sort -V | tail -1`], { encoding: "utf8" }).trim();
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
// Dispositivo fictício: a lista real do computador não deve aparecer nas capturas.
|
||||
const FAKE_DEVICES = [{ path: "/dev/nvme0n1", name: "SSD interno", kind: "NVMe", size: 954 * 1024 ** 3, readable: false, mounted_at: [], removable: false }];
|
||||
const LABEL = "Pendrive 64 GB";
|
||||
|
||||
const variants = [
|
||||
{ theme: "dark", lang: "pt" }, { theme: "dark", lang: "en" },
|
||||
{ theme: "light", lang: "pt" }, { theme: "light", lang: "en" },
|
||||
];
|
||||
|
||||
// Sobe `guara ui` com config isolada (sessões e ajustes próprios) e devolve a URL com o token.
|
||||
async function startServer(theme, lang) {
|
||||
const cfg = mkdtempSync(join(tmpdir(), "guara-shots-"));
|
||||
mkdirSync(cfg, { recursive: true });
|
||||
writeFileSync(join(cfg, "settings.json"), JSON.stringify({ theme, lang, notify: true, maps: false, updates: false }));
|
||||
const child = spawn(bin, ["ui", "--no-open", "--port", "0"], { env: { ...process.env, GUARA_CONFIG_DIR: cfg }, stdio: ["ignore", "ignore", "pipe"] });
|
||||
const url = await new Promise((resolve, reject) => {
|
||||
let buf = "";
|
||||
child.stderr.on("data", (d) => { buf += d; const m = buf.match(/http:\/\/\S+/); if (m) resolve(m[0]); });
|
||||
child.on("exit", () => reject(new Error("guara ui saiu antes de subir")));
|
||||
setTimeout(() => reject(new Error("timeout subindo guara ui")), 20000);
|
||||
});
|
||||
return { url, stop: () => { child.kill(); rmSync(cfg, { recursive: true, force: true }); } };
|
||||
}
|
||||
|
||||
async function newPage(browser, url, w = 1600, h = 940) {
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: w, height: h, deviceScaleFactor: 1 });
|
||||
await page.setRequestInterception(true);
|
||||
page.on("request", (req) => {
|
||||
if (req.url().includes("/rpc/devices")) {
|
||||
req.respond({ status: 200, contentType: "application/json", body: JSON.stringify(FAKE_DEVICES) });
|
||||
} else req.continue();
|
||||
});
|
||||
await page.goto(url, { waitUntil: "networkidle0" });
|
||||
await sleep(1200);
|
||||
return page;
|
||||
}
|
||||
|
||||
// Abre a imagem de teste, faz a varredura profunda e espera terminar.
|
||||
async function scanImage(page) {
|
||||
await page.click("#openImageBtn");
|
||||
await sleep(400);
|
||||
await page.type("#dlg input[name=v]", image);
|
||||
await page.click("#dlg button[value=ok]");
|
||||
await sleep(1200);
|
||||
await page.click("#goDeep");
|
||||
await page.waitForFunction(() => document.querySelector(".xp-tile, .xp-lrow"), { timeout: 120000 });
|
||||
await page.waitForFunction(() => !document.querySelector("#progressArea .progress-card"), { timeout: 120000 });
|
||||
await sleep(2500); // miniaturas e autosave da sessão
|
||||
}
|
||||
|
||||
const relabel = (page) => page.evaluate((t) => {
|
||||
const sub = document.querySelector("#scanSub"); if (sub) sub.innerHTML = `<span class="mono">${t}</span>`;
|
||||
}, LABEL);
|
||||
|
||||
const browser = await puppeteer.launch({ executablePath: chrome, args: ["--no-sandbox"] });
|
||||
try {
|
||||
for (const { theme, lang } of variants) {
|
||||
const srv = await startServer(theme, lang);
|
||||
try {
|
||||
// 1ª rodada: só para deixar uma sessão salva em "Recentes" (a 2ª rodada cria a outra).
|
||||
let page = await newPage(browser, srv.url);
|
||||
await scanImage(page);
|
||||
await page.close();
|
||||
|
||||
page = await newPage(browser, srv.url);
|
||||
await page.evaluate(() => { try { localStorage.setItem("guara-view", "grid"); } catch {} });
|
||||
await page.reload({ waitUntil: "networkidle0" });
|
||||
await sleep(1000);
|
||||
await scanImage(page);
|
||||
await relabel(page);
|
||||
|
||||
// Explorador em grade, com a primeira foto marcada.
|
||||
const chk = await page.$(".xp-tile .xp-chk");
|
||||
if (chk) await chk.click(); else await (await page.$(".xp-tile")).click();
|
||||
await sleep(800);
|
||||
await page.screenshot({ path: `${out}/explorer-${theme}-${lang}.png` });
|
||||
console.log("ok", `explorer-${theme}-${lang}`);
|
||||
|
||||
if (theme === "light") {
|
||||
// Vista de pastas: pasta DCIM marcada e selecionada.
|
||||
await page.click('button[data-view="folders"]');
|
||||
await sleep(1000);
|
||||
const dcim = await page.$('.xp-lrow.xp-dir[data-dir="0"]');
|
||||
await (await dcim.$(".xp-chk")).click();
|
||||
await sleep(400);
|
||||
await dcim.click({ offset: { x: 200, y: 20 } });
|
||||
await sleep(800);
|
||||
await relabel(page);
|
||||
await page.screenshot({ path: `${out}/folders-${theme}-${lang}.png` });
|
||||
console.log("ok", `folders-${theme}-${lang}`);
|
||||
await page.click('button[data-view="grid"]');
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
if (theme === "light") {
|
||||
// Diálogo de configurações, recortado com a moldura borrada ao redor.
|
||||
await page.click("#settingsBtn");
|
||||
await sleep(900);
|
||||
const box = await (await page.$("#dlg")).boundingBox();
|
||||
const pad = 24;
|
||||
await page.screenshot({
|
||||
path: `${out}/settings-language-${lang}.png`,
|
||||
clip: { x: Math.max(0, box.x - pad), y: Math.max(0, box.y - pad), width: box.width + 2 * pad, height: box.height + 2 * pad },
|
||||
});
|
||||
console.log("ok", `settings-language-${lang}`);
|
||||
}
|
||||
} finally {
|
||||
srv.stop();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
Reference in new issue
Block a user