oxedyne/daimond/dev/shot_stack.mjs
5.0 KiB, 1 run
created by r2519314175:193, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // shot_stack.mjs — the divider between two panels stacked in a dock column. |
| 2 | // |
| 3 | // The affordance is CSS-drawn (a 2px rule inside a 16px strip that stands in for |
| 4 | // the column's gap), so the only way to know it is right is to look at it at ink |
| 5 | // level. Four states, each cropped to the handle and blown up 8x: |
| 6 | // |
| 7 | // rest — nothing. Two cards with a gap between them, as before. |
| 8 | // hover — the accent rule, the same answer .phandle gives on the vertical edges. |
| 9 | // drag — held, so the rule stays lit while the boundary moves. |
| 10 | // after — the tuned boundary, reloaded from the saved layout. |
| 11 | // |
| 12 | // Plus the whole dock mid-drag, the same after a reload, and the phone, which |
| 13 | // must draw no divider at all. |
| 14 | // |
| 15 | // node dev/shot_stack.mjs |
| 16 | // |
| 17 | // Needs dev/serve.mjs (DAIMOND_PORT, default 8777). No gateway. |
| 18 | |
| 19 | import { open, signInAs } from './harness.mjs'; |
| 20 | import { execFileSync } from 'node:child_process'; |
| 21 | import path from 'node:path'; |
| 22 | import fs from 'node:fs'; |
| 23 | |
| 24 | const OUT = path.join(process.cwd(), 'dev', 'shots'); |
| 25 | fs.mkdirSync(OUT, { recursive: true }); |
| 26 | |
| 27 | const s = await open({ name: 'stackshot', connect: false }); |
| 28 | const p = s.page; |
| 29 | await p.waitForTimeout(1500); |
| 30 | await p.setViewportSize({ width: 1500, height: 950 }); |
| 31 | |
| 32 | await p.evaluate(() => { |
| 33 | DaimondPanels.setGrid('auto'); |
| 34 | DaimondPanels.panels().filter((x) => x.zone === 'dock').forEach((x) => DaimondPanels.hide(x.id)); |
| 35 | ['work', 'mail'].forEach((id) => DaimondPanels.show(id)); |
| 36 | }); |
| 37 | await p.waitForTimeout(600); |
| 38 | |
| 39 | /// Where the divider is, in page coordinates. |
| 40 | const box = () => p.evaluate(() => { |
| 41 | const h = document.querySelector('#dock .hstack'); |
| 42 | if (!h) return null; |
| 43 | const r = h.getBoundingClientRect(); |
| 44 | return { x: r.x, y: r.y, width: r.width, height: r.height }; |
| 45 | }); |
| 46 | |
| 47 | /// The handle and a little of the cards either side, at eight times size. |
| 48 | const ink = async (name) => { |
| 49 | const b = await box(); |
| 50 | if (!b) { console.log(` ${name}: no divider on screen`); return; } |
| 51 | const clip = { x: Math.round(b.x), y: Math.round(b.y - 12), width: Math.round(b.width), height: Math.round(b.height + 24) }; |
| 52 | const raw = path.join(OUT, `stack-${name}-raw.png`); |
| 53 | await p.screenshot({ path: raw, clip, timeout: 8000 }); |
| 54 | execFileSync('magick', [raw, '-filter', 'point', '-resize', '800%', path.join(OUT, `stack-${name}-8x.png`)]); |
| 55 | fs.unlinkSync(raw); |
| 56 | console.log(` stack-${name}-8x.png ${clip.width}x${clip.height} at 8x`); |
| 57 | }; |
| 58 | |
| 59 | // Rest. |
| 60 | await ink('rest'); |
| 61 | |
| 62 | // Hover. |
| 63 | { |
| 64 | const b = await box(); |
| 65 | await p.mouse.move(b.x + b.width / 2, b.y + b.height / 2); |
| 66 | await p.waitForTimeout(250); |
| 67 | await ink('hover'); |
| 68 | } |
| 69 | |
| 70 | // Held mid-drag: the pointer events are dispatched (headless Chromium drops |
| 71 | // mouse moves after a mousedown on a dock handle), and NOT released, so the |
| 72 | // lit state is what the screenshot catches. |
| 73 | { |
| 74 | await p.evaluate(() => { |
| 75 | const h = document.querySelector('#dock .hstack'); |
| 76 | const r = h.getBoundingClientRect(); |
| 77 | const x = Math.round(r.left + r.width / 2), y = Math.round(r.top + r.height / 2); |
| 78 | Element.prototype.setPointerCapture = function () {}; |
| 79 | Element.prototype.releasePointerCapture = function () {}; |
| 80 | const fire = (type, cy) => h.dispatchEvent(new PointerEvent(type, { |
| 81 | bubbles: true, cancelable: true, pointerId: 1, isPrimary: true, clientX: x, clientY: cy, |
| 82 | })); |
| 83 | fire('pointerdown', y); |
| 84 | for (let k = 1; k <= 10; k++) fire('pointermove', y + Math.round(140 * k / 10)); |
| 85 | }); |
| 86 | await p.waitForTimeout(200); |
| 87 | await ink('drag'); |
| 88 | await p.screenshot({ path: path.join(OUT, 'stack-mid-drag.png'), timeout: 8000 }); |
| 89 | await p.evaluate(() => { |
| 90 | const h = document.querySelector('#dock .hstack'); |
| 91 | const r = h.getBoundingClientRect(); |
| 92 | h.dispatchEvent(new PointerEvent('pointerup', { |
| 93 | bubbles: true, cancelable: true, pointerId: 1, isPrimary: true, |
| 94 | clientX: Math.round(r.left + r.width / 2), clientY: Math.round(r.top + r.height / 2), |
| 95 | })); |
| 96 | }); |
| 97 | await p.waitForTimeout(200); |
| 98 | } |
| 99 | |
| 100 | // The same boundary, built again from the markup. |
| 101 | await p.reload({ waitUntil: 'domcontentloaded' }); |
| 102 | await signInAs(s, 'stackshot'); |
| 103 | await p.waitForTimeout(2500); |
| 104 | await p.screenshot({ path: path.join(OUT, 'stack-after-reload.png'), timeout: 8000 }); |
| 105 | await ink('after'); |
| 106 | console.log(' heights after reload: ' + await p.evaluate(() => |
| 107 | [...document.querySelectorAll('#dock-a > .panel')].filter((k) => k.getClientRects().length) |
| 108 | .map((k) => k.dataset.panel + ' ' + Math.round(k.getBoundingClientRect().height)).join(', '))); |
| 109 | |
| 110 | // The phone, where there is no boundary to hold. |
| 111 | await p.setViewportSize({ width: 390, height: 780 }); |
| 112 | await p.evaluate(() => DaimondPanels.reflow()); |
| 113 | await p.waitForTimeout(600); |
| 114 | // On the Email destination, which is one of the two panels stacked on the |
| 115 | // desktop: full width, full height, and nothing above it to drag. |
| 116 | await p.evaluate(() => { |
| 117 | const x = document.getElementById('admin-close'); |
| 118 | if (x) x.click(); |
| 119 | DaimondPanels.show('mail'); |
| 120 | }); |
| 121 | await p.waitForTimeout(600); |
| 122 | await p.screenshot({ path: path.join(OUT, 'stack-phone.png'), timeout: 8000 }); |
| 123 | console.log(' dividers drawn on the phone: ' + await p.evaluate(() => |
| 124 | [...document.querySelectorAll('.hstack')].filter((h) => h.getClientRects().length).length)); |
| 125 | |
| 126 | await s.close(); |