stitch2 0.24.2 → 0.24.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canvas.html +49 -7
- package/dist/shots.js +7 -2
- package/package.json +1 -1
package/dist/canvas.html
CHANGED
|
@@ -654,6 +654,33 @@ function measure(f) {
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
const minHeight = (s) => (bigFrame(s) ? 800 : 844)
|
|
657
|
+
/** Whether two addresses are the same page: the same place, whatever their query or hash say. */
|
|
658
|
+
function samePage(a, b) {
|
|
659
|
+
try { const x = new URL(a), y = new URL(b); return x.origin === y.origin && x.pathname === y.pathname } catch { return false }
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* A page keeps growing after it loads: a table fetches its rows, an image arrives, a font swaps in. The measurements at load and in the first
|
|
663
|
+
* seconds catch what is there by then; this watches the page itself, so the frame is as tall as its page whenever the page changes size. The
|
|
664
|
+
* observer belongs to the loaded document and is replaced when the page reloads.
|
|
665
|
+
*/
|
|
666
|
+
function watchSize(f, iframe) {
|
|
667
|
+
f.sizeWatch?.disconnect()
|
|
668
|
+
f.sizeWatch = null
|
|
669
|
+
try {
|
|
670
|
+
const doc = iframe.contentDocument
|
|
671
|
+
const Observer = iframe.contentWindow?.ResizeObserver
|
|
672
|
+
if (!doc || !Observer) return
|
|
673
|
+
let queued
|
|
674
|
+
const watch = new Observer(() => {
|
|
675
|
+
clearTimeout(queued)
|
|
676
|
+
// Wait for the page to settle: a table draws many rows in a few frames, and one measure after them is enough.
|
|
677
|
+
queued = setTimeout(() => { if (f.sizeWatch === watch && f.iframe === iframe) measure(f) }, 80)
|
|
678
|
+
})
|
|
679
|
+
watch.observe(doc.documentElement)
|
|
680
|
+
if (doc.body) watch.observe(doc.body)
|
|
681
|
+
f.sizeWatch = watch
|
|
682
|
+
} catch {}
|
|
683
|
+
}
|
|
657
684
|
function setHeight(f, h) {
|
|
658
685
|
h = Math.min(MAX_FRAME_HEIGHT, Math.max(minHeight(f.screen), h))
|
|
659
686
|
HEIGHTS[`${f.screen.path}@${f.screen.width}`] = h
|
|
@@ -677,6 +704,10 @@ const frameSrc = (s) => `/${s.path}?pick&canvas=${s.viewportHeight ?? 844}${lang
|
|
|
677
704
|
// The rest show the picture the server renders (/api/shot). Without pictures (no Chromium), frames in view stay
|
|
678
705
|
// live at any zoom, still at most MAX_LIVE. ----
|
|
679
706
|
const LIVE_ZOOM = 0.3, MAX_LIVE = 6, SHOTS_AT_ONCE = 3
|
|
707
|
+
// A picture costs the server a whole page render. A frame narrower than MIN_SHOT_PX on screen shows nothing you could read, so it is not
|
|
708
|
+
// asked for (fit-all with hundreds of screens would otherwise queue a render of every one, and again after every shared file or link change),
|
|
709
|
+
// and at most SHOTS_QUEUED pictures wait at a time, the nearest to the middle first; as they arrive the next ones are asked for.
|
|
710
|
+
const MIN_SHOT_PX = 64, SHOTS_QUEUED = 24
|
|
680
711
|
let shotRev = 0, shotErrors = 0, syncTimer
|
|
681
712
|
const shotsFailed = () => shotErrors > 2
|
|
682
713
|
const scheduleSync = () => { clearTimeout(syncTimer); syncTimer = setTimeout(syncLive, 120) }
|
|
@@ -708,7 +739,9 @@ function mount(f) {
|
|
|
708
739
|
// `load` also fires for the initial about:blank document. Compare the
|
|
709
740
|
// loaded document itself with the current iframe target (which may have
|
|
710
741
|
// changed for a language/theme reload while this mount was pending).
|
|
711
|
-
|
|
742
|
+
// The address is compared without its query: a page may rewrite its own
|
|
743
|
+
// (a table that keeps its state there), and that is still the same page.
|
|
744
|
+
return f.iframe === iframe && iframe.contentDocument === doc && doc.readyState === 'complete' && samePage(doc.location.href, iframe.src)
|
|
712
745
|
}
|
|
713
746
|
iframe.addEventListener('load', () => {
|
|
714
747
|
if (f.iframe !== iframe) return
|
|
@@ -727,6 +760,7 @@ function mount(f) {
|
|
|
727
760
|
}, 350)
|
|
728
761
|
setTimeout(() => { if (f.iframe === iframe) measure(f) }, 1200)
|
|
729
762
|
setTimeout(() => { if (f.iframe === iframe) measure(f) }, 2500)
|
|
763
|
+
watchSize(f, iframe)
|
|
730
764
|
})()
|
|
731
765
|
})
|
|
732
766
|
iframe.src = frameSrc(f.screen)
|
|
@@ -735,6 +769,8 @@ function mount(f) {
|
|
|
735
769
|
function unmount(f) {
|
|
736
770
|
if (!f.iframe) return
|
|
737
771
|
clearTimeout(f.reveal)
|
|
772
|
+
f.sizeWatch?.disconnect()
|
|
773
|
+
f.sizeWatch = null
|
|
738
774
|
// Cleared first: a page still loading can fire its load event as it's removed.
|
|
739
775
|
const iframe = f.iframe
|
|
740
776
|
f.iframe = null
|
|
@@ -787,7 +823,11 @@ function syncLive() {
|
|
|
787
823
|
// Keep the page until its picture is loaded; switching zoom must never reveal an empty frame.
|
|
788
824
|
for (const f of frames.values()) if (f.iframe && !live.has(f) && (!visible.has(f) || (f.img.complete && f.img.naturalWidth))) unmount(f)
|
|
789
825
|
for (const f of live) mount(f)
|
|
790
|
-
shotQueue = near
|
|
826
|
+
shotQueue = near
|
|
827
|
+
.filter(({ f }) => f.screen.width * view.z >= MIN_SHOT_PX)
|
|
828
|
+
.map(({ f }) => [f, shotUrl(f.screen)])
|
|
829
|
+
.filter(([f, url]) => f.img.getAttribute('src') !== url)
|
|
830
|
+
.slice(0, SHOTS_QUEUED)
|
|
791
831
|
pumpShots()
|
|
792
832
|
}
|
|
793
833
|
|
|
@@ -1168,12 +1208,14 @@ events.onmessage = async (e) => {
|
|
|
1168
1208
|
// (tokens.tailwind.css), the components.
|
|
1169
1209
|
const shared = paths.some((p) => /\.(css|js|mjs)$/.test(p) || p.startsWith('components/'))
|
|
1170
1210
|
if (shared) shotRev++
|
|
1171
|
-
for
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1211
|
+
// One reflow for all the frames that flash, not one each: approving a version can touch every screen that linked to it.
|
|
1212
|
+
const touched = [...frames.values()].filter((f) => shared || html.includes(f.screen.path))
|
|
1213
|
+
for (const f of touched) {
|
|
1214
|
+
f.iframe?.contentWindow?.location.reload()
|
|
1215
|
+
f.el.classList.remove('changed')
|
|
1176
1216
|
}
|
|
1217
|
+
void viewport.offsetWidth
|
|
1218
|
+
for (const f of touched) f.el.classList.add('changed')
|
|
1177
1219
|
scheduleSync()
|
|
1178
1220
|
}
|
|
1179
1221
|
|
package/dist/shots.js
CHANGED
|
@@ -31,8 +31,13 @@ function sharedStamp() {
|
|
|
31
31
|
if (depth < 4)
|
|
32
32
|
walk(join(dir, e.name), depth + 1);
|
|
33
33
|
}
|
|
34
|
-
else if (!e.name.endsWith('.html') && !(depth === 0 && REPORTS.has(e.name)))
|
|
35
|
-
|
|
34
|
+
else if (!e.name.endsWith('.html') && !(depth === 0 && REPORTS.has(e.name))) {
|
|
35
|
+
// A link to nothing (a symlinked folder that is not there on this machine) is not a file that can change a picture.
|
|
36
|
+
try {
|
|
37
|
+
newest = Math.max(newest, statSync(join(dir, e.name)).mtimeMs);
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
}
|
|
36
41
|
}
|
|
37
42
|
};
|
|
38
43
|
walk(ROOT, 0);
|
package/package.json
CHANGED