'use strict'; // Runs the real Ink app against a seeded demo vault and saves 16:10 screenshots. // electron scripts/capture/capture.cjs --ozone-platform=headless --force-device-scale-factor=1.5 // Env: INK_APP (path to the Ink repo, read only), OUT (output dir), THEMES (comma list), SCENES (comma list) const { app, BrowserWindow } = require('electron'); const path = require('node:path'); const os = require('node:os'); const fs = require('node:fs'); const INK = process.env.INK_APP || path.resolve(__dirname, '../../../../App/Ink'); const OUT = process.env.OUT || path.resolve(__dirname, '../../src/assets/screens'); const THEMES = (process.env.THEMES || 'dark,light').split(','); const ONLY = process.env.SCENES ? process.env.SCENES.split(',') : null; const { seedVault } = require('./seed-vault.cjs'); const scenes = require('./scenes.cjs'); const W = 1600, H = 1000; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const theme = process.env.THEME || THEMES[0]; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-shots-')); const vault = path.join(tmp, 'Notes'); fs.mkdirSync(vault); const seeded = seedVault(vault); // No network: a link card gets a fixed title and description instead of fetching. const linkpreview = require(path.join(INK, 'electron/linkpreview.cjs')); linkpreview.hooks.lookup = async () => [{ address: '93.184.216.34' }]; linkpreview.hooks.fetch = async () => new Response( 'JSON Canvas' + '', { status: 200, headers: { 'content-type': 'text/html' } }); app.setPath('userData', tmp); fs.writeFileSync(path.join(tmp, 'settings.json'), JSON.stringify({ vault, lastPage: seeded.lastPage, theme, bounds: { width: W, height: H }, fontSize: 16, outline: false, sidebarCollapsed: false, tagsOpen: true, vaults: { [vault]: seeded.vaultSettings }, })); app.commandLine.appendSwitch('lang', 'en-US'); require(path.join(INK, 'electron/main.cjs')); app.whenReady().then(async () => { let win; for (let i = 0; i < 100 && !(win = BrowserWindow.getAllWindows()[0]); i++) await sleep(100); win.setContentSize(W, H); const wc = win.webContents; if (wc.isLoading()) await new Promise((r) => wc.once('did-finish-load', r)); const js = (code) => wc.executeJavaScript(`(async () => { ${code} })()`); const key = async (keyCode, modifiers = []) => { wc.sendInputEvent({ type: 'keyDown', keyCode, modifiers }); wc.sendInputEvent({ type: 'keyUp', keyCode, modifiers }); await sleep(250); }; const type = async (t, d = 45) => { for (const ch of t) { wc.sendInputEvent({ type: 'char', keyCode: ch }); await sleep(d); } await sleep(200); }; const click = async (x, y) => { wc.sendInputEvent({ type: 'mouseMove', x, y }); wc.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount: 1 }); wc.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount: 1 }); await sleep(250); }; const shot = async (name) => { await sleep(500); const img = await wc.capturePage(); const file = path.join(OUT, `${name}-${theme}.png`); fs.mkdirSync(OUT, { recursive: true }); fs.writeFileSync(file, img.toPNG()); const s = img.getSize(); console.log(`saved ${path.basename(file)} ${s.width}x${s.height}`); }; const ctx = { win, wc, js, key, type, click, shot, sleep, vault, W, H }; try { await sleep(2200); for (const [name, fn] of Object.entries(scenes)) { if (ONLY && !ONLY.includes(name)) continue; try { await fn(ctx); } catch (e) { console.log(`FAILED ${name}: ${e.message}`); } } } finally { app.exit(0); } });