made tiny updates

This commit is contained in:
SinachPat
2026-05-05 22:06:31 +01:00
parent 19b8f29523
commit fdf64ee72c
39 changed files with 5499 additions and 381 deletions
+83 -2
View File
@@ -154,6 +154,10 @@ export function buildProxyFiberHookScript(): string {
var childrenStyleOverrides = {}; // parentNodeId -> { selector -> { property -> value } }
var overrideStyleEl = null; // the <style id="__om_overrides__"> element
// ── Snapshot / thumbnail capture state ───────────────────────────────────
var _snapshotGeneration = 0; // incremented on CANCEL_SNAPSHOT to invalidate in-flight captures
var _html2canvasLoading = null; // cached Promise<html2canvas> — only inject script once
// ── postMessage helper ────────────────────────────────────────────────────
function post(msg) {
try {
@@ -703,6 +707,62 @@ export function buildProxyFiberHookScript(): string {
}
}
break;
case 'CAPTURE_THUMBNAIL':
// Phase 0: capture the full page as a JPEG thumbnail via html2canvas.
// Sent when an artboard transitions Active → Far in the viewport culling system.
loadHtml2Canvas().then(function(h2c) {
return h2c(document.body, {
useCORS: true,
allowTaint: true,
logging: false,
scale: 0.5, // half-resolution thumbnail keeps payload small
imageTimeout: 4000,
});
}).then(function(canvas) {
post({ type: 'THUMBNAIL_READY', dataUrl: canvas.toDataURL('image/jpeg', 0.7) });
}).catch(function() {
post({ type: 'THUMBNAIL_READY', dataUrl: null });
});
break;
case 'UPDATE_ISOLATION_PROPS':
// Phase 0/3: update isolation artboard props and trigger a re-render.
// The isolation page exposes window.__OM_ISO_RENDER__() which calls
// ReactDOM.render / root.render with the new window.__OM_ISO_PROPS__.
if (msg.props && typeof msg.props === 'object') {
window.__OM_ISO_PROPS__ = msg.props;
if (typeof window.__OM_ISO_RENDER__ === 'function') {
try { window.__OM_ISO_RENDER__(); } catch(e) { /* renderer not yet mounted */ }
}
}
break;
case 'CANCEL_SNAPSHOT':
// Phase 4: invalidate any pending CAPTURE_SNAPSHOT by bumping the generation
// counter — any in-flight html2canvas call will see the mismatch and drop its result.
_snapshotGeneration += 1;
break;
case 'CAPTURE_SNAPSHOT':
// Phase 4: capture a PNG of the selected element, used by the Code Preview diff.
// Guards against stale results with a per-capture generation counter.
if (typeof msg.nodeId !== 'string') break;
_snapshotGeneration += 1;
(function(nodeId, gen) {
var sInfo = nodeMap[nodeId];
var sEl = sInfo ? findDomElement(sInfo.fiber) : null;
if (!sEl) {
post({ type: 'SNAPSHOT_READY', dataUrl: null, nodeId: nodeId });
return;
}
loadHtml2Canvas().then(function(h2c) {
if (_snapshotGeneration !== gen) return null;
return h2c(sEl, { useCORS: true, allowTaint: true, logging: false, timeout: 3000 });
}).then(function(canvas) {
if (!canvas || _snapshotGeneration !== gen) return;
post({ type: 'SNAPSHOT_READY', dataUrl: canvas.toDataURL('image/png'), nodeId: nodeId });
}).catch(function() {
if (_snapshotGeneration === gen) post({ type: 'SNAPSHOT_READY', dataUrl: null, nodeId: nodeId });
});
})(msg.nodeId, _snapshotGeneration);
break;
}
});
@@ -716,6 +776,24 @@ export function buildProxyFiberHookScript(): string {
}
}
// ── html2canvas lazy loader ───────────────────────────────────────────────
// html2canvas is not bundled in the fiber hook — inject from CDN on first
// need, caching the Promise so the script tag is added only once.
function loadHtml2Canvas() {
if (typeof window.html2canvas === 'function') {
return Promise.resolve(window.html2canvas);
}
if (_html2canvasLoading) return _html2canvasLoading;
_html2canvasLoading = new Promise(function(resolve, reject) {
var s = document.createElement('script');
s.src = 'https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js';
s.onload = function() { resolve(window.html2canvas); };
s.onerror = function() { _html2canvasLoading = null; reject(new Error('html2canvas load failed')); };
(document.head || document.documentElement).appendChild(s);
});
return _html2canvasLoading;
}
// SPA-safe navigation: push to history and fire popstate so framework
// routers (React Router, Next.js) pick up the route change.
function doNavigate(path) {
@@ -820,8 +898,11 @@ export function buildProxyFiberHookScript(): string {
if (routes.length > 0) post({ type: 'ROUTES_DISCOVERED', routes: routes });
}
// ── Ready signal ──────────────────────────────────────────────────────────
post({ type: 'READY' });
// ── Ready signal (includes root font size for rem→px normalisation) ────────
var rootFontSizePx = parseFloat(
window.getComputedStyle(document.documentElement).getPropertyValue('font-size') || '16'
) || 16;
post({ type: 'READY', rootFontSizePx: rootFontSizePx });
setTimeout(discoverRoutes, 800);
// Re-discover on SPA navigation (Next.js App Router fires popstate on push)
window.addEventListener('popstate', function() { setTimeout(discoverRoutes, 100); });
+22 -3
View File
@@ -46,7 +46,18 @@ export type HostMessage =
* Used for paragraph-spacing: patches margin-bottom on each direct <p> child. */
| { type: 'PATCH_CHILDREN_STYLE'; parentNodeId: string; selector: string; property: string; value: string }
/** Hide a component's DOM element (sets display:none). Non-destructive. */
| { type: 'REMOVE_ELEMENT'; nodeId: string };
| { type: 'REMOVE_ELEMENT'; nodeId: string }
/** Phase 0: Ask the renderer to capture a JPEG thumbnail via html2canvas and post THUMBNAIL_READY.
* Sent when an artboard transitions from Active → Near/Far in the viewport culling system. */
| { type: 'CAPTURE_THUMBNAIL' }
/** Phase 0: Re-render a component isolation artboard with new props (live preview, no code change).
* The iframe sets window.__OM_ISO_PROPS__ and calls window.__OM_ISO_RENDER__(). */
| { type: 'UPDATE_ISOLATION_PROPS'; props: Record<string, unknown> }
/** Phase 4: Ask the renderer to capture a PNG snapshot of the selected element via html2canvas.
* Sent when the user hovers a component > 200ms or clicks "Preview Code Change". */
| { type: 'CAPTURE_SNAPSHOT'; nodeId: string }
/** Phase 4: Cancel an in-flight snapshot capture (superseded by a newer request). */
| { type: 'CANCEL_SNAPSHOT' };
export interface HostEnvelope {
source: typeof HOST_SOURCE;
@@ -57,7 +68,10 @@ export interface HostEnvelope {
// ── Renderer → Host messages ──────────────────────────────────────────────────
export type RendererMessage =
| { type: 'READY' }
/** Phase 0/6: Sent once the fiber hook is initialised and the React runtime is detected.
* rootFontSizePx is read via getComputedStyle(document.documentElement).fontSize so the
* canvas can normalise rem values to px for token matching (Phase 6). */
| { type: 'READY'; rootFontSizePx?: number }
| { type: 'FIBER_TREE_UPDATE'; root: FiberNode }
| { type: 'COMPONENT_SELECTED'; nodeId: string; nodeName?: string; rect: DOMRectLike }
| { type: 'COMPONENT_DESELECTED' }
@@ -75,7 +89,12 @@ export type RendererMessage =
}
/** All discoverable routes found in the running app — sent once after READY
* and again after each SPA navigation. */
| { type: 'ROUTES_DISCOVERED'; routes: Array<{ path: string; label: string }> };
| { type: 'ROUTES_DISCOVERED'; routes: Array<{ path: string; label: string }> }
/** Phase 0: Response to CAPTURE_THUMBNAIL — base64 JPEG data URL, or null on failure.
* The canvas stores the data URL in Zustand and uploads to Supabase Storage. */
| { type: 'THUMBNAIL_READY'; dataUrl: string | null }
/** Phase 4: Response to CAPTURE_SNAPSHOT — base64 PNG of the selected element, or null. */
| { type: 'SNAPSHOT_READY'; dataUrl: string | null; nodeId: string };
export interface RendererEnvelope {
source: typeof RENDERER_SOURCE;