@wasao/kagemusha 0.1.1 → 0.3.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/README.md +168 -79
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +26 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/capture.d.ts +3 -2
- package/dist/commands/capture.d.ts.map +1 -1
- package/dist/commands/capture.js +256 -20
- package/dist/commands/capture.js.map +1 -1
- package/dist/commands/discover.d.ts +2 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +62 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/edit.d.ts +1 -1
- package/dist/commands/edit.d.ts.map +1 -1
- package/dist/commands/edit.js +82 -26
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +240 -105
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +33 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +131 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/validate.js +1 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/editor/inject-script/annotations.d.ts +11 -0
- package/dist/editor/inject-script/annotations.d.ts.map +1 -0
- package/dist/editor/inject-script/annotations.js +409 -0
- package/dist/editor/inject-script/annotations.js.map +1 -0
- package/dist/editor/inject-script/bridge.d.ts +13 -0
- package/dist/editor/inject-script/bridge.d.ts.map +1 -0
- package/dist/editor/inject-script/bridge.js +33 -0
- package/dist/editor/inject-script/bridge.js.map +1 -0
- package/dist/editor/inject-script/crop.d.ts +9 -0
- package/dist/editor/inject-script/crop.d.ts.map +1 -0
- package/dist/editor/inject-script/crop.js +236 -0
- package/dist/editor/inject-script/crop.js.map +1 -0
- package/dist/editor/inject-script/dom.d.ts +7 -0
- package/dist/editor/inject-script/dom.d.ts.map +1 -0
- package/dist/editor/inject-script/dom.js +32 -0
- package/dist/editor/inject-script/dom.js.map +1 -0
- package/dist/editor/inject-script/index.d.ts +2 -0
- package/dist/editor/inject-script/index.d.ts.map +1 -0
- package/dist/editor/inject-script/index.js +56 -0
- package/dist/editor/inject-script/index.js.map +1 -0
- package/dist/editor/inject-script/record.d.ts +5 -0
- package/dist/editor/inject-script/record.d.ts.map +1 -0
- package/dist/editor/inject-script/record.js +398 -0
- package/dist/editor/inject-script/record.js.map +1 -0
- package/dist/editor/inject-script/selector.d.ts +6 -0
- package/dist/editor/inject-script/selector.d.ts.map +1 -0
- package/dist/editor/inject-script/selector.js +112 -0
- package/dist/editor/inject-script/selector.js.map +1 -0
- package/dist/editor/inject-script/state.d.ts +27 -0
- package/dist/editor/inject-script/state.d.ts.map +1 -0
- package/dist/editor/inject-script/state.js +26 -0
- package/dist/editor/inject-script/state.js.map +1 -0
- package/dist/editor/inject-script/svg.d.ts +7 -0
- package/dist/editor/inject-script/svg.d.ts.map +1 -0
- package/dist/editor/inject-script/svg.js +39 -0
- package/dist/editor/inject-script/svg.js.map +1 -0
- package/dist/editor/inject-script/toolbar.d.ts +14 -0
- package/dist/editor/inject-script/toolbar.d.ts.map +1 -0
- package/dist/editor/inject-script/toolbar.js +240 -0
- package/dist/editor/inject-script/toolbar.js.map +1 -0
- package/dist/editor/inject-script/types.d.ts +102 -0
- package/dist/editor/inject-script/types.d.ts.map +1 -0
- package/dist/editor/inject-script/types.js +5 -0
- package/dist/editor/inject-script/types.js.map +1 -0
- package/dist/editor/inject-script.js +1276 -353
- package/dist/index.js +34 -16
- package/dist/index.js.map +1 -1
- package/dist/lib/annotate.d.ts +2 -2
- package/dist/lib/annotate.d.ts.map +1 -1
- package/dist/lib/annotate.js +35 -43
- package/dist/lib/annotate.js.map +1 -1
- package/dist/lib/auth.d.ts +18 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +45 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/aws-error.d.ts +7 -0
- package/dist/lib/aws-error.d.ts.map +1 -0
- package/dist/lib/aws-error.js +74 -0
- package/dist/lib/aws-error.js.map +1 -0
- package/dist/lib/canonical.d.ts +54 -0
- package/dist/lib/canonical.d.ts.map +1 -0
- package/dist/lib/canonical.js +152 -0
- package/dist/lib/canonical.js.map +1 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +23 -13
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/crawl.d.ts +1 -1
- package/dist/lib/crawl.d.ts.map +1 -1
- package/dist/lib/crawl.js +213 -20
- package/dist/lib/crawl.js.map +1 -1
- package/dist/lib/definition.d.ts +2 -0
- package/dist/lib/definition.d.ts.map +1 -0
- package/dist/lib/definition.js +6 -0
- package/dist/lib/definition.js.map +1 -0
- package/dist/lib/diff.d.ts +71 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +40 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/login-error.d.ts +10 -0
- package/dist/lib/login-error.d.ts.map +1 -0
- package/dist/lib/login-error.js +13 -0
- package/dist/lib/login-error.js.map +1 -0
- package/dist/lib/page-ready.d.ts +18 -0
- package/dist/lib/page-ready.d.ts.map +1 -0
- package/dist/lib/page-ready.js +19 -0
- package/dist/lib/page-ready.js.map +1 -0
- package/dist/lib/screenshot.d.ts +11 -2
- package/dist/lib/screenshot.d.ts.map +1 -1
- package/dist/lib/screenshot.js +73 -64
- package/dist/lib/screenshot.js.map +1 -1
- package/dist/lib/staging.d.ts +7 -0
- package/dist/lib/staging.d.ts.map +1 -0
- package/dist/lib/staging.js +21 -0
- package/dist/lib/staging.js.map +1 -0
- package/dist/types.d.ts +10 -23
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -3
- package/dist/commands/preview.d.ts +0 -6
- package/dist/commands/preview.d.ts.map +0 -1
- package/dist/commands/preview.js +0 -33
- package/dist/commands/preview.js.map +0 -1
- package/dist/commands/run.d.ts +0 -6
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands/run.js +0 -39
- package/dist/commands/run.js.map +0 -1
- package/dist/editor/editor/editor.html +0 -313
- package/dist/editor/editor/inject.ts +0 -385
- package/dist/editor/editor.html +0 -338
- package/dist/editor/inject-script.cjs +0 -398
- package/dist/editor/inject-script.cjs.map +0 -1
- package/dist/editor/inject-script.d.cts +0 -2
- package/dist/editor/inject-script.d.cts.map +0 -1
- package/dist/editor/inject-script.d.ts +0 -2
- package/dist/editor/inject-script.d.ts.map +0 -1
- package/dist/editor/inject-script.js.map +0 -1
- package/dist/editor/inject.d.ts +0 -2
- package/dist/editor/inject.d.ts.map +0 -1
- package/dist/editor/inject.js +0 -385
- package/dist/editor/inject.js.map +0 -1
- package/dist/lib/upload.d.ts +0 -9
- package/dist/lib/upload.d.ts.map +0 -1
- package/dist/lib/upload.js +0 -43
- package/dist/lib/upload.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wasao/kagemusha",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Auto-update help center screenshots when your code changes. The shadow warrior for your documentation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -12,15 +12,21 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|
|
14
14
|
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"registry": "https://registry.npmjs.org/",
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
15
19
|
"scripts": {
|
|
16
|
-
"build": "tsc",
|
|
20
|
+
"build": "tsc && pnpm build:inject",
|
|
21
|
+
"build:inject": "esbuild src/editor/inject-script/index.ts --bundle --format=iife --outfile=dist/editor/inject-script.js --platform=browser --target=es2022",
|
|
22
|
+
"build:clean": "rm -rf dist && tsc && pnpm build:inject",
|
|
17
23
|
"dev": "bun --watch src/index.ts",
|
|
18
24
|
"typecheck": "tsc --noEmit",
|
|
19
25
|
"lint": "bunx biome check .",
|
|
20
26
|
"format": "bunx biome format --write .",
|
|
21
27
|
"test": "bun test",
|
|
22
28
|
"prepare": "husky",
|
|
23
|
-
"prepublishOnly": "
|
|
29
|
+
"prepublishOnly": "pnpm run build"
|
|
24
30
|
},
|
|
25
31
|
"keywords": [
|
|
26
32
|
"screenshot",
|
|
@@ -47,7 +53,9 @@
|
|
|
47
53
|
"commander": "^13.0.0",
|
|
48
54
|
"glob": "^11.0.0",
|
|
49
55
|
"inquirer": "^12.0.0",
|
|
56
|
+
"pixelmatch": "^7.0.0",
|
|
50
57
|
"playwright-chromium": "^1.50.0",
|
|
58
|
+
"pngjs": "^7.0.0",
|
|
51
59
|
"sharp": "^0.34.5",
|
|
52
60
|
"yaml": "^2.7.0"
|
|
53
61
|
},
|
|
@@ -56,8 +64,17 @@
|
|
|
56
64
|
"@types/bun": "latest",
|
|
57
65
|
"@types/inquirer": "^9.0.0",
|
|
58
66
|
"@types/node": "^22.0.0",
|
|
67
|
+
"@types/pixelmatch": "^5.2.6",
|
|
68
|
+
"@types/pngjs": "^6.0.5",
|
|
59
69
|
"@types/sharp": "^0.32.0",
|
|
70
|
+
"esbuild": "^0.28.0",
|
|
60
71
|
"husky": "^9.1.7",
|
|
61
72
|
"typescript": "^5.7.0"
|
|
73
|
+
},
|
|
74
|
+
"pnpm": {
|
|
75
|
+
"onlyBuiltDependencies": [
|
|
76
|
+
"playwright-chromium",
|
|
77
|
+
"sharp"
|
|
78
|
+
]
|
|
62
79
|
}
|
|
63
80
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/commands/preview.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAwC3E"}
|
package/dist/commands/preview.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { chromium } from "playwright-chromium";
|
|
3
|
-
import { annotateScreenshots } from "../lib/annotate.js";
|
|
4
|
-
import { findProjectRoot, loadConfig, loadDefinitions } from "../lib/config.js";
|
|
5
|
-
import { captureScreenshots } from "../lib/screenshot.js";
|
|
6
|
-
export async function previewCommand(options) {
|
|
7
|
-
console.log(chalk.bold("\n🥷 Kagemusha — Preview\n"));
|
|
8
|
-
const projectRoot = findProjectRoot();
|
|
9
|
-
const config = loadConfig(projectRoot);
|
|
10
|
-
let definitions = loadDefinitions(projectRoot);
|
|
11
|
-
if (options.id) {
|
|
12
|
-
definitions = definitions.filter((d) => d.id === options.id);
|
|
13
|
-
if (definitions.length === 0) {
|
|
14
|
-
console.log(chalk.red(`Definition not found: ${options.id}`));
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
console.log(chalk.blue(`📸 Capturing ${definitions.length} screenshot(s)...`));
|
|
19
|
-
const results = await captureScreenshots(config, definitions, projectRoot);
|
|
20
|
-
const annotated = await annotateScreenshots(definitions, results, projectRoot);
|
|
21
|
-
console.log(chalk.green("\n✅ Screenshots captured. Opening preview...\n"));
|
|
22
|
-
// Open annotated screenshots in browser
|
|
23
|
-
const browser = await chromium.launch({ headless: false });
|
|
24
|
-
const context = await browser.newContext();
|
|
25
|
-
for (const result of annotated) {
|
|
26
|
-
const page = await context.newPage();
|
|
27
|
-
await page.goto(`file://${result.annotatedPath}`);
|
|
28
|
-
}
|
|
29
|
-
console.log(chalk.gray("Press Ctrl+C to close preview.\n"));
|
|
30
|
-
// Keep process alive until user closes
|
|
31
|
-
await new Promise(() => { });
|
|
32
|
-
}
|
|
33
|
-
//# sourceMappingURL=preview.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/commands/preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAM1D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAuB;IAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;IAEtD,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAEvC,IAAI,WAAW,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC/C,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7D,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC9D,OAAO;QACR,CAAC;IACF,CAAC;IAED,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,IAAI,CAAC,gBAAgB,WAAW,CAAC,MAAM,mBAAmB,CAAC,CACjE,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAC1C,WAAW,EACX,OAAO,EACP,WAAW,CACX,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC,CAAC;IAE3E,wCAAwC;IACxC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;IAE3C,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC,CAAC;IAE5D,uCAAuC;IACvC,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AAC7B,CAAC"}
|
package/dist/commands/run.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAMA,UAAU,UAAU;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DnE"}
|
package/dist/commands/run.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { annotateScreenshots } from "../lib/annotate.js";
|
|
3
|
-
import { findProjectRoot, loadConfig, loadDefinitions } from "../lib/config.js";
|
|
4
|
-
import { captureScreenshots } from "../lib/screenshot.js";
|
|
5
|
-
import { uploadToS3 } from "../lib/upload.js";
|
|
6
|
-
export async function runCommand(options) {
|
|
7
|
-
console.log(chalk.bold("\n🥷 Kagemusha — Running pipeline\n"));
|
|
8
|
-
const projectRoot = findProjectRoot();
|
|
9
|
-
const config = loadConfig(projectRoot);
|
|
10
|
-
let definitions = loadDefinitions(projectRoot);
|
|
11
|
-
if (options.ids) {
|
|
12
|
-
const ids = options.ids.split(",").map((s) => s.trim());
|
|
13
|
-
definitions = definitions.filter((d) => ids.includes(d.id));
|
|
14
|
-
}
|
|
15
|
-
if (definitions.length === 0) {
|
|
16
|
-
console.log(chalk.yellow("No screenshot definitions to process. Skipping."));
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
console.log(chalk.gray(` Found ${definitions.length} definition(s) to process\n`));
|
|
20
|
-
// Step 1: Capture
|
|
21
|
-
console.log(chalk.blue("📸 Capturing screenshots..."));
|
|
22
|
-
const captureResults = await captureScreenshots(config, definitions, projectRoot);
|
|
23
|
-
console.log(chalk.green(` ✓ Captured ${captureResults.length} screenshot(s)\n`));
|
|
24
|
-
// Step 2: Annotate
|
|
25
|
-
console.log(chalk.blue("🎨 Drawing annotations..."));
|
|
26
|
-
const annotatedResults = await annotateScreenshots(definitions, captureResults, projectRoot);
|
|
27
|
-
console.log(chalk.green(` ✓ Annotated ${annotatedResults.length} screenshot(s)\n`));
|
|
28
|
-
// Step 3: Publish
|
|
29
|
-
if (config.publish?.destination === "s3") {
|
|
30
|
-
console.log(chalk.blue("☁️ Uploading to S3..."));
|
|
31
|
-
const uploadResults = await uploadToS3(config, annotatedResults, projectRoot);
|
|
32
|
-
console.log(chalk.green(` ✓ Uploaded ${uploadResults.length} screenshot(s)\n`));
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
console.log(chalk.green(` Screenshots saved locally\n`));
|
|
36
|
-
}
|
|
37
|
-
console.log(chalk.bold.green("✅ Pipeline complete!\n"));
|
|
38
|
-
}
|
|
39
|
-
//# sourceMappingURL=run.js.map
|
package/dist/commands/run.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAM9C,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAmB;IACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC,CAAC;IAE/D,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACvC,IAAI,WAAW,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAE/C,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACxD,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,MAAM,CAAC,iDAAiD,CAAC,CAC/D,CAAC;QACF,OAAO;IACR,CAAC;IAED,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,IAAI,CAAC,WAAW,WAAW,CAAC,MAAM,6BAA6B,CAAC,CACtE,CAAC;IAEF,kBAAkB;IAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,MAAM,kBAAkB,CAC9C,MAAM,EACN,WAAW,EACX,WAAW,CACX,CAAC;IACF,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,KAAK,CAAC,gBAAgB,cAAc,CAAC,MAAM,kBAAkB,CAAC,CACpE,CAAC;IAEF,mBAAmB;IACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACrD,MAAM,gBAAgB,GAAG,MAAM,mBAAmB,CACjD,WAAW,EACX,cAAc,EACd,WAAW,CACX,CAAC;IACF,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,KAAK,CAAC,iBAAiB,gBAAgB,CAAC,MAAM,kBAAkB,CAAC,CACvE,CAAC;IAEF,kBAAkB;IAClB,IAAI,MAAM,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,MAAM,UAAU,CACrC,MAAM,EACN,gBAAgB,EAChB,WAAW,CACX,CAAC;QACF,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,KAAK,CAAC,gBAAgB,aAAa,CAAC,MAAM,kBAAkB,CAAC,CACnE,CAAC;IACH,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<title>Kagemusha Annotation Editor</title>
|
|
6
|
-
<style>
|
|
7
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
-
body { background: #1a1a2e; font-family: -apple-system, BlinkMacSystemFont, sans-serif; overflow: hidden; }
|
|
9
|
-
|
|
10
|
-
.toolbar {
|
|
11
|
-
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
|
12
|
-
background: #16213e; padding: 8px 16px; display: flex; align-items: center; gap: 12px;
|
|
13
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
14
|
-
}
|
|
15
|
-
.toolbar button {
|
|
16
|
-
padding: 6px 14px; border: 1px solid #444; border-radius: 6px;
|
|
17
|
-
background: #1a1a2e; color: #fff; font-size: 13px; cursor: pointer;
|
|
18
|
-
}
|
|
19
|
-
.toolbar button:hover { background: #2a2a4e; }
|
|
20
|
-
.toolbar button.active { background: #6366f1; border-color: #6366f1; }
|
|
21
|
-
.toolbar .separator { width: 1px; height: 24px; background: #444; }
|
|
22
|
-
.toolbar .title { color: #888; font-size: 13px; margin-right: 8px; }
|
|
23
|
-
.toolbar .save-btn { background: #22c55e; border-color: #22c55e; font-weight: 600; margin-left: auto; }
|
|
24
|
-
.toolbar .save-btn:hover { background: #16a34a; }
|
|
25
|
-
|
|
26
|
-
.canvas-area {
|
|
27
|
-
margin-top: 48px; display: flex; justify-content: center; align-items: flex-start;
|
|
28
|
-
padding: 20px; height: calc(100vh - 48px); overflow: auto;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.editor-container {
|
|
32
|
-
position: relative; cursor: crosshair;
|
|
33
|
-
}
|
|
34
|
-
.editor-container img { display: block; width: 100%; height: auto; }
|
|
35
|
-
|
|
36
|
-
.annotation { position: absolute; }
|
|
37
|
-
.annotation.selected { outline: 2px dashed #6366f1; outline-offset: 2px; }
|
|
38
|
-
|
|
39
|
-
.label-input {
|
|
40
|
-
background: none; border: none; color: inherit; font: inherit;
|
|
41
|
-
width: 100%; outline: none;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.hint {
|
|
45
|
-
position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
|
|
46
|
-
color: #666; font-size: 12px;
|
|
47
|
-
}
|
|
48
|
-
</style>
|
|
49
|
-
</head>
|
|
50
|
-
<body>
|
|
51
|
-
|
|
52
|
-
<div class="toolbar">
|
|
53
|
-
<span class="title">🥷 Annotation Editor</span>
|
|
54
|
-
<button id="tool-rect" class="active" onclick="setTool('rect')">▭ Rectangle</button>
|
|
55
|
-
<button id="tool-arrow" onclick="setTool('arrow')">→ Arrow</button>
|
|
56
|
-
<button id="tool-label" onclick="setTool('label')">T Label</button>
|
|
57
|
-
<div class="separator"></div>
|
|
58
|
-
<button onclick="deleteSelected()">🗑 Delete</button>
|
|
59
|
-
<button class="save-btn" onclick="save()">💾 Save</button>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
<div class="canvas-area">
|
|
63
|
-
<div class="editor-container" id="editor">
|
|
64
|
-
<img id="screenshot" />
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
<div class="hint">Click and drag to add annotations. Click to select. Press Delete to remove.</div>
|
|
69
|
-
|
|
70
|
-
<script>
|
|
71
|
-
let tool = 'rect';
|
|
72
|
-
let annotations = [];
|
|
73
|
-
let selectedId = null;
|
|
74
|
-
let dragState = null;
|
|
75
|
-
let nextId = 1;
|
|
76
|
-
|
|
77
|
-
const editor = document.getElementById('editor');
|
|
78
|
-
const img = document.getElementById('screenshot');
|
|
79
|
-
|
|
80
|
-
// Mouse position relative to the editor container (in CSS px, matches display)
|
|
81
|
-
function getPos(e) {
|
|
82
|
-
const r = editor.getBoundingClientRect();
|
|
83
|
-
return { x: e.clientX - r.left, y: e.clientY - r.top };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Ratio to convert display px → image natural px (for saving)
|
|
87
|
-
function getScale() {
|
|
88
|
-
return img.naturalWidth / editor.getBoundingClientRect().width;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
window.initEditor = (screenshotDataUrl, existingDecorations) => {
|
|
92
|
-
img.src = screenshotDataUrl;
|
|
93
|
-
img.onload = () => {
|
|
94
|
-
// Set editor width to fit window
|
|
95
|
-
const maxW = window.innerWidth - 80;
|
|
96
|
-
const maxH = window.innerHeight - 108;
|
|
97
|
-
const ratio = img.naturalWidth / img.naturalHeight;
|
|
98
|
-
let w = maxW;
|
|
99
|
-
if (w / ratio > maxH) w = maxH * ratio;
|
|
100
|
-
editor.style.width = w + 'px';
|
|
101
|
-
|
|
102
|
-
if (existingDecorations) {
|
|
103
|
-
existingDecorations.forEach(d => addFromDecoration(d));
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
function setTool(t) {
|
|
109
|
-
tool = t;
|
|
110
|
-
document.querySelectorAll('.toolbar button').forEach(b => b.classList.remove('active'));
|
|
111
|
-
document.getElementById('tool-' + t)?.classList.add('active');
|
|
112
|
-
deselectAll();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function deselectAll() {
|
|
116
|
-
selectedId = null;
|
|
117
|
-
document.querySelectorAll('.annotation').forEach(el => el.classList.remove('selected'));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function selectEl(el) {
|
|
121
|
-
deselectAll();
|
|
122
|
-
selectedId = parseInt(el.dataset.id);
|
|
123
|
-
el.classList.add('selected');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function deleteSelected() {
|
|
127
|
-
if (!selectedId) return;
|
|
128
|
-
const el = document.querySelector(`[data-id="${selectedId}"]`);
|
|
129
|
-
if (el) el.remove();
|
|
130
|
-
annotations = annotations.filter(a => a.id !== selectedId);
|
|
131
|
-
selectedId = null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
document.addEventListener('keydown', e => {
|
|
135
|
-
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
136
|
-
if (document.activeElement.classList?.contains('label-input')) return;
|
|
137
|
-
deleteSelected();
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// --- CREATE ---
|
|
142
|
-
editor.addEventListener('mousedown', e => {
|
|
143
|
-
if (e.target.closest('.annotation')) return;
|
|
144
|
-
const p = getPos(e);
|
|
145
|
-
deselectAll();
|
|
146
|
-
|
|
147
|
-
if (tool === 'rect') {
|
|
148
|
-
const id = nextId++;
|
|
149
|
-
const el = makeRect(id, p.x, p.y, 0, 0);
|
|
150
|
-
editor.appendChild(el);
|
|
151
|
-
dragState = { type: 'create-rect', id, el, sx: p.x, sy: p.y };
|
|
152
|
-
} else if (tool === 'arrow') {
|
|
153
|
-
const id = nextId++;
|
|
154
|
-
const el = makeArrow(id, p.x, p.y, p.x, p.y);
|
|
155
|
-
editor.appendChild(el);
|
|
156
|
-
dragState = { type: 'create-arrow', id, el, sx: p.x, sy: p.y };
|
|
157
|
-
} else if (tool === 'label') {
|
|
158
|
-
const id = nextId++;
|
|
159
|
-
annotations.push({ id, type: 'label', x: p.x, y: p.y, text: 'Label' });
|
|
160
|
-
const el = makeLabel(id, p.x, p.y, 'Label');
|
|
161
|
-
editor.appendChild(el);
|
|
162
|
-
selectEl(el);
|
|
163
|
-
el.querySelector('.label-input').focus();
|
|
164
|
-
el.querySelector('.label-input').select();
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
document.addEventListener('mousemove', e => {
|
|
169
|
-
if (!dragState) return;
|
|
170
|
-
const p = getPos(e);
|
|
171
|
-
|
|
172
|
-
if (dragState.type === 'create-rect') {
|
|
173
|
-
const el = dragState.el;
|
|
174
|
-
const w = p.x - dragState.sx;
|
|
175
|
-
const h = p.y - dragState.sy;
|
|
176
|
-
el.style.left = (w > 0 ? dragState.sx : p.x) + 'px';
|
|
177
|
-
el.style.top = (h > 0 ? dragState.sy : p.y) + 'px';
|
|
178
|
-
el.style.width = Math.abs(w) + 'px';
|
|
179
|
-
el.style.height = Math.abs(h) + 'px';
|
|
180
|
-
} else if (dragState.type === 'create-arrow') {
|
|
181
|
-
updateArrow(dragState.el, dragState.sx, dragState.sy, p.x, p.y);
|
|
182
|
-
} else if (dragState.type === 'move') {
|
|
183
|
-
const a = annotations.find(a => a.id === dragState.id);
|
|
184
|
-
if (!a) return;
|
|
185
|
-
a.x = p.x - dragState.ox;
|
|
186
|
-
a.y = p.y - dragState.oy;
|
|
187
|
-
dragState.el.style.left = a.x + 'px';
|
|
188
|
-
dragState.el.style.top = a.y + 'px';
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
document.addEventListener('mouseup', e => {
|
|
193
|
-
if (!dragState) return;
|
|
194
|
-
const p = getPos(e);
|
|
195
|
-
|
|
196
|
-
if (dragState.type === 'create-rect') {
|
|
197
|
-
const w = Math.abs(p.x - dragState.sx);
|
|
198
|
-
const h = Math.abs(p.y - dragState.sy);
|
|
199
|
-
if (w < 5 && h < 5) { dragState.el.remove(); }
|
|
200
|
-
else {
|
|
201
|
-
const a = { id: dragState.id, type: 'rect', x: Math.min(dragState.sx, p.x), y: Math.min(dragState.sy, p.y), width: w, height: h };
|
|
202
|
-
annotations.push(a);
|
|
203
|
-
selectEl(dragState.el);
|
|
204
|
-
}
|
|
205
|
-
} else if (dragState.type === 'create-arrow') {
|
|
206
|
-
const dist = Math.hypot(p.x - dragState.sx, p.y - dragState.sy);
|
|
207
|
-
if (dist < 5) { dragState.el.remove(); }
|
|
208
|
-
else {
|
|
209
|
-
annotations.push({ id: dragState.id, type: 'arrow', fromX: dragState.sx, fromY: dragState.sy, toX: p.x, toY: p.y });
|
|
210
|
-
selectEl(dragState.el);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
dragState = null;
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// --- ELEMENTS ---
|
|
217
|
-
function makeRect(id, x, y, w, h) {
|
|
218
|
-
const el = document.createElement('div');
|
|
219
|
-
el.className = 'annotation'; el.dataset.id = id;
|
|
220
|
-
el.style.cssText = `left:${x}px;top:${y}px;width:${w}px;height:${h}px;border:2px solid #FF0000;border-radius:4px;`;
|
|
221
|
-
el.addEventListener('mousedown', e => {
|
|
222
|
-
e.stopPropagation();
|
|
223
|
-
selectEl(el);
|
|
224
|
-
const a = annotations.find(a => a.id === parseInt(el.dataset.id));
|
|
225
|
-
if (a) {
|
|
226
|
-
const pp = getPos(e);
|
|
227
|
-
dragState = { type: 'move', id: a.id, el, ox: pp.x - a.x, oy: pp.y - a.y };
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
return el;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function makeArrow(id, x1, y1, x2, y2) {
|
|
234
|
-
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
235
|
-
svg.classList.add('annotation'); svg.dataset.id = id;
|
|
236
|
-
svg.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;';
|
|
237
|
-
svg.innerHTML = `
|
|
238
|
-
<defs><marker id="ah-${id}" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" fill="#FF0000">
|
|
239
|
-
<polygon points="0 0, 10 3.5, 0 7"/></marker></defs>
|
|
240
|
-
<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#FF0000" stroke-width="2" marker-end="url(#ah-${id})" style="pointer-events:stroke;cursor:pointer;" />`;
|
|
241
|
-
svg.querySelector('line').addEventListener('mousedown', e => {
|
|
242
|
-
e.stopPropagation();
|
|
243
|
-
selectEl(svg);
|
|
244
|
-
});
|
|
245
|
-
return svg;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function updateArrow(svg, x1, y1, x2, y2) {
|
|
249
|
-
const line = svg.querySelector('line');
|
|
250
|
-
line.setAttribute('x1', x1); line.setAttribute('y1', y1);
|
|
251
|
-
line.setAttribute('x2', x2); line.setAttribute('y2', y2);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function makeLabel(id, x, y, text) {
|
|
255
|
-
const el = document.createElement('div');
|
|
256
|
-
el.className = 'annotation'; el.dataset.id = id;
|
|
257
|
-
el.style.cssText = `left:${x}px;top:${y}px;padding:4px 8px;background:#fff;border:1px solid #FF0000;border-radius:4px;color:#FF0000;font-size:14px;min-width:40px;`;
|
|
258
|
-
el.innerHTML = `<input class="label-input" value="${text}" style="color:#FF0000;font-size:14px;" />`;
|
|
259
|
-
el.querySelector('.label-input').addEventListener('input', e => {
|
|
260
|
-
const a = annotations.find(a => a.id === parseInt(el.dataset.id));
|
|
261
|
-
if (a) a.text = e.target.value;
|
|
262
|
-
});
|
|
263
|
-
el.addEventListener('mousedown', e => {
|
|
264
|
-
if (e.target.classList.contains('label-input')) return;
|
|
265
|
-
e.stopPropagation();
|
|
266
|
-
selectEl(el);
|
|
267
|
-
const a = annotations.find(a => a.id === parseInt(el.dataset.id));
|
|
268
|
-
if (a) {
|
|
269
|
-
const pp = getPos(e);
|
|
270
|
-
dragState = { type: 'move', id: a.id, el, ox: pp.x - a.x, oy: pp.y - a.y };
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
return el;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// --- LOAD EXISTING ---
|
|
277
|
-
function addFromDecoration(d) {
|
|
278
|
-
const id = nextId++;
|
|
279
|
-
const s = getScale();
|
|
280
|
-
if (d.type === 'rect' && d.target && 'x' in d.target) {
|
|
281
|
-
const a = { id, type: 'rect', x: d.target.x/s, y: d.target.y/s, width: d.target.width/s, height: d.target.height/s };
|
|
282
|
-
annotations.push(a);
|
|
283
|
-
editor.appendChild(makeRect(id, a.x, a.y, a.width, a.height));
|
|
284
|
-
} else if (d.type === 'arrow' && 'x' in d.from && 'x' in d.to) {
|
|
285
|
-
const a = { id, type: 'arrow', fromX: d.from.x/s, fromY: d.from.y/s, toX: d.to.x/s, toY: d.to.y/s };
|
|
286
|
-
annotations.push(a);
|
|
287
|
-
editor.appendChild(makeArrow(id, a.fromX, a.fromY, a.toX, a.toY));
|
|
288
|
-
} else if (d.type === 'label' && 'x' in d.position) {
|
|
289
|
-
const a = { id, type: 'label', x: d.position.x/s, y: d.position.y/s, text: d.text };
|
|
290
|
-
annotations.push(a);
|
|
291
|
-
editor.appendChild(makeLabel(id, a.x, a.y, a.text));
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// --- SAVE ---
|
|
296
|
-
function save() {
|
|
297
|
-
const s = getScale();
|
|
298
|
-
const decorations = annotations.map(a => {
|
|
299
|
-
if (a.type === 'rect') {
|
|
300
|
-
return { type: 'rect', target: { x: Math.round(a.x*s), y: Math.round(a.y*s), width: Math.round(a.width*s), height: Math.round(a.height*s) }, style: { color: '#FF0000', strokeWidth: 2 } };
|
|
301
|
-
} else if (a.type === 'arrow') {
|
|
302
|
-
return { type: 'arrow', from: { x: Math.round(a.fromX*s), y: Math.round(a.fromY*s) }, to: { x: Math.round(a.toX*s), y: Math.round(a.toY*s) }, style: { color: '#FF0000', strokeWidth: 2 } };
|
|
303
|
-
} else if (a.type === 'label') {
|
|
304
|
-
return { type: 'label', text: a.text, position: { x: Math.round(a.x*s), y: Math.round(a.y*s) }, style: { fontSize: 14, color: '#FF0000', background: '#FFFFFF' } };
|
|
305
|
-
}
|
|
306
|
-
}).filter(Boolean);
|
|
307
|
-
|
|
308
|
-
window.__SAVED_DECORATIONS__ = decorations;
|
|
309
|
-
document.title = 'SAVED';
|
|
310
|
-
}
|
|
311
|
-
</script>
|
|
312
|
-
</body>
|
|
313
|
-
</html>
|