@jlcpcb/cli 0.1.1 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @jlcpcb/cli
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cd6df88: Packages to select: @jlcpcb/cli (minor), @jlcpcb/core (minor)
8
+
9
+ Summary to paste:
10
+
11
+ Add `jlc easyeda install [uuid]` command for EasyEDA community components
12
+
13
+ CLI changes:
14
+ - New `jlc easyeda install [uuid]` subcommand with -p/--project, --with-3d, -f/--force options
15
+ - Dedicated EasyEDAInfoScreen and EasyEDADetailView showing community-specific fields
16
+ - Installation status detection: shows "R Regenerate" if installed, "Enter Install" if not
17
+ - "✓ Installed" indicator next to component title when already in library
18
+ - `jlc install` now rejects non-LCSC IDs with helpful redirect to `jlc easyeda install`
19
+
20
+ Core changes:
21
+ - Add `isEasyEDAInstalled()` method to check if component exists in EasyEDA library
22
+ - Register EasyEDA library in global KiCad tables with portable ${KICAD9_3RD_PARTY} paths
23
+ - Support global installation for community components (no projectPath required)
24
+ - Separate library names (EasyEDA vs EasyEDA-local) to avoid global/local collision
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies [cd6df88]
29
+ - @jlcpcb/core@0.2.0
30
+
3
31
  ## 0.1.1
4
32
 
5
33
  ### Patch Changes
@@ -0,0 +1,528 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>EasyEDA Component Browser</title>
7
+ <style>
8
+ * {
9
+ box-sizing: border-box;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ :root {
15
+ --bg-primary: #1a1a1a;
16
+ --bg-secondary: #2a2a2a;
17
+ --bg-card: #333333;
18
+ --text-primary: #ffffff;
19
+ --text-secondary: #aaaaaa;
20
+ --accent: #4a9eff;
21
+ --accent-hover: #6ab0ff;
22
+ --success: #22c55e;
23
+ --error: #ef4444;
24
+ --border: #444444;
25
+ }
26
+
27
+ body {
28
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
29
+ background: var(--bg-primary);
30
+ color: var(--text-primary);
31
+ min-height: 100vh;
32
+ }
33
+
34
+ header {
35
+ background: var(--bg-secondary);
36
+ padding: 20px;
37
+ border-bottom: 1px solid var(--border);
38
+ position: sticky;
39
+ top: 0;
40
+ z-index: 100;
41
+ }
42
+
43
+ .header-content {
44
+ max-width: 1400px;
45
+ margin: 0 auto;
46
+ }
47
+
48
+ h1 {
49
+ font-size: 1.5rem;
50
+ margin-bottom: 16px;
51
+ color: var(--text-primary);
52
+ }
53
+
54
+ .search-bar {
55
+ display: flex;
56
+ gap: 12px;
57
+ flex-wrap: wrap;
58
+ }
59
+
60
+ #search-input {
61
+ flex: 1;
62
+ min-width: 200px;
63
+ padding: 12px 16px;
64
+ font-size: 16px;
65
+ background: var(--bg-primary);
66
+ border: 1px solid var(--border);
67
+ border-radius: 8px;
68
+ color: var(--text-primary);
69
+ outline: none;
70
+ }
71
+
72
+ #search-input:focus {
73
+ border-color: var(--accent);
74
+ }
75
+
76
+ #source-select {
77
+ padding: 12px 16px;
78
+ font-size: 16px;
79
+ background: var(--bg-primary);
80
+ border: 1px solid var(--border);
81
+ border-radius: 8px;
82
+ color: var(--text-primary);
83
+ cursor: pointer;
84
+ }
85
+
86
+ #search-btn {
87
+ padding: 12px 24px;
88
+ font-size: 16px;
89
+ background: var(--accent);
90
+ border: none;
91
+ border-radius: 8px;
92
+ color: white;
93
+ cursor: pointer;
94
+ font-weight: 500;
95
+ transition: background 0.2s;
96
+ }
97
+
98
+ #search-btn:hover {
99
+ background: var(--accent-hover);
100
+ }
101
+
102
+ main {
103
+ max-width: 1400px;
104
+ margin: 0 auto;
105
+ padding: 24px;
106
+ }
107
+
108
+ #loading {
109
+ display: flex;
110
+ justify-content: center;
111
+ padding: 48px;
112
+ }
113
+
114
+ #loading.hidden {
115
+ display: none;
116
+ }
117
+
118
+ .spinner {
119
+ width: 40px;
120
+ height: 40px;
121
+ border: 3px solid var(--border);
122
+ border-top-color: var(--accent);
123
+ border-radius: 50%;
124
+ animation: spin 1s linear infinite;
125
+ }
126
+
127
+ @keyframes spin {
128
+ to { transform: rotate(360deg); }
129
+ }
130
+
131
+ #results-grid {
132
+ display: grid;
133
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
134
+ gap: 20px;
135
+ }
136
+
137
+ .card {
138
+ background: var(--bg-card);
139
+ border-radius: 12px;
140
+ overflow: hidden;
141
+ border: 1px solid var(--border);
142
+ transition: transform 0.2s, border-color 0.2s;
143
+ }
144
+
145
+ .card:hover {
146
+ transform: translateY(-2px);
147
+ border-color: var(--accent);
148
+ }
149
+
150
+ .card-images {
151
+ display: flex;
152
+ gap: 2px;
153
+ background: var(--border);
154
+ }
155
+
156
+ .image-container {
157
+ flex: 1;
158
+ aspect-ratio: 1;
159
+ background: #000;
160
+ display: flex;
161
+ flex-direction: column;
162
+ align-items: center;
163
+ justify-content: center;
164
+ position: relative;
165
+ cursor: pointer;
166
+ overflow: hidden;
167
+ }
168
+
169
+ .image-container img,
170
+ .image-container svg {
171
+ max-width: 100%;
172
+ max-height: 100%;
173
+ object-fit: contain;
174
+ }
175
+
176
+ .image-label {
177
+ position: absolute;
178
+ bottom: 4px;
179
+ left: 4px;
180
+ font-size: 10px;
181
+ color: var(--text-secondary);
182
+ background: rgba(0,0,0,0.7);
183
+ padding: 2px 6px;
184
+ border-radius: 4px;
185
+ }
186
+
187
+ .symbol-placeholder,
188
+ .footprint-placeholder,
189
+ .no-preview {
190
+ color: var(--text-secondary);
191
+ font-size: 12px;
192
+ }
193
+
194
+ .card-info {
195
+ padding: 12px;
196
+ }
197
+
198
+ .card-title {
199
+ font-weight: 600;
200
+ margin-bottom: 8px;
201
+ white-space: nowrap;
202
+ overflow: hidden;
203
+ text-overflow: ellipsis;
204
+ }
205
+
206
+ .card-package,
207
+ .card-owner {
208
+ font-size: 13px;
209
+ color: var(--text-secondary);
210
+ margin-bottom: 4px;
211
+ }
212
+
213
+ .card-uuid {
214
+ display: flex;
215
+ padding: 8px 12px;
216
+ background: var(--bg-secondary);
217
+ gap: 8px;
218
+ }
219
+
220
+ .card-uuid input {
221
+ flex: 1;
222
+ background: var(--bg-primary);
223
+ border: 1px solid var(--border);
224
+ border-radius: 4px;
225
+ padding: 6px 10px;
226
+ font-size: 11px;
227
+ font-family: monospace;
228
+ color: var(--text-secondary);
229
+ }
230
+
231
+ .copy-btn {
232
+ background: var(--accent);
233
+ border: none;
234
+ border-radius: 4px;
235
+ padding: 6px 10px;
236
+ cursor: pointer;
237
+ color: white;
238
+ display: flex;
239
+ align-items: center;
240
+ justify-content: center;
241
+ transition: background 0.2s;
242
+ }
243
+
244
+ .copy-btn:hover {
245
+ background: var(--accent-hover);
246
+ }
247
+
248
+ .copy-btn.copied {
249
+ background: var(--success);
250
+ }
251
+
252
+ #pagination {
253
+ display: flex;
254
+ justify-content: center;
255
+ align-items: center;
256
+ gap: 16px;
257
+ margin-top: 32px;
258
+ }
259
+
260
+ .page-btn {
261
+ padding: 10px 20px;
262
+ background: var(--bg-card);
263
+ border: 1px solid var(--border);
264
+ border-radius: 8px;
265
+ color: var(--text-primary);
266
+ cursor: pointer;
267
+ transition: background 0.2s, border-color 0.2s;
268
+ }
269
+
270
+ .page-btn:hover:not(:disabled) {
271
+ background: var(--bg-secondary);
272
+ border-color: var(--accent);
273
+ }
274
+
275
+ .page-btn:disabled {
276
+ opacity: 0.5;
277
+ cursor: not-allowed;
278
+ }
279
+
280
+ .page-info {
281
+ color: var(--text-secondary);
282
+ }
283
+
284
+ .no-results,
285
+ .error {
286
+ grid-column: 1 / -1;
287
+ text-align: center;
288
+ padding: 48px;
289
+ color: var(--text-secondary);
290
+ }
291
+
292
+ .error {
293
+ color: var(--error);
294
+ }
295
+
296
+ /* Modal */
297
+ #preview-modal {
298
+ position: fixed;
299
+ inset: 0;
300
+ background: rgba(0, 0, 0, 0.8);
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ z-index: 1000;
305
+ padding: 20px;
306
+ }
307
+
308
+ #preview-modal.hidden {
309
+ display: none;
310
+ }
311
+
312
+ .modal-wrapper {
313
+ background: var(--bg-secondary);
314
+ border-radius: 16px;
315
+ max-width: 900px;
316
+ width: 100%;
317
+ max-height: 90vh;
318
+ overflow: auto;
319
+ position: relative;
320
+ }
321
+
322
+ #modal-close {
323
+ position: absolute;
324
+ top: 16px;
325
+ right: 16px;
326
+ background: var(--bg-card);
327
+ border: 1px solid var(--border);
328
+ border-radius: 8px;
329
+ padding: 8px 12px;
330
+ color: var(--text-primary);
331
+ cursor: pointer;
332
+ font-size: 18px;
333
+ z-index: 10;
334
+ }
335
+
336
+ #modal-close:hover {
337
+ background: var(--bg-primary);
338
+ }
339
+
340
+ #modal-content {
341
+ padding: 24px;
342
+ }
343
+
344
+ .modal-loading {
345
+ text-align: center;
346
+ padding: 48px;
347
+ color: var(--text-secondary);
348
+ }
349
+
350
+ .modal-error {
351
+ text-align: center;
352
+ padding: 48px;
353
+ color: var(--error);
354
+ }
355
+
356
+ .modal-header {
357
+ margin-bottom: 24px;
358
+ }
359
+
360
+ .modal-header h2 {
361
+ margin-bottom: 12px;
362
+ }
363
+
364
+ .modal-uuid {
365
+ display: flex;
366
+ align-items: center;
367
+ gap: 12px;
368
+ }
369
+
370
+ .modal-uuid code {
371
+ background: var(--bg-primary);
372
+ padding: 8px 12px;
373
+ border-radius: 6px;
374
+ font-size: 14px;
375
+ color: var(--text-secondary);
376
+ }
377
+
378
+ .modal-copy {
379
+ padding: 8px 16px;
380
+ font-size: 14px;
381
+ }
382
+
383
+ .modal-previews {
384
+ display: grid;
385
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
386
+ gap: 24px;
387
+ margin-bottom: 24px;
388
+ }
389
+
390
+ .modal-preview h3 {
391
+ margin-bottom: 12px;
392
+ font-size: 14px;
393
+ color: var(--text-secondary);
394
+ }
395
+
396
+ .preview-svg {
397
+ background: #000;
398
+ border-radius: 8px;
399
+ aspect-ratio: 1;
400
+ display: flex;
401
+ align-items: center;
402
+ justify-content: center;
403
+ overflow: hidden;
404
+ }
405
+
406
+ .preview-svg svg {
407
+ width: 100%;
408
+ height: 100%;
409
+ }
410
+
411
+ .preview-svg .no-preview {
412
+ color: var(--text-secondary);
413
+ }
414
+
415
+ .modal-info {
416
+ display: flex;
417
+ gap: 24px;
418
+ color: var(--text-secondary);
419
+ font-size: 14px;
420
+ }
421
+ </style>
422
+ </head>
423
+ <body>
424
+ <header>
425
+ <div class="header-content">
426
+ <h1>EasyEDA Component Browser</h1>
427
+ <div class="search-bar">
428
+ <input type="text" id="search-input" placeholder="Search components (e.g., ESP32, STM32, LM7805)..." autofocus />
429
+ <select id="source-select">
430
+ <option value="user">Community</option>
431
+ <option value="lcsc">LCSC</option>
432
+ <option value="all">All Sources</option>
433
+ </select>
434
+ <button id="search-btn">Search</button>
435
+ </div>
436
+ </div>
437
+ </header>
438
+
439
+ <main>
440
+ <div id="loading" class="hidden">
441
+ <div class="spinner"></div>
442
+ </div>
443
+ <div id="results-grid"></div>
444
+ <div id="pagination"></div>
445
+ </main>
446
+
447
+ <div id="preview-modal" class="hidden">
448
+ <div class="modal-wrapper">
449
+ <button id="modal-close">&times;</button>
450
+ <div id="modal-content"></div>
451
+ </div>
452
+ </div>
453
+
454
+ <script>
455
+ function EJ(Z){let $=[],J=0;while(J<Z.length){let K=Z[J];if(/\s/.test(K)){J++;continue}if(K==="("||K===")"){$.push(K),J++;continue}if(K==='"'){let V="";J++;while(J<Z.length)if(Z[J]==="\\"&&J+1<Z.length)V+=Z[J+1],J+=2;else if(Z[J]==='"'){J++;break}else V+=Z[J],J++;$.push(`"${V}"`);continue}let U="";while(J<Z.length&&!/[\s()]/.test(Z[J]))U+=Z[J],J++;if(U)$.push(U)}return $}function ZJ(Z){if(Z.length===0)return{expr:[],remaining:[]};let $=Z[0];if($==="("){let K=[],U=Z.slice(1);while(U.length>0&&U[0]!==")"){let{expr:V,remaining:j}=ZJ(U);K.push(V),U=j}if(U[0]===")")U=U.slice(1);return{expr:K,remaining:U}}if($===")")return{expr:[],remaining:Z.slice(1)};let J=$;if(J.startsWith('"')&&J.endsWith('"'))J=J.slice(1,-1);return{expr:J,remaining:Z.slice(1)}}function d(Z){let $=EJ(Z),{expr:J}=ZJ($);return J}function A(Z){return Array.isArray(Z)}function X(Z){return typeof Z==="string"}function T(Z){if(A(Z)&&Z.length>0&&X(Z[0]))return Z[0];return}function z(Z,$){if(!A(Z))return;for(let J of Z)if(A(J)&&T(J)===$)return J;return}function I(Z,$){if(!A(Z))return[];let J=[];for(let K of Z)if(A(K)&&T(K)===$)J.push(K);return J}function y(Z,$){let J=z(Z,$);if(J&&J.length>=2&&X(J[1]))return J[1];return}function g(Z,$){let J=y(Z,$);if(J!==void 0){let K=parseFloat(J);if(!isNaN(K))return K}return}function _(Z,$){let J=z(Z,$);if(J&&J.length>=3){let K=parseFloat(X(J[1])?J[1]:""),U=parseFloat(X(J[2])?J[2]:"");if(!isNaN(K)&&!isNaN(U))return{x:K,y:U}}return}function c(Z,$){let J=z(Z,$);if(J&&J.length>=3){let K=parseFloat(X(J[1])?J[1]:""),U=parseFloat(X(J[2])?J[2]:"");if(!isNaN(K)&&!isNaN(U)){let V={x:K,y:U};if(J.length>=4&&X(J[3])){let j=parseFloat(J[3]);if(!isNaN(j))V.rotation=j}return V}}return}function o(Z){let $=z(Z,"size");if($&&$.length>=3){let J=parseFloat(X($[1])?$[1]:""),K=parseFloat(X($[2])?$[2]:"");if(!isNaN(J)&&!isNaN(K))return{width:J,height:K}}return}function P(Z){let $=z(Z,"stroke");if(!$)return;let J=g($,"width")??0.254,K=y($,"type")??"default";return{width:J,type:K}}function L(Z){let $=z(Z,"fill");if(!$)return;let J=y($,"type");if(J)return J;if($.length>=2&&X($[1]))return $[1];return}function u(Z){let $=z(Z,"pts");if(!$)return[];let J=[];for(let K of $)if(A(K)&&T(K)==="xy"&&K.length>=3){let U=parseFloat(X(K[1])?K[1]:""),V=parseFloat(X(K[2])?K[2]:"");if(!isNaN(U)&&!isNaN(V))J.push({x:U,y:V})}return J}function v(Z){let $=y(Z,"layer");if($)return[$];let J=z(Z,"layers");if(!J)return[];let K=[];for(let U=1;U<J.length;U++){let V=J[U];if(X(V))K.push(V)}return K}var B={background:"#FFFFF8",body:"#840000",bodyFill:"#FFFFC4",pin:"#840000",pinText:"#008484",text:"#000000"},w={background:"#000000",fCu:"#840000",bCu:"#008400",in1Cu:"#C2C200",in2Cu:"#C200C2",fSilkS:"#008484",bSilkS:"#840084",fMask:"#840084",bMask:"#848400",fPaste:"#840000",bPaste:"#00C2C2",fFab:"#848484",bFab:"#000084",fCrtYd:"#C2C2C2",bCrtYd:"#848484",edgeCuts:"#C2C200",dwgsUser:"#C2C2C2",cmtsUser:"#848484",margin:"#C200C2",padFront:"#840000",padBack:"#008400",padThruHole:"#C2C200",drill:"#848484"},f=10;function b(Z){return Z.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function a(Z){if(!Z||Z.trim()==="")return S("No symbol data");try{let $=d(Z);if(!A($)||T($)!=="symbol")return S("Invalid symbol format");let J=[],K={minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0},U=I($,"symbol");for(let D of U){for(let H of I(D,"rectangle")){let W=XJ(H,K);if(W)J.push(W)}for(let H of I(D,"polyline")){let W=wJ(H,K);if(W)J.push(W)}for(let H of I(D,"circle")){let W=MJ(H,K);if(W)J.push(W)}for(let H of I(D,"arc")){let W=IJ(H,K);if(W)J.push(W)}for(let H of I(D,"pin")){let W=YJ(H,K);if(W)J.push(W)}}let V=5;if(!isFinite(K.minX))K.minX=-20,K.maxX=20,K.minY=-20,K.maxY=20;let j=(K.maxX-K.minX+V*2)*f,Q=(K.maxY-K.minY+V*2)*f;return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${`${(K.minX-V)*f} ${(-K.maxY-V)*f} ${j} ${Q}`}" width="100%" height="100%" style="background-color: ${B.background}">
456
+ <g transform="scale(${f}, ${-f})">
457
+ ${J.join(`
458
+ `)}
459
+ </g>
460
+ </svg>`}catch($){return S(`Parse error: ${$}`)}}function XJ(Z,$){let J=_(Z,"start"),K=_(Z,"end");if(!J||!K)return"";let U=P(Z),j=L(Z)==="background"?B.bodyFill:"none";E($,J.x,J.y),E($,K.x,K.y);let Q=Math.min(J.x,K.x),G=Math.min(J.y,K.y),D=Math.abs(K.x-J.x),H=Math.abs(K.y-J.y);return`<rect x="${Q}" y="${G}" width="${D}" height="${H}" fill="${j}" stroke="${B.body}" stroke-width="${U?.width??0.254}"/>`}function wJ(Z,$){let J=u(Z);if(J.length<2)return"";let K=P(Z),V=L(Z)==="background"?B.bodyFill:"none";return`<polyline points="${J.map((Q)=>{return E($,Q.x,Q.y),`${Q.x},${Q.y}`}).join(" ")}" fill="${V}" stroke="${B.body}" stroke-width="${K?.width??0.254}" stroke-linecap="round" stroke-linejoin="round"/>`}function MJ(Z,$){let J=_(Z,"center"),K=g(Z,"radius");if(!J||K===void 0)return"";let U=P(Z),j=L(Z)==="background"?B.bodyFill:"none";return E($,J.x-K,J.y-K),E($,J.x+K,J.y+K),`<circle cx="${J.x}" cy="${J.y}" r="${K}" fill="${j}" stroke="${B.body}" stroke-width="${U?.width??0.254}"/>`}function IJ(Z,$){let J=_(Z,"start"),K=_(Z,"mid"),U=_(Z,"end");if(!J||!K||!U)return"";let V=P(Z);return E($,J.x,J.y),E($,K.x,K.y),E($,U.x,U.y),`<path d="${jJ(J,K,U)}" fill="none" stroke="${B.body}" stroke-width="${V?.width??0.254}" stroke-linecap="round"/>`}function jJ(Z,$,J){let{x:K,y:U}=Z,V=$.x,j=$.y,Q=J.x,G=J.y,D=2*(K*(j-G)+V*(G-U)+Q*(U-j));if(Math.abs(D)<0.0001)return`M ${Z.x} ${Z.y} L ${J.x} ${J.y}`;let H=((K*K+U*U)*(j-G)+(V*V+j*j)*(G-U)+(Q*Q+G*G)*(U-j))/D,W=((K*K+U*U)*(Q-V)+(V*V+j*j)*(K-Q)+(Q*Q+G*G)*(V-K))/D,q=Math.sqrt((K-H)**2+(U-W)**2),M=(V-K)*(G-U)-(j-U)*(Q-K)>0?0:1,N=0;return`M ${Z.x} ${Z.y} A ${q} ${q} 0 ${N} ${M} ${J.x} ${J.y}`}function YJ(Z,$){let J=c(Z,"at"),K=g(Z,"length")??2.54;if(!J)return"";let U=z(Z,"name"),V=z(Z,"number"),j=U&&U.length>=2&&X(U[1])?U[1]:"",Q=V&&V.length>=2&&X(V[1])?V[1]:"",G=J.rotation??0,D=G*Math.PI/180,H=J.x+K*Math.cos(D),W=J.y+K*Math.sin(D);E($,J.x,J.y),E($,H,W);let q=[];if(q.push(`<line x1="${J.x}" y1="${J.y}" x2="${H}" y2="${W}" stroke="${B.pin}" stroke-width="0.254"/>`),q.push(`<circle cx="${J.x}" cy="${J.y}" r="0.3" fill="${B.pin}"/>`),j&&j!=="~"){let M=H,N=W,k="start",F="middle";if(G===0)M=H+0.5,k="start";else if(G===180)M=H-0.5,k="end";else if(G===90)N=W+0.5,F="hanging",k="middle";else if(G===270)N=W-0.5,F="alphabetic",k="middle";q.push(`<text x="${M}" y="${N}" fill="${B.pinText}" font-size="1" font-family="sans-serif" text-anchor="${k}" dominant-baseline="${F}" transform="scale(1,-1) translate(0,${-2*N})">${b(j)}</text>`)}if(Q){let Y=(J.x+H)/2,M=(J.y+W)/2,N=G===0||G===180?0.8:0;q.push(`<text x="${Y}" y="${M+N}" fill="${B.pinText}" font-size="0.8" font-family="sans-serif" text-anchor="middle" dominant-baseline="middle" transform="scale(1,-1) translate(0,${-2*(M+N)})">${b(Q)}</text>`)}return q.join(`
461
+ `)}function s(Z){if(!Z||Z.trim()==="")return S("No footprint data");try{let $=d(Z);if(!A($)||T($)!=="footprint")return S("Invalid footprint format");let J=[],K={minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0};for(let G of I($,"pad")){let D=NJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_line")){let D=$J(G,K);if(D)J.push(D)}for(let G of I($,"fp_circle")){let D=KJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_arc")){let D=UJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_rect")){let D=DJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_poly")){let D=VJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_line")){let D=$J(G,K);if(D)J.push(D)}for(let G of I($,"gr_circle")){let D=KJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_arc")){let D=UJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_rect")){let D=DJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_poly")){let D=VJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_text")){let D=zJ(G,K);if(D)J.push(D)}let U=1;if(!isFinite(K.minX))K.minX=-5,K.maxX=5,K.minY=-5,K.maxY=5;let V=K.maxX-K.minX+U*2,j=K.maxY-K.minY+U*2;return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${`${K.minX-U} ${K.minY-U} ${V} ${j}`}" width="100%" height="100%" style="background-color: ${w.background}">
462
+ <g>
463
+ ${J.join(`
464
+ `)}
465
+ </g>
466
+ </svg>`}catch($){return S(`Parse error: ${$}`)}}function C(Z){if(Z.includes("F.Cu"))return w.fCu;if(Z.includes("B.Cu"))return w.bCu;if(/In\d+\.Cu/.test(Z))return w.in1Cu;if(Z.includes("F.SilkS"))return w.fSilkS;if(Z.includes("B.SilkS"))return w.bSilkS;if(Z.includes("F.Mask"))return w.fMask;if(Z.includes("B.Mask"))return w.bMask;if(Z.includes("F.Paste"))return w.fPaste;if(Z.includes("B.Paste"))return w.bPaste;if(Z.includes("F.Fab"))return w.fFab;if(Z.includes("B.Fab"))return w.bFab;if(Z.includes("F.CrtYd"))return w.fCrtYd;if(Z.includes("B.CrtYd"))return w.bCrtYd;if(Z.includes("Edge.Cuts"))return w.edgeCuts;if(Z.includes("Dwgs.User"))return w.dwgsUser;if(Z.includes("Cmts.User"))return w.cmtsUser;if(Z.includes("Margin"))return w.margin;return w.fSilkS}function NJ(Z,$){if(!A(Z)||Z.length<4)return"";let J=X(Z[1])?Z[1]:"",K=X(Z[2])?Z[2]:"",U=X(Z[3])?Z[3]:"",V=c(Z,"at"),j=o(Z);if(!V||!j)return"";E($,V.x-j.width/2,V.y-j.height/2),E($,V.x+j.width/2,V.y+j.height/2);let Q=[],G=v(Z),D;if(K==="thru_hole"||K==="np_thru_hole")D=w.padThruHole;else if(G.some((q)=>q.includes("B.")))D=w.padBack;else D=w.padFront;let H=V.rotation??0,W=H!==0?` transform="rotate(${H}, ${V.x}, ${V.y})"`:"";if(U==="circle"){let q=Math.min(j.width,j.height)/2;Q.push(`<circle cx="${V.x}" cy="${V.y}" r="${q}" fill="${D}"${W}/>`)}else if(U==="oval"){let q=j.width/2,Y=j.height/2;Q.push(`<ellipse cx="${V.x}" cy="${V.y}" rx="${q}" ry="${Y}" fill="${D}"${W}/>`)}else if(U==="roundrect"){let q=g(Z,"roundrect_rratio")??0.25,Y=Math.min(j.width,j.height)*q/2,M=V.x-j.width/2,N=V.y-j.height/2;Q.push(`<rect x="${M}" y="${N}" width="${j.width}" height="${j.height}" rx="${Y}" fill="${D}"${W}/>`)}else if(U==="custom"){let q=z(Z,"primitives");if(q)for(let Y of I(q,"gr_poly")){let M=u(Y);if(M.length>=3){let N=M.map((k)=>{let F=V.x+k.x,m=V.y+k.y;return E($,F,m),`${F},${m}`}).join(" ");Q.push(`<polygon points="${N}" fill="${D}"${W}/>`)}}if(Q.length===0){let Y=V.x-j.width/2,M=V.y-j.height/2;Q.push(`<rect x="${Y}" y="${M}" width="${j.width}" height="${j.height}" fill="${D}"${W}/>`)}}else{let q=V.x-j.width/2,Y=V.y-j.height/2;Q.push(`<rect x="${q}" y="${Y}" width="${j.width}" height="${j.height}" fill="${D}"${W}/>`)}if(K==="thru_hole"||K==="np_thru_hole"){let q=z(Z,"drill");if(q&&q.length>=2)if(X(q[1])&&q[1]==="oval"&&q.length>=4){let M=parseFloat(X(q[2])?q[2]:"0"),N=parseFloat(X(q[3])?q[3]:"0");Q.push(`<ellipse cx="${V.x}" cy="${V.y}" rx="${M/2}" ry="${N/2}" fill="${w.drill}"${W}/>`)}else{let M=parseFloat(X(q[1])?q[1]:"0");Q.push(`<circle cx="${V.x}" cy="${V.y}" r="${M/2}" fill="${w.drill}"/>`)}}if(J){let q=Math.min(j.width,j.height)*0.5;Q.push(`<text x="${V.x}" y="${V.y}" fill="#FFFFFF" font-size="${q}" font-family="sans-serif" text-anchor="middle" dominant-baseline="central">${b(J)}</text>`)}return Q.join(`
467
+ `)}function $J(Z,$){let J=_(Z,"start"),K=_(Z,"end");if(!J||!K)return"";let U=P(Z),j=v(Z)[0]??"F.SilkS";if(j.includes("CrtYd"))return"";let Q=C(j);return E($,J.x,J.y),E($,K.x,K.y),`<line x1="${J.x}" y1="${J.y}" x2="${K.x}" y2="${K.y}" stroke="${Q}" stroke-width="${U?.width??0.15}" stroke-linecap="round"/>`}function KJ(Z,$){let J=_(Z,"center"),K=_(Z,"end");if(!J||!K)return"";let U=Math.sqrt((K.x-J.x)**2+(K.y-J.y)**2),V=P(Z),Q=v(Z)[0]??"F.SilkS";if(Q.includes("CrtYd"))return"";let G=C(Q);return E($,J.x-U,J.y-U),E($,J.x+U,J.y+U),`<circle cx="${J.x}" cy="${J.y}" r="${U}" fill="none" stroke="${G}" stroke-width="${V?.width??0.15}"/>`}function UJ(Z,$){let J=_(Z,"start"),K=_(Z,"mid"),U=_(Z,"end");if(!J||!K||!U)return"";let V=P(Z),Q=v(Z)[0]??"F.SilkS";if(Q.includes("CrtYd"))return"";let G=C(Q);return E($,J.x,J.y),E($,K.x,K.y),E($,U.x,U.y),`<path d="${jJ(J,K,U)}" fill="none" stroke="${G}" stroke-width="${V?.width??0.15}" stroke-linecap="round"/>`}function VJ(Z,$){let J=u(Z);if(J.length<3)return"";let U=v(Z)[0]??"F.Cu";if(U.includes("CrtYd"))return"";let V=C(U),j=P(Z),Q=L(Z),G=Q==="solid"||Q==="yes"?V:"none",D=J.map((q)=>{return E($,q.x,q.y),`${q.x},${q.y}`}).join(" "),H=j?.width??0,W=H>0?` stroke="${V}" stroke-width="${H}"`:"";return`<polygon points="${D}" fill="${G}"${W}/>`}function DJ(Z,$){let J=_(Z,"start"),K=_(Z,"end");if(!J||!K)return"";let V=v(Z)[0]??"F.SilkS";if(V.includes("CrtYd"))return"";let j=C(V),Q=P(Z),D=L(Z)==="solid"?j:"none";E($,J.x,J.y),E($,K.x,K.y);let H=Math.min(J.x,K.x),W=Math.min(J.y,K.y),q=Math.abs(K.x-J.x),Y=Math.abs(K.y-J.y);return`<rect x="${H}" y="${W}" width="${q}" height="${Y}" fill="${D}" stroke="${j}" stroke-width="${Q?.width??0.15}"/>`}function zJ(Z,$){if(!A(Z)||Z.length<3)return"";let J=X(Z[1])?Z[1]:"",K=X(Z[2])?Z[2]:"";if(J==="reference"||J==="value")return"";let U=c(Z,"at");if(!U)return"";let j=v(Z)[0]??"F.SilkS",Q=C(j),G=z(Z,"effects"),D=1,H="middle";if(G){let N=z(G,"font");if(N){let F=o(N);if(F)D=F.height}let k=z(G,"justify");if(k)for(let F=1;F<k.length;F++){let m=X(k[F])?k[F]:"";if(m==="left")H="start";else if(m==="right")H="end"}}let W=K.length*D*0.6,q=D;if(H==="start")E($,U.x,U.y-q/2),E($,U.x+W,U.y+q/2);else if(H==="end")E($,U.x-W,U.y-q/2),E($,U.x,U.y+q/2);else E($,U.x-W/2,U.y-q/2),E($,U.x+W/2,U.y+q/2);let Y=U.rotation??0,M=Y!==0?` transform="rotate(${Y}, ${U.x}, ${U.y})"`:"";return`<text x="${U.x}" y="${U.y}" dy="0.35em" fill="${Q}" font-size="${D}" font-family="sans-serif" text-anchor="${H}"${M}>${b(K)}</text>`}function E(Z,$,J){Z.minX=Math.min(Z.minX,$),Z.maxX=Math.max(Z.maxX,$),Z.minY=Math.min(Z.minY,J),Z.maxY=Math.max(Z.maxY,J)}function S(Z){return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="100%" height="100%">
468
+ <rect width="200" height="100" fill="#FFF0F0"/>
469
+ <text x="100" y="50" fill="#CC0000" font-size="12" font-family="sans-serif" text-anchor="middle" dominant-baseline="central">${b(Z)}</text>
470
+ </svg>`}var JJ=1,h="",WJ="user",i=!1,n=null,O=document.getElementById("search-input"),GJ=document.getElementById("source-select"),_J=document.getElementById("search-btn"),e=document.getElementById("results-grid"),r=document.getElementById("pagination"),FJ=document.getElementById("loading"),l=document.getElementById("preview-modal"),p=document.getElementById("modal-content"),kJ=document.getElementById("modal-close");function qJ(){let Z=window.__INITIAL_QUERY__;if(Z)O.value=Z,h=Z,R();else{let J=new URLSearchParams(window.location.search).get("q");if(J)O.value=J,h=J,R()}O.addEventListener("input",BJ),O.addEventListener("keydown",($)=>{if($.key==="Enter")$.preventDefault(),R()}),_J.addEventListener("click",R),GJ.addEventListener("change",()=>{if(WJ=GJ.value,h)R()}),kJ.addEventListener("click",t),l.addEventListener("click",($)=>{if($.target===l)t()}),document.addEventListener("keydown",($)=>{if($.key==="Escape")t()})}function BJ(){if(n)clearTimeout(n);n=window.setTimeout(()=>{let Z=O.value.trim();if(Z&&Z!==h)h=Z,JJ=1,R()},300)}async function R(){let Z=O.value.trim();if(!Z||i)return;h=Z,i=!0,QJ(!0);try{let $=new URLSearchParams({q:Z,source:WJ,page:String(JJ),limit:"20"}),J=await fetch(`/api/search?${$}`);if(!J.ok)throw Error("Search failed");let K=await J.json();AJ(K.results),RJ(K.pagination)}catch($){console.error("Search error:",$),e.innerHTML='<div class="error">Search failed. Please try again.</div>'}finally{i=!1,QJ(!1)}}function AJ(Z){if(Z.length===0){e.innerHTML='<div class="no-results">No components found. Try a different search term.</div>';return}e.innerHTML=Z.map(($)=>`
471
+ <div class="card" data-uuid="${$.uuid}">
472
+ <div class="card-images">
473
+ <div class="image-container symbol-container" data-uuid="${$.uuid}">
474
+ <div class="symbol-placeholder">Loading...</div>
475
+ <div class="image-label">Symbol</div>
476
+ </div>
477
+ <div class="image-container footprint-container" data-uuid="${$.uuid}">
478
+ <div class="footprint-placeholder">Loading...</div>
479
+ <div class="image-label">Footprint</div>
480
+ </div>
481
+ </div>
482
+ <div class="card-info">
483
+ <div class="card-title" title="${x($.title)}">${x($.title)}</div>
484
+ <div class="card-package">Package: ${x($.package||"Unknown")}</div>
485
+ <div class="card-owner">By: ${x($.owner.nickname||$.owner.username)}</div>
486
+ </div>
487
+ <div class="card-uuid">
488
+ <input type="text" value="${$.uuid}" readonly />
489
+ <button class="copy-btn" data-uuid="${$.uuid}" title="Copy UUID">
490
+ <svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
491
+ </button>
492
+ </div>
493
+ </div>
494
+ `).join(""),document.querySelectorAll(".copy-btn").forEach(($)=>{$.addEventListener("click",(J)=>{J.stopPropagation();let K=$.dataset.uuid;if(K)HJ(K,$)})}),document.querySelectorAll(".image-container").forEach(($)=>{$.addEventListener("click",async()=>{let J=$.dataset.uuid;if(J)vJ(J)})}),Z.forEach(($)=>PJ($.uuid))}async function PJ(Z){let $=document.querySelector(`.symbol-container[data-uuid="${Z}"]`),J=document.querySelector(`.footprint-container[data-uuid="${Z}"]`);if(!$&&!J)return;try{let K=await fetch(`/api/component/${Z}`);if(!K.ok){if($)$.innerHTML='<div class="no-preview">Error</div><div class="image-label">Symbol</div>';if(J)J.innerHTML='<div class="no-preview">Error</div><div class="image-label">Footprint</div>';return}let U=await K.json();if($)if(U.symbolSexpr){let V=a(U.symbolSexpr);$.innerHTML=V+'<div class="image-label">Symbol</div>'}else $.innerHTML='<div class="no-preview">No preview</div><div class="image-label">Symbol</div>';if(J)if(U.footprintSexpr){let V=s(U.footprintSexpr);J.innerHTML=V+'<div class="image-label">Footprint</div>'}else J.innerHTML='<div class="no-preview">No preview</div><div class="image-label">Footprint</div>'}catch{if($)$.innerHTML='<div class="no-preview">Error</div><div class="image-label">Symbol</div>';if(J)J.innerHTML='<div class="no-preview">Error</div><div class="image-label">Footprint</div>'}}async function vJ(Z){l.classList.remove("hidden"),p.innerHTML='<div class="modal-loading">Loading...</div>';try{let $=await fetch(`/api/component/${Z}`);if(!$.ok)throw Error("Failed to fetch component");let J=await $.json(),K=J.symbolSexpr?a(J.symbolSexpr):"",U=J.footprintSexpr?s(J.footprintSexpr):"";p.innerHTML=`
495
+ <div class="modal-header">
496
+ <h2>${x(J.title)}</h2>
497
+ <div class="modal-uuid">
498
+ <code>${J.uuid}</code>
499
+ <button class="copy-btn modal-copy" data-uuid="${J.uuid}">Copy UUID</button>
500
+ </div>
501
+ </div>
502
+ <div class="modal-previews">
503
+ <div class="modal-preview">
504
+ <h3>Symbol (KiCad)</h3>
505
+ <div class="preview-svg">${K||'<div class="no-preview">No symbol preview</div>'}</div>
506
+ </div>
507
+ <div class="modal-preview">
508
+ <h3>Footprint (KiCad)</h3>
509
+ <div class="preview-svg">${U||'<div class="no-preview">No footprint preview</div>'}</div>
510
+ </div>
511
+ </div>
512
+ <div class="modal-info">
513
+ <p><strong>Description:</strong> ${x(J.description||"N/A")}</p>
514
+ ${J.model3d?"<p><strong>3D Model:</strong> Available</p>":""}
515
+ </div>
516
+ `;let V=p.querySelector(".modal-copy");if(V)V.addEventListener("click",()=>{HJ(J.uuid,V)})}catch($){console.error("Modal error:",$),p.innerHTML='<div class="modal-error">Failed to load component details</div>'}}function t(){l.classList.add("hidden")}function RJ(Z){if(Z.totalPages<=1){r.innerHTML="";return}r.innerHTML=`
517
+ <button class="page-btn" ${Z.hasPrev?"":"disabled"} data-page="${Z.page-1}">
518
+ ← Prev
519
+ </button>
520
+ <span class="page-info">Page ${Z.page} of ${Z.totalPages}</span>
521
+ <button class="page-btn" ${Z.hasNext?"":"disabled"} data-page="${Z.page+1}">
522
+ Next →
523
+ </button>
524
+ `,r.querySelectorAll(".page-btn").forEach(($)=>{$.addEventListener("click",()=>{let J=parseInt($.dataset.page||"1",10);if(J>0)JJ=J,R(),window.scrollTo(0,0)})})}async function HJ(Z,$){try{await navigator.clipboard.writeText(Z),$.classList.add("copied"),setTimeout(()=>$.classList.remove("copied"),1500)}catch{let J=document.createElement("input");J.value=Z,document.body.appendChild(J),J.select(),document.execCommand("copy"),document.body.removeChild(J),$.classList.add("copied"),setTimeout(()=>$.classList.remove("copied"),1500)}}function QJ(Z){FJ.classList.toggle("hidden",!Z)}function x(Z){let $=document.createElement("div");return $.textContent=Z,$.innerHTML}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",qJ);else qJ();
525
+
526
+ </script>
527
+ </body>
528
+ </html>