heroshot 0.0.5 → 0.0.6
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/dist/cli.js +323 -95
- package/package.json +1 -1
- package/toolbar/dist/toolbar.js +35 -50
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync } from "fs";
|
|
5
|
+
import path5 from "path";
|
|
5
6
|
import { Command } from "commander";
|
|
6
7
|
|
|
7
8
|
// src/browser.ts
|
|
8
|
-
import { readFileSync as
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
9
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
10
|
+
import path3 from "path";
|
|
11
|
+
import {
|
|
12
|
+
chromium
|
|
13
|
+
} from "playwright";
|
|
12
14
|
|
|
13
15
|
// src/configFile.ts
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
15
17
|
import path from "path";
|
|
16
18
|
|
|
17
19
|
// src/schema.ts
|
|
@@ -48,11 +50,13 @@ var screenshotSchema = z.object({
|
|
|
48
50
|
});
|
|
49
51
|
var browserSchema = z.object({
|
|
50
52
|
viewport: viewportSchema.optional(),
|
|
51
|
-
colorScheme: colorSchemeSchema.optional()
|
|
53
|
+
colorScheme: colorSchemeSchema.optional(),
|
|
54
|
+
/** Device scale factor for retina/high-DPI screenshots (1 = standard, 2 = retina) */
|
|
55
|
+
deviceScaleFactor: z.number().min(1).max(3).optional()
|
|
52
56
|
});
|
|
53
57
|
var configSchema = z.object({
|
|
54
58
|
/** Output directory for screenshots (relative to config file) */
|
|
55
|
-
outputDirectory: z.string().default("
|
|
59
|
+
outputDirectory: z.string().default("heroshots"),
|
|
56
60
|
/** Output format for screenshots (png or jpeg) */
|
|
57
61
|
outputFormat: outputFormatSchema.optional(),
|
|
58
62
|
/** JPEG quality (1-100), only used when outputFormat is 'jpeg' */
|
|
@@ -69,9 +73,52 @@ function parseConfig(input) {
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
// src/configFile.ts
|
|
72
|
-
var
|
|
76
|
+
var HEROSHOT_DIRECTORY_NAME = ".heroshot";
|
|
77
|
+
var CONFIG_FILENAME = "config.json";
|
|
78
|
+
function getHeroshotDirectory(directory = process.cwd()) {
|
|
79
|
+
return path.join(directory, HEROSHOT_DIRECTORY_NAME);
|
|
80
|
+
}
|
|
81
|
+
var README_CONTENT = `# Heroshot
|
|
82
|
+
|
|
83
|
+
This folder contains heroshot configuration and encrypted session data.
|
|
84
|
+
|
|
85
|
+
## Files
|
|
86
|
+
|
|
87
|
+
- \`config.json\` - Screenshot definitions (URLs, selectors, output settings)
|
|
88
|
+
- \`session.enc\` - Encrypted browser session (cookies, localStorage)
|
|
89
|
+
|
|
90
|
+
## Safe to commit
|
|
91
|
+
|
|
92
|
+
This folder is safe to commit to source control:
|
|
93
|
+
|
|
94
|
+
- \`config.json\` contains no secrets
|
|
95
|
+
- \`session.enc\` is encrypted with AES-256-GCM
|
|
96
|
+
|
|
97
|
+
## CI/CD Usage
|
|
98
|
+
|
|
99
|
+
To use heroshot in CI, add your session key as a secret:
|
|
100
|
+
|
|
101
|
+
\`\`\`yaml
|
|
102
|
+
- run: npx heroshot --session-key=\${{ secrets.HEROSHOT_SESSION_KEY }}
|
|
103
|
+
\`\`\`
|
|
104
|
+
|
|
105
|
+
To get your session key, run: \`heroshot session-key\`
|
|
106
|
+
|
|
107
|
+
Learn more: https://heroshot.sh/docs
|
|
108
|
+
`;
|
|
109
|
+
function ensureHeroshotDirectory(directory = process.cwd()) {
|
|
110
|
+
const heroshotPath = getHeroshotDirectory(directory);
|
|
111
|
+
const readmePath = path.join(heroshotPath, "README.md");
|
|
112
|
+
if (!existsSync(heroshotPath)) {
|
|
113
|
+
mkdirSync(heroshotPath, { recursive: true });
|
|
114
|
+
writeFileSync(readmePath, README_CONTENT, "utf8");
|
|
115
|
+
} else if (!existsSync(readmePath)) {
|
|
116
|
+
writeFileSync(readmePath, README_CONTENT, "utf8");
|
|
117
|
+
}
|
|
118
|
+
return heroshotPath;
|
|
119
|
+
}
|
|
73
120
|
function getConfigPath(directory = process.cwd()) {
|
|
74
|
-
return path.join(directory, CONFIG_FILENAME);
|
|
121
|
+
return path.join(directory, HEROSHOT_DIRECTORY_NAME, CONFIG_FILENAME);
|
|
75
122
|
}
|
|
76
123
|
function loadConfig(configPath) {
|
|
77
124
|
if (!existsSync(configPath)) {
|
|
@@ -82,6 +129,10 @@ function loadConfig(configPath) {
|
|
|
82
129
|
return parseConfig(json);
|
|
83
130
|
}
|
|
84
131
|
function saveConfig(configPath, config) {
|
|
132
|
+
const parentDirectory = path.dirname(configPath);
|
|
133
|
+
if (!existsSync(parentDirectory)) {
|
|
134
|
+
mkdirSync(parentDirectory, { recursive: true });
|
|
135
|
+
}
|
|
85
136
|
const content = JSON.stringify(config, null, 2);
|
|
86
137
|
writeFileSync(configPath, content, "utf8");
|
|
87
138
|
}
|
|
@@ -103,45 +154,156 @@ log.error = (message) => {
|
|
|
103
154
|
console.error(message);
|
|
104
155
|
};
|
|
105
156
|
|
|
106
|
-
// src/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
157
|
+
// src/session.ts
|
|
158
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes, scryptSync } from "crypto";
|
|
159
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
160
|
+
import { homedir } from "os";
|
|
161
|
+
import path2 from "path";
|
|
162
|
+
var SESSION_FILENAME = "session.enc";
|
|
163
|
+
var KEYS_DIRECTORY = path2.join(homedir(), ".heroshot", "keys");
|
|
164
|
+
var ALGORITHM = "aes-256-gcm";
|
|
165
|
+
var KEY_LENGTH = 32;
|
|
166
|
+
var IV_LENGTH = 16;
|
|
167
|
+
var AUTH_TAG_LENGTH = 16;
|
|
168
|
+
var SALT_LENGTH = 16;
|
|
169
|
+
var SESSION_KEY_LENGTH = 20;
|
|
170
|
+
function generateSessionKey() {
|
|
171
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
172
|
+
const bytes = randomBytes(SESSION_KEY_LENGTH);
|
|
173
|
+
return [...bytes].map((byte) => alphabet[byte % alphabet.length]).join("");
|
|
174
|
+
}
|
|
175
|
+
function deriveKey(sessionKey, salt) {
|
|
176
|
+
return scryptSync(sessionKey, salt, KEY_LENGTH);
|
|
177
|
+
}
|
|
178
|
+
function encrypt(data, sessionKey) {
|
|
179
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
180
|
+
const key = deriveKey(sessionKey, salt);
|
|
181
|
+
const iv = randomBytes(IV_LENGTH);
|
|
182
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
183
|
+
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
184
|
+
const authTag = cipher.getAuthTag();
|
|
185
|
+
return Buffer.concat([salt, iv, authTag, encrypted]);
|
|
186
|
+
}
|
|
187
|
+
function decrypt(encryptedData, sessionKey) {
|
|
188
|
+
const salt = encryptedData.subarray(0, SALT_LENGTH);
|
|
189
|
+
const iv = encryptedData.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
190
|
+
const authTag = encryptedData.subarray(
|
|
191
|
+
SALT_LENGTH + IV_LENGTH,
|
|
192
|
+
SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
|
|
193
|
+
);
|
|
194
|
+
const ciphertext = encryptedData.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
195
|
+
const key = deriveKey(sessionKey, salt);
|
|
196
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
197
|
+
decipher.setAuthTag(authTag);
|
|
198
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
199
|
+
return decrypted.toString("utf8");
|
|
200
|
+
}
|
|
201
|
+
function getProjectId(directory = process.cwd()) {
|
|
202
|
+
const hash = createHash("sha256").update(directory).digest("hex");
|
|
203
|
+
return hash.slice(0, 16);
|
|
204
|
+
}
|
|
205
|
+
function getSessionPath(directory = process.cwd()) {
|
|
206
|
+
return path2.join(getHeroshotDirectory(directory), SESSION_FILENAME);
|
|
207
|
+
}
|
|
208
|
+
function getLocalKeyPath(directory = process.cwd()) {
|
|
209
|
+
const projectId = getProjectId(directory);
|
|
210
|
+
return path2.join(KEYS_DIRECTORY, projectId);
|
|
211
|
+
}
|
|
212
|
+
function saveLocalKey(sessionKey, directory = process.cwd()) {
|
|
213
|
+
if (!existsSync2(KEYS_DIRECTORY)) {
|
|
214
|
+
mkdirSync2(KEYS_DIRECTORY, { recursive: true, mode: 448 });
|
|
215
|
+
}
|
|
216
|
+
const keyPath = getLocalKeyPath(directory);
|
|
217
|
+
writeFileSync2(keyPath, sessionKey, { encoding: "utf8", mode: 384 });
|
|
218
|
+
}
|
|
219
|
+
function loadLocalKey(directory = process.cwd()) {
|
|
220
|
+
const keyPath = getLocalKeyPath(directory);
|
|
221
|
+
if (!existsSync2(keyPath)) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
return readFileSync2(keyPath, "utf8").trim();
|
|
225
|
+
}
|
|
226
|
+
function saveSession(storageState, sessionKey, directory = process.cwd()) {
|
|
227
|
+
const heroshotPath = getHeroshotDirectory(directory);
|
|
228
|
+
if (!existsSync2(heroshotPath)) {
|
|
229
|
+
mkdirSync2(heroshotPath, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
const json = JSON.stringify(storageState);
|
|
232
|
+
const encrypted = encrypt(json, sessionKey);
|
|
233
|
+
const sessionPath = getSessionPath(directory);
|
|
234
|
+
writeFileSync2(sessionPath, encrypted, { mode: 420 });
|
|
235
|
+
}
|
|
236
|
+
function loadSession(sessionKey, directory = process.cwd()) {
|
|
237
|
+
const sessionPath = getSessionPath(directory);
|
|
238
|
+
if (!existsSync2(sessionPath)) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const encrypted = readFileSync2(sessionPath);
|
|
243
|
+
const json = decrypt(encrypted, sessionKey);
|
|
244
|
+
const parsed = JSON.parse(json);
|
|
245
|
+
if (parsed && typeof parsed === "object") {
|
|
246
|
+
return parsed;
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
111
252
|
}
|
|
253
|
+
function sessionExists(directory = process.cwd()) {
|
|
254
|
+
return existsSync2(getSessionPath(directory));
|
|
255
|
+
}
|
|
256
|
+
function getSessionKey(cliKey, directory = process.cwd()) {
|
|
257
|
+
if (cliKey) {
|
|
258
|
+
return cliKey;
|
|
259
|
+
}
|
|
260
|
+
const environmentKey = process.env["HEROSHOT_SESSION_KEY"];
|
|
261
|
+
if (environmentKey) {
|
|
262
|
+
return environmentKey;
|
|
263
|
+
}
|
|
264
|
+
return loadLocalKey(directory);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/browser.ts
|
|
268
|
+
var TOOLBAR_DIR = path3.join(import.meta.dirname, "..", "toolbar");
|
|
112
269
|
var DEFAULT_VIEWPORT = { width: 1280, height: 800 };
|
|
113
270
|
var BROWSER_CHANNELS = ["chrome", "chromium"];
|
|
114
|
-
async function
|
|
271
|
+
async function launchBrowser(options = {}) {
|
|
115
272
|
const viewport = options.viewport ?? DEFAULT_VIEWPORT;
|
|
116
|
-
|
|
117
|
-
headless: options.headless ?? false,
|
|
118
|
-
viewport
|
|
119
|
-
};
|
|
273
|
+
let browser = null;
|
|
120
274
|
for (const channel of BROWSER_CHANNELS) {
|
|
121
275
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
276
|
+
browser = await chromium.launch({
|
|
277
|
+
headless: options.headless ?? false,
|
|
124
278
|
channel
|
|
125
279
|
});
|
|
126
|
-
|
|
280
|
+
break;
|
|
127
281
|
} catch {
|
|
128
282
|
continue;
|
|
129
283
|
}
|
|
130
284
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
285
|
+
if (!browser) {
|
|
286
|
+
const message = [
|
|
287
|
+
"",
|
|
288
|
+
"Error: No browser found.",
|
|
289
|
+
"",
|
|
290
|
+
"Heroshot needs a browser to capture screenshots. Options:",
|
|
291
|
+
"",
|
|
292
|
+
" 1. Install Chrome (recommended):",
|
|
293
|
+
" https://www.google.com/chrome/",
|
|
294
|
+
"",
|
|
295
|
+
" 2. Or install Playwright browsers:",
|
|
296
|
+
" npx playwright install chromium",
|
|
297
|
+
""
|
|
298
|
+
].join("\n");
|
|
299
|
+
throw new Error(message);
|
|
300
|
+
}
|
|
301
|
+
const context = await browser.newContext({
|
|
302
|
+
viewport,
|
|
303
|
+
...options.deviceScaleFactor && { deviceScaleFactor: options.deviceScaleFactor },
|
|
304
|
+
...options.storageState && { storageState: options.storageState }
|
|
305
|
+
});
|
|
306
|
+
return { browser, context };
|
|
145
307
|
}
|
|
146
308
|
var exposedPages = /* @__PURE__ */ new WeakSet();
|
|
147
309
|
async function injectToolbar(page, options) {
|
|
@@ -177,16 +339,30 @@ async function injectToolbar(page, options) {
|
|
|
177
339
|
},
|
|
178
340
|
};
|
|
179
341
|
`);
|
|
180
|
-
const scriptPath =
|
|
181
|
-
const script =
|
|
342
|
+
const scriptPath = path3.join(TOOLBAR_DIR, "dist", "toolbar.js");
|
|
343
|
+
const script = readFileSync3(scriptPath, "utf8");
|
|
182
344
|
await page.addScriptTag({ content: script });
|
|
183
345
|
}
|
|
184
346
|
async function setup() {
|
|
185
347
|
log.verbose("Opening browser...");
|
|
186
|
-
|
|
348
|
+
ensureHeroshotDirectory();
|
|
187
349
|
const configPath = getConfigPath();
|
|
188
350
|
const config = loadConfig(configPath);
|
|
189
351
|
const viewport = config.browser?.viewport ?? DEFAULT_VIEWPORT;
|
|
352
|
+
let sessionKey = loadLocalKey();
|
|
353
|
+
const isNewKey = !sessionKey;
|
|
354
|
+
if (!sessionKey) {
|
|
355
|
+
sessionKey = generateSessionKey();
|
|
356
|
+
saveLocalKey(sessionKey);
|
|
357
|
+
}
|
|
358
|
+
let storageState;
|
|
359
|
+
if (sessionExists()) {
|
|
360
|
+
const state = loadSession(sessionKey);
|
|
361
|
+
if (state) {
|
|
362
|
+
storageState = state;
|
|
363
|
+
log.verbose("Loaded existing session.");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
190
366
|
const allScreenshots = config.screenshots.map((screenshot, index) => ({
|
|
191
367
|
id: screenshot.id,
|
|
192
368
|
name: screenshot.name,
|
|
@@ -201,7 +377,7 @@ async function setup() {
|
|
|
201
377
|
let pendingJob = null;
|
|
202
378
|
let selectedId = null;
|
|
203
379
|
let sidebarExpanded = false;
|
|
204
|
-
const context = await
|
|
380
|
+
const { browser, context } = await launchBrowser({ headless: false, viewport, storageState });
|
|
205
381
|
const handleEvent = (event) => {
|
|
206
382
|
switch (event.type) {
|
|
207
383
|
case "screenshot-added": {
|
|
@@ -248,7 +424,15 @@ async function setup() {
|
|
|
248
424
|
break;
|
|
249
425
|
}
|
|
250
426
|
case "done": {
|
|
251
|
-
void
|
|
427
|
+
void (async () => {
|
|
428
|
+
try {
|
|
429
|
+
const currentStorageState = await context.storageState();
|
|
430
|
+
saveSession(currentStorageState, sessionKey);
|
|
431
|
+
log.verbose("Session saved.");
|
|
432
|
+
} catch {
|
|
433
|
+
}
|
|
434
|
+
await browser.close();
|
|
435
|
+
})();
|
|
252
436
|
break;
|
|
253
437
|
}
|
|
254
438
|
}
|
|
@@ -278,40 +462,46 @@ async function setup() {
|
|
|
278
462
|
await page.goto("https://heroshot.sh/welcome", { waitUntil: "domcontentloaded" });
|
|
279
463
|
log("Pick elements to screenshot. Close browser or click Done when finished.");
|
|
280
464
|
await new Promise((resolve) => {
|
|
281
|
-
|
|
465
|
+
browser.once("disconnected", () => resolve());
|
|
282
466
|
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
log.verbose(`~ ${element.name} (updated)`);
|
|
304
|
-
}
|
|
467
|
+
const latestConfig = loadConfig(configPath);
|
|
468
|
+
for (const element of allScreenshots) {
|
|
469
|
+
if (!newlyAddedIds.has(element.id)) continue;
|
|
470
|
+
const filename = element.name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/(?:^-|-$)/g, "") + ".png";
|
|
471
|
+
const screenshot = {
|
|
472
|
+
id: element.id,
|
|
473
|
+
name: element.name,
|
|
474
|
+
url: element.url,
|
|
475
|
+
selector: element.selector,
|
|
476
|
+
filename,
|
|
477
|
+
...element.padding && { padding: element.padding },
|
|
478
|
+
...element.scroll && { scroll: element.scroll }
|
|
479
|
+
};
|
|
480
|
+
const existingIndex = latestConfig.screenshots.findIndex((item) => item.id === element.id);
|
|
481
|
+
if (existingIndex === -1) {
|
|
482
|
+
latestConfig.screenshots.push(screenshot);
|
|
483
|
+
log.verbose(`+ ${element.name}`);
|
|
484
|
+
} else {
|
|
485
|
+
latestConfig.screenshots[existingIndex] = screenshot;
|
|
486
|
+
log.verbose(`~ ${element.name} (updated)`);
|
|
305
487
|
}
|
|
306
|
-
|
|
307
|
-
|
|
488
|
+
}
|
|
489
|
+
saveConfig(configPath, latestConfig);
|
|
490
|
+
log.verbose(`Config saved: ${configPath}`);
|
|
491
|
+
if (isNewKey) {
|
|
492
|
+
log("");
|
|
493
|
+
log("Session encrypted and saved to .heroshot/session.enc");
|
|
494
|
+
log("");
|
|
495
|
+
log("To print your session key, run: heroshot session-key");
|
|
496
|
+
log("");
|
|
497
|
+
log("For CI, add HEROSHOT_SESSION_KEY as a repository secret.");
|
|
308
498
|
}
|
|
309
499
|
return { hasScreenshots: allScreenshots.length > 0 };
|
|
310
500
|
}
|
|
311
501
|
|
|
312
502
|
// src/sync.ts
|
|
313
|
-
import { existsSync as
|
|
314
|
-
import
|
|
503
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
504
|
+
import path4 from "path";
|
|
315
505
|
async function findElement(page, selector, maxAttempts = 10, intervalMs = 500) {
|
|
316
506
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
317
507
|
const handle = await page.evaluateHandle(`
|
|
@@ -348,10 +538,10 @@ async function findElement(page, selector, maxAttempts = 10, intervalMs = 500) {
|
|
|
348
538
|
return null;
|
|
349
539
|
}
|
|
350
540
|
function addFilenameSuffix(filename, suffix) {
|
|
351
|
-
const extension =
|
|
352
|
-
const base =
|
|
353
|
-
const directory =
|
|
354
|
-
return
|
|
541
|
+
const extension = path4.extname(filename);
|
|
542
|
+
const base = path4.basename(filename, extension);
|
|
543
|
+
const directory = path4.dirname(filename);
|
|
544
|
+
return path4.join(directory, `${base}${suffix}${extension}`);
|
|
355
545
|
}
|
|
356
546
|
async function takeScreenshot(target, outputPath, format, quality, clip) {
|
|
357
547
|
const isPage = "goto" in target;
|
|
@@ -387,10 +577,10 @@ async function captureScreenshot(page, screenshot, outputDirectory, captureOptio
|
|
|
387
577
|
await page.evaluate(`window.scrollTo(${scroll.x}, ${scroll.y})`);
|
|
388
578
|
await page.waitForTimeout(100);
|
|
389
579
|
}
|
|
390
|
-
const outputPath =
|
|
391
|
-
const outputDirectoryPath =
|
|
392
|
-
if (!
|
|
393
|
-
|
|
580
|
+
const outputPath = path4.join(outputDirectory, finalFilename);
|
|
581
|
+
const outputDirectoryPath = path4.dirname(outputPath);
|
|
582
|
+
if (!existsSync3(outputDirectoryPath)) {
|
|
583
|
+
mkdirSync3(outputDirectoryPath, { recursive: true });
|
|
394
584
|
}
|
|
395
585
|
try {
|
|
396
586
|
if (selector) {
|
|
@@ -443,8 +633,21 @@ async function captureAndLog(page, screenshot, outputDirectory, captureOptions,
|
|
|
443
633
|
error: result.error
|
|
444
634
|
};
|
|
445
635
|
}
|
|
636
|
+
function loadEncryptedSession(sessionKeyOption) {
|
|
637
|
+
const sessionKey = getSessionKey(sessionKeyOption);
|
|
638
|
+
if (!sessionKey || !sessionExists()) {
|
|
639
|
+
return void 0;
|
|
640
|
+
}
|
|
641
|
+
const state = loadSession(sessionKey);
|
|
642
|
+
if (state) {
|
|
643
|
+
log.verbose("Using encrypted session.");
|
|
644
|
+
return state;
|
|
645
|
+
}
|
|
646
|
+
log.verbose("Failed to decrypt session - using fresh browser.");
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
446
649
|
async function sync(options = {}) {
|
|
447
|
-
const configPath = getConfigPath();
|
|
650
|
+
const configPath = options.configPath ?? getConfigPath();
|
|
448
651
|
const config = loadConfig(configPath);
|
|
449
652
|
if (config.screenshots.length === 0) {
|
|
450
653
|
log("No screenshots defined.");
|
|
@@ -457,12 +660,17 @@ async function sync(options = {}) {
|
|
|
457
660
|
return { total: 0, success: 0, failed: 0, results: [] };
|
|
458
661
|
}
|
|
459
662
|
log.verbose(`Syncing ${screenshots.length} screenshot(s)...`);
|
|
460
|
-
const configDirectory =
|
|
461
|
-
const
|
|
663
|
+
const configDirectory = path4.dirname(configPath);
|
|
664
|
+
const projectRoot = path4.dirname(configDirectory);
|
|
665
|
+
const outputDirectory = path4.resolve(projectRoot, config.outputDirectory);
|
|
666
|
+
const storageState = loadEncryptedSession(options.sessionKey);
|
|
462
667
|
const viewport = config.browser?.viewport ?? { width: 1280, height: 800 };
|
|
463
|
-
const
|
|
668
|
+
const deviceScaleFactor = config.browser?.deviceScaleFactor;
|
|
669
|
+
const { browser, context } = await launchBrowser({
|
|
464
670
|
headless: true,
|
|
465
|
-
viewport
|
|
671
|
+
viewport,
|
|
672
|
+
deviceScaleFactor,
|
|
673
|
+
storageState
|
|
466
674
|
});
|
|
467
675
|
const page = await context.newPage();
|
|
468
676
|
const colorSchemeSetting = config.browser?.colorScheme;
|
|
@@ -491,7 +699,7 @@ async function sync(options = {}) {
|
|
|
491
699
|
}
|
|
492
700
|
}
|
|
493
701
|
}
|
|
494
|
-
await
|
|
702
|
+
await browser.close();
|
|
495
703
|
const { length: totalCount } = results;
|
|
496
704
|
const successfulResults = results.filter(({ success }) => success);
|
|
497
705
|
const { length: successCount } = successfulResults;
|
|
@@ -510,19 +718,28 @@ async function sync(options = {}) {
|
|
|
510
718
|
}
|
|
511
719
|
|
|
512
720
|
// src/cli.ts
|
|
721
|
+
var packageJsonPath = path5.join(import.meta.dirname, "..", "package.json");
|
|
722
|
+
var packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
|
|
723
|
+
var version = packageJson && typeof packageJson === "object" && "version" in packageJson ? String(packageJson.version) : "0.0.0";
|
|
513
724
|
var program = new Command();
|
|
514
|
-
program.name("heroshot").description("Define your screenshots once, update them forever with one command").version(
|
|
725
|
+
program.name("heroshot").description("Define your screenshots once, update them forever with one command").version(version).option("-v, --verbose", "Show detailed output").option("-c, --config <path>", "Path to config file").option("-s, --session-key <key>", "Session key for encrypted auth (or set HEROSHOT_SESSION_KEY)").hook("preAction", () => {
|
|
515
726
|
const options = program.opts();
|
|
516
727
|
setVerbose(options.verbose ?? false);
|
|
517
728
|
});
|
|
518
729
|
program.command("run", { isDefault: true, hidden: true }).description("Run heroshot (setup if no config, otherwise sync)").action(async () => {
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
730
|
+
const options = program.opts();
|
|
731
|
+
const configPath = options.config ? path5.resolve(options.config) : getConfigPath();
|
|
732
|
+
if (existsSync4(configPath)) {
|
|
733
|
+
const result = await sync({ configPath, sessionKey: options.sessionKey });
|
|
522
734
|
if (result.failed > 0) {
|
|
523
735
|
process.exitCode = 1;
|
|
524
736
|
}
|
|
525
737
|
} else {
|
|
738
|
+
if (options.config) {
|
|
739
|
+
log(`Config file not found: ${configPath}`);
|
|
740
|
+
process.exitCode = 1;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
526
743
|
const { hasScreenshots } = await setup();
|
|
527
744
|
if (hasScreenshots) {
|
|
528
745
|
log("");
|
|
@@ -533,21 +750,32 @@ program.command("run", { isDefault: true, hidden: true }).description("Run heros
|
|
|
533
750
|
}
|
|
534
751
|
}
|
|
535
752
|
});
|
|
536
|
-
program.command("config").description("Open browser to add/edit screenshot definitions").option("--reset", "Clear existing
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
753
|
+
program.command("config").description("Open browser to add/edit screenshot definitions").option("--reset", "Clear existing session and start fresh").option("--only", "Only run config, skip sync afterwards").action(async (commandOptions) => {
|
|
754
|
+
const globalOptions = program.opts();
|
|
755
|
+
if (commandOptions.reset) {
|
|
756
|
+
const sessionPath = getSessionPath();
|
|
757
|
+
if (existsSync4(sessionPath)) {
|
|
758
|
+
rmSync(sessionPath);
|
|
759
|
+
log.verbose("Session cleared.");
|
|
542
760
|
}
|
|
543
761
|
}
|
|
544
762
|
const { hasScreenshots } = await setup();
|
|
545
|
-
if (hasScreenshots && !
|
|
763
|
+
if (hasScreenshots && !commandOptions.only) {
|
|
546
764
|
log("");
|
|
547
|
-
const
|
|
765
|
+
const configPath = globalOptions.config ? path5.resolve(globalOptions.config) : void 0;
|
|
766
|
+
const result = await sync({ configPath, sessionKey: globalOptions.sessionKey });
|
|
548
767
|
if (result.failed > 0) {
|
|
549
768
|
process.exitCode = 1;
|
|
550
769
|
}
|
|
551
770
|
}
|
|
552
771
|
});
|
|
772
|
+
program.command("session-key").description("Print the session key for this project (for CI setup)").action(() => {
|
|
773
|
+
const sessionKey = loadLocalKey();
|
|
774
|
+
if (sessionKey) {
|
|
775
|
+
log(sessionKey);
|
|
776
|
+
} else {
|
|
777
|
+
log('No session key found. Run "heroshot config" first to generate one.');
|
|
778
|
+
process.exitCode = 1;
|
|
779
|
+
}
|
|
780
|
+
});
|
|
553
781
|
program.parse();
|
package/package.json
CHANGED
package/toolbar/dist/toolbar.js
CHANGED
|
@@ -3949,14 +3949,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
3949
3949
|
function isEditingNewElement() {
|
|
3950
3950
|
return get(isNewElement);
|
|
3951
3951
|
}
|
|
3952
|
-
function getSelectedElementSide() {
|
|
3953
|
-
const element = get(selectedElement) ?? get(currentElement);
|
|
3954
|
-
if (!element) return null;
|
|
3955
|
-
const rect = element.getBoundingClientRect();
|
|
3956
|
-
const viewportCenter = globalThis.innerWidth / 2;
|
|
3957
|
-
const elementCenter = rect.left + rect.width / 2;
|
|
3958
|
-
return elementCenter < viewportCenter ? "left" : "right";
|
|
3959
|
-
}
|
|
3960
3952
|
function getOverlayRects(element, _scrollX, _scrollY, padding) {
|
|
3961
3953
|
if (!element) return null;
|
|
3962
3954
|
const rect = element.getBoundingClientRect();
|
|
@@ -4082,8 +4074,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4082
4074
|
confirmDraft,
|
|
4083
4075
|
getCurrentPadding,
|
|
4084
4076
|
getCurrentScroll,
|
|
4085
|
-
isEditingNewElement
|
|
4086
|
-
getSelectedElementSide
|
|
4077
|
+
isEditingNewElement
|
|
4087
4078
|
};
|
|
4088
4079
|
var fragment = comment();
|
|
4089
4080
|
event("scroll", $window, handleScroll);
|
|
@@ -4299,23 +4290,26 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4299
4290
|
}
|
|
4300
4291
|
delegate(["click", "mousedown"]);
|
|
4301
4292
|
var root_2 = /* @__PURE__ */ from_html(`<p class="text-xs text-slate-500 mt-2">Will capture two screenshots: -light and -dark variants</p>`);
|
|
4302
|
-
var root_1$2 = /* @__PURE__ */ from_html(`<div class="fixed inset-0 bg-black/50 z-[2147483647] flex items-center justify-center pointer-events-auto" role="button" tabindex="0"><div class="bg-slate-800 rounded-lg p-6 w-80 shadow-2xl" role="dialog" aria-modal="true" aria-label="Settings" tabindex="-1"><h2 class="text-lg font-semibold text-white mb-4">Settings</h2> <div class="mb-4"><span class="block text-sm text-slate-400 mb-2">Viewport Size</span> <div class="flex gap-2 items-center"><label class="sr-only" for="viewport-width">Width</label> <input id="viewport-width" type="number" class="w-20 px-2 py-1 bg-slate-700 text-white rounded border border-slate-600 focus:border-blue-500 focus:outline-none" min="320" max="3840"/> <span class="text-slate-400">x</span> <label class="sr-only" for="viewport-height">Height</label> <input id="viewport-height" type="number" class="w-20 px-2 py-1 bg-slate-700 text-white rounded border border-slate-600 focus:border-blue-500 focus:outline-none" min="200" max="2160"/> <span class="text-slate-500 text-sm">px</span></div></div> <div class="mb-6"><span class="block text-sm text-slate-400 mb-2">Color Scheme</span> <div class="flex gap-2"><button type="button">Auto</button> <button type="button">Light</button> <button type="button">Dark</button> <button type="button" title="Capture both light and dark versions">Both</button></div> <!></div> <div class="flex justify-end gap-2"><button type="button" class="px-4 py-2 rounded bg-slate-700 text-white hover:bg-slate-600 transition-colors">Cancel</button> <button type="button" class="px-4 py-2 rounded bg-green-500 text-white hover:bg-green-600 transition-colors">Save</button></div></div></div>`);
|
|
4293
|
+
var root_1$2 = /* @__PURE__ */ from_html(`<div class="fixed inset-0 bg-black/50 z-[2147483647] flex items-center justify-center pointer-events-auto" role="button" tabindex="0"><div class="bg-slate-800 rounded-lg p-6 w-80 shadow-2xl" role="dialog" aria-modal="true" aria-label="Settings" tabindex="-1"><h2 class="text-lg font-semibold text-white mb-4">Settings</h2> <div class="mb-4"><span class="block text-sm text-slate-400 mb-2">Viewport Size</span> <div class="flex gap-2 items-center"><label class="sr-only" for="viewport-width">Width</label> <input id="viewport-width" type="number" class="w-20 px-2 py-1 bg-slate-700 text-white rounded border border-slate-600 focus:border-blue-500 focus:outline-none" min="320" max="3840"/> <span class="text-slate-400">x</span> <label class="sr-only" for="viewport-height">Height</label> <input id="viewport-height" type="number" class="w-20 px-2 py-1 bg-slate-700 text-white rounded border border-slate-600 focus:border-blue-500 focus:outline-none" min="200" max="2160"/> <span class="text-slate-500 text-sm">px</span></div></div> <div class="mb-4"><span class="block text-sm text-slate-400 mb-2">Scale (Retina)</span> <div class="flex gap-2"><button type="button">1x</button> <button type="button">2x</button> <button type="button">3x</button></div> <p class="text-xs text-slate-500 mt-2">Higher scale = sharper images, larger file size</p></div> <div class="mb-6"><span class="block text-sm text-slate-400 mb-2">Color Scheme</span> <div class="flex gap-2"><button type="button">Auto</button> <button type="button">Light</button> <button type="button">Dark</button> <button type="button" title="Capture both light and dark versions">Both</button></div> <!></div> <div class="flex justify-end gap-2"><button type="button" class="px-4 py-2 rounded bg-slate-700 text-white hover:bg-slate-600 transition-colors">Cancel</button> <button type="button" class="px-4 py-2 rounded bg-green-500 text-white hover:bg-green-600 transition-colors">Save</button></div></div></div>`);
|
|
4303
4294
|
function SettingsModal($$anchor, $$props) {
|
|
4304
4295
|
push($$props, true);
|
|
4305
4296
|
let width = /* @__PURE__ */ state(0);
|
|
4306
4297
|
let height = /* @__PURE__ */ state(0);
|
|
4307
4298
|
let colorScheme = /* @__PURE__ */ state(void 0);
|
|
4299
|
+
let deviceScaleFactor = /* @__PURE__ */ state(void 0);
|
|
4308
4300
|
user_effect(() => {
|
|
4309
4301
|
if ($$props.visible) {
|
|
4310
4302
|
set(width, $$props.settings.viewport.width, true);
|
|
4311
4303
|
set(height, $$props.settings.viewport.height, true);
|
|
4312
4304
|
set(colorScheme, $$props.settings.colorScheme, true);
|
|
4305
|
+
set(deviceScaleFactor, $$props.settings.deviceScaleFactor, true);
|
|
4313
4306
|
}
|
|
4314
4307
|
});
|
|
4315
4308
|
function handleSave() {
|
|
4316
4309
|
$$props.onSave({
|
|
4317
4310
|
viewport: { width: get(width), height: get(height) },
|
|
4318
|
-
colorScheme: get(colorScheme)
|
|
4311
|
+
colorScheme: get(colorScheme),
|
|
4312
|
+
deviceScaleFactor: get(deviceScaleFactor)
|
|
4319
4313
|
});
|
|
4320
4314
|
$$props.onClose();
|
|
4321
4315
|
}
|
|
@@ -4353,23 +4347,31 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4353
4347
|
var div_4 = sibling(div_2, 2);
|
|
4354
4348
|
var div_5 = sibling(child(div_4), 2);
|
|
4355
4349
|
var button = child(div_5);
|
|
4356
|
-
button.__click = () =>
|
|
4350
|
+
button.__click = () => set(deviceScaleFactor, void 0);
|
|
4351
|
+
var button_1 = sibling(button, 2);
|
|
4352
|
+
button_1.__click = () => set(deviceScaleFactor, 2);
|
|
4353
|
+
var button_2 = sibling(button_1, 2);
|
|
4354
|
+
button_2.__click = () => set(deviceScaleFactor, 3);
|
|
4355
|
+
var div_6 = sibling(div_4, 2);
|
|
4356
|
+
var div_7 = sibling(child(div_6), 2);
|
|
4357
|
+
var button_3 = child(div_7);
|
|
4358
|
+
button_3.__click = () => {
|
|
4357
4359
|
set(colorScheme, void 0);
|
|
4358
4360
|
applyColorScheme();
|
|
4359
4361
|
};
|
|
4360
|
-
var
|
|
4361
|
-
|
|
4362
|
+
var button_4 = sibling(button_3, 2);
|
|
4363
|
+
button_4.__click = () => {
|
|
4362
4364
|
set(colorScheme, "light");
|
|
4363
4365
|
applyColorScheme("light");
|
|
4364
4366
|
};
|
|
4365
|
-
var
|
|
4366
|
-
|
|
4367
|
+
var button_5 = sibling(button_4, 2);
|
|
4368
|
+
button_5.__click = () => {
|
|
4367
4369
|
set(colorScheme, "dark");
|
|
4368
4370
|
applyColorScheme("dark");
|
|
4369
4371
|
};
|
|
4370
|
-
var
|
|
4371
|
-
|
|
4372
|
-
var node_1 = sibling(
|
|
4372
|
+
var button_6 = sibling(button_5, 2);
|
|
4373
|
+
button_6.__click = () => set(colorScheme, "both");
|
|
4374
|
+
var node_1 = sibling(div_7, 2);
|
|
4373
4375
|
{
|
|
4374
4376
|
var consequent = ($$anchor3) => {
|
|
4375
4377
|
var p = root_2();
|
|
@@ -4379,19 +4381,22 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4379
4381
|
if (get(colorScheme) === "both") $$render(consequent);
|
|
4380
4382
|
});
|
|
4381
4383
|
}
|
|
4382
|
-
var
|
|
4383
|
-
var
|
|
4384
|
-
|
|
4384
|
+
var div_8 = sibling(div_6, 2);
|
|
4385
|
+
var button_7 = child(div_8);
|
|
4386
|
+
button_7.__click = function(...$$args) {
|
|
4385
4387
|
var _a2;
|
|
4386
4388
|
(_a2 = $$props.onClose) == null ? void 0 : _a2.apply(this, $$args);
|
|
4387
4389
|
};
|
|
4388
|
-
var
|
|
4389
|
-
|
|
4390
|
+
var button_8 = sibling(button_7, 2);
|
|
4391
|
+
button_8.__click = handleSave;
|
|
4390
4392
|
template_effect(() => {
|
|
4391
|
-
set_class(button, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(
|
|
4392
|
-
set_class(button_1, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(
|
|
4393
|
-
set_class(button_2, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(
|
|
4394
|
-
set_class(button_3, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(colorScheme) ===
|
|
4393
|
+
set_class(button, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(deviceScaleFactor) === void 0 || get(deviceScaleFactor) === 1 ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300 hover:bg-slate-600"}`);
|
|
4394
|
+
set_class(button_1, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(deviceScaleFactor) === 2 ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300 hover:bg-slate-600"}`);
|
|
4395
|
+
set_class(button_2, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(deviceScaleFactor) === 3 ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300 hover:bg-slate-600"}`);
|
|
4396
|
+
set_class(button_3, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(colorScheme) === void 0 ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300 hover:bg-slate-600"}`);
|
|
4397
|
+
set_class(button_4, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(colorScheme) === "light" ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300 hover:bg-slate-600"}`);
|
|
4398
|
+
set_class(button_5, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(colorScheme) === "dark" ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300 hover:bg-slate-600"}`);
|
|
4399
|
+
set_class(button_6, 1, `px-3 py-1.5 rounded text-sm transition-colors ${get(colorScheme) === "both" ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300 hover:bg-slate-600"}`);
|
|
4395
4400
|
});
|
|
4396
4401
|
bind_value(input, () => get(width), ($$value) => set(width, $$value));
|
|
4397
4402
|
bind_value(input_1, () => get(height), ($$value) => set(height, $$value));
|
|
@@ -4465,7 +4470,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4465
4470
|
function Sidebar($$anchor, $$props) {
|
|
4466
4471
|
push($$props, true);
|
|
4467
4472
|
append_styles($$anchor, $$css$1);
|
|
4468
|
-
let side = /* @__PURE__ */ user_derived(() => $$props.selectedElementPosition === "right" ? "left" : "right");
|
|
4469
4473
|
let localEditingId = /* @__PURE__ */ state(null);
|
|
4470
4474
|
let editValue = /* @__PURE__ */ state("");
|
|
4471
4475
|
let inputElement = /* @__PURE__ */ state(null);
|
|
@@ -4562,7 +4566,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4562
4566
|
return "bg-slate-700/50 hover:bg-slate-600";
|
|
4563
4567
|
}
|
|
4564
4568
|
let dragTransform = /* @__PURE__ */ user_derived(() => get(dragOffsetX) !== 0 || get(dragOffsetY) !== 0 ? `transform: translate(${get(dragOffsetX)}px, ${get(dragOffsetY)}px);` : "");
|
|
4565
|
-
let positionStyle = /* @__PURE__ */ user_derived(() =>
|
|
4569
|
+
let positionStyle = /* @__PURE__ */ user_derived(() => `right: 16px; left: auto; ${get(dragTransform)}`);
|
|
4566
4570
|
var div = root$1();
|
|
4567
4571
|
div.__keydown = stopKeyboardEvent;
|
|
4568
4572
|
div.__keyup = (event2) => event2.stopPropagation();
|
|
@@ -4754,7 +4758,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4754
4758
|
// ID of draft item (not yet saved)
|
|
4755
4759
|
);
|
|
4756
4760
|
let selectedScreenshotId = /* @__PURE__ */ state(proxy($$props.initialSelectedId ?? null));
|
|
4757
|
-
let selectedElementPosition = /* @__PURE__ */ state(null);
|
|
4758
4761
|
let elementPicker;
|
|
4759
4762
|
let screenshotCount = /* @__PURE__ */ user_derived(() => get(screenshots).length);
|
|
4760
4763
|
let sidebarButtonClass = /* @__PURE__ */ user_derived(() => get(sidebarExpanded) ? "bg-blue-600" : "bg-slate-700 hover:bg-slate-600");
|
|
@@ -4782,7 +4785,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4782
4785
|
// Focus the name input
|
|
4783
4786
|
true
|
|
4784
4787
|
);
|
|
4785
|
-
set(selectedElementPosition, elementPicker.getSelectedElementSide(), true);
|
|
4786
4788
|
}
|
|
4787
4789
|
function handlePaddingUpdate(id, padding) {
|
|
4788
4790
|
const hasPadding = padding.top > 0 || padding.right > 0 || padding.bottom > 0 || padding.left > 0;
|
|
@@ -4813,11 +4815,9 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4813
4815
|
set(editingId, null);
|
|
4814
4816
|
}
|
|
4815
4817
|
set(selectedScreenshotId, null);
|
|
4816
|
-
set(selectedElementPosition, null);
|
|
4817
4818
|
}
|
|
4818
4819
|
function handleDeselect() {
|
|
4819
4820
|
set(selectedScreenshotId, null);
|
|
4820
|
-
set(selectedElementPosition, null);
|
|
4821
4821
|
}
|
|
4822
4822
|
function handleDraftConfirm(id) {
|
|
4823
4823
|
if (id === get(draftId)) {
|
|
@@ -4864,12 +4864,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4864
4864
|
const currentUrl = globalThis.location.href;
|
|
4865
4865
|
if (screenshot.url === currentUrl) {
|
|
4866
4866
|
elementPicker.highlightElement(screenshot.selector, screenshot.id);
|
|
4867
|
-
globalThis.setTimeout(
|
|
4868
|
-
() => {
|
|
4869
|
-
set(selectedElementPosition, elementPicker.getSelectedElementSide(), true);
|
|
4870
|
-
},
|
|
4871
|
-
200
|
|
4872
|
-
);
|
|
4873
4867
|
} else {
|
|
4874
4868
|
emit({
|
|
4875
4869
|
type: "screenshot-selected",
|
|
@@ -4881,12 +4875,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4881
4875
|
}
|
|
4882
4876
|
function executePendingJob(job) {
|
|
4883
4877
|
elementPicker.highlightElement(job.selector, job.screenshotId);
|
|
4884
|
-
globalThis.setTimeout(
|
|
4885
|
-
() => {
|
|
4886
|
-
set(selectedElementPosition, elementPicker.getSelectedElementSide(), true);
|
|
4887
|
-
},
|
|
4888
|
-
200
|
|
4889
|
-
);
|
|
4890
4878
|
emit({ type: "job-complete" });
|
|
4891
4879
|
}
|
|
4892
4880
|
function isToolbarJob(value) {
|
|
@@ -4984,9 +4972,6 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
4984
4972
|
get selectedId() {
|
|
4985
4973
|
return get(selectedScreenshotId);
|
|
4986
4974
|
},
|
|
4987
|
-
get selectedElementPosition() {
|
|
4988
|
-
return get(selectedElementPosition);
|
|
4989
|
-
},
|
|
4990
4975
|
onToggle: () => set(sidebarExpanded, !get(sidebarExpanded)),
|
|
4991
4976
|
onSelect: handleSelectScreenshot,
|
|
4992
4977
|
onRemove: handleRemoveScreenshot,
|