@reshotdev/screenshot 0.0.1-beta.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.
- package/LICENSE +190 -0
- package/README.md +388 -0
- package/package.json +64 -0
- package/src/commands/auth.js +259 -0
- package/src/commands/chrome.js +140 -0
- package/src/commands/ci-run.js +123 -0
- package/src/commands/ci-setup.js +288 -0
- package/src/commands/drifts.js +423 -0
- package/src/commands/import-tests.js +309 -0
- package/src/commands/ingest.js +458 -0
- package/src/commands/init.js +633 -0
- package/src/commands/publish.js +1721 -0
- package/src/commands/pull.js +303 -0
- package/src/commands/record.js +94 -0
- package/src/commands/run.js +476 -0
- package/src/commands/setup-wizard.js +740 -0
- package/src/commands/setup.js +137 -0
- package/src/commands/status.js +275 -0
- package/src/commands/sync.js +621 -0
- package/src/commands/ui.js +248 -0
- package/src/commands/validate-docs.js +529 -0
- package/src/index.js +462 -0
- package/src/lib/api-client.js +815 -0
- package/src/lib/capture-engine.js +1623 -0
- package/src/lib/capture-script-runner.js +3120 -0
- package/src/lib/ci-detect.js +137 -0
- package/src/lib/config.js +1240 -0
- package/src/lib/diff-engine.js +642 -0
- package/src/lib/hash.js +74 -0
- package/src/lib/image-crop.js +396 -0
- package/src/lib/matrix.js +89 -0
- package/src/lib/output-path-template.js +318 -0
- package/src/lib/playwright-runner.js +252 -0
- package/src/lib/polished-clip.js +553 -0
- package/src/lib/privacy-engine.js +408 -0
- package/src/lib/progress-tracker.js +142 -0
- package/src/lib/record-browser-injection.js +654 -0
- package/src/lib/record-cdp.js +612 -0
- package/src/lib/record-clip.js +343 -0
- package/src/lib/record-config.js +623 -0
- package/src/lib/record-screenshot.js +360 -0
- package/src/lib/record-terminal.js +123 -0
- package/src/lib/recorder-service.js +781 -0
- package/src/lib/secrets.js +51 -0
- package/src/lib/selector-strategies.js +859 -0
- package/src/lib/standalone-mode.js +400 -0
- package/src/lib/storage-providers.js +569 -0
- package/src/lib/style-engine.js +684 -0
- package/src/lib/ui-api.js +4677 -0
- package/src/lib/ui-assets.js +373 -0
- package/src/lib/ui-executor.js +587 -0
- package/src/lib/variant-injector.js +591 -0
- package/src/lib/viewport-presets.js +454 -0
- package/src/lib/worker-pool.js +118 -0
- package/web/cropper/index.html +436 -0
- package/web/manager/dist/assets/index--ZgioErz.js +507 -0
- package/web/manager/dist/assets/index-n468W0Wr.css +1 -0
- package/web/manager/dist/index.html +27 -0
- package/web/subtitle-editor/index.html +295 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Reshot - Crop Screenshot</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
16
|
+
background: #1a1a1a;
|
|
17
|
+
color: #fff;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: 20px;
|
|
22
|
+
min-height: 100vh;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
h1 {
|
|
26
|
+
margin-bottom: 20px;
|
|
27
|
+
font-size: 24px;
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.instructions {
|
|
32
|
+
background: #2a2a2a;
|
|
33
|
+
padding: 15px 20px;
|
|
34
|
+
border-radius: 8px;
|
|
35
|
+
margin-bottom: 20px;
|
|
36
|
+
max-width: 800px;
|
|
37
|
+
font-size: 14px;
|
|
38
|
+
color: #aaa;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.container {
|
|
42
|
+
position: relative;
|
|
43
|
+
display: inline-block;
|
|
44
|
+
max-width: 90vw;
|
|
45
|
+
max-height: 60vh;
|
|
46
|
+
border: 2px solid #444;
|
|
47
|
+
border-radius: 4px;
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#image {
|
|
52
|
+
display: block;
|
|
53
|
+
max-width: 100%;
|
|
54
|
+
max-height: 60vh;
|
|
55
|
+
user-select: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#crop-overlay {
|
|
59
|
+
position: absolute;
|
|
60
|
+
top: 0;
|
|
61
|
+
left: 0;
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 100%;
|
|
64
|
+
cursor: crosshair;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#crop-rect {
|
|
68
|
+
position: absolute;
|
|
69
|
+
border: 2px solid #00ff00;
|
|
70
|
+
background: rgba(0, 255, 0, 0.1);
|
|
71
|
+
display: none;
|
|
72
|
+
box-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.resize-handle {
|
|
76
|
+
position: absolute;
|
|
77
|
+
width: 10px;
|
|
78
|
+
height: 10px;
|
|
79
|
+
background: #00ff00;
|
|
80
|
+
border: 1px solid #fff;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.handle-nw { top: -5px; left: -5px; cursor: nw-resize; }
|
|
84
|
+
.handle-ne { top: -5px; right: -5px; cursor: ne-resize; }
|
|
85
|
+
.handle-sw { bottom: -5px; left: -5px; cursor: sw-resize; }
|
|
86
|
+
.handle-se { bottom: -5px; right: -5px; cursor: se-resize; }
|
|
87
|
+
|
|
88
|
+
.options {
|
|
89
|
+
margin-top: 15px;
|
|
90
|
+
padding: 15px 20px;
|
|
91
|
+
background: #2a2a2a;
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
gap: 10px;
|
|
96
|
+
max-width: 600px;
|
|
97
|
+
width: 100%;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.option-row {
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
gap: 10px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.option-row label {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 8px;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
color: #ccc;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.option-row input[type="checkbox"] {
|
|
116
|
+
width: 18px;
|
|
117
|
+
height: 18px;
|
|
118
|
+
accent-color: #00ff00;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.option-row input[type="number"] {
|
|
122
|
+
width: 60px;
|
|
123
|
+
padding: 6px 8px;
|
|
124
|
+
background: #333;
|
|
125
|
+
border: 1px solid #555;
|
|
126
|
+
border-radius: 4px;
|
|
127
|
+
color: #fff;
|
|
128
|
+
font-size: 14px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.controls {
|
|
132
|
+
margin-top: 20px;
|
|
133
|
+
display: flex;
|
|
134
|
+
gap: 10px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
button {
|
|
138
|
+
padding: 12px 24px;
|
|
139
|
+
font-size: 14px;
|
|
140
|
+
font-weight: 500;
|
|
141
|
+
border: none;
|
|
142
|
+
border-radius: 6px;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
transition: all 0.2s;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
button:hover {
|
|
148
|
+
transform: translateY(-1px);
|
|
149
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.btn-primary {
|
|
153
|
+
background: #00ff00;
|
|
154
|
+
color: #000;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.btn-secondary {
|
|
158
|
+
background: #444;
|
|
159
|
+
color: #fff;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.btn-secondary:hover {
|
|
163
|
+
background: #555;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.crop-info {
|
|
167
|
+
margin-top: 15px;
|
|
168
|
+
padding: 10px 15px;
|
|
169
|
+
background: #2a2a2a;
|
|
170
|
+
border-radius: 4px;
|
|
171
|
+
font-size: 12px;
|
|
172
|
+
font-family: 'Courier New', monospace;
|
|
173
|
+
color: #aaa;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.message {
|
|
177
|
+
margin-top: 15px;
|
|
178
|
+
padding: 10px 15px;
|
|
179
|
+
border-radius: 4px;
|
|
180
|
+
font-size: 14px;
|
|
181
|
+
display: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.message.success {
|
|
185
|
+
background: #00ff0020;
|
|
186
|
+
color: #00ff00;
|
|
187
|
+
border: 1px solid #00ff00;
|
|
188
|
+
display: block;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.help-text {
|
|
192
|
+
font-size: 12px;
|
|
193
|
+
color: #888;
|
|
194
|
+
margin-left: 26px;
|
|
195
|
+
margin-top: 2px;
|
|
196
|
+
}
|
|
197
|
+
</style>
|
|
198
|
+
</head>
|
|
199
|
+
<body>
|
|
200
|
+
<h1>📐 Crop Screenshot</h1>
|
|
201
|
+
|
|
202
|
+
<div class="instructions">
|
|
203
|
+
Click and drag on the image to define the crop area. Drag the corners to resize.
|
|
204
|
+
Enable "Apply to all subsequent captures" to reuse this crop for future screenshots in the same scenario.
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="container" id="container">
|
|
208
|
+
<img id="image" src="/image" alt="Screenshot to crop">
|
|
209
|
+
<div id="crop-overlay">
|
|
210
|
+
<div id="crop-rect">
|
|
211
|
+
<div class="resize-handle handle-nw"></div>
|
|
212
|
+
<div class="resize-handle handle-ne"></div>
|
|
213
|
+
<div class="resize-handle handle-sw"></div>
|
|
214
|
+
<div class="resize-handle handle-se"></div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div class="crop-info" id="crop-info">
|
|
220
|
+
No crop defined yet
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div class="options">
|
|
224
|
+
<div class="option-row">
|
|
225
|
+
<label>
|
|
226
|
+
<input type="checkbox" id="persist-crop" checked>
|
|
227
|
+
Apply to all subsequent captures in this scenario
|
|
228
|
+
</label>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="help-text">
|
|
231
|
+
When enabled, all future screenshots and variations will use the same crop region automatically.
|
|
232
|
+
</div>
|
|
233
|
+
<div class="option-row">
|
|
234
|
+
<label>
|
|
235
|
+
Padding:
|
|
236
|
+
<input type="number" id="padding" value="0" min="0" max="100"> px
|
|
237
|
+
</label>
|
|
238
|
+
</div>
|
|
239
|
+
<div class="help-text">
|
|
240
|
+
Add uniform padding around the crop region for better visual framing.
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div class="controls">
|
|
245
|
+
<button class="btn-secondary" onclick="resetCrop()">Reset</button>
|
|
246
|
+
<button class="btn-primary" onclick="saveCrop()">Save Crop</button>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div class="message" id="message"></div>
|
|
250
|
+
|
|
251
|
+
<script>
|
|
252
|
+
const image = document.getElementById('image');
|
|
253
|
+
const container = document.getElementById('container');
|
|
254
|
+
const overlay = document.getElementById('crop-overlay');
|
|
255
|
+
const cropRect = document.getElementById('crop-rect');
|
|
256
|
+
const cropInfo = document.getElementById('crop-info');
|
|
257
|
+
|
|
258
|
+
let isDrawing = false;
|
|
259
|
+
let isResizing = false;
|
|
260
|
+
let resizeHandle = null;
|
|
261
|
+
let startX = 0;
|
|
262
|
+
let startY = 0;
|
|
263
|
+
let cropData = { x: 0, y: 0, width: 0, height: 0 };
|
|
264
|
+
|
|
265
|
+
// Get image position and scale
|
|
266
|
+
function getImageBounds() {
|
|
267
|
+
const rect = image.getBoundingClientRect();
|
|
268
|
+
const containerRect = container.getBoundingClientRect();
|
|
269
|
+
return {
|
|
270
|
+
left: rect.left - containerRect.left,
|
|
271
|
+
top: rect.top - containerRect.top,
|
|
272
|
+
width: rect.width,
|
|
273
|
+
height: rect.height,
|
|
274
|
+
naturalWidth: image.naturalWidth,
|
|
275
|
+
naturalHeight: image.naturalHeight
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Convert screen coordinates to image coordinates
|
|
280
|
+
function screenToImage(x, y) {
|
|
281
|
+
const bounds = getImageBounds();
|
|
282
|
+
const scaleX = bounds.naturalWidth / bounds.width;
|
|
283
|
+
const scaleY = bounds.naturalHeight / bounds.height;
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
x: Math.round((x - bounds.left) * scaleX),
|
|
287
|
+
y: Math.round((y - bounds.top) * scaleY)
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Update crop info display
|
|
292
|
+
function updateCropInfo() {
|
|
293
|
+
cropInfo.textContent = `x: ${cropData.x}px, y: ${cropData.y}px, width: ${cropData.width}px, height: ${cropData.height}px`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Mouse down - start drawing or resizing
|
|
297
|
+
overlay.addEventListener('mousedown', (e) => {
|
|
298
|
+
if (e.target.classList.contains('resize-handle')) {
|
|
299
|
+
isResizing = true;
|
|
300
|
+
resizeHandle = e.target;
|
|
301
|
+
startX = e.clientX;
|
|
302
|
+
startY = e.clientY;
|
|
303
|
+
} else {
|
|
304
|
+
isDrawing = true;
|
|
305
|
+
const containerRect = container.getBoundingClientRect();
|
|
306
|
+
startX = e.clientX - containerRect.left;
|
|
307
|
+
startY = e.clientY - containerRect.top;
|
|
308
|
+
cropRect.style.left = startX + 'px';
|
|
309
|
+
cropRect.style.top = startY + 'px';
|
|
310
|
+
cropRect.style.width = '0px';
|
|
311
|
+
cropRect.style.height = '0px';
|
|
312
|
+
cropRect.style.display = 'block';
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Mouse move - update crop rectangle
|
|
317
|
+
document.addEventListener('mousemove', (e) => {
|
|
318
|
+
if (isDrawing) {
|
|
319
|
+
const containerRect = container.getBoundingClientRect();
|
|
320
|
+
const currentX = e.clientX - containerRect.left;
|
|
321
|
+
const currentY = e.clientY - containerRect.top;
|
|
322
|
+
|
|
323
|
+
const width = currentX - startX;
|
|
324
|
+
const height = currentY - startY;
|
|
325
|
+
|
|
326
|
+
cropRect.style.width = Math.abs(width) + 'px';
|
|
327
|
+
cropRect.style.height = Math.abs(height) + 'px';
|
|
328
|
+
cropRect.style.left = (width < 0 ? currentX : startX) + 'px';
|
|
329
|
+
cropRect.style.top = (height < 0 ? currentY : startY) + 'px';
|
|
330
|
+
} else if (isResizing && resizeHandle) {
|
|
331
|
+
const deltaX = e.clientX - startX;
|
|
332
|
+
const deltaY = e.clientY - startY;
|
|
333
|
+
|
|
334
|
+
const rect = cropRect.getBoundingClientRect();
|
|
335
|
+
const containerRect = container.getBoundingClientRect();
|
|
336
|
+
|
|
337
|
+
if (resizeHandle.classList.contains('handle-se')) {
|
|
338
|
+
cropRect.style.width = (rect.width + deltaX) + 'px';
|
|
339
|
+
cropRect.style.height = (rect.height + deltaY) + 'px';
|
|
340
|
+
} else if (resizeHandle.classList.contains('handle-sw')) {
|
|
341
|
+
cropRect.style.width = (rect.width - deltaX) + 'px';
|
|
342
|
+
cropRect.style.height = (rect.height + deltaY) + 'px';
|
|
343
|
+
cropRect.style.left = (rect.left - containerRect.left + deltaX) + 'px';
|
|
344
|
+
} else if (resizeHandle.classList.contains('handle-ne')) {
|
|
345
|
+
cropRect.style.width = (rect.width + deltaX) + 'px';
|
|
346
|
+
cropRect.style.height = (rect.height - deltaY) + 'px';
|
|
347
|
+
cropRect.style.top = (rect.top - containerRect.top + deltaY) + 'px';
|
|
348
|
+
} else if (resizeHandle.classList.contains('handle-nw')) {
|
|
349
|
+
cropRect.style.width = (rect.width - deltaX) + 'px';
|
|
350
|
+
cropRect.style.height = (rect.height - deltaY) + 'px';
|
|
351
|
+
cropRect.style.left = (rect.left - containerRect.left + deltaX) + 'px';
|
|
352
|
+
cropRect.style.top = (rect.top - containerRect.top + deltaY) + 'px';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
startX = e.clientX;
|
|
356
|
+
startY = e.clientY;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Mouse up - finish drawing or resizing
|
|
361
|
+
document.addEventListener('mouseup', () => {
|
|
362
|
+
if (isDrawing || isResizing) {
|
|
363
|
+
// Calculate final crop coordinates in image pixels
|
|
364
|
+
const rect = cropRect.getBoundingClientRect();
|
|
365
|
+
const containerRect = container.getBoundingClientRect();
|
|
366
|
+
|
|
367
|
+
const topLeft = screenToImage(rect.left, rect.top);
|
|
368
|
+
const bottomRight = screenToImage(rect.right, rect.bottom);
|
|
369
|
+
|
|
370
|
+
cropData = {
|
|
371
|
+
x: topLeft.x,
|
|
372
|
+
y: topLeft.y,
|
|
373
|
+
width: bottomRight.x - topLeft.x,
|
|
374
|
+
height: bottomRight.y - topLeft.y
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
updateCropInfo();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
isDrawing = false;
|
|
381
|
+
isResizing = false;
|
|
382
|
+
resizeHandle = null;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Reset crop
|
|
386
|
+
function resetCrop() {
|
|
387
|
+
cropRect.style.display = 'none';
|
|
388
|
+
cropData = { x: 0, y: 0, width: 0, height: 0 };
|
|
389
|
+
cropInfo.textContent = 'No crop defined yet';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Save crop with full crop configuration
|
|
393
|
+
async function saveCrop() {
|
|
394
|
+
if (cropData.width === 0 || cropData.height === 0) {
|
|
395
|
+
alert('Please define a crop area first');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const persistCrop = document.getElementById('persist-crop').checked;
|
|
400
|
+
const padding = parseInt(document.getElementById('padding').value) || 0;
|
|
401
|
+
|
|
402
|
+
// Build the full crop configuration object
|
|
403
|
+
const cropConfig = {
|
|
404
|
+
enabled: true,
|
|
405
|
+
region: {
|
|
406
|
+
x: cropData.x,
|
|
407
|
+
y: cropData.y,
|
|
408
|
+
width: cropData.width,
|
|
409
|
+
height: cropData.height
|
|
410
|
+
},
|
|
411
|
+
scaleMode: 'none',
|
|
412
|
+
preserveAspectRatio: true,
|
|
413
|
+
persistToScenario: persistCrop, // Save to scenario config for reuse
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// Add padding if specified
|
|
417
|
+
if (padding > 0) {
|
|
418
|
+
cropConfig.padding = {
|
|
419
|
+
top: padding,
|
|
420
|
+
right: padding,
|
|
421
|
+
bottom: padding,
|
|
422
|
+
left: padding
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const response = await fetch('/crop', {
|
|
428
|
+
method: 'POST',
|
|
429
|
+
headers: { 'Content-Type': 'application/json' },
|
|
430
|
+
body: JSON.stringify(cropConfig)
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
if (response.ok) {
|
|
434
|
+
const message = document.getElementById('message');
|
|
435
|
+
const persistText = persistCrop ? ' This crop will be applied to all future captures.' : '';
|
|
436
|
+
message.textContent = '✔ Crop saved!' + persistText + ' You can close this tab.';
|