@spark-apps/piclet 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,178 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" 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>Remove BG</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
+ </style>
17
+ </head>
18
+ <body>
19
+ <div class="app">
20
+ <div class="hd"><b>PicLet</b><span>Remove Background</span></div>
21
+ <div class="meta">
22
+ <div>File<b id="fn">-</b></div>
23
+ <div>Size<b id="sz">-</b></div>
24
+ </div>
25
+ <!-- Form with inline preview -->
26
+ <div id="F" class="form">
27
+ <div class="preview-area" id="pA">
28
+ <span class="placeholder">Adjust fuzz to preview</span>
29
+ </div>
30
+ <div class="preview-info" id="pI"></div>
31
+ <div class="row" data-tip="Color matching sensitivity (higher = more colors removed)">
32
+ <label>Tolerance</label>
33
+ <input type="number" id="fN" min="0" max="100" value="10">
34
+ <input type="range" id="fR" min="0" max="100" value="10">
35
+ </div>
36
+ <div class="opts">
37
+ <label class="opt" data-tip="Remove empty edges after background removal"><input type="checkbox" id="cT" checked><span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>Auto-trim</label>
38
+ <label class="opt" data-tip="Only remove background from edges, keep inner areas"><input type="checkbox" id="cP"><span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>Edges only</label>
39
+ <label class="opt" data-tip="Add transparent padding to make output square"><input type="checkbox" id="cS"><span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>Make square</label>
40
+ </div>
41
+ <div class="btns">
42
+ <button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
43
+ <button class="btn btn-p" id="applyBtn" onclick="apply()">Apply</button>
44
+ </div>
45
+ </div>
46
+ <!-- Loading state -->
47
+ <div class="ld" id="L"><div class="sp"></div><span id="lT">Processing...</span></div>
48
+ <!-- Log -->
49
+ <div class="log" id="G"></div>
50
+ <!-- Done state -->
51
+ <div class="dn" id="D">
52
+ <h4 id="dT"></h4>
53
+ <p id="dM"></p>
54
+ <div class="btns" style="width:100%;margin-top:8px">
55
+ <button class="btn btn-p" onclick="PicLet.close()">Done</button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ <script>
60
+ const { $, log, fetchJson, postJson } = PicLet;
61
+ const fN = $('fN'), fR = $('fR'), pA = $('pA'), pI = $('pI');
62
+
63
+ let previewTimeout = null;
64
+ let isSliding = false;
65
+ let lastPreviewOpts = null;
66
+
67
+ // Sync slider and input
68
+ fN.oninput = () => { fR.value = fN.value; schedulePreview(); };
69
+ fR.oninput = () => { fN.value = fR.value; };
70
+
71
+ // Track mouse state on slider
72
+ fR.addEventListener('mousedown', () => { isSliding = true; });
73
+ fR.addEventListener('mouseup', () => { isSliding = false; schedulePreview(); });
74
+ fR.addEventListener('mouseleave', () => { if (isSliding) { isSliding = false; schedulePreview(); } });
75
+
76
+ // Checkbox changes trigger preview
77
+ $('cT').onchange = schedulePreview;
78
+ $('cP').onchange = schedulePreview;
79
+ $('cS').onchange = schedulePreview;
80
+
81
+ // Get current options
82
+ function getOptions() {
83
+ return {
84
+ fuzz: +fN.value || 10,
85
+ trim: $('cT').checked,
86
+ preserveInner: $('cP').checked,
87
+ makeSquare: $('cS').checked
88
+ };
89
+ }
90
+
91
+ // Check if options changed
92
+ function optionsChanged() {
93
+ const current = JSON.stringify(getOptions());
94
+ if (current === lastPreviewOpts) return false;
95
+ lastPreviewOpts = current;
96
+ return true;
97
+ }
98
+
99
+ // Schedule preview (debounced, only when not sliding)
100
+ function schedulePreview() {
101
+ if (isSliding) return;
102
+ if (previewTimeout) clearTimeout(previewTimeout);
103
+ previewTimeout = setTimeout(() => {
104
+ if (!isSliding && optionsChanged()) {
105
+ generatePreview();
106
+ }
107
+ }, 300);
108
+ }
109
+
110
+ // Generate preview
111
+ async function generatePreview() {
112
+ // Show loading spinner
113
+ pA.innerHTML = '<div class="mini-sp"></div>';
114
+ pI.textContent = '';
115
+
116
+ try {
117
+ const result = await postJson('/api/preview', getOptions());
118
+
119
+ if (result.success && result.imageData) {
120
+ const img = document.createElement('img');
121
+ img.src = result.imageData;
122
+ img.alt = 'Preview';
123
+ pA.innerHTML = '';
124
+ pA.appendChild(img);
125
+ pI.textContent = result.width && result.height ? `${result.width}×${result.height}` : '';
126
+ } else {
127
+ pA.innerHTML = '<span class="placeholder">' + (result.error || 'Preview failed') + '</span>';
128
+ pI.textContent = '';
129
+ }
130
+ } catch (e) {
131
+ pA.innerHTML = '<span class="placeholder">Preview error</span>';
132
+ pI.textContent = '';
133
+ }
134
+ }
135
+
136
+ // Load initial data
137
+ fetchJson('/api/info').then(d => {
138
+ $('fn').textContent = d.fileName;
139
+ $('sz').textContent = d.width + '×' + d.height;
140
+ if (d.defaults) {
141
+ fN.value = fR.value = d.defaults.fuzz;
142
+ $('cT').checked = d.defaults.trim;
143
+ $('cP').checked = d.defaults.preserveInner;
144
+ $('cS').checked = d.defaults.makeSquare;
145
+ }
146
+ // Generate initial preview
147
+ setTimeout(generatePreview, 100);
148
+ });
149
+
150
+ // Apply (process for real)
151
+ async function apply() {
152
+ $('F').classList.add('hide');
153
+ $('L').classList.add('on');
154
+ $('lT').textContent = 'Processing...';
155
+ $('G').classList.add('on');
156
+
157
+ try {
158
+ const result = await postJson('/api/process', getOptions());
159
+
160
+ if (result.logs) {
161
+ result.logs.forEach(l => log('G', l.type[0], l.message));
162
+ }
163
+
164
+ $('L').classList.remove('on');
165
+ $('D').classList.add('on', result.success ? 'ok' : 'err');
166
+ $('dT').textContent = result.success ? 'Done' : 'Failed';
167
+ $('dM').textContent = result.success ? result.output : result.error;
168
+ } catch (e) {
169
+ log('G', 'e', e.message);
170
+ $('L').classList.remove('on');
171
+ $('D').classList.add('on', 'err');
172
+ $('dT').textContent = 'Error';
173
+ $('dM').textContent = e.message;
174
+ }
175
+ }
176
+ </script>
177
+ </body>
178
+ </html>
@@ -0,0 +1,195 @@
1
+ <!DOCTYPE html>
2
+ <html 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>Scale Image</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
+ .dim-section{display:flex;flex-direction:column;gap:8px}
17
+ .dim-row{display:flex;align-items:center;gap:8px}
18
+ .dim-row label{font-size:11px;color:var(--txt3);width:44px;flex-shrink:0}
19
+ .dim-row input[type="range"]{flex:1;height:4px;background:var(--bg3);border-radius:3px;-webkit-appearance:none}
20
+ .dim-row input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;background:var(--acc);border-radius:50%;cursor:pointer}
21
+ .dim-row .val{width:58px;font-size:12px;color:var(--acc2);text-align:right;font-weight:500}
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div class="app">
26
+ <div class="hd"><b>PicLet</b><span>Scale Image</span></div>
27
+ <div class="meta">
28
+ <div>File<b id="fn">-</b></div>
29
+ <div>Size<b id="sz">-</b></div>
30
+ </div>
31
+ <!-- Form -->
32
+ <div id="F" class="form">
33
+ <div class="preview-area" id="pA">
34
+ <span class="placeholder">Adjust to preview</span>
35
+ </div>
36
+ <div class="preview-info" id="pI"></div>
37
+ <div class="dim-section">
38
+ <div class="dim-row">
39
+ <label>Width</label>
40
+ <input type="range" id="wR" min="16" max="1000" value="500">
41
+ <span class="val" id="wV">500px</span>
42
+ </div>
43
+ <div class="dim-row">
44
+ <label>Height</label>
45
+ <input type="range" id="hR" min="16" max="1000" value="500">
46
+ <span class="val" id="hV">500px</span>
47
+ </div>
48
+ </div>
49
+ <div class="opts">
50
+ <label class="opt" data-tip="Add transparent padding to make output square"><input type="checkbox" id="cS"><span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>Square output</label>
51
+ </div>
52
+ <div class="btns">
53
+ <button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
54
+ <button class="btn btn-p" onclick="apply()">Apply</button>
55
+ </div>
56
+ </div>
57
+ <!-- Loading state -->
58
+ <div class="ld" id="L"><div class="sp"></div><span id="lT">Scaling...</span></div>
59
+ <!-- Log -->
60
+ <div class="log" id="G"></div>
61
+ <!-- Done state -->
62
+ <div class="dn" id="D">
63
+ <h4 id="dT"></h4>
64
+ <p id="dM"></p>
65
+ <div class="btns" style="width:100%;margin-top:8px">
66
+ <button class="btn btn-p" onclick="PicLet.close()">Done</button>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <script>
71
+ const { $, log, fetchJson, postJson } = PicLet;
72
+ const wR = $('wR'), hR = $('hR'), wV = $('wV'), hV = $('hV');
73
+ const pA = $('pA'), pI = $('pI');
74
+
75
+ let origW = 1000, origH = 1000;
76
+ let previewTimeout = null;
77
+ let isSliding = false;
78
+ let lastPreviewOpts = null;
79
+
80
+ // Update value displays
81
+ function updateValues() {
82
+ wV.textContent = wR.value + 'px';
83
+ hV.textContent = hR.value + 'px';
84
+ }
85
+
86
+ // Slider input handlers
87
+ wR.oninput = updateValues;
88
+ hR.oninput = updateValues;
89
+
90
+ // Track sliding state
91
+ [wR, hR].forEach(slider => {
92
+ slider.addEventListener('mousedown', () => { isSliding = true; });
93
+ slider.addEventListener('mouseup', () => { isSliding = false; schedulePreview(); });
94
+ slider.addEventListener('mouseleave', () => { if (isSliding) { isSliding = false; schedulePreview(); } });
95
+ });
96
+
97
+ // Checkbox change
98
+ $('cS').onchange = schedulePreview;
99
+
100
+ // Get current options
101
+ function getOptions() {
102
+ return { width: +wR.value, height: +hR.value, makeSquare: $('cS').checked };
103
+ }
104
+
105
+ // Check if options changed
106
+ function optionsChanged() {
107
+ const current = JSON.stringify(getOptions());
108
+ if (current === lastPreviewOpts) return false;
109
+ lastPreviewOpts = current;
110
+ return true;
111
+ }
112
+
113
+ // Schedule preview
114
+ function schedulePreview() {
115
+ if (isSliding) return;
116
+ if (previewTimeout) clearTimeout(previewTimeout);
117
+ previewTimeout = setTimeout(() => {
118
+ if (!isSliding && optionsChanged()) {
119
+ generatePreview();
120
+ }
121
+ }, 300);
122
+ }
123
+
124
+ // Generate preview
125
+ async function generatePreview() {
126
+ pA.innerHTML = '<div class="mini-sp"></div>';
127
+ pI.textContent = '';
128
+
129
+ try {
130
+ const result = await postJson('/api/preview', getOptions());
131
+
132
+ if (result.success && result.imageData) {
133
+ const img = document.createElement('img');
134
+ img.src = result.imageData;
135
+ img.alt = 'Preview';
136
+ pA.innerHTML = '';
137
+ pA.appendChild(img);
138
+ pI.textContent = result.width && result.height ? `${result.width}×${result.height}` : '';
139
+ } else {
140
+ pA.innerHTML = '<span class="placeholder">' + (result.error || 'Preview failed') + '</span>';
141
+ pI.textContent = '';
142
+ }
143
+ } catch (e) {
144
+ pA.innerHTML = '<span class="placeholder">Preview error</span>';
145
+ pI.textContent = '';
146
+ }
147
+ }
148
+
149
+ // Load initial data
150
+ fetchJson('/api/info').then(d => {
151
+ $('fn').textContent = d.fileName;
152
+ $('sz').textContent = d.width + '×' + d.height;
153
+ origW = d.width;
154
+ origH = d.height;
155
+
156
+ // Max = original dimensions
157
+ wR.max = origW;
158
+ hR.max = origH;
159
+
160
+ // Default = original size
161
+ wR.value = origW;
162
+ hR.value = origH;
163
+ updateValues();
164
+
165
+ // Generate initial preview
166
+ setTimeout(generatePreview, 100);
167
+ });
168
+
169
+ // Apply
170
+ async function apply() {
171
+ $('F').classList.add('hide');
172
+ $('L').classList.add('on');
173
+ $('G').classList.add('on');
174
+
175
+ try {
176
+ const result = await postJson('/api/process', getOptions());
177
+ if (result.logs) {
178
+ result.logs.forEach(l => log('G', l.type[0], l.message));
179
+ }
180
+
181
+ $('L').classList.remove('on');
182
+ $('D').classList.add('on', result.success ? 'ok' : 'err');
183
+ $('dT').textContent = result.success ? 'Done' : 'Failed';
184
+ $('dM').textContent = result.success ? result.output : result.error;
185
+ } catch (e) {
186
+ log('G', 'e', e.message);
187
+ $('L').classList.remove('on');
188
+ $('D').classList.add('on', 'err');
189
+ $('dT').textContent = 'Error';
190
+ $('dM').textContent = e.message;
191
+ }
192
+ }
193
+ </script>
194
+ </body>
195
+ </html>
@@ -0,0 +1,179 @@
1
+ <!DOCTYPE html>
2
+ <html data-piclet data-width="380" data-height="560">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Store Pack</title>
7
+ <link rel="stylesheet" href="/css/theme.css">
8
+ <script src="/js/piclet.js"></script>
9
+ <style>
10
+ .preset-select{width:100%;padding:10px 12px;background:var(--bg2);border:1px solid var(--brd);border-radius:6px;color:var(--txt);font:inherit;font-size:13px;cursor:pointer}
11
+ .preset-select:focus{outline:none;border-color:var(--acc)}
12
+ .preset-desc{font-size:11px;color:var(--txt3);margin-top:4px}
13
+ .icon-list{height:140px;background:var(--bg2);border-radius:6px;padding:8px;overflow-y:auto;font:11px/1.5 Consolas,monospace}
14
+ .icon-list div{display:flex;justify-content:space-between;padding:2px 4px;border-radius:3px}
15
+ .icon-list div:hover{background:var(--bg3)}
16
+ .icon-list .name{color:var(--txt2)}
17
+ .icon-list .size{color:var(--acc2);font-weight:500}
18
+ .icon-list .ratio{color:var(--txt3);font-size:10px;margin-left:4px}
19
+ .section-label{font-size:10px;color:var(--txt3);text-transform:uppercase;letter-spacing:0.5px;margin:8px 0 4px}
20
+ .scale-opts{display:flex;flex-direction:column;gap:4px}
21
+ .scale-opt{display:flex;align-items:center;gap:8px;padding:8px;background:var(--bg2);border:1px solid var(--brd);border-radius:5px;cursor:pointer;font-size:12px;color:var(--txt2)}
22
+ .scale-opt:hover{border-color:var(--acc);color:var(--txt)}
23
+ .scale-opt input{display:none}
24
+ .scale-opt input:checked+.radio{background:var(--acc);border-color:var(--acc)}
25
+ .scale-opt input:checked+.radio::after{opacity:1}
26
+ .radio{width:14px;height:14px;border:1px solid var(--brd);border-radius:50%;position:relative;flex-shrink:0}
27
+ .radio::after{content:'';position:absolute;top:3px;left:3px;width:6px;height:6px;background:#fff;border-radius:50%;opacity:0}
28
+ .scale-opt span{flex:1}
29
+ .scale-opt small{font-size:10px;color:var(--txt3)}
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <div class="app">
34
+ <div class="hd"><b>PicLet</b><span>Store Pack Generator</span></div>
35
+ <div class="meta">
36
+ <div>File<b id="fn">-</b></div>
37
+ <div>Size<b id="sz">-</b></div>
38
+ </div>
39
+ <!-- Form state -->
40
+ <div id="F" class="form">
41
+ <div>
42
+ <select class="preset-select" id="pS" onchange="updatePreset()">
43
+ <option value="">Loading...</option>
44
+ </select>
45
+ <div class="preset-desc" id="pD"></div>
46
+ </div>
47
+ <div class="section-label">Output Sizes</div>
48
+ <div class="icon-list" id="pI"></div>
49
+ <div class="section-label">Scaling Mode</div>
50
+ <div class="scale-opts">
51
+ <label class="scale-opt">
52
+ <input type="radio" name="scale" value="fit" checked>
53
+ <span class="radio"></span>
54
+ <span>Fit & Pad</span>
55
+ <small>Center, transparent padding</small>
56
+ </label>
57
+ <label class="scale-opt">
58
+ <input type="radio" name="scale" value="fill">
59
+ <span class="radio"></span>
60
+ <span>Fill & Crop</span>
61
+ <small>Cover area, crop edges</small>
62
+ </label>
63
+ <label class="scale-opt">
64
+ <input type="radio" name="scale" value="stretch">
65
+ <span class="radio"></span>
66
+ <span>Stretch</span>
67
+ <small>Distort to exact size</small>
68
+ </label>
69
+ </div>
70
+ <div class="btns">
71
+ <button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
72
+ <button class="btn btn-p" onclick="generate()">Generate</button>
73
+ </div>
74
+ </div>
75
+ <!-- Loading state -->
76
+ <div class="ld" id="L"><div class="sp"></div><span id="lT">Generating...</span></div>
77
+ <!-- Log -->
78
+ <div class="log" id="G"></div>
79
+ <!-- Done state -->
80
+ <div class="dn" id="D">
81
+ <h4 id="dT"></h4>
82
+ <p id="dM"></p>
83
+ <div class="btns" style="width:100%;margin-top:8px">
84
+ <button class="btn btn-p" onclick="PicLet.close()">Done</button>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ <script>
89
+ const { $, log, fetchJson, postJson } = PicLet;
90
+
91
+ let presets = [];
92
+ let currentPreset = null;
93
+
94
+ // Get aspect ratio label
95
+ function getRatio(w, h) {
96
+ if (w === h) return '1:1';
97
+ const gcd = (a, b) => b ? gcd(b, a % b) : a;
98
+ const d = gcd(w, h);
99
+ return `${w/d}:${h/d}`;
100
+ }
101
+
102
+ // Update preset display
103
+ function updatePreset() {
104
+ const id = $('pS').value;
105
+ currentPreset = presets.find(p => p.id === id);
106
+
107
+ if (currentPreset && currentPreset.icons) {
108
+ $('pD').textContent = currentPreset.description;
109
+ $('pI').innerHTML = currentPreset.icons.map(i => {
110
+ const ratio = getRatio(i.width, i.height);
111
+ const ratioClass = ratio !== '1:1' ? `<span class="ratio">${ratio}</span>` : '';
112
+ return `<div><span class="name">${i.filename}</span><span class="size">${i.width}×${i.height}${ratioClass}</span></div>`;
113
+ }).join('');
114
+ } else {
115
+ $('pD').textContent = '';
116
+ $('pI').innerHTML = '<div class="name">No icons defined</div>';
117
+ }
118
+ }
119
+
120
+ // Load initial data
121
+ fetchJson('/api/info').then(d => {
122
+ $('fn').textContent = d.fileName;
123
+ $('sz').textContent = d.width + '×' + d.height;
124
+
125
+ if (d.defaults && d.defaults.presets) {
126
+ presets = d.defaults.presets;
127
+ const select = $('pS');
128
+ select.innerHTML = presets.map(p =>
129
+ `<option value="${p.id}">${p.name}</option>`
130
+ ).join('');
131
+
132
+ if (presets.length > 0) {
133
+ select.value = presets[0].id;
134
+ updatePreset();
135
+ }
136
+ }
137
+ });
138
+
139
+ // Get selected scale mode
140
+ function getScaleMode() {
141
+ return document.querySelector('input[name="scale"]:checked')?.value || 'fit';
142
+ }
143
+
144
+ // Generate all images
145
+ async function generate() {
146
+ if (!currentPreset) {
147
+ alert('Please select a preset');
148
+ return;
149
+ }
150
+
151
+ $('F').classList.add('hide');
152
+ $('L').classList.add('on');
153
+ $('G').classList.add('on');
154
+
155
+ try {
156
+ const result = await postJson('/api/process', {
157
+ preset: currentPreset.id,
158
+ scaleMode: getScaleMode()
159
+ });
160
+
161
+ if (result.logs) {
162
+ result.logs.forEach(l => log('G', l.type[0], l.message));
163
+ }
164
+
165
+ $('L').classList.remove('on');
166
+ $('D').classList.add('on', result.success ? 'ok' : 'err');
167
+ $('dT').textContent = result.success ? 'Done' : 'Failed';
168
+ $('dM').textContent = result.success ? result.output : result.error;
169
+ } catch (e) {
170
+ log('G', 'e', e.message);
171
+ $('L').classList.remove('on');
172
+ $('D').classList.add('on', 'err');
173
+ $('dT').textContent = 'Error';
174
+ $('dM').textContent = e.message;
175
+ }
176
+ }
177
+ </script>
178
+ </body>
179
+ </html>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,44 @@
1
+ ' PicLet Hidden Launcher
2
+ ' Runs WSL commands without showing cmd.exe window
3
+ ' Usage: wscript.exe launcher.vbs <tool> <filepath> <flags>
4
+
5
+ Set WshShell = CreateObject("WScript.Shell")
6
+ Set fso = CreateObject("Scripting.FileSystemObject")
7
+ Set args = WScript.Arguments
8
+
9
+ If args.Count < 2 Then
10
+ WScript.Quit 1
11
+ End If
12
+
13
+ tool = args(0)
14
+ filePath = args(1)
15
+
16
+ ' Get the directory where this script is located
17
+ scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
18
+ loadingHta = scriptDir & "\gui\loading.hta"
19
+
20
+ ' Show frameless loading window immediately using HTA
21
+ If fso.FileExists(loadingHta) Then
22
+ WshShell.Run "mshta """ & loadingHta & """", 1, False
23
+ End If
24
+
25
+ ' Convert Windows path to WSL path format
26
+ ' D:\path\to\file.png -> /mnt/d/path/to/file.png
27
+ If Mid(filePath, 2, 1) = ":" Then
28
+ driveLetter = LCase(Left(filePath, 1))
29
+ restOfPath = Mid(filePath, 3)
30
+ restOfPath = Replace(restOfPath, "\", "/")
31
+ wslPath = "/mnt/" & driveLetter & restOfPath
32
+ Else
33
+ wslPath = Replace(filePath, "\", "/")
34
+ End If
35
+
36
+ ' Build flags from remaining arguments
37
+ flags = ""
38
+ For i = 2 To args.Count - 1
39
+ flags = flags & " " & args(i)
40
+ Next
41
+
42
+ ' Run wsl command hidden (0 = hidden, False = don't wait)
43
+ cmd = "wsl piclet " & tool & " """ & wslPath & """" & flags
44
+ WshShell.Run cmd, 0, False
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@spark-apps/piclet",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight image tools for content creators",
5
+ "type": "module",
6
+ "bin": {
7
+ "piclet": "dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup",
11
+ "dev": "tsup --watch",
12
+ "postinstall": "node dist/cli.js install",
13
+ "preuninstall": "node dist/cli.js uninstall",
14
+ "lint": "biome check .",
15
+ "lint:fix": "biome check --write .",
16
+ "format": "biome format --write .",
17
+ "typecheck": "tsc --noEmit",
18
+ "prepublishOnly": "bun run build"
19
+ },
20
+ "keywords": [
21
+ "image",
22
+ "icon",
23
+ "ico",
24
+ "imagemagick",
25
+ "wsl",
26
+ "windows",
27
+ "context-menu",
28
+ "png",
29
+ "resize",
30
+ "scale"
31
+ ],
32
+ "author": "spark-apps",
33
+ "license": "MIT",
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/muammar-yacoob/PicLet.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/muammar-yacoob/PicLet/issues"
43
+ },
44
+ "homepage": "https://github.com/muammar-yacoob/PicLet#readme",
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "os": [
49
+ "linux"
50
+ ],
51
+ "files": [
52
+ "dist"
53
+ ],
54
+ "devDependencies": {
55
+ "@biomejs/biome": "^1.9.4",
56
+ "@types/express": "^5.0.6",
57
+ "@types/figlet": "^1.7.0",
58
+ "@types/gradient-string": "^1.1.6",
59
+ "@types/node": "^22.0.0",
60
+ "@types/prompts": "^2.4.9",
61
+ "tsup": "^8.5.0",
62
+ "typescript": "^5.7.0"
63
+ },
64
+ "dependencies": {
65
+ "chalk": "^5.6.2",
66
+ "commander": "^14.0.2",
67
+ "express": "^5.2.1",
68
+ "figlet": "^1.8.0",
69
+ "gradient-string": "^3.0.0",
70
+ "prompts": "^2.4.2"
71
+ }
72
+ }