@wasao/kagemusha 0.1.1 → 0.2.0

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.
Files changed (104) hide show
  1. package/README.md +496 -57
  2. package/dist/commands/add.d.ts +6 -0
  3. package/dist/commands/add.d.ts.map +1 -0
  4. package/dist/commands/add.js +26 -0
  5. package/dist/commands/add.js.map +1 -0
  6. package/dist/commands/capture.d.ts +3 -2
  7. package/dist/commands/capture.d.ts.map +1 -1
  8. package/dist/commands/capture.js +234 -20
  9. package/dist/commands/capture.js.map +1 -1
  10. package/dist/commands/discover.d.ts +2 -0
  11. package/dist/commands/discover.d.ts.map +1 -0
  12. package/dist/commands/discover.js +62 -0
  13. package/dist/commands/discover.js.map +1 -0
  14. package/dist/commands/edit.d.ts.map +1 -1
  15. package/dist/commands/edit.js +40 -20
  16. package/dist/commands/edit.js.map +1 -1
  17. package/dist/commands/init.d.ts +1 -1
  18. package/dist/commands/init.d.ts.map +1 -1
  19. package/dist/commands/init.js +206 -102
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/list.d.ts +2 -0
  22. package/dist/commands/list.d.ts.map +1 -0
  23. package/dist/commands/list.js +33 -0
  24. package/dist/commands/list.js.map +1 -0
  25. package/dist/commands/login.d.ts +6 -0
  26. package/dist/commands/login.d.ts.map +1 -0
  27. package/dist/commands/login.js +142 -0
  28. package/dist/commands/login.js.map +1 -0
  29. package/dist/commands/validate.js +1 -1
  30. package/dist/commands/validate.js.map +1 -1
  31. package/dist/editor/inject-script.js +407 -33
  32. package/dist/editor/inject-script.js.map +1 -1
  33. package/dist/index.js +27 -16
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/annotate.d.ts +2 -2
  36. package/dist/lib/annotate.d.ts.map +1 -1
  37. package/dist/lib/annotate.js +35 -43
  38. package/dist/lib/annotate.js.map +1 -1
  39. package/dist/lib/auth.d.ts +18 -0
  40. package/dist/lib/auth.d.ts.map +1 -0
  41. package/dist/lib/auth.js +45 -0
  42. package/dist/lib/auth.js.map +1 -0
  43. package/dist/lib/aws-error.d.ts +7 -0
  44. package/dist/lib/aws-error.d.ts.map +1 -0
  45. package/dist/lib/aws-error.js +74 -0
  46. package/dist/lib/aws-error.js.map +1 -0
  47. package/dist/lib/canonical.d.ts +22 -0
  48. package/dist/lib/canonical.d.ts.map +1 -0
  49. package/dist/lib/canonical.js +92 -0
  50. package/dist/lib/canonical.js.map +1 -0
  51. package/dist/lib/config.d.ts +2 -0
  52. package/dist/lib/config.d.ts.map +1 -1
  53. package/dist/lib/config.js +23 -13
  54. package/dist/lib/config.js.map +1 -1
  55. package/dist/lib/crawl.d.ts +1 -1
  56. package/dist/lib/crawl.d.ts.map +1 -1
  57. package/dist/lib/crawl.js +213 -20
  58. package/dist/lib/crawl.js.map +1 -1
  59. package/dist/lib/definition.d.ts +2 -0
  60. package/dist/lib/definition.d.ts.map +1 -0
  61. package/dist/lib/definition.js +6 -0
  62. package/dist/lib/definition.js.map +1 -0
  63. package/dist/lib/diff.d.ts +52 -0
  64. package/dist/lib/diff.d.ts.map +1 -0
  65. package/dist/lib/diff.js +41 -0
  66. package/dist/lib/diff.js.map +1 -0
  67. package/dist/lib/login-error.d.ts +10 -0
  68. package/dist/lib/login-error.d.ts.map +1 -0
  69. package/dist/lib/login-error.js +13 -0
  70. package/dist/lib/login-error.js.map +1 -0
  71. package/dist/lib/screenshot.d.ts +8 -2
  72. package/dist/lib/screenshot.d.ts.map +1 -1
  73. package/dist/lib/screenshot.js +44 -61
  74. package/dist/lib/screenshot.js.map +1 -1
  75. package/dist/lib/staging.d.ts +8 -0
  76. package/dist/lib/staging.d.ts.map +1 -0
  77. package/dist/lib/staging.js +24 -0
  78. package/dist/lib/staging.js.map +1 -0
  79. package/dist/types.d.ts +5 -23
  80. package/dist/types.d.ts.map +1 -1
  81. package/package.json +18 -11
  82. package/dist/commands/preview.d.ts +0 -6
  83. package/dist/commands/preview.d.ts.map +0 -1
  84. package/dist/commands/preview.js +0 -33
  85. package/dist/commands/preview.js.map +0 -1
  86. package/dist/commands/run.d.ts +0 -6
  87. package/dist/commands/run.d.ts.map +0 -1
  88. package/dist/commands/run.js +0 -39
  89. package/dist/commands/run.js.map +0 -1
  90. package/dist/editor/editor/editor.html +0 -313
  91. package/dist/editor/editor/inject.ts +0 -385
  92. package/dist/editor/editor.html +0 -338
  93. package/dist/editor/inject-script.cjs +0 -398
  94. package/dist/editor/inject-script.cjs.map +0 -1
  95. package/dist/editor/inject-script.d.cts +0 -2
  96. package/dist/editor/inject-script.d.cts.map +0 -1
  97. package/dist/editor/inject.d.ts +0 -2
  98. package/dist/editor/inject.d.ts.map +0 -1
  99. package/dist/editor/inject.js +0 -385
  100. package/dist/editor/inject.js.map +0 -1
  101. package/dist/lib/upload.d.ts +0 -9
  102. package/dist/lib/upload.d.ts.map +0 -1
  103. package/dist/lib/upload.js +0 -43
  104. package/dist/lib/upload.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACN,eAAe,EACf,UAAU,EACV,eAAe,EACf,cAAc,EACd,kBAAkB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,eAAe;IACpC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;IAEvD,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,IAAI,CAAC;QACJ,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;QAEtC,kBAAkB;QAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,SAAS,GAAG,IAAI,CAAC;YACjB,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC;YACtC,CAAC;QACF,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,uBAAuB;QACvB,MAAM,WAAW,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,IAAI,CAAC,cAAc,WAAW,CAAC,MAAM,mBAAmB,CAAC,CAC/D,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,SAAS,GAAG,IAAI,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;gBACrD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;oBAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC;gBACxC,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC3C,CAAC;QACF,CAAC;QAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,MAAM,CAAC,mDAAmD,CAAC,CACjE,CAAC;QACH,CAAC;IACF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,SAAS,GAAG,IAAI,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAQ,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC3D,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACN,eAAe,EACf,UAAU,EACV,eAAe,EACf,cAAc,EACd,kBAAkB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,eAAe;IACpC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;IAEvD,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,IAAI,CAAC;QACJ,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;QAEtC,kBAAkB;QAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,SAAS,GAAG,IAAI,CAAC;YACjB,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC;YACtC,CAAC;QACF,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,uBAAuB;QACvB,MAAM,WAAW,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,IAAI,CAAC,cAAc,WAAW,CAAC,MAAM,mBAAmB,CAAC,CAC/D,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,SAAS,GAAG,IAAI,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC;gBACrD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;oBAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC;gBACxC,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC3C,CAAC;QACF,CAAC;QAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,MAAM,CAAC,uDAAuD,CAAC,CACrE,CAAC;QACH,CAAC;IACF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,SAAS,GAAG,IAAI,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAQ,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC3D,CAAC;AACF,CAAC"}
@@ -3,13 +3,20 @@
3
3
  // This is a plain script (not a module) — no imports/exports.
4
4
  // Type helpers for window properties set by edit.ts
5
5
  const _win = window;
6
- const TOOLBAR_HEIGHT = 48;
6
+ const TOOLBAR_HEIGHT_FALLBACK = 48;
7
+ let toolbarHeight = TOOLBAR_HEIGHT_FALLBACK;
7
8
  const svgNS = "http://www.w3.org/2000/svg";
8
9
  let tool = "rect";
9
10
  let annotations = [];
10
11
  let selectedId = null;
11
12
  let dragState = null;
12
13
  let nextId = 1;
14
+ // Capture state — persisted on save (in page CSS pixels, NOT DPR-scaled)
15
+ let captureMode = "fullPage";
16
+ let captureCrop = null;
17
+ let cropDragState = null;
18
+ const HANDLE_SIZE = 10;
19
+ const MIN_CROP = 10;
13
20
  // --- TOOLBAR ---
14
21
  const toolbar = document.createElement("div");
15
22
  toolbar.id = "kagemusha-toolbar";
@@ -17,17 +24,20 @@ toolbar.innerHTML = `
17
24
  <style>
18
25
  #kagemusha-toolbar {
19
26
  position: fixed; top: 0; left: 0; right: 0; z-index: 999999;
20
- background: #16213e; padding: 8px 16px; display: flex; align-items: center; gap: 12px;
27
+ background: #16213e; padding: 8px 16px; display: flex; align-items: center; gap: 10px;
21
28
  box-shadow: 0 2px 8px rgba(0,0,0,0.3); font-family: -apple-system, sans-serif;
29
+ flex-wrap: nowrap; overflow-x: auto;
22
30
  }
23
31
  #kagemusha-toolbar button {
24
- padding: 6px 14px; border: 1px solid #444; border-radius: 6px;
32
+ padding: 6px 12px; border: 1px solid #444; border-radius: 6px;
25
33
  background: #1a1a2e; color: #fff; font-size: 13px; cursor: pointer;
26
34
  }
27
35
  #kagemusha-toolbar button:hover { background: #2a2a4e; }
28
36
  #kagemusha-toolbar button.active { background: #6366f1; border-color: #6366f1; }
37
+ #kagemusha-toolbar button.cap-btn.active { background: #0ea5e9; border-color: #0ea5e9; }
29
38
  #kagemusha-toolbar .sep { width: 1px; height: 24px; background: #444; }
30
39
  #kagemusha-toolbar .title { color: #888; font-size: 13px; }
40
+ #kagemusha-toolbar .group-label { color: #7a89b0; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; margin-right: -4px; }
31
41
  #kagemusha-toolbar .save-btn { background: #22c55e; border-color: #22c55e; font-weight: 600; margin-left: auto; }
32
42
  #kagemusha-toolbar .save-btn:hover { background: #16a34a; }
33
43
  #kagemusha-svg-layer {
@@ -35,16 +45,31 @@ toolbar.innerHTML = `
35
45
  z-index: 999998; pointer-events: none;
36
46
  }
37
47
  #kagemusha-svg-layer.drawing { pointer-events: auto; cursor: crosshair; }
48
+ #kagemusha-svg-layer.cropping { pointer-events: auto; cursor: crosshair; }
38
49
  #kagemusha-svg-layer .annotation { pointer-events: auto; cursor: move; }
50
+ #kagemusha-svg-layer.cropping .annotation { pointer-events: none; }
39
51
  #kagemusha-svg-layer .annotation.selected { filter: drop-shadow(0 0 3px #6366f1); }
52
+ #kagemusha-svg-layer .capture-crop-box { fill: rgba(14,165,233,0.10); stroke: #0ea5e9; stroke-width: 2; stroke-dasharray: 8 4; pointer-events: none; }
53
+ #kagemusha-svg-layer.cropping .capture-crop-box { pointer-events: auto; cursor: move; }
54
+ #kagemusha-svg-layer .crop-handle { fill: #fff; stroke: #0ea5e9; stroke-width: 2; pointer-events: none; }
55
+ #kagemusha-svg-layer.cropping .crop-handle { pointer-events: auto; }
56
+ #kagemusha-svg-layer .crop-handle.nw, #kagemusha-svg-layer .crop-handle.se { cursor: nwse-resize; }
57
+ #kagemusha-svg-layer .crop-handle.ne, #kagemusha-svg-layer .crop-handle.sw { cursor: nesw-resize; }
58
+ #kagemusha-svg-layer .crop-handle.n, #kagemusha-svg-layer .crop-handle.s { cursor: ns-resize; }
59
+ #kagemusha-svg-layer .crop-handle.e, #kagemusha-svg-layer .crop-handle.w { cursor: ew-resize; }
40
60
  .kagemusha-hint {
41
61
  position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
42
62
  color: #fff; background: rgba(0,0,0,0.7); padding: 6px 16px; border-radius: 8px;
43
63
  font-size: 12px; z-index: 999999; font-family: -apple-system, sans-serif;
44
64
  }
45
65
  </style>
46
- <span class="title">🥷 Annotation Editor</span>
47
- <button id="kg-tool-rect" class="active">▭ Rectangle</button>
66
+ <span class="title">🥷</span>
67
+ <span class="group-label">Capture</span>
68
+ <button id="kg-cap-full" class="cap-btn active">📷 Full</button>
69
+ <button id="kg-cap-crop" class="cap-btn">✂️ Crop</button>
70
+ <div class="sep"></div>
71
+ <span class="group-label">Annotate</span>
72
+ <button id="kg-tool-rect" class="active">▭ Rect</button>
48
73
  <button id="kg-tool-arrow">→ Arrow</button>
49
74
  <button id="kg-tool-label">T Label</button>
50
75
  <div class="sep"></div>
@@ -52,10 +77,13 @@ toolbar.innerHTML = `
52
77
  <button class="save-btn" id="kg-save">💾 Save</button>
53
78
  `;
54
79
  document.body.appendChild(toolbar);
55
- document.body.style.paddingTop = `${TOOLBAR_HEIGHT}px`;
80
+ toolbarHeight =
81
+ Math.ceil(toolbar.getBoundingClientRect().height) || TOOLBAR_HEIGHT_FALLBACK;
82
+ document.body.style.paddingTop = `${toolbarHeight}px`;
56
83
  const hint = document.createElement("div");
57
84
  hint.className = "kagemusha-hint";
58
- hint.textContent = "Click and drag to add annotations. Click to select. Press Delete to remove.";
85
+ hint.textContent =
86
+ "Click and drag to add annotations. Click to select. Press Delete to remove.";
59
87
  document.body.appendChild(hint);
60
88
  // --- SVG LAYER ---
61
89
  const svg = document.createElementNS(svgNS, "svg");
@@ -69,13 +97,20 @@ const updateSvgSize = () => {
69
97
  updateSvgSize();
70
98
  window.addEventListener("resize", updateSvgSize);
71
99
  const defs = document.createElementNS(svgNS, "defs");
72
- defs.innerHTML = '<marker id="kg-arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" fill="#FF0000"><polygon points="0 0, 10 3.5, 0 7"/></marker>';
100
+ defs.innerHTML =
101
+ '<marker id="kg-arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" fill="#FF0000"><polygon points="0 0, 10 3.5, 0 7"/></marker>';
73
102
  svg.appendChild(defs);
103
+ // Capture layer goes BEFORE annotations so it renders behind them
104
+ const captureGroup = document.createElementNS(svgNS, "g");
105
+ captureGroup.id = "kagemusha-capture-group";
106
+ svg.appendChild(captureGroup);
74
107
  // --- HELPERS ---
75
108
  const getPos = (e) => ({ x: e.pageX, y: e.pageY });
76
109
  const deselectAll = () => {
77
110
  selectedId = null;
78
- svg.querySelectorAll(".annotation").forEach((el) => { el.classList.remove("selected"); });
111
+ svg.querySelectorAll(".annotation").forEach((el) => {
112
+ el.classList.remove("selected");
113
+ });
79
114
  };
80
115
  const selectEl = (el) => {
81
116
  deselectAll();
@@ -98,7 +133,13 @@ const startMove = (e, id) => {
98
133
  return;
99
134
  selectEl(el);
100
135
  const p = getPos(e);
101
- dragState = { type: "move", id, el: el, lastX: p.x, lastY: p.y };
136
+ dragState = {
137
+ type: "move",
138
+ id,
139
+ el: el,
140
+ lastX: p.x,
141
+ lastY: p.y,
142
+ };
102
143
  };
103
144
  const measureTextWidth = (text, fontSize) => {
104
145
  const tmp = document.createElementNS(svgNS, "text");
@@ -133,19 +174,111 @@ const createLabelGroup = (id, x, y, text, fontSize = 14) => {
133
174
  g.appendChild(txt);
134
175
  return g;
135
176
  };
177
+ // --- CAPTURE HELPERS ---
178
+ const clearCaptureVisual = () => {
179
+ captureGroup.replaceChildren();
180
+ };
181
+ const HANDLE_POSITIONS = [
182
+ { handle: "nw", pos: (c) => ({ cx: c.x, cy: c.y }) },
183
+ { handle: "n", pos: (c) => ({ cx: c.x + c.w / 2, cy: c.y }) },
184
+ { handle: "ne", pos: (c) => ({ cx: c.x + c.w, cy: c.y }) },
185
+ { handle: "e", pos: (c) => ({ cx: c.x + c.w, cy: c.y + c.h / 2 }) },
186
+ { handle: "se", pos: (c) => ({ cx: c.x + c.w, cy: c.y + c.h }) },
187
+ { handle: "s", pos: (c) => ({ cx: c.x + c.w / 2, cy: c.y + c.h }) },
188
+ { handle: "sw", pos: (c) => ({ cx: c.x, cy: c.y + c.h }) },
189
+ { handle: "w", pos: (c) => ({ cx: c.x, cy: c.y + c.h / 2 }) },
190
+ ];
191
+ const redrawCropVisual = () => {
192
+ if (captureMode !== "crop" || !captureCrop) {
193
+ clearCaptureVisual();
194
+ return;
195
+ }
196
+ clearCaptureVisual();
197
+ const r = document.createElementNS(svgNS, "rect");
198
+ r.setAttribute("x", String(captureCrop.x));
199
+ r.setAttribute("y", String(captureCrop.y));
200
+ r.setAttribute("width", String(captureCrop.w));
201
+ r.setAttribute("height", String(captureCrop.h));
202
+ r.setAttribute("class", "capture-crop-box");
203
+ captureGroup.appendChild(r);
204
+ for (const { handle, pos } of HANDLE_POSITIONS) {
205
+ const { cx, cy } = pos(captureCrop);
206
+ const h = document.createElementNS(svgNS, "rect");
207
+ h.setAttribute("x", String(cx - HANDLE_SIZE / 2));
208
+ h.setAttribute("y", String(cy - HANDLE_SIZE / 2));
209
+ h.setAttribute("width", String(HANDLE_SIZE));
210
+ h.setAttribute("height", String(HANDLE_SIZE));
211
+ h.setAttribute("class", `crop-handle ${handle}`);
212
+ h.dataset.handle = handle;
213
+ captureGroup.appendChild(h);
214
+ }
215
+ };
216
+ const updateCaptureUi = () => {
217
+ document
218
+ .querySelectorAll("#kagemusha-toolbar .cap-btn")
219
+ .forEach((b) => {
220
+ b.classList.remove("active");
221
+ });
222
+ const activeId = captureMode === "fullPage" ? "kg-cap-full" : "kg-cap-crop";
223
+ document.getElementById(activeId)?.classList.add("active");
224
+ svg.classList.toggle("cropping", tool === "crop");
225
+ svg.classList.toggle("drawing", tool === "rect" || tool === "arrow" || tool === "label");
226
+ };
227
+ const setCaptureMode = (mode, opts = {}) => {
228
+ captureMode = mode;
229
+ if (mode === "fullPage") {
230
+ captureCrop = null;
231
+ tool = "rect";
232
+ clearCaptureVisual();
233
+ }
234
+ else {
235
+ if (opts.resetSelection)
236
+ captureCrop = null;
237
+ tool = "crop";
238
+ redrawCropVisual();
239
+ }
240
+ // Mark the matching annotation tool button too
241
+ document
242
+ .querySelectorAll("#kg-tool-rect, #kg-tool-arrow, #kg-tool-label")
243
+ .forEach((b) => {
244
+ b.classList.remove("active");
245
+ });
246
+ if (tool === "rect" || tool === "arrow" || tool === "label") {
247
+ document.getElementById(`kg-tool-${tool}`)?.classList.add("active");
248
+ }
249
+ updateCaptureUi();
250
+ deselectAll();
251
+ };
136
252
  // --- TOOLS ---
137
253
  const setTool = (t) => {
138
254
  tool = t;
139
- document.querySelectorAll("#kagemusha-toolbar button").forEach((b) => { b.classList.remove("active"); });
255
+ document
256
+ .querySelectorAll("#kg-tool-rect, #kg-tool-arrow, #kg-tool-label")
257
+ .forEach((b) => {
258
+ b.classList.remove("active");
259
+ });
140
260
  document.getElementById(`kg-tool-${t}`)?.classList.add("active");
141
- svg.classList.toggle("drawing", true);
261
+ // Switching to an annotation tool exits capture-edit interaction
262
+ // (capture settings themselves are preserved).
263
+ updateCaptureUi();
142
264
  deselectAll();
143
265
  };
144
- document.getElementById("kg-tool-rect")?.addEventListener("click", () => setTool("rect"));
145
- document.getElementById("kg-tool-arrow")?.addEventListener("click", () => setTool("arrow"));
146
- document.getElementById("kg-tool-label")?.addEventListener("click", () => setTool("label"));
266
+ document
267
+ .getElementById("kg-tool-rect")
268
+ ?.addEventListener("click", () => setTool("rect"));
269
+ document
270
+ .getElementById("kg-tool-arrow")
271
+ ?.addEventListener("click", () => setTool("arrow"));
272
+ document
273
+ .getElementById("kg-tool-label")
274
+ ?.addEventListener("click", () => setTool("label"));
275
+ document
276
+ .getElementById("kg-cap-full")
277
+ ?.addEventListener("click", () => setCaptureMode("fullPage"));
278
+ document
279
+ .getElementById("kg-cap-crop")
280
+ ?.addEventListener("click", () => setCaptureMode("crop", { resetSelection: !captureCrop }));
147
281
  document.getElementById("kg-delete")?.addEventListener("click", deleteSelected);
148
- document.getElementById("kg-save")?.addEventListener("click", save);
149
282
  document.addEventListener("keydown", (e) => {
150
283
  if (e.key === "Delete" || e.key === "Backspace") {
151
284
  if (document.activeElement?.tagName === "INPUT")
@@ -158,6 +291,45 @@ svg.addEventListener("mousedown", (e) => {
158
291
  if (e.target?.closest(".annotation"))
159
292
  return;
160
293
  const p = getPos(e);
294
+ if (tool === "crop") {
295
+ e.preventDefault();
296
+ deselectAll();
297
+ const target = e.target;
298
+ const handleAttr = target.dataset?.handle;
299
+ // Resize from a handle
300
+ if (handleAttr && captureCrop) {
301
+ cropDragState = {
302
+ kind: "resize",
303
+ handle: handleAttr,
304
+ sx: p.x,
305
+ sy: p.y,
306
+ orig: { ...captureCrop },
307
+ };
308
+ return;
309
+ }
310
+ // Move the existing crop body
311
+ if (target?.classList?.contains("capture-crop-box") && captureCrop) {
312
+ cropDragState = {
313
+ kind: "move",
314
+ sx: p.x,
315
+ sy: p.y,
316
+ orig: { ...captureCrop },
317
+ };
318
+ return;
319
+ }
320
+ // Otherwise: drag-to-create new crop (replaces existing)
321
+ cropDragState = { kind: "create", sx: p.x, sy: p.y };
322
+ captureCrop = null;
323
+ clearCaptureVisual();
324
+ const r = document.createElementNS(svgNS, "rect");
325
+ r.setAttribute("x", String(p.x));
326
+ r.setAttribute("y", String(p.y));
327
+ r.setAttribute("width", "0");
328
+ r.setAttribute("height", "0");
329
+ r.setAttribute("class", "capture-crop-box");
330
+ captureGroup.appendChild(r);
331
+ return;
332
+ }
161
333
  deselectAll();
162
334
  if (tool === "rect") {
163
335
  const id = `a${nextId++}`;
@@ -196,7 +368,8 @@ svg.addEventListener("mousedown", (e) => {
196
368
  input.type = "text";
197
369
  input.value = "";
198
370
  input.placeholder = "Type label...";
199
- input.style.cssText = "position:fixed;z-index:9999999;padding:4px 8px;background:#fff;border:none;border-radius:4px;color:#FF0000;font-size:14px;font-family:-apple-system,sans-serif;outline:2px solid #6366f1;min-width:80px;box-shadow:0 2px 8px rgba(0,0,0,0.2);";
371
+ input.style.cssText =
372
+ "position:fixed;z-index:9999999;padding:4px 8px;background:#fff;border:none;border-radius:4px;color:#FF0000;font-size:14px;font-family:-apple-system,sans-serif;outline:2px solid #6366f1;min-width:80px;box-shadow:0 2px 8px rgba(0,0,0,0.2);";
200
373
  input.style.left = `${e.clientX}px`;
201
374
  input.style.top = `${e.clientY}px`;
202
375
  document.body.appendChild(input);
@@ -232,6 +405,72 @@ svg.addEventListener("mousedown", (e) => {
232
405
  });
233
406
  // --- MOUSE: DRAG ---
234
407
  document.addEventListener("mousemove", (e) => {
408
+ if (cropDragState) {
409
+ const p = getPos(e);
410
+ if (cropDragState.kind === "create") {
411
+ const x = Math.min(cropDragState.sx, p.x);
412
+ const y = Math.min(cropDragState.sy, p.y);
413
+ const w = Math.abs(p.x - cropDragState.sx);
414
+ const h = Math.abs(p.y - cropDragState.sy);
415
+ const r = captureGroup.firstChild;
416
+ if (r) {
417
+ r.setAttribute("x", String(x));
418
+ r.setAttribute("y", String(y));
419
+ r.setAttribute("width", String(w));
420
+ r.setAttribute("height", String(h));
421
+ }
422
+ return;
423
+ }
424
+ if (cropDragState.kind === "move") {
425
+ const dx = p.x - cropDragState.sx;
426
+ const dy = p.y - cropDragState.sy;
427
+ captureCrop = {
428
+ x: cropDragState.orig.x + dx,
429
+ y: cropDragState.orig.y + dy,
430
+ w: cropDragState.orig.w,
431
+ h: cropDragState.orig.h,
432
+ };
433
+ redrawCropVisual();
434
+ return;
435
+ }
436
+ // resize
437
+ const dx = p.x - cropDragState.sx;
438
+ const dy = p.y - cropDragState.sy;
439
+ const o = cropDragState.orig;
440
+ let nx = o.x;
441
+ let ny = o.y;
442
+ let nw = o.w;
443
+ let nh = o.h;
444
+ const h = cropDragState.handle;
445
+ if (h.includes("w")) {
446
+ nx = o.x + dx;
447
+ nw = o.w - dx;
448
+ }
449
+ if (h.includes("e")) {
450
+ nw = o.w + dx;
451
+ }
452
+ if (h.includes("n")) {
453
+ ny = o.y + dy;
454
+ nh = o.h - dy;
455
+ }
456
+ if (h.includes("s")) {
457
+ nh = o.h + dy;
458
+ }
459
+ // Keep min size and prevent flip-through
460
+ if (nw < MIN_CROP) {
461
+ if (h.includes("w"))
462
+ nx = o.x + (o.w - MIN_CROP);
463
+ nw = MIN_CROP;
464
+ }
465
+ if (nh < MIN_CROP) {
466
+ if (h.includes("n"))
467
+ ny = o.y + (o.h - MIN_CROP);
468
+ nh = MIN_CROP;
469
+ }
470
+ captureCrop = { x: nx, y: ny, w: nw, h: nh };
471
+ redrawCropVisual();
472
+ return;
473
+ }
235
474
  if (!dragState)
236
475
  return;
237
476
  const p = getPos(e);
@@ -283,6 +522,28 @@ document.addEventListener("mousemove", (e) => {
283
522
  });
284
523
  // --- MOUSE: UP ---
285
524
  document.addEventListener("mouseup", (e) => {
525
+ if (cropDragState) {
526
+ if (cropDragState.kind === "create") {
527
+ const p = getPos(e);
528
+ const w = Math.abs(p.x - cropDragState.sx);
529
+ const h = Math.abs(p.y - cropDragState.sy);
530
+ if (w < 5 || h < 5) {
531
+ cropDragState = null;
532
+ redrawCropVisual();
533
+ return;
534
+ }
535
+ captureCrop = {
536
+ x: Math.min(cropDragState.sx, p.x),
537
+ y: Math.min(cropDragState.sy, p.y),
538
+ w,
539
+ h,
540
+ };
541
+ }
542
+ // move / resize: state is already applied via redrawCropVisual on mousemove
543
+ cropDragState = null;
544
+ redrawCropVisual();
545
+ return;
546
+ }
286
547
  if (!dragState)
287
548
  return;
288
549
  const p = getPos(e);
@@ -293,7 +554,14 @@ document.addEventListener("mouseup", (e) => {
293
554
  dragState.el.remove();
294
555
  }
295
556
  else {
296
- const a = { id: dragState.id, type: "rect", x: Math.min(dragState.sx ?? 0, p.x), y: Math.min(dragState.sy ?? 0, p.y), width: w, height: h };
557
+ const a = {
558
+ id: dragState.id,
559
+ type: "rect",
560
+ x: Math.min(dragState.sx ?? 0, p.x),
561
+ y: Math.min(dragState.sy ?? 0, p.y),
562
+ width: w,
563
+ height: h,
564
+ };
297
565
  annotations.push(a);
298
566
  selectEl(dragState.el);
299
567
  const capturedId = dragState.id;
@@ -306,7 +574,14 @@ document.addEventListener("mouseup", (e) => {
306
574
  dragState.el.remove();
307
575
  }
308
576
  else {
309
- const a = { id: dragState.id, type: "arrow", fromX: dragState.sx, fromY: dragState.sy, toX: p.x, toY: p.y };
577
+ const a = {
578
+ id: dragState.id,
579
+ type: "arrow",
580
+ fromX: dragState.sx,
581
+ fromY: dragState.sy,
582
+ toX: p.x,
583
+ toY: p.y,
584
+ };
310
585
  annotations.push(a);
311
586
  selectEl(dragState.el);
312
587
  const capturedId = dragState.id;
@@ -317,12 +592,12 @@ document.addEventListener("mouseup", (e) => {
317
592
  });
318
593
  // --- LOAD EXISTING ---
319
594
  _win.__kagemusha_loadAnnotations = (decorations) => {
320
- const dpr = _win.__kagemusha_dpr || 1;
595
+ const dpr = window.devicePixelRatio || 1;
321
596
  for (const d of decorations) {
322
597
  const id = `a${nextId++}`;
323
598
  if (d.type === "rect" && d.target) {
324
599
  const rx = d.target.x / dpr;
325
- const ry = d.target.y / dpr + TOOLBAR_HEIGHT;
600
+ const ry = d.target.y / dpr + toolbarHeight;
326
601
  const rw = d.target.width / dpr;
327
602
  const rh = d.target.height / dpr;
328
603
  const rect = document.createElementNS(svgNS, "rect");
@@ -337,14 +612,21 @@ _win.__kagemusha_loadAnnotations = (decorations) => {
337
612
  rect.classList.add("annotation");
338
613
  rect.dataset.id = id;
339
614
  svg.appendChild(rect);
340
- annotations.push({ id, type: "rect", x: rx, y: ry, width: rw, height: rh });
615
+ annotations.push({
616
+ id,
617
+ type: "rect",
618
+ x: rx,
619
+ y: ry,
620
+ width: rw,
621
+ height: rh,
622
+ });
341
623
  rect.addEventListener("mousedown", (ev) => startMove(ev, id));
342
624
  }
343
625
  else if (d.type === "arrow" && d.from && d.to) {
344
626
  const ax1 = d.from.x / dpr;
345
- const ay1 = d.from.y / dpr + TOOLBAR_HEIGHT;
627
+ const ay1 = d.from.y / dpr + toolbarHeight;
346
628
  const ax2 = d.to.x / dpr;
347
- const ay2 = d.to.y / dpr + TOOLBAR_HEIGHT;
629
+ const ay2 = d.to.y / dpr + toolbarHeight;
348
630
  const line = document.createElementNS(svgNS, "line");
349
631
  line.setAttribute("x1", String(ax1));
350
632
  line.setAttribute("y1", String(ay1));
@@ -356,12 +638,19 @@ _win.__kagemusha_loadAnnotations = (decorations) => {
356
638
  line.classList.add("annotation");
357
639
  line.dataset.id = id;
358
640
  svg.appendChild(line);
359
- annotations.push({ id, type: "arrow", fromX: ax1, fromY: ay1, toX: ax2, toY: ay2 });
641
+ annotations.push({
642
+ id,
643
+ type: "arrow",
644
+ fromX: ax1,
645
+ fromY: ay1,
646
+ toX: ax2,
647
+ toY: ay2,
648
+ });
360
649
  line.addEventListener("mousedown", (ev) => startMove(ev, id));
361
650
  }
362
651
  else if (d.type === "label" && d.position) {
363
652
  const lx = d.position.x / dpr;
364
- const ly = d.position.y / dpr + TOOLBAR_HEIGHT;
653
+ const ly = d.position.y / dpr + toolbarHeight;
365
654
  const fontSize = (d.style?.fontSize ?? 14) / dpr;
366
655
  const g = createLabelGroup(id, lx, ly, d.text ?? "", fontSize);
367
656
  svg.appendChild(g);
@@ -370,25 +659,110 @@ _win.__kagemusha_loadAnnotations = (decorations) => {
370
659
  }
371
660
  }
372
661
  };
662
+ // --- LOAD EXISTING CAPTURE CONFIG ---
663
+ _win.__kagemusha_loadCapture = (capture) => {
664
+ if (!capture ||
665
+ !["fullPage", "crop"].includes(capture.mode ?? "")) {
666
+ // Unknown / legacy mode (e.g. removed "selector") — fall back to fullPage
667
+ setCaptureMode("fullPage");
668
+ setTool("rect");
669
+ return;
670
+ }
671
+ if (capture.mode === "fullPage") {
672
+ setCaptureMode("fullPage");
673
+ // Leave annotation tool active after load
674
+ setTool("rect");
675
+ return;
676
+ }
677
+ if (capture.mode === "crop") {
678
+ // crop is stored in page CSS pixels (not DPR-scaled); shift by toolbar for display
679
+ const sx = capture.crop.start.x;
680
+ const sy = capture.crop.start.y + toolbarHeight;
681
+ const ex = capture.crop.end.x;
682
+ const ey = capture.crop.end.y + toolbarHeight;
683
+ captureCrop = { x: sx, y: sy, w: ex - sx, h: ey - sy };
684
+ captureMode = "crop";
685
+ tool = "rect";
686
+ redrawCropVisual();
687
+ updateCaptureUi();
688
+ }
689
+ };
373
690
  // --- SAVE ---
374
- function save() {
375
- const dpr = _win.__kagemusha_dpr || 1;
691
+ const save = () => {
692
+ if (captureMode === "crop" && !captureCrop) {
693
+ alert("Crop mode is active but no area is drawn.\nDrag to define an area, or switch to Full Page.");
694
+ return;
695
+ }
696
+ const dpr = window.devicePixelRatio || 1;
376
697
  const s = Math.round;
377
698
  const decorations = annotations
378
699
  .map((a) => {
379
700
  if (a.type === "rect") {
380
- return { type: "rect", target: { x: s((a.x ?? 0) * dpr), y: s(((a.y ?? 0) - TOOLBAR_HEIGHT) * dpr), width: s((a.width ?? 0) * dpr), height: s((a.height ?? 0) * dpr) }, style: { color: "#FF0000", strokeWidth: s(3 * dpr) } };
701
+ return {
702
+ type: "rect",
703
+ target: {
704
+ x: s((a.x ?? 0) * dpr),
705
+ y: s(((a.y ?? 0) - toolbarHeight) * dpr),
706
+ width: s((a.width ?? 0) * dpr),
707
+ height: s((a.height ?? 0) * dpr),
708
+ },
709
+ style: { color: "#FF0000", strokeWidth: s(3 * dpr) },
710
+ };
381
711
  }
382
712
  if (a.type === "arrow") {
383
- return { type: "arrow", from: { x: s((a.fromX ?? 0) * dpr), y: s(((a.fromY ?? 0) - TOOLBAR_HEIGHT) * dpr) }, to: { x: s((a.toX ?? 0) * dpr), y: s(((a.toY ?? 0) - TOOLBAR_HEIGHT) * dpr) }, style: { color: "#FF0000", strokeWidth: s(3 * dpr) } };
713
+ return {
714
+ type: "arrow",
715
+ from: {
716
+ x: s((a.fromX ?? 0) * dpr),
717
+ y: s(((a.fromY ?? 0) - toolbarHeight) * dpr),
718
+ },
719
+ to: {
720
+ x: s((a.toX ?? 0) * dpr),
721
+ y: s(((a.toY ?? 0) - toolbarHeight) * dpr),
722
+ },
723
+ style: { color: "#FF0000", strokeWidth: s(3 * dpr) },
724
+ };
384
725
  }
385
726
  if (a.type === "label") {
386
- return { type: "label", text: a.text, position: { x: s((a.x ?? 0) * dpr), y: s(((a.y ?? 0) - TOOLBAR_HEIGHT) * dpr) }, style: { fontSize: s(14 * dpr), color: "#FF0000", background: "#FFFFFF" } };
727
+ return {
728
+ type: "label",
729
+ text: a.text,
730
+ position: {
731
+ x: s((a.x ?? 0) * dpr),
732
+ y: s(((a.y ?? 0) - toolbarHeight) * dpr),
733
+ },
734
+ style: {
735
+ fontSize: s(14 * dpr),
736
+ color: "#FF0000",
737
+ background: "#FFFFFF",
738
+ },
739
+ };
387
740
  }
388
741
  return null;
389
742
  })
390
743
  .filter((d) => d !== null);
391
- _win.__kagemusha_save(JSON.stringify(decorations));
392
- }
744
+ let capture;
745
+ if (captureMode === "crop" && captureCrop) {
746
+ // Store in page CSS pixels (Playwright's clip is in CSS pixels, not DPR)
747
+ capture = {
748
+ mode: "crop",
749
+ crop: {
750
+ start: {
751
+ x: Math.round(captureCrop.x),
752
+ y: Math.round(captureCrop.y - toolbarHeight),
753
+ },
754
+ end: {
755
+ x: Math.round(captureCrop.x + captureCrop.w),
756
+ y: Math.round(captureCrop.y + captureCrop.h - toolbarHeight),
757
+ },
758
+ },
759
+ };
760
+ }
761
+ else {
762
+ capture = { mode: "fullPage" };
763
+ }
764
+ _win.__kagemusha_save(JSON.stringify({ decorations, capture }));
765
+ };
766
+ document.getElementById("kg-save")?.addEventListener("click", save);
393
767
  export {};
394
768
  //# sourceMappingURL=inject-script.js.map