@spark-apps/piclet 1.0.3 → 1.0.5
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 +493 -192
- package/dist/cli.js.map +1 -1
- package/dist/gui/css/theme.css +24 -10
- package/dist/gui/piclet.html +377 -50
- package/package.json +1 -1
- package/dist/gui/border.html +0 -229
- package/dist/gui/extract-frames.html +0 -156
- package/dist/gui/filter.html +0 -180
- package/dist/gui/iconpack.html +0 -113
- package/dist/gui/makeicon.html +0 -165
- package/dist/gui/recolor.html +0 -243
- package/dist/gui/remove-bg.html +0 -178
- package/dist/gui/rescale.html +0 -195
- package/dist/gui/storepack.html +0 -179
- package/dist/gui/transform.html +0 -202
package/dist/gui/border.html
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" data-piclet data-width="400" data-height="440">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>Add Border</title>
|
|
7
|
-
<link rel="stylesheet" href="/css/theme.css">
|
|
8
|
-
<script src="/js/piclet.js"></script>
|
|
9
|
-
<style>
|
|
10
|
-
.preview-area{flex:1;display:flex;align-items:center;justify-content:center;background:var(--bg2);border-radius:6px;min-height:140px;overflow:hidden;position:relative}
|
|
11
|
-
.preview-area::before{content:'';position:absolute;inset:0;background:repeating-conic-gradient(#1a1a1d 0% 25%, #222 0% 50%) 50%/16px 16px;z-index:0}
|
|
12
|
-
.preview-area img{max-width:100%;max-height:100%;object-fit:contain;position:relative;z-index:1}
|
|
13
|
-
.preview-area .placeholder{position:relative;z-index:1;font-size:11px;color:var(--txt3)}
|
|
14
|
-
.preview-area .mini-sp{width:16px;height:16px;border:2px solid var(--bg3);border-top-color:var(--acc);border-radius:50%;animation:s .5s linear infinite;position:relative;z-index:1}
|
|
15
|
-
.preview-info{font-size:10px;color:var(--txt3);text-align:center;margin-top:4px;height:14px}
|
|
16
|
-
.controls{display:flex;flex-direction:column;gap:10px}
|
|
17
|
-
.control-row{display:flex;align-items:center;gap:10px}
|
|
18
|
-
.control-row label{font-size:11px;color:var(--txt3);width:50px;flex-shrink:0}
|
|
19
|
-
.control-row input[type="range"]{flex:1;height:4px;background:var(--bg3);border-radius:3px;-webkit-appearance:none}
|
|
20
|
-
.control-row input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;background:var(--acc);border-radius:50%;cursor:pointer}
|
|
21
|
-
.control-row .val{width:40px;font-size:12px;color:var(--acc2);text-align:right;font-weight:500}
|
|
22
|
-
.color-row{display:flex;align-items:center;gap:10px}
|
|
23
|
-
.color-row label{font-size:11px;color:var(--txt3);width:50px;flex-shrink:0}
|
|
24
|
-
.color-input-wrap{flex:1;display:flex;align-items:center;gap:8px}
|
|
25
|
-
.color-preview{width:28px;height:28px;border-radius:4px;border:1px solid var(--brd);cursor:pointer;flex-shrink:0}
|
|
26
|
-
.color-input-wrap input[type="color"]{position:absolute;opacity:0;width:28px;height:28px;cursor:pointer}
|
|
27
|
-
.color-input-wrap input[type="text"]{flex:1;padding:6px 10px;background:var(--bg2);border:1px solid var(--brd);border-radius:5px;color:var(--txt);font:inherit;font-size:12px}
|
|
28
|
-
.color-input-wrap input[type="text"]:focus{outline:none;border-color:var(--acc)}
|
|
29
|
-
.preset-colors{display:flex;gap:4px;margin-left:auto}
|
|
30
|
-
.preset-color{width:20px;height:20px;border-radius:3px;border:1px solid var(--brd);cursor:pointer;transition:transform .1s}
|
|
31
|
-
.preset-color:hover{transform:scale(1.1)}
|
|
32
|
-
</style>
|
|
33
|
-
</head>
|
|
34
|
-
<body>
|
|
35
|
-
<div class="app">
|
|
36
|
-
<div class="hd"><b>PicLet</b><span>Add Border</span></div>
|
|
37
|
-
<div class="meta">
|
|
38
|
-
<div>File<b id="fn">-</b></div>
|
|
39
|
-
<div>Size<b id="sz">-</b></div>
|
|
40
|
-
</div>
|
|
41
|
-
<!-- Form -->
|
|
42
|
-
<div id="F" class="form">
|
|
43
|
-
<div class="preview-area" id="pA">
|
|
44
|
-
<span class="placeholder">Adjust to preview</span>
|
|
45
|
-
</div>
|
|
46
|
-
<div class="preview-info" id="pI"></div>
|
|
47
|
-
<div class="controls">
|
|
48
|
-
<div class="control-row">
|
|
49
|
-
<label>Width</label>
|
|
50
|
-
<input type="range" id="wR" min="1" max="100" value="10">
|
|
51
|
-
<span class="val" id="wV">10px</span>
|
|
52
|
-
</div>
|
|
53
|
-
<div class="color-row">
|
|
54
|
-
<label>Color</label>
|
|
55
|
-
<div class="color-input-wrap">
|
|
56
|
-
<div class="color-preview" id="cP" style="background:#ffffff"></div>
|
|
57
|
-
<input type="color" id="cC" value="#ffffff">
|
|
58
|
-
<input type="text" id="cT" value="#ffffff" placeholder="#ffffff">
|
|
59
|
-
<div class="preset-colors">
|
|
60
|
-
<div class="preset-color" style="background:#ffffff" data-color="#ffffff"></div>
|
|
61
|
-
<div class="preset-color" style="background:#000000" data-color="#000000"></div>
|
|
62
|
-
<div class="preset-color" style="background:#eab308" data-color="#eab308"></div>
|
|
63
|
-
<div class="preset-color" style="background:#ef4444" data-color="#ef4444"></div>
|
|
64
|
-
<div class="preset-color" style="background:#3b82f6" data-color="#3b82f6"></div>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
<div class="btns">
|
|
70
|
-
<button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
|
|
71
|
-
<button class="btn btn-p" onclick="apply()">Apply</button>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
<!-- Loading state -->
|
|
75
|
-
<div class="ld" id="L"><div class="sp"></div><span id="lT">Adding border...</span></div>
|
|
76
|
-
<!-- Log -->
|
|
77
|
-
<div class="log" id="G"></div>
|
|
78
|
-
<!-- Done state -->
|
|
79
|
-
<div class="dn" id="D">
|
|
80
|
-
<h4 id="dT"></h4>
|
|
81
|
-
<p id="dM"></p>
|
|
82
|
-
<div class="btns" style="width:100%;margin-top:8px">
|
|
83
|
-
<button class="btn btn-p" onclick="PicLet.close()">Done</button>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
<script>
|
|
88
|
-
const { $, log, fetchJson, postJson } = PicLet;
|
|
89
|
-
const pA = $('pA'), pI = $('pI');
|
|
90
|
-
const wR = $('wR'), wV = $('wV');
|
|
91
|
-
const cC = $('cC'), cT = $('cT'), cP = $('cP');
|
|
92
|
-
|
|
93
|
-
let previewTimeout = null;
|
|
94
|
-
let isSliding = false;
|
|
95
|
-
let lastPreviewOpts = null;
|
|
96
|
-
|
|
97
|
-
// Width slider
|
|
98
|
-
wR.oninput = () => { wV.textContent = wR.value + 'px'; };
|
|
99
|
-
wR.addEventListener('mousedown', () => { isSliding = true; });
|
|
100
|
-
wR.addEventListener('mouseup', () => { isSliding = false; schedulePreview(); });
|
|
101
|
-
wR.addEventListener('mouseleave', () => { if (isSliding) { isSliding = false; schedulePreview(); } });
|
|
102
|
-
|
|
103
|
-
// Color picker
|
|
104
|
-
cC.oninput = () => {
|
|
105
|
-
cT.value = cC.value;
|
|
106
|
-
cP.style.background = cC.value;
|
|
107
|
-
schedulePreview();
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
cT.oninput = () => {
|
|
111
|
-
const val = cT.value;
|
|
112
|
-
if (/^#[0-9a-fA-F]{6}$/.test(val)) {
|
|
113
|
-
cC.value = val;
|
|
114
|
-
cP.style.background = val;
|
|
115
|
-
schedulePreview();
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
cT.onblur = () => {
|
|
120
|
-
schedulePreview();
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
cP.onclick = () => cC.click();
|
|
124
|
-
|
|
125
|
-
// Preset colors
|
|
126
|
-
document.querySelectorAll('.preset-color').forEach(el => {
|
|
127
|
-
el.onclick = () => {
|
|
128
|
-
const color = el.dataset.color;
|
|
129
|
-
cC.value = color;
|
|
130
|
-
cT.value = color;
|
|
131
|
-
cP.style.background = color;
|
|
132
|
-
schedulePreview();
|
|
133
|
-
};
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Get current options
|
|
137
|
-
function getOptions() {
|
|
138
|
-
return {
|
|
139
|
-
width: +wR.value,
|
|
140
|
-
color: cT.value || '#ffffff'
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Check if options changed
|
|
145
|
-
function optionsChanged() {
|
|
146
|
-
const current = JSON.stringify(getOptions());
|
|
147
|
-
if (current === lastPreviewOpts) return false;
|
|
148
|
-
lastPreviewOpts = current;
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Schedule preview
|
|
153
|
-
function schedulePreview() {
|
|
154
|
-
if (isSliding) return;
|
|
155
|
-
if (previewTimeout) clearTimeout(previewTimeout);
|
|
156
|
-
previewTimeout = setTimeout(() => {
|
|
157
|
-
if (!isSliding && optionsChanged()) {
|
|
158
|
-
generatePreview();
|
|
159
|
-
}
|
|
160
|
-
}, 300);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Generate preview
|
|
164
|
-
async function generatePreview() {
|
|
165
|
-
pA.innerHTML = '<div class="mini-sp"></div>';
|
|
166
|
-
pI.textContent = '';
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
const result = await postJson('/api/preview', getOptions());
|
|
170
|
-
|
|
171
|
-
if (result.success && result.imageData) {
|
|
172
|
-
const img = document.createElement('img');
|
|
173
|
-
img.src = result.imageData;
|
|
174
|
-
img.alt = 'Preview';
|
|
175
|
-
pA.innerHTML = '';
|
|
176
|
-
pA.appendChild(img);
|
|
177
|
-
pI.textContent = result.width && result.height ? `${result.width}×${result.height}` : '';
|
|
178
|
-
} else {
|
|
179
|
-
pA.innerHTML = '<span class="placeholder">' + (result.error || 'Preview failed') + '</span>';
|
|
180
|
-
pI.textContent = '';
|
|
181
|
-
}
|
|
182
|
-
} catch (e) {
|
|
183
|
-
pA.innerHTML = '<span class="placeholder">Preview error</span>';
|
|
184
|
-
pI.textContent = '';
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Load initial data
|
|
189
|
-
fetchJson('/api/info').then(d => {
|
|
190
|
-
$('fn').textContent = d.fileName;
|
|
191
|
-
$('sz').textContent = d.width + '×' + d.height;
|
|
192
|
-
if (d.defaults) {
|
|
193
|
-
wR.value = d.defaults.width || 10;
|
|
194
|
-
wV.textContent = wR.value + 'px';
|
|
195
|
-
const color = d.defaults.color || '#ffffff';
|
|
196
|
-
cC.value = color;
|
|
197
|
-
cT.value = color;
|
|
198
|
-
cP.style.background = color;
|
|
199
|
-
}
|
|
200
|
-
setTimeout(generatePreview, 100);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Apply
|
|
204
|
-
async function apply() {
|
|
205
|
-
$('F').classList.add('hide');
|
|
206
|
-
$('L').classList.add('on');
|
|
207
|
-
$('G').classList.add('on');
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
const result = await postJson('/api/process', getOptions());
|
|
211
|
-
if (result.logs) {
|
|
212
|
-
result.logs.forEach(l => log('G', l.type[0], l.message));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
$('L').classList.remove('on');
|
|
216
|
-
$('D').classList.add('on', result.success ? 'ok' : 'err');
|
|
217
|
-
$('dT').textContent = result.success ? 'Done' : 'Failed';
|
|
218
|
-
$('dM').textContent = result.success ? result.output : result.error;
|
|
219
|
-
} catch (e) {
|
|
220
|
-
log('G', 'e', e.message);
|
|
221
|
-
$('L').classList.remove('on');
|
|
222
|
-
$('D').classList.add('on', 'err');
|
|
223
|
-
$('dT').textContent = 'Error';
|
|
224
|
-
$('dM').textContent = e.message;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
</script>
|
|
228
|
-
</body>
|
|
229
|
-
</html>
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html data-piclet data-width="400" data-height="420">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>Extract Frames</title>
|
|
7
|
-
<link rel="stylesheet" href="/css/theme.css">
|
|
8
|
-
<script src="/js/piclet.js"></script>
|
|
9
|
-
<style>
|
|
10
|
-
.preview-area{flex:1;display:flex;align-items:center;justify-content:center;background:var(--bg2);border-radius:6px;min-height:160px;overflow:hidden;position:relative}
|
|
11
|
-
.preview-area::before{content:'';position:absolute;inset:0;background:repeating-conic-gradient(#1a1a1d 0% 25%, #222 0% 50%) 50%/16px 16px;z-index:0}
|
|
12
|
-
.preview-area img{max-width:100%;max-height:100%;object-fit:contain;position:relative;z-index:1}
|
|
13
|
-
.preview-area .placeholder{position:relative;z-index:1;font-size:11px;color:var(--txt3)}
|
|
14
|
-
.preview-area .mini-sp{width:16px;height:16px;border:2px solid var(--bg3);border-top-color:var(--acc);border-radius:50%;animation:s .5s linear infinite;position:relative;z-index:1}
|
|
15
|
-
.preview-info{font-size:10px;color:var(--txt3);text-align:center;margin-top:4px;height:14px}
|
|
16
|
-
.frame-nav{display:flex;align-items:center;justify-content:center;gap:12px;margin-top:8px}
|
|
17
|
-
.frame-nav button{width:32px;height:32px;border-radius:6px;border:none;background:var(--bg3);color:var(--txt2);font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center}
|
|
18
|
-
.frame-nav button:hover{background:var(--acc);color:#fff}
|
|
19
|
-
.frame-nav button:disabled{opacity:0.3;cursor:not-allowed}
|
|
20
|
-
.frame-nav span{font-size:12px;color:var(--txt2);min-width:80px;text-align:center}
|
|
21
|
-
.info-box{background:var(--bg2);border-radius:6px;padding:12px;margin-top:8px;text-align:center}
|
|
22
|
-
.info-box .count{font-size:24px;font-weight:600;color:var(--acc2)}
|
|
23
|
-
.info-box .label{font-size:11px;color:var(--txt3);margin-top:2px}
|
|
24
|
-
</style>
|
|
25
|
-
</head>
|
|
26
|
-
<body>
|
|
27
|
-
<div class="app">
|
|
28
|
-
<div class="hd"><b>PicLet</b><span>Extract Frames</span></div>
|
|
29
|
-
<div class="meta">
|
|
30
|
-
<div>File<b id="fn">-</b></div>
|
|
31
|
-
<div>Size<b id="sz">-</b></div>
|
|
32
|
-
</div>
|
|
33
|
-
<!-- Form -->
|
|
34
|
-
<div id="F" class="form">
|
|
35
|
-
<div class="preview-area" id="pA">
|
|
36
|
-
<span class="placeholder">Loading...</span>
|
|
37
|
-
</div>
|
|
38
|
-
<div class="preview-info" id="pI"></div>
|
|
39
|
-
<div class="frame-nav">
|
|
40
|
-
<button id="bP" title="Previous frame"><</button>
|
|
41
|
-
<span id="fN">Frame 1 / 1</span>
|
|
42
|
-
<button id="bN" title="Next frame">></button>
|
|
43
|
-
</div>
|
|
44
|
-
<div class="info-box">
|
|
45
|
-
<div class="count" id="fC">0</div>
|
|
46
|
-
<div class="label">frames will be extracted as PNG files</div>
|
|
47
|
-
</div>
|
|
48
|
-
<div class="btns">
|
|
49
|
-
<button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
|
|
50
|
-
<button class="btn btn-p" onclick="apply()">Extract All</button>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
<!-- Loading state -->
|
|
54
|
-
<div class="ld" id="L"><div class="sp"></div><span id="lT">Extracting...</span></div>
|
|
55
|
-
<!-- Log -->
|
|
56
|
-
<div class="log" id="G"></div>
|
|
57
|
-
<!-- Done state -->
|
|
58
|
-
<div class="dn" id="D">
|
|
59
|
-
<h4 id="dT"></h4>
|
|
60
|
-
<p id="dM"></p>
|
|
61
|
-
<div class="btns" style="width:100%;margin-top:8px">
|
|
62
|
-
<button class="btn btn-p" onclick="PicLet.close()">Done</button>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
<script>
|
|
67
|
-
const { $, log, fetchJson, postJson } = PicLet;
|
|
68
|
-
const pA = $('pA'), pI = $('pI'), fN = $('fN'), fC = $('fC');
|
|
69
|
-
const bP = $('bP'), bN = $('bN');
|
|
70
|
-
|
|
71
|
-
let frameCount = 1;
|
|
72
|
-
let currentFrame = 0;
|
|
73
|
-
|
|
74
|
-
// Update frame display
|
|
75
|
-
function updateFrameDisplay() {
|
|
76
|
-
fN.textContent = `Frame ${currentFrame + 1} / ${frameCount}`;
|
|
77
|
-
bP.disabled = currentFrame === 0;
|
|
78
|
-
bN.disabled = currentFrame >= frameCount - 1;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Load frame preview
|
|
82
|
-
async function loadFrame(index) {
|
|
83
|
-
pA.innerHTML = '<div class="mini-sp"></div>';
|
|
84
|
-
pI.textContent = '';
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const result = await postJson('/api/preview', { frameIndex: index });
|
|
88
|
-
if (result.success && result.imageData) {
|
|
89
|
-
const img = document.createElement('img');
|
|
90
|
-
img.src = result.imageData;
|
|
91
|
-
img.alt = `Frame ${index + 1}`;
|
|
92
|
-
pA.innerHTML = '';
|
|
93
|
-
pA.appendChild(img);
|
|
94
|
-
pI.textContent = result.width && result.height ? `${result.width}x${result.height}` : '';
|
|
95
|
-
} else {
|
|
96
|
-
pA.innerHTML = '<span class="placeholder">' + (result.error || 'Preview failed') + '</span>';
|
|
97
|
-
}
|
|
98
|
-
} catch (e) {
|
|
99
|
-
pA.innerHTML = '<span class="placeholder">Preview error</span>';
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Navigation
|
|
104
|
-
bP.onclick = () => {
|
|
105
|
-
if (currentFrame > 0) {
|
|
106
|
-
currentFrame--;
|
|
107
|
-
updateFrameDisplay();
|
|
108
|
-
loadFrame(currentFrame);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
bN.onclick = () => {
|
|
113
|
-
if (currentFrame < frameCount - 1) {
|
|
114
|
-
currentFrame++;
|
|
115
|
-
updateFrameDisplay();
|
|
116
|
-
loadFrame(currentFrame);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// Load initial data
|
|
121
|
-
fetchJson('/api/info').then(d => {
|
|
122
|
-
$('fn').textContent = d.fileName;
|
|
123
|
-
$('sz').textContent = d.width + 'x' + d.height;
|
|
124
|
-
frameCount = d.defaults?.frameCount || 1;
|
|
125
|
-
fC.textContent = frameCount;
|
|
126
|
-
updateFrameDisplay();
|
|
127
|
-
loadFrame(0);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Apply
|
|
131
|
-
async function apply() {
|
|
132
|
-
$('F').classList.add('hide');
|
|
133
|
-
$('L').classList.add('on');
|
|
134
|
-
$('G').classList.add('on');
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const result = await postJson('/api/process', {});
|
|
138
|
-
if (result.logs) {
|
|
139
|
-
result.logs.forEach(l => log('G', l.type[0], l.message));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
$('L').classList.remove('on');
|
|
143
|
-
$('D').classList.add('on', result.success ? 'ok' : 'err');
|
|
144
|
-
$('dT').textContent = result.success ? 'Done' : 'Failed';
|
|
145
|
-
$('dM').textContent = result.success ? result.output : result.error;
|
|
146
|
-
} catch (e) {
|
|
147
|
-
log('G', 'e', e.message);
|
|
148
|
-
$('L').classList.remove('on');
|
|
149
|
-
$('D').classList.add('on', 'err');
|
|
150
|
-
$('dT').textContent = 'Error';
|
|
151
|
-
$('dM').textContent = e.message;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
</script>
|
|
155
|
-
</body>
|
|
156
|
-
</html>
|
package/dist/gui/filter.html
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" data-piclet data-width="400" data-height="440">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>Filter</title>
|
|
7
|
-
<link rel="stylesheet" href="/css/theme.css">
|
|
8
|
-
<script src="/js/piclet.js"></script>
|
|
9
|
-
<style>
|
|
10
|
-
.preview-area{flex:1;display:flex;align-items:center;justify-content:center;background:var(--bg2);border-radius:6px;min-height:140px;overflow:hidden;position:relative}
|
|
11
|
-
.preview-area::before{content:'';position:absolute;inset:0;background:repeating-conic-gradient(#1a1a1d 0% 25%, #222 0% 50%) 50%/16px 16px;z-index:0}
|
|
12
|
-
.preview-area img{max-width:100%;max-height:100%;object-fit:contain;position:relative;z-index:1}
|
|
13
|
-
.preview-area .placeholder{position:relative;z-index:1;font-size:11px;color:var(--txt3)}
|
|
14
|
-
.preview-area .mini-sp{width:16px;height:16px;border:2px solid var(--bg3);border-top-color:var(--acc);border-radius:50%;animation:s .5s linear infinite;position:relative;z-index:1}
|
|
15
|
-
.preview-info{font-size:10px;color:var(--txt3);text-align:center;margin-top:4px;height:14px}
|
|
16
|
-
.filter-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;padding:4px 0}
|
|
17
|
-
.filter-btn{display:flex;flex-direction:column;align-items:center;gap:4px;padding:10px 8px;background:var(--bg2);border:1px solid var(--brd);border-radius:6px;color:var(--txt2);font:inherit;font-size:11px;cursor:pointer;transition:all .15s}
|
|
18
|
-
.filter-btn:hover{background:var(--bg3);color:var(--txt)}
|
|
19
|
-
.filter-btn.active{background:var(--acc);border-color:var(--acc);color:#000;font-weight:500}
|
|
20
|
-
.filter-icon{width:24px;height:24px;border-radius:4px;border:1px solid var(--brd)}
|
|
21
|
-
.filter-btn.active .filter-icon{border-color:#000}
|
|
22
|
-
.filter-icon.gray{background:linear-gradient(135deg,#888,#333)}
|
|
23
|
-
.filter-icon.sepia{background:linear-gradient(135deg,#d4a574,#8b6914)}
|
|
24
|
-
.filter-icon.invert{background:linear-gradient(135deg,#fff,#000)}
|
|
25
|
-
.filter-icon.vintage{background:linear-gradient(135deg,#a89078,#5c4a3a)}
|
|
26
|
-
.filter-icon.vivid{background:linear-gradient(135deg,#ff6b6b,#4ecdc4)}
|
|
27
|
-
</style>
|
|
28
|
-
</head>
|
|
29
|
-
<body>
|
|
30
|
-
<div class="app">
|
|
31
|
-
<div class="hd"><b>PicLet</b><span>Filter</span></div>
|
|
32
|
-
<div class="meta">
|
|
33
|
-
<div>File<b id="fn">-</b></div>
|
|
34
|
-
<div>Size<b id="sz">-</b></div>
|
|
35
|
-
</div>
|
|
36
|
-
<!-- Form -->
|
|
37
|
-
<div id="F" class="form">
|
|
38
|
-
<div class="preview-area" id="pA">
|
|
39
|
-
<span class="placeholder">Select filter to preview</span>
|
|
40
|
-
</div>
|
|
41
|
-
<div class="preview-info" id="pI"></div>
|
|
42
|
-
<div class="filter-grid">
|
|
43
|
-
<button class="filter-btn active" data-filter="grayscale" onclick="selectFilter('grayscale')">
|
|
44
|
-
<div class="filter-icon gray"></div>
|
|
45
|
-
Grayscale
|
|
46
|
-
</button>
|
|
47
|
-
<button class="filter-btn" data-filter="sepia" onclick="selectFilter('sepia')">
|
|
48
|
-
<div class="filter-icon sepia"></div>
|
|
49
|
-
Sepia
|
|
50
|
-
</button>
|
|
51
|
-
<button class="filter-btn" data-filter="invert" onclick="selectFilter('invert')">
|
|
52
|
-
<div class="filter-icon invert"></div>
|
|
53
|
-
Invert
|
|
54
|
-
</button>
|
|
55
|
-
<button class="filter-btn" data-filter="vintage" onclick="selectFilter('vintage')">
|
|
56
|
-
<div class="filter-icon vintage"></div>
|
|
57
|
-
Vintage
|
|
58
|
-
</button>
|
|
59
|
-
<button class="filter-btn" data-filter="vivid" onclick="selectFilter('vivid')">
|
|
60
|
-
<div class="filter-icon vivid"></div>
|
|
61
|
-
Vivid
|
|
62
|
-
</button>
|
|
63
|
-
</div>
|
|
64
|
-
<div class="btns">
|
|
65
|
-
<button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
|
|
66
|
-
<button class="btn btn-p" onclick="apply()">Apply</button>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
<!-- Loading state -->
|
|
70
|
-
<div class="ld" id="L"><div class="sp"></div><span id="lT">Applying filter...</span></div>
|
|
71
|
-
<!-- Log -->
|
|
72
|
-
<div class="log" id="G"></div>
|
|
73
|
-
<!-- Done state -->
|
|
74
|
-
<div class="dn" id="D">
|
|
75
|
-
<h4 id="dT"></h4>
|
|
76
|
-
<p id="dM"></p>
|
|
77
|
-
<div class="btns" style="width:100%;margin-top:8px">
|
|
78
|
-
<button class="btn btn-p" onclick="PicLet.close()">Done</button>
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
<script>
|
|
83
|
-
const { $, log, fetchJson, postJson } = PicLet;
|
|
84
|
-
const pA = $('pA'), pI = $('pI');
|
|
85
|
-
|
|
86
|
-
let currentFilter = 'grayscale';
|
|
87
|
-
let previewTimeout = null;
|
|
88
|
-
let lastPreviewOpts = null;
|
|
89
|
-
|
|
90
|
-
// Select filter
|
|
91
|
-
function selectFilter(type) {
|
|
92
|
-
currentFilter = type;
|
|
93
|
-
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
94
|
-
btn.classList.toggle('active', btn.dataset.filter === type);
|
|
95
|
-
});
|
|
96
|
-
schedulePreview();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Get current options
|
|
100
|
-
function getOptions() {
|
|
101
|
-
return { filter: currentFilter };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Check if options changed
|
|
105
|
-
function optionsChanged() {
|
|
106
|
-
const current = JSON.stringify(getOptions());
|
|
107
|
-
if (current === lastPreviewOpts) return false;
|
|
108
|
-
lastPreviewOpts = current;
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Schedule preview
|
|
113
|
-
function schedulePreview() {
|
|
114
|
-
if (previewTimeout) clearTimeout(previewTimeout);
|
|
115
|
-
previewTimeout = setTimeout(() => {
|
|
116
|
-
if (optionsChanged()) {
|
|
117
|
-
generatePreview();
|
|
118
|
-
}
|
|
119
|
-
}, 200);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Generate preview
|
|
123
|
-
async function generatePreview() {
|
|
124
|
-
pA.innerHTML = '<div class="mini-sp"></div>';
|
|
125
|
-
pI.textContent = '';
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
const result = await postJson('/api/preview', getOptions());
|
|
129
|
-
|
|
130
|
-
if (result.success && result.imageData) {
|
|
131
|
-
const img = document.createElement('img');
|
|
132
|
-
img.src = result.imageData;
|
|
133
|
-
img.alt = 'Preview';
|
|
134
|
-
pA.innerHTML = '';
|
|
135
|
-
pA.appendChild(img);
|
|
136
|
-
pI.textContent = result.width && result.height ? `${result.width}×${result.height}` : '';
|
|
137
|
-
} else {
|
|
138
|
-
pA.innerHTML = '<span class="placeholder">' + (result.error || 'Preview failed') + '</span>';
|
|
139
|
-
pI.textContent = '';
|
|
140
|
-
}
|
|
141
|
-
} catch (e) {
|
|
142
|
-
pA.innerHTML = '<span class="placeholder">Preview error</span>';
|
|
143
|
-
pI.textContent = '';
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Load initial data
|
|
148
|
-
fetchJson('/api/info').then(d => {
|
|
149
|
-
$('fn').textContent = d.fileName;
|
|
150
|
-
$('sz').textContent = d.width + '×' + d.height;
|
|
151
|
-
setTimeout(generatePreview, 100);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Apply
|
|
155
|
-
async function apply() {
|
|
156
|
-
$('F').classList.add('hide');
|
|
157
|
-
$('L').classList.add('on');
|
|
158
|
-
$('G').classList.add('on');
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const result = await postJson('/api/process', getOptions());
|
|
162
|
-
if (result.logs) {
|
|
163
|
-
result.logs.forEach(l => log('G', l.type[0], l.message));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
$('L').classList.remove('on');
|
|
167
|
-
$('D').classList.add('on', result.success ? 'ok' : 'err');
|
|
168
|
-
$('dT').textContent = result.success ? 'Done' : 'Failed';
|
|
169
|
-
$('dM').textContent = result.success ? result.output : result.error;
|
|
170
|
-
} catch (e) {
|
|
171
|
-
log('G', 'e', e.message);
|
|
172
|
-
$('L').classList.remove('on');
|
|
173
|
-
$('D').classList.add('on', 'err');
|
|
174
|
-
$('dT').textContent = 'Error';
|
|
175
|
-
$('dM').textContent = e.message;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
</script>
|
|
179
|
-
</body>
|
|
180
|
-
</html>
|
package/dist/gui/iconpack.html
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html data-piclet data-width="340" data-height="310">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>Icon Pack</title>
|
|
7
|
-
<link rel="stylesheet" href="/css/theme.css">
|
|
8
|
-
<script src="/js/piclet.js"></script>
|
|
9
|
-
<style>
|
|
10
|
-
.platforms{display:flex;flex-direction:column;gap:4px;flex:1}
|
|
11
|
-
.platforms .opt{padding:8px 10px;background:var(--bg2);border-radius:6px;border:1px solid var(--brd)}
|
|
12
|
-
.platforms .opt:hover{border-color:var(--acc)}
|
|
13
|
-
.platform-info{font-size:10px;color:var(--txt3);margin-left:22px;margin-top:2px}
|
|
14
|
-
</style>
|
|
15
|
-
</head>
|
|
16
|
-
<body>
|
|
17
|
-
<div class="app">
|
|
18
|
-
<div class="hd"><b>PicLet</b><span>Icon Pack Generator</span></div>
|
|
19
|
-
<div class="meta">
|
|
20
|
-
<div>File<b id="fn">-</b></div>
|
|
21
|
-
<div>Size<b id="sz">-</b></div>
|
|
22
|
-
</div>
|
|
23
|
-
<!-- Form state -->
|
|
24
|
-
<div id="F" class="form">
|
|
25
|
-
<div class="platforms">
|
|
26
|
-
<label class="opt">
|
|
27
|
-
<input type="checkbox" id="cW" checked>
|
|
28
|
-
<span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
|
|
29
|
-
Web
|
|
30
|
-
<div class="platform-info">favicon, PWA, apple-touch-icon</div>
|
|
31
|
-
</label>
|
|
32
|
-
<label class="opt">
|
|
33
|
-
<input type="checkbox" id="cA" checked>
|
|
34
|
-
<span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
|
|
35
|
-
Android
|
|
36
|
-
<div class="platform-info">mipmap folders, Play Store icon</div>
|
|
37
|
-
</label>
|
|
38
|
-
<label class="opt">
|
|
39
|
-
<input type="checkbox" id="cI" checked>
|
|
40
|
-
<span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
|
|
41
|
-
iOS
|
|
42
|
-
<div class="platform-info">App icons, App Store icon</div>
|
|
43
|
-
</label>
|
|
44
|
-
</div>
|
|
45
|
-
<div class="warn-box" id="wB"></div>
|
|
46
|
-
<div class="btns">
|
|
47
|
-
<button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
|
|
48
|
-
<button class="btn btn-p" onclick="generate()">Generate</button>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
<!-- Loading state -->
|
|
52
|
-
<div class="ld" id="L"><div class="sp"></div><span id="lT">Generating icons...</span></div>
|
|
53
|
-
<!-- Log -->
|
|
54
|
-
<div class="log" id="G"></div>
|
|
55
|
-
<!-- Done state -->
|
|
56
|
-
<div class="dn" id="D">
|
|
57
|
-
<h4 id="dT"></h4>
|
|
58
|
-
<p id="dM"></p>
|
|
59
|
-
<div class="btns" style="width:100%;margin-top:8px">
|
|
60
|
-
<button class="btn btn-p" onclick="PicLet.close()">Done</button>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
<script>
|
|
65
|
-
const { $, log, fetchJson, postJson } = PicLet;
|
|
66
|
-
|
|
67
|
-
// Load initial data and check for warnings
|
|
68
|
-
fetchJson('/api/info').then(d => {
|
|
69
|
-
$('fn').textContent = d.fileName;
|
|
70
|
-
$('sz').textContent = d.width + '×' + d.height;
|
|
71
|
-
if (d.width < 1024 || d.height < 1024) {
|
|
72
|
-
$('wB').textContent = 'Source < 1024px. Larger images produce better quality.';
|
|
73
|
-
$('wB').classList.add('on');
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Generate icons
|
|
78
|
-
async function generate() {
|
|
79
|
-
const web = $('cW').checked;
|
|
80
|
-
const android = $('cA').checked;
|
|
81
|
-
const ios = $('cI').checked;
|
|
82
|
-
|
|
83
|
-
if (!web && !android && !ios) {
|
|
84
|
-
alert('Please select at least one platform');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
$('F').classList.add('hide');
|
|
89
|
-
$('L').classList.add('on');
|
|
90
|
-
$('G').classList.add('on');
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const result = await postJson('/api/process', { web, android, ios });
|
|
94
|
-
|
|
95
|
-
if (result.logs) {
|
|
96
|
-
result.logs.forEach(l => log('G', l.type[0], l.message));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
$('L').classList.remove('on');
|
|
100
|
-
$('D').classList.add('on', result.success ? 'ok' : 'err');
|
|
101
|
-
$('dT').textContent = result.success ? 'Done' : 'Failed';
|
|
102
|
-
$('dM').textContent = result.success ? result.output : result.error;
|
|
103
|
-
} catch (e) {
|
|
104
|
-
log('G', 'e', e.message);
|
|
105
|
-
$('L').classList.remove('on');
|
|
106
|
-
$('D').classList.add('on', 'err');
|
|
107
|
-
$('dT').textContent = 'Error';
|
|
108
|
-
$('dM').textContent = e.message;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
</script>
|
|
112
|
-
</body>
|
|
113
|
-
</html>
|