codeplay-common 4.7.2 → 4.7.3
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/files/scripts/frame-samples.js +117 -0
- package/files/take-screen-image.js +204 -25
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const sharp = require('sharp');
|
|
4
|
+
|
|
5
|
+
const frameStyles = {
|
|
6
|
+
android: {
|
|
7
|
+
graphite: { label: 'A · Graphite Android', detail: 'Slim dark bezel', outer: '#151a24', edge: '#626d7f', inner: '#080b12', radius: 47 },
|
|
8
|
+
cobalt: { label: 'B · Cobalt Android', detail: 'Blue accent bezel', outer: '#1945bc', edge: '#8aa9ff', inner: '#07102f', radius: 43 },
|
|
9
|
+
},
|
|
10
|
+
ios: {
|
|
11
|
+
titanium: { label: 'C · Titanium iPhone', detail: 'Light metal edge', outer: '#c7ced6', edge: '#eef2f6', inner: '#222936', radius: 53 },
|
|
12
|
+
midnight: { label: 'D · Midnight iPhone', detail: 'Dark rounded edge', outer: '#202333', edge: '#586076', inner: '#080b12', radius: 53 },
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function getStatusBarSvg(platform, time) {
|
|
17
|
+
if (platform === 'ios') {
|
|
18
|
+
return `<rect x="12" y="22" width="316" height="40" fill="#f5f7fc"/>
|
|
19
|
+
<text x="29" y="47" font-family="Arial,sans-serif" font-size="14" font-weight="700" fill="#050505">${time}</text>
|
|
20
|
+
<circle cx="246" cy="40" r="2" fill="#c5c7cb"/><circle cx="254" cy="40" r="2" fill="#c5c7cb"/>
|
|
21
|
+
<circle cx="262" cy="40" r="2" fill="#c5c7cb"/><circle cx="270" cy="40" r="2" fill="#c5c7cb"/>
|
|
22
|
+
<path d="M275 35 Q284 27 293 35 M278 39 Q284 34 290 39 M282 43 Q284 41 286 43" fill="none" stroke="#050505" stroke-width="2.5" stroke-linecap="round"/>
|
|
23
|
+
<rect x="295" y="33" width="17" height="13" rx="3" fill="none" stroke="#a7aaae" stroke-width="1.5"/>
|
|
24
|
+
<rect x="297" y="35" width="13" height="9" rx="2" fill="#050505"/>
|
|
25
|
+
<rect x="313" y="37" width="2" height="5" rx="1" fill="#a7aaae"/>`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `<rect x="12" y="22" width="316" height="40" fill="#9075ed"/>
|
|
29
|
+
<text x="29" y="47" font-family="Arial,sans-serif" font-size="14" fill="#ffffff">${time}</text>
|
|
30
|
+
<circle cx="86" cy="41" r="5" fill="none" stroke="#ffffff" stroke-width="1.2"/>
|
|
31
|
+
<circle cx="84" cy="40" r="0.8" fill="#ffffff"/><circle cx="88" cy="40" r="0.8" fill="#ffffff"/>
|
|
32
|
+
<g transform="translate(-15,0)">
|
|
33
|
+
<text x="200" y="36" font-family="Arial,sans-serif" font-size="7" font-weight="700" fill="#ffffff">0.26</text>
|
|
34
|
+
<text x="200" y="44" font-family="Arial,sans-serif" font-size="6" font-weight="700" fill="#ffffff">KB/s</text>
|
|
35
|
+
<path d="M222 43 L235 33 L235 43 Z M242 43 L254 33 L254 43 Z" fill="#ffffff"/>
|
|
36
|
+
<text x="257" y="39" font-family="Arial,sans-serif" font-size="7" font-weight="700" fill="#ffffff">Vo</text>
|
|
37
|
+
<text x="257" y="46" font-family="Arial,sans-serif" font-size="6" font-weight="700" fill="#ffffff">WiFi</text>
|
|
38
|
+
<path d="M274 35 Q283 27 292 35 M277 39 Q283 34 289 39 M281 43 Q283 41 285 43" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round"/>
|
|
39
|
+
<rect x="296" y="34" width="19" height="11" rx="2" fill="none" stroke="#ffffff" stroke-width="1.4"/>
|
|
40
|
+
<path d="M305 33 L301 40 L306 40 L304 46 L312 38 L307 38 L310 33 Z" fill="#ffffff"/>
|
|
41
|
+
<text x="317" y="43" font-family="Arial,sans-serif" font-size="7" fill="#ffffff">87%</text>
|
|
42
|
+
</g>`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createFallbackScreenshot(platform) {
|
|
46
|
+
const width = platform === 'ios' ? 1320 : 1080;
|
|
47
|
+
const height = platform === 'ios' ? 2868 : 2400;
|
|
48
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
49
|
+
<rect width="100%" height="100%" fill="#f5f7fc"/>
|
|
50
|
+
<rect x="8%" y="26%" width="84%" height="20%" rx="60" fill="#d7ddff"/>
|
|
51
|
+
<text x="50%" y="37%" text-anchor="middle" font-family="Arial,sans-serif" font-size="62" font-weight="700" fill="#4446c6">Image Compressor</text>
|
|
52
|
+
<rect x="8%" y="62%" width="84%" height="8%" rx="55" fill="#5145e7"/>
|
|
53
|
+
<text x="50%" y="67%" text-anchor="middle" font-family="Arial,sans-serif" font-size="42" font-weight="700" fill="#ffffff">Select images</text>
|
|
54
|
+
</svg>`;
|
|
55
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function getSampleScreenshot(autoScreenshotDirectory, platform) {
|
|
59
|
+
const directory = path.join(autoScreenshotDirectory, 'Store-image-Screenshot', platform === 'ios' ? 'iPhone' : 'Android');
|
|
60
|
+
if (fs.existsSync(directory)) {
|
|
61
|
+
const screenshot = fs.readdirSync(directory).filter((name) => /\.png$/i.test(name)).sort()[0];
|
|
62
|
+
if (screenshot) return fs.readFileSync(path.join(directory, screenshot));
|
|
63
|
+
}
|
|
64
|
+
return createFallbackScreenshot(platform);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createSampleCard(style, platform, screenshotUrl, index) {
|
|
68
|
+
const screenRadius = platform === 'ios' ? 40 : 34;
|
|
69
|
+
const sampleTime = platform === 'ios' ? (index ? '12:21' : '12:20') : (index ? '10:56' : '10:55');
|
|
70
|
+
const camera = platform === 'ios'
|
|
71
|
+
? '<rect x="130" y="27" width="80" height="24" rx="12" fill="#05070b"/>'
|
|
72
|
+
: '';
|
|
73
|
+
const navigation = platform === 'android'
|
|
74
|
+
? '<rect x="12" y="717" width="316" height="16" fill="#f4f7f8"/><rect x="107" y="725" width="126" height="3" rx="1.5" fill="#7a7b7c"/>'
|
|
75
|
+
: '';
|
|
76
|
+
|
|
77
|
+
return `<g transform="translate(${index * 510},0)">
|
|
78
|
+
<rect x="12" y="12" width="486" height="926" rx="25" fill="#ffffff" stroke="#dce2ee"/>
|
|
79
|
+
<text x="35" y="52" fill="#172137" font-family="Arial,sans-serif" font-size="24" font-weight="700">${style.label}</text>
|
|
80
|
+
<text x="35" y="80" fill="#6c7890" font-family="Arial,sans-serif" font-size="14">${style.detail} · ${platform === 'ios' ? 'iOS' : 'Android'} status bar</text>
|
|
81
|
+
<g transform="translate(85,115)">
|
|
82
|
+
<rect x="1" y="7" width="338" height="753" rx="47" fill="#0a1225" opacity="0.2"/>
|
|
83
|
+
<rect x="1" y="1" width="338" height="753" rx="${style.radius}" fill="${style.outer}" stroke="${style.edge}" stroke-width="2"/>
|
|
84
|
+
<rect x="7" y="7" width="326" height="741" rx="${style.radius - 5}" fill="${style.inner}"/>
|
|
85
|
+
<clipPath id="screen-${index}"><rect x="12" y="22" width="316" height="711" rx="${screenRadius}"/></clipPath>
|
|
86
|
+
<g clip-path="url(#screen-${index})">
|
|
87
|
+
<rect x="12" y="22" width="316" height="711" fill="#f5f7fc"/>
|
|
88
|
+
<image href="${screenshotUrl}" x="12" y="62" width="316" height="671" preserveAspectRatio="xMidYMin slice"/>
|
|
89
|
+
${getStatusBarSvg(platform, sampleTime)}
|
|
90
|
+
${navigation}
|
|
91
|
+
</g>
|
|
92
|
+
${camera}
|
|
93
|
+
<rect x="338" y="177" width="4" height="66" rx="2" fill="${style.edge}"/>
|
|
94
|
+
</g>
|
|
95
|
+
</g>`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function createFrameSamples(autoScreenshotDirectory) {
|
|
99
|
+
fs.mkdirSync(autoScreenshotDirectory, { recursive: true });
|
|
100
|
+
const paths = [];
|
|
101
|
+
|
|
102
|
+
for (const platform of ['android', 'ios']) {
|
|
103
|
+
const screenshot = await getSampleScreenshot(autoScreenshotDirectory, platform);
|
|
104
|
+
const screenshotUrl = `data:image/png;base64,${screenshot.toString('base64')}`;
|
|
105
|
+
const cards = Object.values(frameStyles[platform])
|
|
106
|
+
.map((style, index) => createSampleCard(style, platform, screenshotUrl, index))
|
|
107
|
+
.join('');
|
|
108
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1020" height="950" viewBox="0 0 1020 950"><rect width="1020" height="950" fill="#eef2f9"/>${cards}</svg>`;
|
|
109
|
+
const output = path.join(autoScreenshotDirectory, `${platform}-frame-options.png`);
|
|
110
|
+
await sharp(Buffer.from(svg)).png().toFile(output);
|
|
111
|
+
paths.push(output);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return paths;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { frameStyles, createFrameSamples };
|
|
@@ -10,6 +10,7 @@ const path = require('node:path');
|
|
|
10
10
|
const readline = require('node:readline/promises');
|
|
11
11
|
const { spawn, spawnSync } = require('node:child_process');
|
|
12
12
|
const sharp = require('sharp');
|
|
13
|
+
const { frameStyles, createFrameSamples } = require('./scripts/frame-samples.js');
|
|
13
14
|
|
|
14
15
|
const screenshotDirectory = path.join(__dirname, 'Auto-Screenshot', 'Store-image-Screenshot');
|
|
15
16
|
const inFrameDirectory = path.join(__dirname, 'Auto-Screenshot', 'Store-image-Frame');
|
|
@@ -21,6 +22,9 @@ const playstoreAssetsDirectory = path.join(aiAssetsDirectory, 'Playstore-Assets'
|
|
|
21
22
|
const capacitorConfigPath = path.join(__dirname, 'capacitor.config.json');
|
|
22
23
|
const amazonIconPath = path.join(__dirname, 'resources', 'icon-only.png');
|
|
23
24
|
const temporaryDirectory = path.join(__dirname, 'agent-temp');
|
|
25
|
+
let selectedFrameStyles = { android: 'cobalt', ios: 'midnight' };
|
|
26
|
+
let frameCaptureSequence = 0;
|
|
27
|
+
const frameTimeBase = Date.now();
|
|
24
28
|
const amazonAssetDefinitions = [
|
|
25
29
|
{ fileName: 'icon-1280x720.png', type: 'icon', width: 1280, height: 720 },
|
|
26
30
|
{ fileName: 'background-image-1920x1080.png', type: 'background-image', width: 1920, height: 1080 },
|
|
@@ -462,6 +466,7 @@ function parseArguments() {
|
|
|
462
466
|
samsungAssets: false,
|
|
463
467
|
dryRun: false,
|
|
464
468
|
frameExisting: false,
|
|
469
|
+
reframeExisting: false,
|
|
465
470
|
vivoExisting: false,
|
|
466
471
|
oppoExisting: false,
|
|
467
472
|
help: false,
|
|
@@ -470,6 +475,8 @@ function parseArguments() {
|
|
|
470
475
|
deleteNames: '',
|
|
471
476
|
prefix: '',
|
|
472
477
|
profiles: '',
|
|
478
|
+
androidFrame: '',
|
|
479
|
+
iosFrame: '',
|
|
473
480
|
};
|
|
474
481
|
|
|
475
482
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -483,6 +490,8 @@ function parseArguments() {
|
|
|
483
490
|
options.dryRun = true;
|
|
484
491
|
} else if (argument === '--frame-existing') {
|
|
485
492
|
options.frameExisting = true;
|
|
493
|
+
} else if (argument === '--reframe-existing') {
|
|
494
|
+
options.reframeExisting = true;
|
|
486
495
|
} else if (argument === '--vivo-existing') {
|
|
487
496
|
options.vivoExisting = true;
|
|
488
497
|
} else if (argument === '--oppo-existing') {
|
|
@@ -501,6 +510,12 @@ function parseArguments() {
|
|
|
501
510
|
} else if (argument === '--profiles') {
|
|
502
511
|
options.profiles = args[index + 1] || '';
|
|
503
512
|
index += 1;
|
|
513
|
+
} else if (argument === '--android-frame') {
|
|
514
|
+
options.androidFrame = args[index + 1] || '';
|
|
515
|
+
index += 1;
|
|
516
|
+
} else if (argument === '--ios-frame') {
|
|
517
|
+
options.iosFrame = args[index + 1] || '';
|
|
518
|
+
index += 1;
|
|
504
519
|
} else {
|
|
505
520
|
throw new Error(`Unknown argument: ${argument}`);
|
|
506
521
|
}
|
|
@@ -540,8 +555,10 @@ function printHelp() {
|
|
|
540
555
|
console.log('Usage:');
|
|
541
556
|
console.log(' node take-screen-image.js');
|
|
542
557
|
console.log(' node take-screen-image.js --profiles iphone,ipad');
|
|
558
|
+
console.log(' node take-screen-image.js --android-frame graphite --ios-frame titanium');
|
|
543
559
|
console.log(' node take-screen-image.js --prefix home --profiles android-phone,ipad');
|
|
544
560
|
console.log(' node take-screen-image.js --frame-existing');
|
|
561
|
+
console.log(' node take-screen-image.js --reframe-existing --profiles android-phone,iphone');
|
|
545
562
|
console.log(' node take-screen-image.js --vivo-existing');
|
|
546
563
|
console.log(' node take-screen-image.js --oppo-existing');
|
|
547
564
|
console.log(' node take-screen-image.js --amazon-assets');
|
|
@@ -551,6 +568,52 @@ function printHelp() {
|
|
|
551
568
|
console.log(' node take-screen-image.js --delete 3,4,5');
|
|
552
569
|
console.log('');
|
|
553
570
|
console.log('A normal capture automatically creates any missing Amazon, Samsung, and Play Store assets.');
|
|
571
|
+
console.log('Frame defaults: Android cobalt (B), iPhone midnight (D).');
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async function chooseFrameStyles(options) {
|
|
575
|
+
const choices = { android: options.androidFrame, ios: options.iosFrame };
|
|
576
|
+
const defaults = { android: 'cobalt', ios: 'midnight' };
|
|
577
|
+
const aliases = {
|
|
578
|
+
android: { a: 'graphite', b: 'cobalt', 1: 'graphite', 2: 'cobalt' },
|
|
579
|
+
ios: { c: 'titanium', d: 'midnight', 1: 'titanium', 2: 'midnight' },
|
|
580
|
+
};
|
|
581
|
+
const canPrompt = process.stdin.isTTY && !options.prefix;
|
|
582
|
+
const terminal = canPrompt ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
for (const platform of ['android', 'ios']) {
|
|
586
|
+
if (!choices[platform] && terminal) {
|
|
587
|
+
const names = Object.values(frameStyles[platform]).map((style) => style.label);
|
|
588
|
+
choices[platform] = await terminal.question(
|
|
589
|
+
`${platform === 'ios' ? 'iPhone' : 'Android'} frame (${names.join(' / ')}), Enter for ${defaults[platform]}: `,
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
choices[platform] = (choices[platform] || defaults[platform]).trim().toLowerCase();
|
|
593
|
+
choices[platform] = aliases[platform][choices[platform]] || choices[platform];
|
|
594
|
+
if (!frameStyles[platform][choices[platform]]) {
|
|
595
|
+
throw new Error(`Unknown ${platform} frame: ${choices[platform]}. Choose ${Object.keys(frameStyles[platform]).join(' or ')}.`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
} finally {
|
|
599
|
+
if (terminal) terminal.close();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
console.log(`Frame selection: Android ${choices.android}, iPhone ${choices.ios}.`);
|
|
603
|
+
return choices;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function prepareFrameSelection(options) {
|
|
607
|
+
const samplePaths = await createFrameSamples(autoScreenshotDirectory);
|
|
608
|
+
for (const samplePath of samplePaths) console.log(`Frame choices: ${samplePath}`);
|
|
609
|
+
selectedFrameStyles = await chooseFrameStyles(options);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function getFrameDisplayTime(platform) {
|
|
613
|
+
const time = new Date(frameTimeBase + frameCaptureSequence * 60000);
|
|
614
|
+
frameCaptureSequence += 1;
|
|
615
|
+
const hour = platform === 'ios' ? (time.getHours() % 12 || 12) : time.getHours();
|
|
616
|
+
return `${String(hour).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`;
|
|
554
617
|
}
|
|
555
618
|
|
|
556
619
|
function deleteMatchingImages(input) {
|
|
@@ -1190,11 +1253,11 @@ async function waitForReplayLayout(client, snapshot) {
|
|
|
1190
1253
|
|
|
1191
1254
|
function getFrameAdornment(frameType) {
|
|
1192
1255
|
if (frameType === 'iphone') {
|
|
1193
|
-
return '
|
|
1256
|
+
return '';
|
|
1194
1257
|
}
|
|
1195
1258
|
|
|
1196
1259
|
if (frameType === 'android-phone') {
|
|
1197
|
-
return '
|
|
1260
|
+
return '';
|
|
1198
1261
|
}
|
|
1199
1262
|
|
|
1200
1263
|
if (frameType === 'tablet') {
|
|
@@ -1220,10 +1283,11 @@ function getFrameAdornment(frameType) {
|
|
|
1220
1283
|
return '<div class="xr-status"></div>';
|
|
1221
1284
|
}
|
|
1222
1285
|
|
|
1223
|
-
function createStoreDeviceFrameHtml(profile, screenshotBuffer, imageWidth, imageHeight, canvasWidth, canvasHeight, imageFormat) {
|
|
1286
|
+
function createStoreDeviceFrameHtml(profile, screenshotBuffer, imageWidth, imageHeight, canvasWidth, canvasHeight, imageFormat, displayTime) {
|
|
1224
1287
|
const screenshotDataUrl = `data:image/${imageFormat === 'jpeg' ? 'jpeg' : 'png'};base64,${screenshotBuffer.toString('base64')}`;
|
|
1225
1288
|
const screenWidth = Math.floor(Math.min(canvasWidth - 160, (canvasHeight - 180) * imageWidth / imageHeight));
|
|
1226
1289
|
const accent = profile.name === 'oppo-store' ? '#76e4c2' : '#9daaff';
|
|
1290
|
+
const frameStyle = frameStyles.android[selectedFrameStyles.android];
|
|
1227
1291
|
|
|
1228
1292
|
return `<!doctype html>
|
|
1229
1293
|
<html>
|
|
@@ -1252,17 +1316,27 @@ function createStoreDeviceFrameHtml(profile, screenshotBuffer, imageWidth, image
|
|
|
1252
1316
|
z-index: 1;
|
|
1253
1317
|
width: ${screenWidth + 48}px;
|
|
1254
1318
|
padding: 34px 18px 20px;
|
|
1255
|
-
border: 6px solid
|
|
1319
|
+
border: 6px solid ${frameStyle.edge};
|
|
1256
1320
|
border-radius: 48px;
|
|
1257
|
-
background:
|
|
1321
|
+
background: ${frameStyle.outer};
|
|
1258
1322
|
box-shadow: 0 38px 85px rgba(0, 0, 0, 0.58), 0 0 0 2px rgba(255, 255, 255, 0.19) inset;
|
|
1259
1323
|
}
|
|
1260
1324
|
.screen {
|
|
1325
|
+
display: flex;
|
|
1326
|
+
flex-direction: column;
|
|
1261
1327
|
width: ${screenWidth}px;
|
|
1328
|
+
aspect-ratio: ${imageWidth} / ${imageHeight};
|
|
1329
|
+
overflow: hidden;
|
|
1262
1330
|
background: #fff;
|
|
1263
1331
|
box-shadow: 0 0 0 2px #03060c;
|
|
1264
1332
|
}
|
|
1265
|
-
.screen img { display: block; width: 100%; height:
|
|
1333
|
+
.screen img { display: block; width: 100%; height: 0; min-height: 0; flex: 1 1 0; object-fit: fill; }
|
|
1334
|
+
.phone-statusbar { position: relative; flex: none; height: 5%; display: flex; align-items: center; background: #9075ed; color: #fff; font-family: Arial, sans-serif; }
|
|
1335
|
+
.status-time { margin-left: 5%; font-size: ${Math.max(11, Math.round(screenWidth * 0.03))}px; white-space: nowrap; }
|
|
1336
|
+
.android-notification { width: 5%; height: 45%; margin-left: 2%; }
|
|
1337
|
+
.android-status-icons { width: 42%; height: 72%; margin-left: auto; margin-right: 2%; }
|
|
1338
|
+
.android-navigation { flex: none; height: 2.2%; display: grid; place-items: center; background: #f4f7f8; }
|
|
1339
|
+
.gesture-pill { width: 40%; height: 14%; border-radius: 999px; background: #7a7b7c; }
|
|
1266
1340
|
.camera {
|
|
1267
1341
|
position: absolute;
|
|
1268
1342
|
top: 11px;
|
|
@@ -1288,16 +1362,58 @@ function createStoreDeviceFrameHtml(profile, screenshotBuffer, imageWidth, image
|
|
|
1288
1362
|
<body>
|
|
1289
1363
|
<main class="device">
|
|
1290
1364
|
<div class="camera"></div>
|
|
1291
|
-
<div class="screen"
|
|
1365
|
+
<div class="screen">${getPhoneStatusBar('android', displayTime)}<img src="${screenshotDataUrl}" /><div class="android-navigation"><div class="gesture-pill"></div></div></div>
|
|
1292
1366
|
<div class="side-button"></div>
|
|
1293
1367
|
</main>
|
|
1294
1368
|
</body>
|
|
1295
1369
|
</html>`;
|
|
1296
1370
|
}
|
|
1297
1371
|
|
|
1298
|
-
function
|
|
1372
|
+
function getPhoneStatusBar(platform, time) {
|
|
1373
|
+
if (platform === 'ios') {
|
|
1374
|
+
return `<div class="phone-statusbar ios-statusbar">
|
|
1375
|
+
<span class="status-time">${time}</span>
|
|
1376
|
+
<div class="dynamic-island"></div>
|
|
1377
|
+
<svg viewBox="0 0 90 28" aria-hidden="true">
|
|
1378
|
+
<circle cx="5" cy="14" r="2" fill="#c5c7cb"></circle><circle cx="13" cy="14" r="2" fill="#c5c7cb"></circle>
|
|
1379
|
+
<circle cx="21" cy="14" r="2" fill="#c5c7cb"></circle><circle cx="29" cy="14" r="2" fill="#c5c7cb"></circle>
|
|
1380
|
+
<path d="M36 10 Q46 2 56 10 M39 14 Q46 8 53 14 M44 18 Q46 16 48 18" fill="none" stroke="#050505" stroke-width="2.5" stroke-linecap="round"></path>
|
|
1381
|
+
<rect x="61" y="7" width="22" height="15" rx="4" fill="none" stroke="#a7aaae" stroke-width="1.5"></rect>
|
|
1382
|
+
<rect x="64" y="10" width="16" height="9" rx="2" fill="#050505"></rect>
|
|
1383
|
+
<rect x="85" y="12" width="2" height="5" rx="1" fill="#a7aaae"></rect>
|
|
1384
|
+
</svg>
|
|
1385
|
+
</div>`;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
return `<div class="phone-statusbar android-statusbar">
|
|
1389
|
+
<span class="status-time">${time}</span>
|
|
1390
|
+
<svg class="android-notification" viewBox="0 0 18 18" aria-hidden="true">
|
|
1391
|
+
<circle cx="9" cy="9" r="6" fill="none" stroke="#ffffff" stroke-width="1.5"></circle>
|
|
1392
|
+
<circle cx="7" cy="8" r="1" fill="#ffffff"></circle><circle cx="11" cy="8" r="1" fill="#ffffff"></circle>
|
|
1393
|
+
</svg>
|
|
1394
|
+
<svg class="android-status-icons" viewBox="0 0 132 28" aria-hidden="true">
|
|
1395
|
+
<text x="1" y="11" font-size="8" font-weight="700" fill="#ffffff">0.26</text>
|
|
1396
|
+
<text x="1" y="19" font-size="7" font-weight="700" fill="#ffffff">KB/s</text>
|
|
1397
|
+
<path d="M24 20 L37 9 L37 20 Z M42 20 L54 9 L54 20 Z" fill="#ffffff"></path>
|
|
1398
|
+
<text x="57" y="12" font-size="8" font-weight="700" fill="#ffffff">Vo</text>
|
|
1399
|
+
<text x="57" y="20" font-size="7" font-weight="700" fill="#ffffff">WiFi</text>
|
|
1400
|
+
<path d="M76 11 Q85 3 94 11 M79 15 Q85 9 91 15 M83 19 Q85 17 87 19" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round"></path>
|
|
1401
|
+
<rect x="97" y="8" width="18" height="13" rx="2" fill="none" stroke="#ffffff" stroke-width="1.4"></rect>
|
|
1402
|
+
<path d="M106 7 L102 14 L107 14 L105 22 L113 13 L108 13 L111 7 Z" fill="#ffffff"></path>
|
|
1403
|
+
<text x="117" y="18" font-size="8" fill="#ffffff">87%</text>
|
|
1404
|
+
</svg>
|
|
1405
|
+
</div>`;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function createDeviceFrameHtml(profile, screenshotBuffer, imageWidth = profile.width, imageHeight = profile.height, imageFormat = 'png', displayTime = '') {
|
|
1299
1409
|
const screenshotDataUrl = `data:image/${imageFormat === 'jpeg' ? 'jpeg' : 'png'};base64,${screenshotBuffer.toString('base64')}`;
|
|
1300
1410
|
const aspectRatio = `${imageWidth} / ${imageHeight}`;
|
|
1411
|
+
const phonePlatform = profile.frameType === 'iphone' ? 'ios' : profile.frameType === 'android-phone' ? 'android' : null;
|
|
1412
|
+
const phoneStyle = phonePlatform && frameStyles[phonePlatform][selectedFrameStyles[phonePlatform]];
|
|
1413
|
+
const phoneStatusBar = phonePlatform ? getPhoneStatusBar(phonePlatform, displayTime) : '';
|
|
1414
|
+
const phoneNavigation = phonePlatform === 'android'
|
|
1415
|
+
? '<div class="android-navigation"><div class="gesture-pill"></div></div>'
|
|
1416
|
+
: '';
|
|
1301
1417
|
|
|
1302
1418
|
return `<!doctype html>
|
|
1303
1419
|
<html>
|
|
@@ -1316,6 +1432,8 @@ function createDeviceFrameHtml(profile, screenshotBuffer, imageWidth = profile.w
|
|
|
1316
1432
|
linear-gradient(145deg, #111827 0%, #070b12 52%, #020409 100%);
|
|
1317
1433
|
font-family: Arial, sans-serif;
|
|
1318
1434
|
}
|
|
1435
|
+
body.phone-frame { background: #eef2f9; }
|
|
1436
|
+
body.phone-frame::before { display: none; }
|
|
1319
1437
|
body::before {
|
|
1320
1438
|
content: '';
|
|
1321
1439
|
position: absolute;
|
|
@@ -1341,7 +1459,7 @@ function createDeviceFrameHtml(profile, screenshotBuffer, imageWidth = profile.w
|
|
|
1341
1459
|
.screen {
|
|
1342
1460
|
position: relative;
|
|
1343
1461
|
width: 100%;
|
|
1344
|
-
height:
|
|
1462
|
+
height: 100%;
|
|
1345
1463
|
overflow: hidden;
|
|
1346
1464
|
background: #f5f7fc;
|
|
1347
1465
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.95) inset;
|
|
@@ -1355,14 +1473,65 @@ function createDeviceFrameHtml(profile, screenshotBuffer, imageWidth = profile.w
|
|
|
1355
1473
|
}
|
|
1356
1474
|
.device.android-phone,
|
|
1357
1475
|
.device.iphone {
|
|
1358
|
-
height:
|
|
1359
|
-
padding:
|
|
1476
|
+
height: 84%;
|
|
1477
|
+
padding: 2.5%;
|
|
1360
1478
|
border-radius: clamp(24px, 8vw, 70px);
|
|
1361
1479
|
}
|
|
1362
|
-
|
|
1363
|
-
.device.
|
|
1364
|
-
|
|
1480
|
+
${phoneStyle ? `
|
|
1481
|
+
.device.${profile.frameType} {
|
|
1482
|
+
background: ${phoneStyle.outer};
|
|
1483
|
+
border-color: ${phoneStyle.edge};
|
|
1484
|
+
border-radius: calc(84vh * ${imageWidth / imageHeight * phoneStyle.radius / 340});
|
|
1485
|
+
box-shadow: 0 4vh 9vh rgba(0, 0, 0, 0.48), 0 0 0 0.55vw ${phoneStyle.inner} inset;
|
|
1486
|
+
}
|
|
1487
|
+
.device.${profile.frameType} .screen {
|
|
1488
|
+
display: flex;
|
|
1489
|
+
flex-direction: column;
|
|
1490
|
+
height: 100%;
|
|
1491
|
+
border-radius: calc(84vh * ${imageWidth / imageHeight * phoneStyle.radius / 340} - 2.5vw - clamp(2px, 0.45vw, 9px));
|
|
1492
|
+
}
|
|
1493
|
+
.device.${profile.frameType} .screen img {
|
|
1494
|
+
flex: 1 1 0;
|
|
1495
|
+
min-height: 0;
|
|
1496
|
+
object-fit: fill;
|
|
1497
|
+
}` : ''}
|
|
1498
|
+
.phone-statusbar {
|
|
1499
|
+
position: relative;
|
|
1500
|
+
z-index: 2;
|
|
1501
|
+
flex: none;
|
|
1502
|
+
height: 5%;
|
|
1503
|
+
display: flex;
|
|
1504
|
+
align-items: center;
|
|
1505
|
+
font-family: Arial, sans-serif;
|
|
1506
|
+
}
|
|
1507
|
+
.status-time {
|
|
1508
|
+
margin-left: 5%;
|
|
1509
|
+
font-size: clamp(11px, 3vw, 40px);
|
|
1510
|
+
white-space: nowrap;
|
|
1511
|
+
}
|
|
1512
|
+
.ios-statusbar { background: #f5f7fc; color: #050505; font-weight: 700; }
|
|
1513
|
+
.ios-statusbar svg { width: 27%; height: 62%; margin-left: auto; margin-right: 3%; }
|
|
1514
|
+
.ios-statusbar .dynamic-island {
|
|
1515
|
+
position: absolute;
|
|
1516
|
+
top: 22%;
|
|
1517
|
+
left: 50%;
|
|
1518
|
+
width: 25%;
|
|
1519
|
+
height: 56%;
|
|
1520
|
+
transform: translateX(-50%);
|
|
1521
|
+
border-radius: 999px;
|
|
1522
|
+
background: #000;
|
|
1523
|
+
}
|
|
1524
|
+
.android-statusbar { background: #9075ed; color: #fff; }
|
|
1525
|
+
.android-notification { width: 5%; height: 45%; margin-left: 2%; }
|
|
1526
|
+
.android-status-icons { width: 42%; height: 72%; margin-left: auto; margin-right: 2%; }
|
|
1527
|
+
.android-navigation {
|
|
1528
|
+
flex: none;
|
|
1529
|
+
height: 2.2%;
|
|
1530
|
+
display: grid;
|
|
1531
|
+
place-items: center;
|
|
1532
|
+
background: #f4f7f8;
|
|
1365
1533
|
}
|
|
1534
|
+
.gesture-pill { width: 40%; height: 14%; border-radius: 999px; background: #7a7b7c; }
|
|
1366
1535
|
.camera-dot {
|
|
1367
1536
|
position: absolute;
|
|
1368
1537
|
z-index: 3;
|
|
@@ -1528,9 +1697,9 @@ function createDeviceFrameHtml(profile, screenshotBuffer, imageWidth = profile.w
|
|
|
1528
1697
|
}
|
|
1529
1698
|
</style>
|
|
1530
1699
|
</head>
|
|
1531
|
-
<body>
|
|
1700
|
+
<body class="${phonePlatform ? 'phone-frame' : ''}">
|
|
1532
1701
|
<main class="device ${profile.frameType}">
|
|
1533
|
-
<div class="screen"
|
|
1702
|
+
<div class="screen">${phoneStatusBar}<img src="${screenshotDataUrl}" />${phoneNavigation}</div>
|
|
1534
1703
|
${getFrameAdornment(profile.frameType)}
|
|
1535
1704
|
</main>
|
|
1536
1705
|
</body>
|
|
@@ -2189,6 +2358,8 @@ async function captureFramedScreenshot(
|
|
|
2189
2358
|
const frameWidth = profile.framedWidth || profile.width;
|
|
2190
2359
|
const frameHeight = profile.framedHeight || profile.height;
|
|
2191
2360
|
const frameScaleFactor = hasCustomFrameSize ? 1 : profile.deviceScaleFactor;
|
|
2361
|
+
const phonePlatform = profile.frameType === 'iphone' ? 'ios' : profile.frameType === 'android-phone' ? 'android' : null;
|
|
2362
|
+
const displayTime = phonePlatform ? getFrameDisplayTime(phonePlatform) : '';
|
|
2192
2363
|
|
|
2193
2364
|
await client.send('Emulation.setDeviceMetricsOverride', {
|
|
2194
2365
|
width: frameWidth,
|
|
@@ -2207,9 +2378,9 @@ async function captureFramedScreenshot(
|
|
|
2207
2378
|
html: profile.name === 'vivo-store' || profile.name === 'oppo-store'
|
|
2208
2379
|
? createStoreDeviceFrameHtml(
|
|
2209
2380
|
profile, screenshotBuffer, originalImageMetadata.width, originalImageMetadata.height,
|
|
2210
|
-
frameWidth, frameHeight, originalImageMetadata.format,
|
|
2381
|
+
frameWidth, frameHeight, originalImageMetadata.format, displayTime,
|
|
2211
2382
|
)
|
|
2212
|
-
: createDeviceFrameHtml(profile, screenshotBuffer, frameWidth, frameHeight),
|
|
2383
|
+
: createDeviceFrameHtml(profile, screenshotBuffer, frameWidth, frameHeight, 'png', displayTime),
|
|
2213
2384
|
});
|
|
2214
2385
|
await client.send('Runtime.evaluate', {
|
|
2215
2386
|
expression: `new Promise((resolve) => {
|
|
@@ -2358,7 +2529,7 @@ async function captureScreenshotSet(
|
|
|
2358
2529
|
console.log(`Responsive screenshot set completed: ${screenshotFileName}`);
|
|
2359
2530
|
}
|
|
2360
2531
|
|
|
2361
|
-
async function frameExistingScreenshots(selectedProfiles) {
|
|
2532
|
+
async function frameExistingScreenshots(selectedProfiles, overwriteExisting = false) {
|
|
2362
2533
|
let replayBrowser;
|
|
2363
2534
|
let replayClient;
|
|
2364
2535
|
let framedCount = 0;
|
|
@@ -2389,7 +2560,7 @@ async function frameExistingScreenshots(selectedProfiles) {
|
|
|
2389
2560
|
fs.readFileSync(screenshotPath),
|
|
2390
2561
|
profile,
|
|
2391
2562
|
screenshotFileName,
|
|
2392
|
-
|
|
2563
|
+
!overwriteExisting,
|
|
2393
2564
|
);
|
|
2394
2565
|
if (wasCreated) framedCount += 1;
|
|
2395
2566
|
} catch (error) {
|
|
@@ -2678,6 +2849,10 @@ async function main() {
|
|
|
2678
2849
|
return;
|
|
2679
2850
|
}
|
|
2680
2851
|
|
|
2852
|
+
if (options.vivoExisting || options.oppoExisting) {
|
|
2853
|
+
await prepareFrameSelection(options);
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2681
2856
|
if (options.vivoExisting) {
|
|
2682
2857
|
await createVivoAssetsFromExistingScreenshots();
|
|
2683
2858
|
return;
|
|
@@ -2688,17 +2863,21 @@ async function main() {
|
|
|
2688
2863
|
return;
|
|
2689
2864
|
}
|
|
2690
2865
|
|
|
2866
|
+
const selectedProfiles = selectProfiles(options.profiles);
|
|
2867
|
+
|
|
2691
2868
|
if (!options.dryRun) {
|
|
2692
|
-
await
|
|
2869
|
+
await prepareFrameSelection(options);
|
|
2693
2870
|
}
|
|
2694
2871
|
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
if (options.frameExisting) {
|
|
2698
|
-
await frameExistingScreenshots(selectedProfiles);
|
|
2872
|
+
if (options.frameExisting || options.reframeExisting) {
|
|
2873
|
+
await frameExistingScreenshots(selectedProfiles, options.reframeExisting);
|
|
2699
2874
|
return;
|
|
2700
2875
|
}
|
|
2701
2876
|
|
|
2877
|
+
if (!options.dryRun) {
|
|
2878
|
+
await createMissingStoreAssets();
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2702
2881
|
const interactiveMode = process.stdin.isTTY && !options.prefix && !options.dryRun;
|
|
2703
2882
|
const deviceId = getConnectedDevice();
|
|
2704
2883
|
const appId = getAppId();
|