@spark-apps/piclet 1.0.0 → 1.0.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.
@@ -1,179 +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>
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>
@@ -0,0 +1,202 @@
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>Transform</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
+ .transform-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:4px 0}
17
+ .transform-btn{display:flex;align-items:center;gap:8px;padding:10px 12px;background:var(--bg2);border:1px solid var(--brd);border-radius:6px;color:var(--txt2);font:inherit;font-size:12px;cursor:pointer;transition:all .15s}
18
+ .transform-btn:hover{background:var(--bg3);color:var(--txt)}
19
+ .transform-btn.active{background:var(--acc);border-color:var(--acc);color:#000;font-weight:500}
20
+ .transform-btn svg{width:16px;height:16px;flex-shrink:0}
21
+ .transform-btn.active svg{stroke:#000}
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div class="app">
26
+ <div class="hd"><b>PicLet</b><span>Transform</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">Select transform to preview</span>
35
+ </div>
36
+ <div class="preview-info" id="pI"></div>
37
+ <div class="transform-grid">
38
+ <button class="transform-btn active" data-transform="flip-h" onclick="selectTransform('flip-h')">
39
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
40
+ <path d="M8 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h3"/>
41
+ <path d="M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3"/>
42
+ <path d="M12 20v2"/>
43
+ <path d="M12 14v2"/>
44
+ <path d="M12 8v2"/>
45
+ <path d="M12 2v2"/>
46
+ </svg>
47
+ Flip H
48
+ </button>
49
+ <button class="transform-btn" data-transform="flip-v" onclick="selectTransform('flip-v')">
50
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
51
+ <path d="M3 8V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v3"/>
52
+ <path d="M3 16v3a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-3"/>
53
+ <path d="M4 12H2"/>
54
+ <path d="M10 12H8"/>
55
+ <path d="M16 12h-2"/>
56
+ <path d="M22 12h-2"/>
57
+ </svg>
58
+ Flip V
59
+ </button>
60
+ <button class="transform-btn" data-transform="rotate-90" onclick="selectTransform('rotate-90')">
61
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
62
+ <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
63
+ <path d="M21 3v5h-5"/>
64
+ </svg>
65
+ 90°
66
+ </button>
67
+ <button class="transform-btn" data-transform="rotate-180" onclick="selectTransform('rotate-180')">
68
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
69
+ <path d="M17 3a9 9 0 1 1-5.5 16.1"/>
70
+ <path d="M21 8l-4-5"/>
71
+ <path d="M21 8l-5 4"/>
72
+ <path d="M7 21a9 9 0 0 1 5.5-16.1"/>
73
+ <path d="M3 16l4 5"/>
74
+ <path d="M3 16l5-4"/>
75
+ </svg>
76
+ 180°
77
+ </button>
78
+ <button class="transform-btn" data-transform="rotate-270" onclick="selectTransform('rotate-270')" style="grid-column:span 2">
79
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
80
+ <path d="M3 12a9 9 0 1 0 9-9c-2.52 0-4.93 1-6.74 2.74L3 8"/>
81
+ <path d="M3 3v5h5"/>
82
+ </svg>
83
+ 270° (Counter-clockwise)
84
+ </button>
85
+ </div>
86
+ <div class="btns">
87
+ <button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
88
+ <button class="btn btn-p" onclick="apply()">Apply</button>
89
+ </div>
90
+ </div>
91
+ <!-- Loading state -->
92
+ <div class="ld" id="L"><div class="sp"></div><span id="lT">Transforming...</span></div>
93
+ <!-- Log -->
94
+ <div class="log" id="G"></div>
95
+ <!-- Done state -->
96
+ <div class="dn" id="D">
97
+ <h4 id="dT"></h4>
98
+ <p id="dM"></p>
99
+ <div class="btns" style="width:100%;margin-top:8px">
100
+ <button class="btn btn-p" onclick="PicLet.close()">Done</button>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ <script>
105
+ const { $, log, fetchJson, postJson } = PicLet;
106
+ const pA = $('pA'), pI = $('pI');
107
+
108
+ let currentTransform = 'flip-h';
109
+ let previewTimeout = null;
110
+ let lastPreviewOpts = null;
111
+
112
+ // Select transform
113
+ function selectTransform(type) {
114
+ currentTransform = type;
115
+ document.querySelectorAll('.transform-btn').forEach(btn => {
116
+ btn.classList.toggle('active', btn.dataset.transform === type);
117
+ });
118
+ schedulePreview();
119
+ }
120
+
121
+ // Get current options
122
+ function getOptions() {
123
+ return { transform: currentTransform };
124
+ }
125
+
126
+ // Check if options changed
127
+ function optionsChanged() {
128
+ const current = JSON.stringify(getOptions());
129
+ if (current === lastPreviewOpts) return false;
130
+ lastPreviewOpts = current;
131
+ return true;
132
+ }
133
+
134
+ // Schedule preview
135
+ function schedulePreview() {
136
+ if (previewTimeout) clearTimeout(previewTimeout);
137
+ previewTimeout = setTimeout(() => {
138
+ if (optionsChanged()) {
139
+ generatePreview();
140
+ }
141
+ }, 200);
142
+ }
143
+
144
+ // Generate preview
145
+ async function generatePreview() {
146
+ pA.innerHTML = '<div class="mini-sp"></div>';
147
+ pI.textContent = '';
148
+
149
+ try {
150
+ const result = await postJson('/api/preview', getOptions());
151
+
152
+ if (result.success && result.imageData) {
153
+ const img = document.createElement('img');
154
+ img.src = result.imageData;
155
+ img.alt = 'Preview';
156
+ pA.innerHTML = '';
157
+ pA.appendChild(img);
158
+ pI.textContent = result.width && result.height ? `${result.width}×${result.height}` : '';
159
+ } else {
160
+ pA.innerHTML = '<span class="placeholder">' + (result.error || 'Preview failed') + '</span>';
161
+ pI.textContent = '';
162
+ }
163
+ } catch (e) {
164
+ pA.innerHTML = '<span class="placeholder">Preview error</span>';
165
+ pI.textContent = '';
166
+ }
167
+ }
168
+
169
+ // Load initial data
170
+ fetchJson('/api/info').then(d => {
171
+ $('fn').textContent = d.fileName;
172
+ $('sz').textContent = d.width + '×' + d.height;
173
+ setTimeout(generatePreview, 100);
174
+ });
175
+
176
+ // Apply
177
+ async function apply() {
178
+ $('F').classList.add('hide');
179
+ $('L').classList.add('on');
180
+ $('G').classList.add('on');
181
+
182
+ try {
183
+ const result = await postJson('/api/process', getOptions());
184
+ if (result.logs) {
185
+ result.logs.forEach(l => log('G', l.type[0], l.message));
186
+ }
187
+
188
+ $('L').classList.remove('on');
189
+ $('D').classList.add('on', result.success ? 'ok' : 'err');
190
+ $('dT').textContent = result.success ? 'Done' : 'Failed';
191
+ $('dM').textContent = result.success ? result.output : result.error;
192
+ } catch (e) {
193
+ log('G', 'e', e.message);
194
+ $('L').classList.remove('on');
195
+ $('D').classList.add('on', 'err');
196
+ $('dT').textContent = 'Error';
197
+ $('dM').textContent = e.message;
198
+ }
199
+ }
200
+ </script>
201
+ </body>
202
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spark-apps/piclet",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "Lightweight image tools for content creators",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "scale"
31
31
  ],
32
32
  "author": "spark-apps",
33
- "license": "MIT",
33
+ "license": "AGPL-3.0",
34
34
  "publishConfig": {
35
35
  "access": "public"
36
36
  },