@ingcreators/annot-product-docs-astro 0.1.0 → 0.2.1
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/CHANGELOG.md +303 -0
- package/README.md +37 -1
- package/dist/cache.d.ts +46 -0
- package/dist/components/HistoryEntry.astro +34 -0
- package/dist/components/Overlay.astro +50 -0
- package/dist/components/Screen.astro +27 -0
- package/dist/components/ScreenList.astro +58 -0
- package/dist/components/Transition.astro +48 -0
- package/dist/components/TransitionGraph.astro +46 -0
- package/dist/components/TransitionTable.astro +55 -0
- package/dist/components/types.d.ts +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +18 -0
- package/dist/integration.d.ts +46 -0
- package/dist/playwright/fixture.d.ts +72 -0
- package/dist/playwright/index.d.ts +5 -0
- package/dist/playwright/index.js +225 -0
- package/dist/playwright/rebase.d.ts +36 -0
- package/dist/render-BSQOeqPB.js +164 -0
- package/dist/render.d.ts +129 -0
- package/package.json +20 -8
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
// `<TransitionTable>` — tabular list of every `<Transition>`
|
|
3
|
+
// on the page. Pass an array of transition descriptors as the
|
|
4
|
+
// `entries` prop; the table renders them with one row per
|
|
5
|
+
// transition. The reader sees "trigger / event / target" as
|
|
6
|
+
// columns plus a body cell with the prose.
|
|
7
|
+
//
|
|
8
|
+
// MDX usage:
|
|
9
|
+
// <TransitionTable
|
|
10
|
+
// entries={[
|
|
11
|
+
// { trigger: "Sign in", on: "click", to: "dashboard", body: "..." },
|
|
12
|
+
// { trigger: "Sign up", on: "click", to: "signup", body: "..." },
|
|
13
|
+
// ]}
|
|
14
|
+
// />
|
|
15
|
+
//
|
|
16
|
+
// The companion `<Transition>` component is the inline variant
|
|
17
|
+
// for free-flowing prose. Both compile to data attributes the
|
|
18
|
+
// reader CSS can style consistently.
|
|
19
|
+
|
|
20
|
+
export interface TransitionEntry {
|
|
21
|
+
trigger: string;
|
|
22
|
+
on?: string;
|
|
23
|
+
to?: string;
|
|
24
|
+
body?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
entries: TransitionEntry[];
|
|
29
|
+
}
|
|
30
|
+
const { entries } = Astro.props;
|
|
31
|
+
---
|
|
32
|
+
<table class="annot-transition-table">
|
|
33
|
+
<thead>
|
|
34
|
+
<tr>
|
|
35
|
+
<th>Trigger</th>
|
|
36
|
+
<th>Event</th>
|
|
37
|
+
<th>Target</th>
|
|
38
|
+
<th>Notes</th>
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
<tbody>
|
|
42
|
+
{entries.map((entry) => (
|
|
43
|
+
<tr
|
|
44
|
+
data-trigger={entry.trigger}
|
|
45
|
+
data-on={entry.on}
|
|
46
|
+
data-to={entry.to}
|
|
47
|
+
>
|
|
48
|
+
<td>{entry.trigger}</td>
|
|
49
|
+
<td>{entry.on ?? "—"}</td>
|
|
50
|
+
<td>{entry.to ?? "—"}</td>
|
|
51
|
+
<td set:html={entry.body ?? ""} />
|
|
52
|
+
</tr>
|
|
53
|
+
))}
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MatchKey } from '@ingcreators/annot-product-docs';
|
|
2
|
+
export type Match = MatchKey;
|
|
3
|
+
export type OverlayIntent = "info" | "warning" | "error" | "success" | "neutral" | "required" | "action";
|
|
4
|
+
export interface TransitionEntry {
|
|
5
|
+
trigger: string;
|
|
6
|
+
on?: string;
|
|
7
|
+
to?: string;
|
|
8
|
+
body?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ScreenListEntry {
|
|
11
|
+
id: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
href?: string;
|
|
14
|
+
order?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface GraphEdge {
|
|
17
|
+
from: string;
|
|
18
|
+
to: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
}
|
|
21
|
+
export type GraphDirection = "TB" | "LR" | "BT" | "RL";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { CacheKeyInput, FileCache } from './cache.js';
|
|
2
|
+
export { cacheKey, createFileCache, createMemoryCache, RENDER_PIPELINE_VERSION, } from './cache.js';
|
|
3
|
+
export type { GraphDirection, GraphEdge, Match, OverlayIntent, ScreenListEntry, TransitionEntry, } from './components/types.js';
|
|
4
|
+
export type { ProductDocsIntegrationOptions } from './integration.js';
|
|
5
|
+
export { productDocsIntegration } from './integration.js';
|
|
6
|
+
export type { RenderAnnotatedScreenOptions, RenderResult, } from './render.js';
|
|
7
|
+
export { parseSnapshotBoxes, renderAnnotatedScreen, resolveMdxAnnotations, svgFromBboxAnnotations, } from './render.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { a as e, c as t, i as n, n as r, o as i, r as a, s as o, t as s } from "./render-BSQOeqPB.js";
|
|
2
|
+
//#region src/integration.ts
|
|
3
|
+
var c = "@ingcreators/annot-product-docs-astro";
|
|
4
|
+
function l(e = {}) {
|
|
5
|
+
let t = {
|
|
6
|
+
contentDir: e.contentDir ?? "docs",
|
|
7
|
+
configPath: e.configPath ?? "annot-docs.config.ts",
|
|
8
|
+
verbose: e.verbose ?? !1
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
name: c,
|
|
12
|
+
hooks: { "astro:config:setup": ({ logger: e }) => {
|
|
13
|
+
t.verbose && e.info(`${c} installed — contentDir=${t.contentDir} configPath=${t.configPath}`);
|
|
14
|
+
} }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { e as RENDER_PIPELINE_VERSION, i as cacheKey, o as createFileCache, t as createMemoryCache, s as parseSnapshotBoxes, l as productDocsIntegration, r as renderAnnotatedScreen, a as resolveMdxAnnotations, n as svgFromBboxAnnotations };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AstroIntegration } from 'astro';
|
|
2
|
+
export interface ProductDocsIntegrationOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Directory (relative to the Astro project root) that contains
|
|
5
|
+
* the `annot:`-frontmatter MDX files the integration should
|
|
6
|
+
* walk for the Image Service + component-config defaults.
|
|
7
|
+
* Default: `"docs"`.
|
|
8
|
+
*/
|
|
9
|
+
contentDir?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Path to the `annot-docs.config.ts` config file. Default:
|
|
12
|
+
* `"annot-docs.config.ts"`. Future PRs read this for the
|
|
13
|
+
* `meta` defaults + per-book template lookups.
|
|
14
|
+
*/
|
|
15
|
+
configPath?: string;
|
|
16
|
+
/**
|
|
17
|
+
* If `true`, the integration logs a debug line on every
|
|
18
|
+
* config:setup. Useful for verifying the integration is
|
|
19
|
+
* installed correctly in an Astro project. Default: `false`.
|
|
20
|
+
*/
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Astro integration factory. Drop into `astro.config.mjs`:
|
|
25
|
+
*
|
|
26
|
+
* ```js
|
|
27
|
+
* import { defineConfig } from "astro/config";
|
|
28
|
+
* import { productDocsIntegration } from "@ingcreators/annot-product-docs-astro";
|
|
29
|
+
*
|
|
30
|
+
* export default defineConfig({
|
|
31
|
+
* integrations: [productDocsIntegration()],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Phase 2 PR 1 ships the scaffold — the hooks are no-ops aside
|
|
36
|
+
* from optional verbose logging. PR 2 adds the Image Service;
|
|
37
|
+
* PR 3 wires up the seven docs components.
|
|
38
|
+
*/
|
|
39
|
+
export declare function productDocsIntegration(options?: ProductDocsIntegrationOptions): AstroIntegration;
|
|
40
|
+
/**
|
|
41
|
+
* Default-export shape some Astro integration authors prefer.
|
|
42
|
+
* Both `import productDocsIntegration from ...` (default) and
|
|
43
|
+
* `import { productDocsIntegration }` (named) resolve to the
|
|
44
|
+
* same factory; consumers can pick whichever feels natural.
|
|
45
|
+
*/
|
|
46
|
+
export default productDocsIntegration;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { BboxAnnotation } from '@ingcreators/annot-annotator';
|
|
2
|
+
/**
|
|
3
|
+
* Compositional options for `page.screenshot({ annot })` /
|
|
4
|
+
* `locator.screenshot({ annot })`. Each field is an independent
|
|
5
|
+
* contribution to the embedded XMP record:
|
|
6
|
+
*
|
|
7
|
+
* - `mdx` — refresh + resolve MDX `<Overlay>` annotations
|
|
8
|
+
* - `overlays`— inline `BboxAnnotation[]` (the DSL accepted by
|
|
9
|
+
* `@ingcreators/annot-annotator`)
|
|
10
|
+
* - `tags` — provenance metadata written verbatim into the XMP
|
|
11
|
+
* - `editable`— bake-vs-preserve toggle (default `true`)
|
|
12
|
+
*
|
|
13
|
+
* `annot: true` / `annot: {}` is treated as a no-op shorthand — the
|
|
14
|
+
* fixture detects no contribution and the screenshot falls through
|
|
15
|
+
* to vanilla Playwright behaviour.
|
|
16
|
+
*/
|
|
17
|
+
export interface AnnotScreenshotOptions {
|
|
18
|
+
/** Refresh the MDX `annot:snapshot` block and resolve the
|
|
19
|
+
* `<Screen id>`'s overlays. The MDX file is rewritten in-place
|
|
20
|
+
* with the current page's aria-snapshot before overlays resolve.
|
|
21
|
+
*/
|
|
22
|
+
mdx?: {
|
|
23
|
+
id: string;
|
|
24
|
+
path: string;
|
|
25
|
+
};
|
|
26
|
+
/** Caller-supplied annotations — merged with MDX-derived ones if
|
|
27
|
+
* `mdx` is also set. Same DSL `@ingcreators/annot-annotator`
|
|
28
|
+
* accepts. */
|
|
29
|
+
overlays?: BboxAnnotation[];
|
|
30
|
+
/** Provenance metadata written verbatim into the PNG's XMP. The
|
|
31
|
+
* fixture adds no defaults; callers who want `WELL_KNOWN_TAG_KEYS`
|
|
32
|
+
* (`source` / `screen` / `capturedAt` / `commit`) write them. */
|
|
33
|
+
tags?: Record<string, string>;
|
|
34
|
+
/** When `true` (default): annotations stored as SVG in XMP +
|
|
35
|
+
* original capture embedded → re-editable in Annot Cloud. When
|
|
36
|
+
* `false`: annotations baked into the visible pixels, no XMP
|
|
37
|
+
* layer, no embedded original — flat PNG, no round-trip. */
|
|
38
|
+
editable?: boolean;
|
|
39
|
+
}
|
|
40
|
+
declare module "@playwright/test" {
|
|
41
|
+
interface PageScreenshotOptions {
|
|
42
|
+
annot?: AnnotScreenshotOptions;
|
|
43
|
+
}
|
|
44
|
+
interface LocatorScreenshotOptions {
|
|
45
|
+
annot?: AnnotScreenshotOptions;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Idempotent prototype patch — wrap `screenshot` to intercept the
|
|
50
|
+
* `annot` opt while falling through to the original method when
|
|
51
|
+
* absent / empty. Exported for unit tests; production usage flows
|
|
52
|
+
* through the fixture `extend({ page })` body which calls this once
|
|
53
|
+
* per worker on the first page (and once for the locator prototype).
|
|
54
|
+
*/
|
|
55
|
+
export declare function patchScreenshot(proto: {
|
|
56
|
+
screenshot: (opts?: unknown) => Promise<Buffer>;
|
|
57
|
+
}): void;
|
|
58
|
+
/**
|
|
59
|
+
* `test = base.extend({ page })` — drop-in for `@playwright/test`'s
|
|
60
|
+
* `test` plus a one-time patch of `Page.prototype.screenshot` AND
|
|
61
|
+
* `Locator.prototype.screenshot` (per worker process) so calls
|
|
62
|
+
* carrying `annot: { ... }` run the annot pipeline above.
|
|
63
|
+
*
|
|
64
|
+
* The base `test` is `@ingcreators/annot-product-docs`'s test which
|
|
65
|
+
* already extends `@ingcreators/annot-playwright` — callers get
|
|
66
|
+
* `page` + `annotator` + `screen` plus the annot-aware screenshot.
|
|
67
|
+
*/
|
|
68
|
+
export declare const test: import('@playwright/test').TestType<import('@playwright/test').PlaywrightTestArgs & import('@playwright/test').PlaywrightTestOptions & {
|
|
69
|
+
annotator: import('@ingcreators/annot-playwright').PlaywrightAnnotator;
|
|
70
|
+
} & {
|
|
71
|
+
screen: import('@ingcreators/annot-product-docs').Screen;
|
|
72
|
+
}, import('@playwright/test').PlaywrightWorkerArgs & import('@playwright/test').PlaywrightWorkerOptions>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { expect } from '@playwright/test';
|
|
2
|
+
export type { AnnotScreenshotOptions } from './fixture.js';
|
|
3
|
+
export { patchScreenshot, test } from './fixture.js';
|
|
4
|
+
export type { Clip, RebaseResult } from './rebase.js';
|
|
5
|
+
export { describeAnnotation, rebaseAnnotations } from './rebase.js';
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { i as e, r as t } from "../render-BSQOeqPB.js";
|
|
2
|
+
import { writeFile as n } from "node:fs/promises";
|
|
3
|
+
import { createAnnotator as r } from "@ingcreators/annot-annotator";
|
|
4
|
+
import { captureScreen as i, test as a } from "@ingcreators/annot-product-docs";
|
|
5
|
+
import { expect as o } from "@playwright/test";
|
|
6
|
+
import { writePngWithTagsOnly as s } from "@ingcreators/annot-core/xmp-bytes";
|
|
7
|
+
//#region src/playwright/rebase.ts
|
|
8
|
+
function c(e, t) {
|
|
9
|
+
let n = [], r = [];
|
|
10
|
+
for (let i of e) {
|
|
11
|
+
let e = l(i, t);
|
|
12
|
+
e ? n.push(e) : r.push(i);
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
kept: n,
|
|
16
|
+
dropped: r
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function l(e, t) {
|
|
20
|
+
switch (e.type) {
|
|
21
|
+
case "rect": return u(e, t);
|
|
22
|
+
case "numberedBadge": return d(e, t);
|
|
23
|
+
case "callout": return f(e, t);
|
|
24
|
+
case "circle": return p(e, t);
|
|
25
|
+
case "arrow": return m(e, t);
|
|
26
|
+
case "text": return h(e, t);
|
|
27
|
+
case "raw": return g(e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function u(e, t) {
|
|
31
|
+
return y(e.bbox, t) ? {
|
|
32
|
+
...e,
|
|
33
|
+
bbox: _(e.bbox, t)
|
|
34
|
+
} : null;
|
|
35
|
+
}
|
|
36
|
+
function d(e, t) {
|
|
37
|
+
return y(e.bbox, t) ? {
|
|
38
|
+
...e,
|
|
39
|
+
bbox: _(e.bbox, t),
|
|
40
|
+
imageWidth: t.width,
|
|
41
|
+
imageHeight: t.height
|
|
42
|
+
} : null;
|
|
43
|
+
}
|
|
44
|
+
function f(e, t) {
|
|
45
|
+
return !y(e.targetBbox, t) || !b(e.at, t) ? null : {
|
|
46
|
+
...e,
|
|
47
|
+
at: v(e.at, t),
|
|
48
|
+
targetBbox: _(e.targetBbox, t)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function p(e, t) {
|
|
52
|
+
return y({
|
|
53
|
+
x: e.center.x - e.radius,
|
|
54
|
+
y: e.center.y - e.radius,
|
|
55
|
+
width: e.radius * 2,
|
|
56
|
+
height: e.radius * 2
|
|
57
|
+
}, t) ? {
|
|
58
|
+
...e,
|
|
59
|
+
center: v(e.center, t)
|
|
60
|
+
} : null;
|
|
61
|
+
}
|
|
62
|
+
function m(e, t) {
|
|
63
|
+
return !b(e.from, t) || !b(e.to, t) ? null : {
|
|
64
|
+
...e,
|
|
65
|
+
from: v(e.from, t),
|
|
66
|
+
to: v(e.to, t)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function h(e, t) {
|
|
70
|
+
return b(e.at, t) ? {
|
|
71
|
+
...e,
|
|
72
|
+
at: v(e.at, t)
|
|
73
|
+
} : null;
|
|
74
|
+
}
|
|
75
|
+
function g(e) {
|
|
76
|
+
return e;
|
|
77
|
+
}
|
|
78
|
+
function _(e, t) {
|
|
79
|
+
return {
|
|
80
|
+
x: e.x - t.x,
|
|
81
|
+
y: e.y - t.y,
|
|
82
|
+
width: e.width,
|
|
83
|
+
height: e.height
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function v(e, t) {
|
|
87
|
+
return {
|
|
88
|
+
x: e.x - t.x,
|
|
89
|
+
y: e.y - t.y
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function y(e, t) {
|
|
93
|
+
return e.x >= t.x && e.y >= t.y && e.x + e.width <= t.x + t.width && e.y + e.height <= t.y + t.height;
|
|
94
|
+
}
|
|
95
|
+
function b(e, t) {
|
|
96
|
+
return e.x >= t.x && e.y >= t.y && e.x <= t.x + t.width && e.y <= t.y + t.height;
|
|
97
|
+
}
|
|
98
|
+
function x(e) {
|
|
99
|
+
switch (e.type) {
|
|
100
|
+
case "rect":
|
|
101
|
+
case "numberedBadge": return `${e.type}@(${e.bbox.x},${e.bbox.y},${e.bbox.width},${e.bbox.height})`;
|
|
102
|
+
case "callout": return `callout@(${e.targetBbox.x},${e.targetBbox.y},${e.targetBbox.width},${e.targetBbox.height})`;
|
|
103
|
+
case "circle": return `circle@(${e.center.x},${e.center.y},r=${e.radius})`;
|
|
104
|
+
case "arrow": return `arrow@(${e.from.x},${e.from.y})→(${e.to.x},${e.to.y})`;
|
|
105
|
+
case "text": return `text@(${e.at.x},${e.at.y})`;
|
|
106
|
+
case "raw": return "raw[<svg fragment>]";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/playwright/fixture.ts
|
|
111
|
+
var S = Symbol.for("@ingcreators/annot:screenshot-patched");
|
|
112
|
+
function C(e) {
|
|
113
|
+
let t = e;
|
|
114
|
+
if (t[S]) return;
|
|
115
|
+
let n = e.screenshot;
|
|
116
|
+
e.screenshot = async function(e = {}) {
|
|
117
|
+
let t = e?.annot;
|
|
118
|
+
return w(t) ? D.call(this, n, e) : n.call(this, e);
|
|
119
|
+
}, t[S] = !0;
|
|
120
|
+
}
|
|
121
|
+
function w(e) {
|
|
122
|
+
return !e || e === !0 ? !1 : !!(e.mdx || e.overlays && e.overlays.length > 0 || e.tags);
|
|
123
|
+
}
|
|
124
|
+
function T(e) {
|
|
125
|
+
return typeof e.boundingBox == "function";
|
|
126
|
+
}
|
|
127
|
+
function E(e) {
|
|
128
|
+
return T(e) ? e.page() : e;
|
|
129
|
+
}
|
|
130
|
+
async function D(e, t) {
|
|
131
|
+
let { annot: r, path: a, ...o } = t, s = o, c = r.editable ?? !0;
|
|
132
|
+
r.mdx && await i(E(this), {
|
|
133
|
+
id: r.mdx.id,
|
|
134
|
+
mdxPath: r.mdx.path
|
|
135
|
+
});
|
|
136
|
+
let l = await O(this, t.clip), u = await A({
|
|
137
|
+
rawBytes: k(await e.call(this, {
|
|
138
|
+
...s,
|
|
139
|
+
path: void 0
|
|
140
|
+
})),
|
|
141
|
+
annot: r,
|
|
142
|
+
editable: c,
|
|
143
|
+
clip: l
|
|
144
|
+
});
|
|
145
|
+
return a && await n(a, u), Buffer.from(u);
|
|
146
|
+
}
|
|
147
|
+
async function O(e, t) {
|
|
148
|
+
if (T(e)) {
|
|
149
|
+
let t = await e.boundingBox();
|
|
150
|
+
if (!t) throw Error("locator.screenshot({ annot }): locator has no bounding box (probably not visible). Re-test with a stable selector / waitFor().");
|
|
151
|
+
return t;
|
|
152
|
+
}
|
|
153
|
+
return t ?? null;
|
|
154
|
+
}
|
|
155
|
+
function k(e) {
|
|
156
|
+
return e instanceof Uint8Array && !(e instanceof Buffer) ? e : new Uint8Array(e.buffer, e.byteOffset, e.byteLength);
|
|
157
|
+
}
|
|
158
|
+
async function A(n) {
|
|
159
|
+
let { rawBytes: i, annot: a, editable: o, clip: l } = n, u = N(i), d = [];
|
|
160
|
+
if (a.mdx) {
|
|
161
|
+
let e = l ? {
|
|
162
|
+
width: l.x + l.width,
|
|
163
|
+
height: l.y + l.height
|
|
164
|
+
} : u, n = await t({
|
|
165
|
+
mdxPath: a.mdx.path,
|
|
166
|
+
screenId: a.mdx.id,
|
|
167
|
+
dims: e
|
|
168
|
+
});
|
|
169
|
+
d.push(...n);
|
|
170
|
+
}
|
|
171
|
+
a.overlays && d.push(...a.overlays);
|
|
172
|
+
let f;
|
|
173
|
+
if (l) {
|
|
174
|
+
let { kept: e, dropped: t } = c(d, l);
|
|
175
|
+
f = e, t.length > 0 && j(t);
|
|
176
|
+
} else f = d;
|
|
177
|
+
let p = !!(a.mdx || a.overlays);
|
|
178
|
+
if (p && !o) {
|
|
179
|
+
let t = e(f), n = `data:image/png;base64,${Buffer.from(i).toString("base64")}`, o = r({ loadSystemFonts: !0 }).toPng({
|
|
180
|
+
originalDataUrl: n,
|
|
181
|
+
annotationsSvg: t,
|
|
182
|
+
width: u.width,
|
|
183
|
+
height: u.height
|
|
184
|
+
});
|
|
185
|
+
return a.tags ? s(o, a.tags) : o;
|
|
186
|
+
}
|
|
187
|
+
if (p) {
|
|
188
|
+
let t = e(f), n = `data:image/png;base64,${Buffer.from(i).toString("base64")}`;
|
|
189
|
+
return r({ loadSystemFonts: !0 }).toEditablePng({
|
|
190
|
+
originalDataUrl: n,
|
|
191
|
+
annotationsSvg: t,
|
|
192
|
+
width: u.width,
|
|
193
|
+
height: u.height,
|
|
194
|
+
tags: a.tags
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return a.tags ? s(i, a.tags) : i;
|
|
198
|
+
}
|
|
199
|
+
function j(e) {
|
|
200
|
+
let t = e.map(x).join(", "), n = `annot fixture: dropped ${e.length} overlay(s) outside the screenshot clip — ${t}`;
|
|
201
|
+
console.warn(n), M(n, e);
|
|
202
|
+
}
|
|
203
|
+
function M(e, t) {
|
|
204
|
+
try {
|
|
205
|
+
let n = a.info?.();
|
|
206
|
+
if (!n?.annotations) return;
|
|
207
|
+
n.annotations.push({
|
|
208
|
+
type: "warning",
|
|
209
|
+
description: `${e} (${t.length} dropped)`
|
|
210
|
+
});
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
function N(e) {
|
|
214
|
+
if (e.length < 24) throw Error("Playwright annot fixture: raw screenshot too short to contain PNG IHDR.");
|
|
215
|
+
let t = new DataView(e.buffer, e.byteOffset, e.byteLength);
|
|
216
|
+
return {
|
|
217
|
+
width: t.getUint32(16, !1),
|
|
218
|
+
height: t.getUint32(20, !1)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
var P = a.extend({ page: async ({ page: e }, t) => {
|
|
222
|
+
C(Object.getPrototypeOf(e)), C(Object.getPrototypeOf(e.locator("html"))), await t(e);
|
|
223
|
+
} });
|
|
224
|
+
//#endregion
|
|
225
|
+
export { x as describeAnnotation, o as expect, C as patchScreenshot, c as rebaseAnnotations, P as test };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BboxAnnotation } from '@ingcreators/annot-annotator';
|
|
2
|
+
export interface Clip {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface RebaseResult {
|
|
9
|
+
/** Annotations whose coordinates were rebased into clip-space. */
|
|
10
|
+
kept: BboxAnnotation[];
|
|
11
|
+
/** Annotations that fell outside `clip` and were dropped. The
|
|
12
|
+
* caller surfaces these as `RenderResult.droppedOverlays` /
|
|
13
|
+
* `test.info().annotations` warnings. */
|
|
14
|
+
dropped: BboxAnnotation[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Rebase + filter a `BboxAnnotation[]` against a clip rectangle.
|
|
18
|
+
*
|
|
19
|
+
* - `kept` = annotations whose visible coords were translated by
|
|
20
|
+
* `(-clip.x, -clip.y)`. The new coords are in the clipped image's
|
|
21
|
+
* coordinate space (0,0 at the top-left of the screenshot).
|
|
22
|
+
* - `dropped` = annotations whose bbox / endpoints fell outside the
|
|
23
|
+
* clip. Returned verbatim (un-rebased) so the caller can log
|
|
24
|
+
* which originals were skipped.
|
|
25
|
+
*
|
|
26
|
+
* `numberedBadge`'s `imageWidth` / `imageHeight` are also rebased
|
|
27
|
+
* to match the clip dimensions, so `placement: "auto"` picks the
|
|
28
|
+
* corner against the cropped image edge rather than the page edge.
|
|
29
|
+
*/
|
|
30
|
+
export declare function rebaseAnnotations(annotations: BboxAnnotation[], clip: Clip): RebaseResult;
|
|
31
|
+
/**
|
|
32
|
+
* Format a short identifier for an annotation so dropped diagnostics
|
|
33
|
+
* stay readable. We don't have a stable id field on the DSL types —
|
|
34
|
+
* use `type` + first bbox coords as a heuristic.
|
|
35
|
+
*/
|
|
36
|
+
export declare function describeAnnotation(ann: BboxAnnotation): string;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash as e } from "node:crypto";
|
|
2
|
+
import { mkdir as t, readFile as n, writeFile as r } from "node:fs/promises";
|
|
3
|
+
import { dirname as i, isAbsolute as a, join as o, resolve as s } from "node:path";
|
|
4
|
+
import { bboxAnnotationsToSvg as c, createAnnotator as l } from "@ingcreators/annot-annotator";
|
|
5
|
+
import { parseMdxFile as u } from "@ingcreators/annot-product-docs";
|
|
6
|
+
//#region src/cache.ts
|
|
7
|
+
var d = 1;
|
|
8
|
+
function f(t) {
|
|
9
|
+
let n = e("sha256");
|
|
10
|
+
return n.update("v1\n"), n.update(t.mdxSource), n.update("\0"), n.update(t.screenId), t.editable && n.update("\0editable"), n.digest("hex");
|
|
11
|
+
}
|
|
12
|
+
function p(e) {
|
|
13
|
+
return {
|
|
14
|
+
dir: e,
|
|
15
|
+
async get(t) {
|
|
16
|
+
try {
|
|
17
|
+
let r = await n(o(e, `${t}.png`));
|
|
18
|
+
return new Uint8Array(r.buffer, r.byteOffset, r.byteLength);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
if (e.code === "ENOENT") return null;
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
async set(n, a) {
|
|
25
|
+
let s = o(e, `${n}.png`);
|
|
26
|
+
await t(i(s), { recursive: !0 }), await r(s, a);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function m() {
|
|
31
|
+
let e = /* @__PURE__ */ new Map();
|
|
32
|
+
return {
|
|
33
|
+
dir: ":memory:",
|
|
34
|
+
async get(t) {
|
|
35
|
+
return e.get(t) ?? null;
|
|
36
|
+
},
|
|
37
|
+
async set(t, n) {
|
|
38
|
+
e.set(t, n);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/render.ts
|
|
44
|
+
async function h(e) {
|
|
45
|
+
let t = e.cwd ?? process.cwd(), n = a(e.mdxPath) ? e.mdxPath : s(t, e.mdxPath), r = await u(n);
|
|
46
|
+
if (!r) throw Error(`renderAnnotatedScreen: ${e.mdxPath} has no \`annot:\` frontmatter — cannot render.`);
|
|
47
|
+
let o = r.screens.find((t) => t.id === e.screenId);
|
|
48
|
+
if (!o) throw Error(`renderAnnotatedScreen: ${e.mdxPath} has no <Screen id="${e.screenId}"> block.`);
|
|
49
|
+
let c = g(e.editable), d = f({
|
|
50
|
+
mdxSource: r.source,
|
|
51
|
+
screenId: e.screenId,
|
|
52
|
+
editable: c !== null
|
|
53
|
+
});
|
|
54
|
+
if (e.cache) {
|
|
55
|
+
let t = await e.cache.get(d);
|
|
56
|
+
if (t) return {
|
|
57
|
+
bytes: t,
|
|
58
|
+
fromCache: !0,
|
|
59
|
+
hadBoundingBoxes: !0
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
let p = e.basePngBytes ?? await C(r, o.src, i(n)), m = w(p), h = v(r.commentBlocks.snapshot ?? ""), y = b(o.overlays, h, m), S, T;
|
|
63
|
+
if (y.length === 0) if (T = !1, c !== null) {
|
|
64
|
+
let e = `data:image/png;base64,${Buffer.from(p).toString("base64")}`;
|
|
65
|
+
S = l({ loadSystemFonts: !0 }).toEditablePng({
|
|
66
|
+
originalDataUrl: e,
|
|
67
|
+
annotationsSvg: _(),
|
|
68
|
+
width: m.width,
|
|
69
|
+
height: m.height,
|
|
70
|
+
tags: c.tags
|
|
71
|
+
});
|
|
72
|
+
} else S = p;
|
|
73
|
+
else {
|
|
74
|
+
let e = `data:image/png;base64,${Buffer.from(p).toString("base64")}`, t = x(y), n = l({ loadSystemFonts: !0 }), r = {
|
|
75
|
+
originalDataUrl: e,
|
|
76
|
+
annotationsSvg: t,
|
|
77
|
+
width: m.width,
|
|
78
|
+
height: m.height
|
|
79
|
+
};
|
|
80
|
+
S = c === null ? n.toPng(r) : n.toEditablePng({
|
|
81
|
+
...r,
|
|
82
|
+
tags: c.tags
|
|
83
|
+
}), T = !0;
|
|
84
|
+
}
|
|
85
|
+
return e.cache && await e.cache.set(d, S), {
|
|
86
|
+
bytes: S,
|
|
87
|
+
fromCache: !1,
|
|
88
|
+
hadBoundingBoxes: T
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function g(e) {
|
|
92
|
+
return e ? e === !0 ? {} : e : null;
|
|
93
|
+
}
|
|
94
|
+
function _() {
|
|
95
|
+
return "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>";
|
|
96
|
+
}
|
|
97
|
+
function v(e) {
|
|
98
|
+
let t = [];
|
|
99
|
+
for (let n of e.split(/\r?\n/)) {
|
|
100
|
+
if (!n.trim()) continue;
|
|
101
|
+
let e = n.match(/^\s*-\s+([a-z]+)(?:\s+"([^"]*?)")?/);
|
|
102
|
+
if (!e) continue;
|
|
103
|
+
let r = e[1] ?? "", i = e[2] ?? "", a = n.match(/\[ref=(e\d+)\]/)?.[1], o = n.match(/\[box=(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)\]/);
|
|
104
|
+
!a || !o || t.push({
|
|
105
|
+
role: r,
|
|
106
|
+
name: i,
|
|
107
|
+
ref: a,
|
|
108
|
+
box: {
|
|
109
|
+
x: Number.parseFloat(o[1]),
|
|
110
|
+
y: Number.parseFloat(o[2]),
|
|
111
|
+
width: Number.parseFloat(o[3]),
|
|
112
|
+
height: Number.parseFloat(o[4])
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return t;
|
|
117
|
+
}
|
|
118
|
+
async function y(e) {
|
|
119
|
+
let t = e.cwd ?? process.cwd(), n = await u(a(e.mdxPath) ? e.mdxPath : s(t, e.mdxPath));
|
|
120
|
+
if (!n) throw Error(`resolveMdxAnnotations: ${e.mdxPath} has no \`annot:\` frontmatter — cannot resolve.`);
|
|
121
|
+
let r = n.screens.find((t) => t.id === e.screenId);
|
|
122
|
+
if (!r) throw Error(`resolveMdxAnnotations: ${e.mdxPath} has no <Screen id="${e.screenId}"> block.`);
|
|
123
|
+
let i = v(n.commentBlocks.snapshot ?? "");
|
|
124
|
+
return b(r.overlays, i, e.dims);
|
|
125
|
+
}
|
|
126
|
+
function b(e, t, n) {
|
|
127
|
+
let r = [], i = 1;
|
|
128
|
+
for (let a of e) {
|
|
129
|
+
let e = t.find((e) => e.role === a.match.role && e.name === a.match.name);
|
|
130
|
+
if (!e) continue;
|
|
131
|
+
let o = a.number ?? i++;
|
|
132
|
+
r.push({
|
|
133
|
+
type: "numberedBadge",
|
|
134
|
+
bbox: e.box,
|
|
135
|
+
number: o,
|
|
136
|
+
placement: "auto",
|
|
137
|
+
imageWidth: n.width,
|
|
138
|
+
imageHeight: n.height,
|
|
139
|
+
intent: a.intent === "action" ? "warning" : a.intent === "required" ? "error" : "info"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return r;
|
|
143
|
+
}
|
|
144
|
+
function x(e) {
|
|
145
|
+
return `<svg xmlns="http://www.w3.org/2000/svg">${c(e)}</svg>`;
|
|
146
|
+
}
|
|
147
|
+
function S(e) {
|
|
148
|
+
return e.length === 0 ? "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>" : `<svg xmlns="http://www.w3.org/2000/svg">${c(e)}</svg>`;
|
|
149
|
+
}
|
|
150
|
+
async function C(e, t, r) {
|
|
151
|
+
if (!t) throw Error("renderAnnotatedScreen: <Screen src=...> is required — cannot render without a base image.");
|
|
152
|
+
let i = await n(a(t) ? t : s(r, t));
|
|
153
|
+
return new Uint8Array(i.buffer, i.byteOffset, i.byteLength);
|
|
154
|
+
}
|
|
155
|
+
function w(e) {
|
|
156
|
+
if (e.length < 24) throw Error("renderAnnotatedScreen: base PNG too short to contain IHDR.");
|
|
157
|
+
let t = new DataView(e.buffer, e.byteOffset, e.byteLength);
|
|
158
|
+
return {
|
|
159
|
+
width: t.getUint32(16, !1),
|
|
160
|
+
height: t.getUint32(20, !1)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { d as a, m as c, S as i, h as n, f as o, y as r, p as s, v as t };
|