@spark-apps/piclet 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +311 -23
- package/dist/cli.js.map +1 -1
- package/dist/gui/css/theme.css +16 -10
- package/dist/gui/piclet.html +319 -15
- package/package.json +1 -1
package/dist/gui/css/theme.css
CHANGED
|
@@ -58,21 +58,27 @@ input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;hei
|
|
|
58
58
|
@keyframes s{to{transform:rotate(360deg)}}
|
|
59
59
|
.ld span{font-size:12px;color:var(--txt3)}
|
|
60
60
|
|
|
61
|
-
/* Log -
|
|
62
|
-
.log{display:none;
|
|
63
|
-
.log.on{display:
|
|
61
|
+
/* Log with preview - 6 lines high with thumbnail */
|
|
62
|
+
.log-container{display:none;gap:8px;height:calc(6 * 1.4em + 16px);flex-shrink:0}
|
|
63
|
+
.log-container.on{display:flex}
|
|
64
|
+
.log{flex:1;font:10px/1.4 Consolas,monospace;background:var(--bg2);border-radius:5px;padding:8px;overflow-y:auto;min-width:0}
|
|
64
65
|
.log p{margin:2px 0}
|
|
65
66
|
.log .i{color:var(--txt3)}
|
|
66
67
|
.log .s{color:var(--ok)}
|
|
67
68
|
.log .e{color:var(--err)}
|
|
69
|
+
.log-preview{width:80px;height:80px;object-fit:contain;background:var(--bg2);border-radius:5px;border:1px solid var(--brd);flex-shrink:0;display:none;align-self:center}
|
|
70
|
+
.log-preview.on{display:block}
|
|
68
71
|
|
|
69
|
-
/* Done
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.
|
|
72
|
+
/* Done modal */
|
|
73
|
+
#doneModal{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:100;align-items:center;justify-content:center}
|
|
74
|
+
#doneModal.on{display:flex}
|
|
75
|
+
.done-modal{background:var(--bg2);border:1px solid var(--brd);border-radius:8px;padding:16px 24px;text-align:center;min-width:200px}
|
|
76
|
+
.done-modal h4{font-size:14px;margin:0 0 4px}
|
|
77
|
+
.done-modal.ok h4{color:var(--ok)}
|
|
78
|
+
.done-modal.err h4{color:var(--err)}
|
|
79
|
+
.done-modal p{font-size:11px;color:var(--txt2);margin:0 0 12px;word-break:break-word}
|
|
80
|
+
.done-btns{display:flex;gap:8px;justify-content:center}
|
|
81
|
+
.done-btns .btn{padding:8px 16px;font-size:12px}
|
|
76
82
|
|
|
77
83
|
/* Warning/info box */
|
|
78
84
|
.warn-box{display:none;font-size:10px;color:#ca8a04;background:rgba(202,138,4,0.1);padding:6px 8px;border-radius:4px;text-align:center}
|
package/dist/gui/piclet.html
CHANGED
|
@@ -162,6 +162,11 @@
|
|
|
162
162
|
.gif-export-btn{display:none;width:100%;padding:8px 4px;font-size:10px;background:var(--acc);border:none;border-radius:4px;color:#000;cursor:pointer;font-weight:600;margin-top:6px}
|
|
163
163
|
.gif-export-btn.show{display:block}
|
|
164
164
|
.gif-export-btn:hover{background:var(--acc2)}
|
|
165
|
+
.frame-actions{display:none;gap:4px;margin-top:6px}
|
|
166
|
+
.frame-actions.show{display:flex}
|
|
167
|
+
.frame-actions button{flex:1;padding:6px 4px;font-size:9px;background:var(--bg3);border:1px solid var(--brd);border-radius:4px;color:var(--txt2);cursor:pointer}
|
|
168
|
+
.frame-actions button:hover{border-color:var(--acc);color:var(--acc)}
|
|
169
|
+
.frame-actions button.del:hover{border-color:#c44;color:#c44}
|
|
165
170
|
|
|
166
171
|
/* Export modal */
|
|
167
172
|
.export-modal{background:var(--bg2);border:1px solid var(--brd);border-radius:8px;padding:16px 20px;min-width:280px;max-width:320px}
|
|
@@ -227,7 +232,12 @@
|
|
|
227
232
|
<span>Frames</span>
|
|
228
233
|
</div>
|
|
229
234
|
<div class="frame-strip-scroll" id="frameScroll"></div>
|
|
235
|
+
<div class="frame-actions" id="frameActions">
|
|
236
|
+
<button class="del" onclick="deleteSelectedFrame()" title="Delete frame">Del</button>
|
|
237
|
+
<button onclick="replaceSelectedFrame()" title="Replace frame">Replace</button>
|
|
238
|
+
</div>
|
|
230
239
|
<button class="gif-export-btn" id="gifExportBtn" onclick="showExportModal()">Export</button>
|
|
240
|
+
<input type="file" id="replaceInput" accept=".png,.jpg,.jpeg" style="display:none" onchange="handleReplaceFile(event)">
|
|
231
241
|
</div>
|
|
232
242
|
|
|
233
243
|
<!-- Left divider (for frame panel) -->
|
|
@@ -280,6 +290,12 @@
|
|
|
280
290
|
</div>
|
|
281
291
|
<label class="opt"><input type="checkbox" id="rb-trim" checked onchange="schedulePreview()"><span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>Auto-trim edges</label>
|
|
282
292
|
<label class="opt"><input type="checkbox" id="rb-edges" onchange="schedulePreview()"><span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>Border only (preserve inner)</label>
|
|
293
|
+
<label class="opt"><input type="checkbox" id="rb-edgedetect" onchange="toggleEdgeDetect()"><span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>Feather edges (smoother cuts)</label>
|
|
294
|
+
<div class="tool-row" id="rb-edgeRow" style="display:none">
|
|
295
|
+
<label>Softness</label>
|
|
296
|
+
<input type="range" id="rb-edgestr" min="0" max="100" value="50" oninput="$('rb-edgestrV').textContent=this.value+'%'" onchange="schedulePreview()">
|
|
297
|
+
<span class="val" id="rb-edgestrV">50%</span>
|
|
298
|
+
</div>
|
|
283
299
|
</div>
|
|
284
300
|
</div>
|
|
285
301
|
</div>
|
|
@@ -379,15 +395,21 @@
|
|
|
379
395
|
|
|
380
396
|
<!-- Loading state -->
|
|
381
397
|
<div class="ld" id="L"><div class="sp"></div><span id="lT">Processing...</span></div>
|
|
382
|
-
<!-- Log -->
|
|
383
|
-
<div class="log" id="
|
|
384
|
-
|
|
385
|
-
|
|
398
|
+
<!-- Log with preview -->
|
|
399
|
+
<div class="log-container" id="LC">
|
|
400
|
+
<div class="log" id="G"></div>
|
|
401
|
+
<img class="log-preview" id="LP">
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<!-- Done modal -->
|
|
406
|
+
<div class="modal-overlay" id="doneModal">
|
|
407
|
+
<div class="done-modal" id="D">
|
|
386
408
|
<h4 id="dT"></h4>
|
|
387
409
|
<p id="dM"></p>
|
|
388
|
-
<div class="btns"
|
|
410
|
+
<div class="done-btns">
|
|
389
411
|
<button class="btn btn-g" onclick="reset()">Back</button>
|
|
390
|
-
<button class="btn" onclick="openOutputFolder()" id="openFolderBtn"
|
|
412
|
+
<button class="btn" onclick="openOutputFolder()" id="openFolderBtn">Open Folder</button>
|
|
391
413
|
<button class="btn btn-p" onclick="PicLet.close()">Done</button>
|
|
392
414
|
</div>
|
|
393
415
|
</div>
|
|
@@ -403,6 +425,55 @@
|
|
|
403
425
|
</div>
|
|
404
426
|
</div>
|
|
405
427
|
|
|
428
|
+
<!-- Simplify GIF modal -->
|
|
429
|
+
<div class="modal-overlay" id="simplifyModal" onclick="hideSimplifyModal(event)">
|
|
430
|
+
<div class="export-modal" onclick="event.stopPropagation()">
|
|
431
|
+
<h3>Large GIF Detected</h3>
|
|
432
|
+
<p style="font-size:11px;color:var(--txt2);margin-bottom:12px">
|
|
433
|
+
This GIF has <b id="simplifyFrameCount">0</b> frames which may be slow to process.
|
|
434
|
+
Would you like to simplify it by skipping frames?
|
|
435
|
+
</p>
|
|
436
|
+
<div class="export-options">
|
|
437
|
+
<label class="export-opt" onclick="selectSimplifyOption(1)">
|
|
438
|
+
<input type="radio" name="simplifyType" value="1">
|
|
439
|
+
<span class="radio"></span>
|
|
440
|
+
<div class="export-opt-content">
|
|
441
|
+
<div class="export-opt-title">Keep All Frames</div>
|
|
442
|
+
<div class="export-opt-desc">Use the original GIF as-is</div>
|
|
443
|
+
</div>
|
|
444
|
+
</label>
|
|
445
|
+
<label class="export-opt selected" onclick="selectSimplifyOption(2)">
|
|
446
|
+
<input type="radio" name="simplifyType" value="2" checked>
|
|
447
|
+
<span class="radio"></span>
|
|
448
|
+
<div class="export-opt-content">
|
|
449
|
+
<div class="export-opt-title">Skip Every 2nd Frame</div>
|
|
450
|
+
<div class="export-opt-desc" id="simplifyDesc2">Reduces to ~0 frames</div>
|
|
451
|
+
</div>
|
|
452
|
+
</label>
|
|
453
|
+
<label class="export-opt" onclick="selectSimplifyOption(3)">
|
|
454
|
+
<input type="radio" name="simplifyType" value="3">
|
|
455
|
+
<span class="radio"></span>
|
|
456
|
+
<div class="export-opt-content">
|
|
457
|
+
<div class="export-opt-title">Skip Every 3rd Frame</div>
|
|
458
|
+
<div class="export-opt-desc" id="simplifyDesc3">Reduces to ~0 frames</div>
|
|
459
|
+
</div>
|
|
460
|
+
</label>
|
|
461
|
+
<label class="export-opt" onclick="selectSimplifyOption(4)">
|
|
462
|
+
<input type="radio" name="simplifyType" value="4">
|
|
463
|
+
<span class="radio"></span>
|
|
464
|
+
<div class="export-opt-content">
|
|
465
|
+
<div class="export-opt-title">Skip Every 4th Frame</div>
|
|
466
|
+
<div class="export-opt-desc" id="simplifyDesc4">Reduces to ~0 frames</div>
|
|
467
|
+
</div>
|
|
468
|
+
</label>
|
|
469
|
+
</div>
|
|
470
|
+
<div class="export-modal-btns">
|
|
471
|
+
<button class="cancel" onclick="hideSimplifyModal()">Cancel</button>
|
|
472
|
+
<button class="confirm" onclick="confirmSimplify()">Continue</button>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
|
|
406
477
|
<!-- Export modal -->
|
|
407
478
|
<div class="modal-overlay" id="exportModal" onclick="hideExportModal(event)">
|
|
408
479
|
<div class="export-modal" onclick="event.stopPropagation()">
|
|
@@ -645,6 +716,16 @@ function toggleRatioLock() {
|
|
|
645
716
|
schedulePreview();
|
|
646
717
|
}
|
|
647
718
|
|
|
719
|
+
function toggleEdgeDetect() {
|
|
720
|
+
const enabled = $('rb-edgedetect').checked;
|
|
721
|
+
$('rb-edgeRow').style.display = enabled ? 'flex' : 'none';
|
|
722
|
+
// Edge detect and preserve inner are mutually exclusive
|
|
723
|
+
if (enabled) {
|
|
724
|
+
$('rb-edges').checked = false;
|
|
725
|
+
}
|
|
726
|
+
schedulePreview();
|
|
727
|
+
}
|
|
728
|
+
|
|
648
729
|
// Get combined options for all active tools
|
|
649
730
|
function getOptions() {
|
|
650
731
|
const opts = { tools: Array.from(activeTools) };
|
|
@@ -653,7 +734,9 @@ function getOptions() {
|
|
|
653
734
|
opts.removebg = {
|
|
654
735
|
fuzz: +$('rb-fuzz').value,
|
|
655
736
|
trim: $('rb-trim').checked,
|
|
656
|
-
preserveInner: $('rb-edges').checked
|
|
737
|
+
preserveInner: $('rb-edges').checked,
|
|
738
|
+
edgeDetect: $('rb-edgedetect').checked,
|
|
739
|
+
edgeStrength: +$('rb-edgestr').value
|
|
657
740
|
};
|
|
658
741
|
}
|
|
659
742
|
|
|
@@ -897,11 +980,24 @@ let isPlaying = false;
|
|
|
897
980
|
let playInterval = null;
|
|
898
981
|
let playSpeed = 100; // ms per frame
|
|
899
982
|
|
|
900
|
-
function showFrameStrip() {
|
|
983
|
+
function showFrameStrip(skipSimplifyCheck = false) {
|
|
984
|
+
// Check for large GIFs and offer to simplify
|
|
985
|
+
if (!skipSimplifyCheck && imageInfo.frameCount >= LARGE_GIF_THRESHOLD) {
|
|
986
|
+
checkForLargeGif(imageInfo.frameCount, () => {
|
|
987
|
+
// After simplification (or if user keeps all frames), show the strip
|
|
988
|
+
actuallyShowFrameStrip();
|
|
989
|
+
});
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
actuallyShowFrameStrip();
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function actuallyShowFrameStrip() {
|
|
901
996
|
$('framePanel').classList.add('show');
|
|
902
997
|
$('dividerLeft').classList.add('show');
|
|
903
998
|
$('playControls').classList.add('show');
|
|
904
999
|
$('gifExportBtn').classList.add('show');
|
|
1000
|
+
$('frameActions').classList.add('show');
|
|
905
1001
|
$('frameCount').textContent = imageInfo.frameCount;
|
|
906
1002
|
selectedFrameIndex = 0;
|
|
907
1003
|
gifFrames = [];
|
|
@@ -914,6 +1010,7 @@ function hideFrameStrip() {
|
|
|
914
1010
|
$('dividerLeft').classList.remove('show');
|
|
915
1011
|
$('playControls').classList.remove('show');
|
|
916
1012
|
$('gifExportBtn').classList.remove('show');
|
|
1013
|
+
$('frameActions').classList.remove('show');
|
|
917
1014
|
stopPlayback();
|
|
918
1015
|
gifFrames = [];
|
|
919
1016
|
framePreviewCache = {};
|
|
@@ -1178,8 +1275,9 @@ async function runExport(opts, message) {
|
|
|
1178
1275
|
$('M').classList.add('hide');
|
|
1179
1276
|
$('B').classList.add('hide');
|
|
1180
1277
|
$('L').classList.add('on');
|
|
1181
|
-
$('
|
|
1278
|
+
$('LC').classList.add('on');
|
|
1182
1279
|
$('G').innerHTML = '';
|
|
1280
|
+
$('LP').classList.remove('on');
|
|
1183
1281
|
$('lT').textContent = message;
|
|
1184
1282
|
|
|
1185
1283
|
try {
|
|
@@ -1187,6 +1285,8 @@ async function runExport(opts, message) {
|
|
|
1187
1285
|
if (result.logs) {
|
|
1188
1286
|
result.logs.forEach(l => log('G', l.type[0], l.message));
|
|
1189
1287
|
}
|
|
1288
|
+
// Try to show output preview
|
|
1289
|
+
if (result.success) await showLogPreview();
|
|
1190
1290
|
showDone(result.success, result.success ? 'Done' : 'Failed', result.success ? result.output : result.error);
|
|
1191
1291
|
} catch (e) {
|
|
1192
1292
|
log('G', 'e', e.message);
|
|
@@ -1194,6 +1294,193 @@ async function runExport(opts, message) {
|
|
|
1194
1294
|
}
|
|
1195
1295
|
}
|
|
1196
1296
|
|
|
1297
|
+
// ── Frame Delete/Replace ──
|
|
1298
|
+
|
|
1299
|
+
async function deleteSelectedFrame() {
|
|
1300
|
+
if (!isGifFile || gifFrames.length <= 1) {
|
|
1301
|
+
showAlert('Cannot delete the only frame');
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
stopPlayback();
|
|
1306
|
+
|
|
1307
|
+
// Show loading on the frame
|
|
1308
|
+
const thumb = document.querySelector(`.frame-thumb[data-index="${selectedFrameIndex}"]`);
|
|
1309
|
+
if (thumb) thumb.classList.add('processing');
|
|
1310
|
+
|
|
1311
|
+
try {
|
|
1312
|
+
const result = await postJson('/api/delete-frame', { frameIndex: selectedFrameIndex });
|
|
1313
|
+
|
|
1314
|
+
if (result.success) {
|
|
1315
|
+
imageInfo.frameCount = result.frameCount;
|
|
1316
|
+
$('frameCount').textContent = result.frameCount;
|
|
1317
|
+
|
|
1318
|
+
// Adjust selected index if needed
|
|
1319
|
+
if (selectedFrameIndex >= result.frameCount) {
|
|
1320
|
+
selectedFrameIndex = result.frameCount - 1;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Reload frame thumbnails
|
|
1324
|
+
gifFrames = [];
|
|
1325
|
+
framePreviewCache = {};
|
|
1326
|
+
loadFrameThumbnails();
|
|
1327
|
+
|
|
1328
|
+
// Show updated frame
|
|
1329
|
+
setTimeout(() => selectFrame(selectedFrameIndex), 100);
|
|
1330
|
+
} else {
|
|
1331
|
+
if (thumb) thumb.classList.remove('processing');
|
|
1332
|
+
showAlert('Failed to delete frame: ' + (result.error || 'Unknown error'));
|
|
1333
|
+
}
|
|
1334
|
+
} catch (err) {
|
|
1335
|
+
if (thumb) thumb.classList.remove('processing');
|
|
1336
|
+
showAlert('Failed to delete frame: ' + err.message);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function replaceSelectedFrame() {
|
|
1341
|
+
if (!isGifFile) return;
|
|
1342
|
+
$('replaceInput').click();
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
async function handleReplaceFile(e) {
|
|
1346
|
+
const file = e.target.files[0];
|
|
1347
|
+
if (!file) return;
|
|
1348
|
+
e.target.value = ''; // Reset for future use
|
|
1349
|
+
|
|
1350
|
+
stopPlayback();
|
|
1351
|
+
|
|
1352
|
+
// Show loading on the frame
|
|
1353
|
+
const thumb = document.querySelector(`.frame-thumb[data-index="${selectedFrameIndex}"]`);
|
|
1354
|
+
if (thumb) thumb.classList.add('processing');
|
|
1355
|
+
|
|
1356
|
+
try {
|
|
1357
|
+
// Read file as base64
|
|
1358
|
+
const reader = new FileReader();
|
|
1359
|
+
reader.onload = async () => {
|
|
1360
|
+
const base64 = reader.result.split(',')[1]; // Remove data URL prefix
|
|
1361
|
+
|
|
1362
|
+
const result = await postJson('/api/replace-frame', {
|
|
1363
|
+
frameIndex: selectedFrameIndex,
|
|
1364
|
+
imageData: base64
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
if (result.success) {
|
|
1368
|
+
// Reload frame thumbnails
|
|
1369
|
+
gifFrames = [];
|
|
1370
|
+
framePreviewCache = {};
|
|
1371
|
+
loadFrameThumbnails();
|
|
1372
|
+
|
|
1373
|
+
// Show updated frame
|
|
1374
|
+
setTimeout(() => selectFrame(selectedFrameIndex), 100);
|
|
1375
|
+
} else {
|
|
1376
|
+
if (thumb) thumb.classList.remove('processing');
|
|
1377
|
+
showAlert('Failed to replace frame: ' + (result.error || 'Unknown error'));
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
reader.readAsDataURL(file);
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
if (thumb) thumb.classList.remove('processing');
|
|
1383
|
+
showAlert('Failed to replace frame: ' + err.message);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// ── GIF Simplification ──
|
|
1388
|
+
|
|
1389
|
+
const LARGE_GIF_THRESHOLD = 50; // Frames threshold for showing simplify prompt
|
|
1390
|
+
let selectedSimplifyOption = 2; // Default to skip every 2nd frame
|
|
1391
|
+
let pendingSimplifyCallback = null;
|
|
1392
|
+
|
|
1393
|
+
function checkForLargeGif(frameCount, callback) {
|
|
1394
|
+
if (frameCount >= LARGE_GIF_THRESHOLD) {
|
|
1395
|
+
showSimplifyModal(frameCount, callback);
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
if (callback) callback();
|
|
1399
|
+
return false;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function showSimplifyModal(frameCount, callback) {
|
|
1403
|
+
pendingSimplifyCallback = callback;
|
|
1404
|
+
|
|
1405
|
+
// Update modal content
|
|
1406
|
+
$('simplifyFrameCount').textContent = frameCount;
|
|
1407
|
+
$('simplifyDesc2').textContent = `Reduces to ~${Math.ceil(frameCount / 2)} frames`;
|
|
1408
|
+
$('simplifyDesc3').textContent = `Reduces to ~${Math.ceil(frameCount / 3)} frames`;
|
|
1409
|
+
$('simplifyDesc4').textContent = `Reduces to ~${Math.ceil(frameCount / 4)} frames`;
|
|
1410
|
+
|
|
1411
|
+
// Reset selection to skip every 2nd (recommended for most cases)
|
|
1412
|
+
selectedSimplifyOption = 2;
|
|
1413
|
+
document.querySelectorAll('#simplifyModal .export-opt').forEach(opt => {
|
|
1414
|
+
const input = opt.querySelector('input');
|
|
1415
|
+
opt.classList.toggle('selected', input.value === '2');
|
|
1416
|
+
input.checked = input.value === '2';
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
$('simplifyModal').classList.add('on');
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function hideSimplifyModal(e) {
|
|
1423
|
+
if (!e || e.target === $('simplifyModal')) {
|
|
1424
|
+
$('simplifyModal').classList.remove('on');
|
|
1425
|
+
pendingSimplifyCallback = null;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function selectSimplifyOption(value) {
|
|
1430
|
+
selectedSimplifyOption = value;
|
|
1431
|
+
document.querySelectorAll('#simplifyModal .export-opt').forEach(opt => {
|
|
1432
|
+
const input = opt.querySelector('input');
|
|
1433
|
+
opt.classList.toggle('selected', +input.value === value);
|
|
1434
|
+
input.checked = +input.value === value;
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
async function confirmSimplify() {
|
|
1439
|
+
// Save callback before hiding modal (hideSimplifyModal clears it)
|
|
1440
|
+
const callback = pendingSimplifyCallback;
|
|
1441
|
+
hideSimplifyModal();
|
|
1442
|
+
|
|
1443
|
+
if (selectedSimplifyOption === 1) {
|
|
1444
|
+
// Keep all frames - just continue
|
|
1445
|
+
if (callback) callback();
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Show loading state
|
|
1450
|
+
pA.innerHTML = '<div class="mini-sp"></div>';
|
|
1451
|
+
pI.textContent = 'Simplifying GIF...';
|
|
1452
|
+
|
|
1453
|
+
try {
|
|
1454
|
+
const result = await postJson('/api/simplify-gif', { skipFactor: selectedSimplifyOption });
|
|
1455
|
+
|
|
1456
|
+
if (result.success) {
|
|
1457
|
+
// Update image info with simplified version
|
|
1458
|
+
imageInfo.width = result.width;
|
|
1459
|
+
imageInfo.height = result.height;
|
|
1460
|
+
imageInfo.frameCount = result.frameCount;
|
|
1461
|
+
|
|
1462
|
+
// Update display
|
|
1463
|
+
$('origSize').textContent = imageInfo.width + '×' + imageInfo.height;
|
|
1464
|
+
|
|
1465
|
+
// Refresh frame strip (skip simplify check since we just simplified)
|
|
1466
|
+
currentFrameCount = result.frameCount;
|
|
1467
|
+
if (result.frameCount > 1) {
|
|
1468
|
+
showFrameStrip(true); // Skip simplify check
|
|
1469
|
+
} else {
|
|
1470
|
+
hideFrameStrip();
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
if (callback) callback();
|
|
1474
|
+
} else {
|
|
1475
|
+
showAlert('Failed to simplify GIF: ' + (result.error || 'Unknown error'));
|
|
1476
|
+
showOriginal();
|
|
1477
|
+
}
|
|
1478
|
+
} catch (err) {
|
|
1479
|
+
showAlert('Failed to simplify GIF: ' + err.message);
|
|
1480
|
+
showOriginal();
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1197
1484
|
// ── Store Assets Preset Management ──
|
|
1198
1485
|
|
|
1199
1486
|
// Handle preset selection change
|
|
@@ -1373,8 +1660,9 @@ async function apply() {
|
|
|
1373
1660
|
$('M').classList.add('hide');
|
|
1374
1661
|
$('B').classList.add('hide');
|
|
1375
1662
|
$('L').classList.add('on');
|
|
1376
|
-
$('
|
|
1663
|
+
$('LC').classList.add('on');
|
|
1377
1664
|
$('G').innerHTML = '';
|
|
1665
|
+
$('LP').classList.remove('on');
|
|
1378
1666
|
|
|
1379
1667
|
const toolNames = Array.from(activeTools).map(t => {
|
|
1380
1668
|
const names = { removebg: 'Remove BG', scale: 'Scale', icons: 'Icons', storepack: 'Store Assets' };
|
|
@@ -1389,6 +1677,8 @@ async function apply() {
|
|
|
1389
1677
|
result.logs.forEach(l => log('G', l.type[0], l.message));
|
|
1390
1678
|
}
|
|
1391
1679
|
|
|
1680
|
+
// Try to show output preview
|
|
1681
|
+
if (result.success) await showLogPreview();
|
|
1392
1682
|
showDone(result.success, result.success ? 'Done' : 'Failed', result.success ? result.output : result.error);
|
|
1393
1683
|
} catch (e) {
|
|
1394
1684
|
log('G', 'e', e.message);
|
|
@@ -1398,11 +1688,12 @@ async function apply() {
|
|
|
1398
1688
|
|
|
1399
1689
|
// Reset to main view
|
|
1400
1690
|
function reset() {
|
|
1401
|
-
$('
|
|
1402
|
-
$('
|
|
1691
|
+
$('doneModal').classList.remove('on');
|
|
1692
|
+
$('D').classList.remove('ok', 'err');
|
|
1693
|
+
$('LC').classList.remove('on');
|
|
1694
|
+
$('LP').classList.remove('on');
|
|
1403
1695
|
$('M').classList.remove('hide');
|
|
1404
1696
|
$('B').classList.remove('hide');
|
|
1405
|
-
$('openFolderBtn').style.display = 'none';
|
|
1406
1697
|
lastOutputSuccess = false;
|
|
1407
1698
|
}
|
|
1408
1699
|
|
|
@@ -1420,10 +1711,23 @@ function openOutputFolder() {
|
|
|
1420
1711
|
postJson('/api/open-folder', {});
|
|
1421
1712
|
}
|
|
1422
1713
|
|
|
1423
|
-
// Show
|
|
1714
|
+
// Show output preview thumbnail in log area
|
|
1715
|
+
async function showLogPreview() {
|
|
1716
|
+
try {
|
|
1717
|
+
const result = await fetchJson('/api/output-preview');
|
|
1718
|
+
if (result.success && result.imageData) {
|
|
1719
|
+
$('LP').src = result.imageData;
|
|
1720
|
+
$('LP').classList.add('on');
|
|
1721
|
+
}
|
|
1722
|
+
} catch { /* ignore */ }
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// Show done modal
|
|
1424
1726
|
function showDone(success, title, message) {
|
|
1425
1727
|
$('L').classList.remove('on');
|
|
1426
|
-
$('
|
|
1728
|
+
$('doneModal').classList.add('on');
|
|
1729
|
+
$('D').classList.add(success ? 'ok' : 'err');
|
|
1730
|
+
$('D').classList.remove(success ? 'err' : 'ok');
|
|
1427
1731
|
$('dT').textContent = title;
|
|
1428
1732
|
$('dM').textContent = message;
|
|
1429
1733
|
lastOutputSuccess = success;
|