sigpro 1.0.14 → 1.2.39

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.
Files changed (109) hide show
  1. package/Readme.md +164 -1008
  2. package/dist/sigpro.editor.js +1 -0
  3. package/dist/sigpro.grid.js +78 -0
  4. package/dist/sigpro.js +1 -0
  5. package/dist/sigpro.ui.css +2 -0
  6. package/dist/sigpro.ui.js +1 -0
  7. package/dist/sigpro.utils.js +1 -0
  8. package/dist/sigpro.vite.js +4 -0
  9. package/package.json +64 -14
  10. package/sigpro.d.ts +395 -0
  11. package/.github/workflows/publish.yml +0 -25
  12. package/bun.lock +0 -385
  13. package/docs/404.html +0 -22
  14. package/docs/api/components.html +0 -595
  15. package/docs/api/effects.html +0 -787
  16. package/docs/api/fetch.html +0 -873
  17. package/docs/api/pages.html +0 -405
  18. package/docs/api/quick.html +0 -217
  19. package/docs/api/routing.html +0 -628
  20. package/docs/api/signals.html +0 -683
  21. package/docs/api/storage.html +0 -820
  22. package/docs/assets/api_components.md.BlFwj17l.js +0 -571
  23. package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
  24. package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
  25. package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
  26. package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
  27. package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
  28. package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
  29. package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
  30. package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
  31. package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
  32. package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
  33. package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
  34. package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
  35. package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
  36. package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
  37. package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
  38. package/docs/assets/app.DtmzNmNl.js +0 -1
  39. package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
  40. package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
  41. package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
  42. package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
  43. package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
  44. package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
  45. package/docs/assets/index.md.uvMJmU4o.js +0 -1
  46. package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
  47. package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  48. package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  49. package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  50. package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  51. package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  52. package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  53. package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  54. package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  55. package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  56. package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  57. package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  58. package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  59. package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  60. package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  61. package/docs/assets/style.DJRheFKp.css +0 -1
  62. package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
  63. package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
  64. package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
  65. package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
  66. package/docs/guide/getting-started.html +0 -196
  67. package/docs/guide/why.html +0 -47
  68. package/docs/hashmap.json +0 -1
  69. package/docs/index.html +0 -25
  70. package/docs/logo.svg +0 -118
  71. package/docs/ui/intro.html +0 -25
  72. package/docs/vite/plugin.html +0 -249
  73. package/docs/vp-icons.css +0 -1
  74. package/index.js +0 -3
  75. package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  76. package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  77. package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
  78. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
  79. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
  80. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
  81. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
  82. package/packages/docs/.vitepress/cache/deps/package.json +0 -3
  83. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
  84. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  85. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  86. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  87. package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
  88. package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
  89. package/packages/docs/.vitepress/config.js +0 -68
  90. package/packages/docs/api/components.md +0 -760
  91. package/packages/docs/api/effects.md +0 -1039
  92. package/packages/docs/api/fetch.md +0 -998
  93. package/packages/docs/api/pages.md +0 -497
  94. package/packages/docs/api/quick.md +0 -436
  95. package/packages/docs/api/routing.md +0 -784
  96. package/packages/docs/api/signals.md +0 -899
  97. package/packages/docs/api/storage.md +0 -952
  98. package/packages/docs/guide/getting-started.md +0 -308
  99. package/packages/docs/guide/why.md +0 -135
  100. package/packages/docs/index.md +0 -84
  101. package/packages/docs/logo.svg +0 -118
  102. package/packages/docs/public/logo.svg +0 -118
  103. package/packages/docs/ui/intro.md +0 -16
  104. package/packages/docs/vite/plugin.md +0 -423
  105. package/packages/sigpro/plugin.js +0 -91
  106. package/packages/sigpro/plugin.min.js +0 -1
  107. package/packages/sigpro/sigpro.js +0 -631
  108. package/packages/sigpro/sigpro.min.js +0 -1
  109. package/vite.config.js +0 -24
@@ -1,998 +0,0 @@
1
- # Fetch API 🌐
2
-
3
- SigPro provides a simple, lightweight wrapper around the native Fetch API that integrates seamlessly with signals for loading state management. It's designed for common use cases with sensible defaults.
4
-
5
- ## Core Concepts
6
-
7
- ### What is `$.fetch`?
8
-
9
- A ultra-simple fetch wrapper that:
10
- - **Automatically handles JSON** serialization and parsing
11
- - **Integrates with signals** for loading state
12
- - **Returns `null` on error** (no try/catch needed for basic usage)
13
- - **Works great with effects** for reactive data fetching
14
-
15
- ## `$.fetch(url, data, [loading])`
16
-
17
- Makes a POST request with JSON data and optional loading signal.
18
-
19
- ```javascript
20
- import { $ } from 'sigpro';
21
-
22
- const loading = $(false);
23
-
24
- async function loadUser() {
25
- const user = await $.fetch('/api/user', { id: 123 }, loading);
26
- if (user) {
27
- console.log('User loaded:', user);
28
- }
29
- }
30
- ```
31
-
32
- ## 📋 API Reference
33
-
34
- ### Parameters
35
-
36
- | Parameter | Type | Description |
37
- |-----------|------|-------------|
38
- | `url` | `string` | Endpoint URL |
39
- | `data` | `Object` | Data to send (automatically JSON.stringify'd) |
40
- | `loading` | `Function` (optional) | Signal function to track loading state |
41
-
42
- ### Returns
43
-
44
- | Return | Description |
45
- |--------|-------------|
46
- | `Promise<Object\|null>` | Parsed JSON response or `null` on error |
47
-
48
- ## 🎯 Basic Examples
49
-
50
- ### Simple Data Fetching
51
-
52
- ```javascript
53
- import { $ } from 'sigpro';
54
-
55
- const userData = $(null);
56
-
57
- async function fetchUser(id) {
58
- const data = await $.fetch('/api/user', { id });
59
- if (data) {
60
- userData(data);
61
- }
62
- }
63
-
64
- fetchUser(123);
65
- ```
66
-
67
- ### With Loading State
68
-
69
- ```javascript
70
- import { $, html } from 'sigpro';
71
-
72
- const user = $(null);
73
- const loading = $(false);
74
-
75
- async function loadUser(id) {
76
- const data = await $.fetch('/api/user', { id }, loading);
77
- if (data) user(data);
78
- }
79
-
80
- // In your template
81
- html`
82
- <div>
83
- ${() => loading() ? html`
84
- <div class="spinner">Loading...</div>
85
- ` : user() ? html`
86
- <div>
87
- <h2>${user().name}</h2>
88
- <p>Email: ${user().email}</p>
89
- </div>
90
- ` : html`
91
- <p>No user found</p>
92
- `}
93
- </div>
94
- `;
95
- ```
96
-
97
- ### In an Effect
98
-
99
- ```javascript
100
- import { $ } from 'sigpro';
101
-
102
- const userId = $(1);
103
- const user = $(null);
104
- const loading = $(false);
105
-
106
- $.effect(() => {
107
- const id = userId();
108
- if (id) {
109
- $.fetch(`/api/users/${id}`, null, loading).then(data => {
110
- if (data) user(data);
111
- });
112
- }
113
- });
114
-
115
- userId(2); // Automatically fetches new user
116
- ```
117
-
118
- ## 🚀 Advanced Examples
119
-
120
- ### User Profile with Loading States
121
-
122
- ```javascript
123
- import { $, html } from 'sigpro';
124
-
125
- const Profile = () => {
126
- const userId = $(1);
127
- const user = $(null);
128
- const loading = $(false);
129
- const error = $(null);
130
-
131
- const fetchUser = async (id) => {
132
- error(null);
133
- const data = await $.fetch('/api/user', { id }, loading);
134
- if (data) {
135
- user(data);
136
- } else {
137
- error('Failed to load user');
138
- }
139
- };
140
-
141
- // Fetch when userId changes
142
- $.effect(() => {
143
- fetchUser(userId());
144
- });
145
-
146
- return html`
147
- <div class="profile">
148
- <div class="user-selector">
149
- <button @click=${() => userId(1)}>User 1</button>
150
- <button @click=${() => userId(2)}>User 2</button>
151
- <button @click=${() => userId(3)}>User 3</button>
152
- </div>
153
-
154
- ${() => {
155
- if (loading()) {
156
- return html`<div class="spinner">Loading profile...</div>`;
157
- }
158
-
159
- if (error()) {
160
- return html`<div class="error">${error()}</div>`;
161
- }
162
-
163
- if (user()) {
164
- return html`
165
- <div class="user-info">
166
- <h2>${user().name}</h2>
167
- <p>Email: ${user().email}</p>
168
- <p>Role: ${user().role}</p>
169
- <p>Joined: ${new Date(user().joined).toLocaleDateString()}</p>
170
- </div>
171
- `;
172
- }
173
-
174
- return html`<p>Select a user</p>`;
175
- }}
176
- </div>
177
- `;
178
- };
179
- ```
180
-
181
- ### Todo List with API
182
-
183
- ```javascript
184
- import { $, html } from 'sigpro';
185
-
186
- const TodoApp = () => {
187
- const todos = $([]);
188
- const loading = $(false);
189
- const newTodo = $('');
190
- const filter = $('all'); // 'all', 'active', 'completed'
191
-
192
- // Load todos
193
- const loadTodos = async () => {
194
- const data = await $.fetch('/api/todos', {}, loading);
195
- if (data) todos(data);
196
- };
197
-
198
- // Add todo
199
- const addTodo = async () => {
200
- if (!newTodo().trim()) return;
201
-
202
- const todo = await $.fetch('/api/todos', {
203
- text: newTodo(),
204
- completed: false
205
- });
206
-
207
- if (todo) {
208
- todos([...todos(), todo]);
209
- newTodo('');
210
- }
211
- };
212
-
213
- // Toggle todo
214
- const toggleTodo = async (id, completed) => {
215
- const updated = await $.fetch(`/api/todos/${id}`, {
216
- completed: !completed
217
- });
218
-
219
- if (updated) {
220
- todos(todos().map(t =>
221
- t.id === id ? updated : t
222
- ));
223
- }
224
- };
225
-
226
- // Delete todo
227
- const deleteTodo = async (id) => {
228
- const result = await $.fetch(`/api/todos/${id}/delete`, {});
229
- if (result) {
230
- todos(todos().filter(t => t.id !== id));
231
- }
232
- };
233
-
234
- // Filtered todos
235
- const filteredTodos = $(() => {
236
- const currentFilter = filter();
237
- if (currentFilter === 'all') return todos();
238
- if (currentFilter === 'active') {
239
- return todos().filter(t => !t.completed);
240
- }
241
- return todos().filter(t => t.completed);
242
- });
243
-
244
- // Load on mount
245
- loadTodos();
246
-
247
- return html`
248
- <div class="todo-app">
249
- <h1>Todo List</h1>
250
-
251
- <div class="add-todo">
252
- <input
253
- type="text"
254
- :value=${newTodo}
255
- @keydown.enter=${addTodo}
256
- placeholder="Add a new todo..."
257
- />
258
- <button @click=${addTodo}>Add</button>
259
- </div>
260
-
261
- <div class="filters">
262
- <button
263
- class:active=${() => filter() === 'all'}
264
- @click=${() => filter('all')}
265
- >
266
- All
267
- </button>
268
- <button
269
- class:active=${() => filter() === 'active'}
270
- @click=${() => filter('active')}
271
- >
272
- Active
273
- </button>
274
- <button
275
- class:active=${() => filter() === 'completed'}
276
- @click=${() => filter('completed')}
277
- >
278
- Completed
279
- </button>
280
- </div>
281
-
282
- ${() => loading() ? html`
283
- <div class="spinner">Loading todos...</div>
284
- ) : html`
285
- <ul class="todo-list">
286
- ${filteredTodos().map(todo => html`
287
- <li class="todo-item">
288
- <input
289
- type="checkbox"
290
- :checked=${todo.completed}
291
- @change=${() => toggleTodo(todo.id, todo.completed)}
292
- />
293
- <span class:completed=${todo.completed}>${todo.text}</span>
294
- <button @click=${() => deleteTodo(todo.id)}>🗑️</button>
295
- </li>
296
- `)}
297
- </ul>
298
- `}
299
- </div>
300
- `;
301
- };
302
- ```
303
-
304
- ### Infinite Scroll with Pagination
305
-
306
- ```javascript
307
- import { $, html } from 'sigpro';
308
-
309
- const InfiniteScroll = () => {
310
- const posts = $([]);
311
- const page = $(1);
312
- const loading = $(false);
313
- const hasMore = $(true);
314
- const error = $(null);
315
-
316
- const loadMore = async () => {
317
- if (loading() || !hasMore()) return;
318
-
319
- const data = await $.fetch('/api/posts', {
320
- page: page(),
321
- limit: 10
322
- }, loading);
323
-
324
- if (data) {
325
- if (data.posts.length === 0) {
326
- hasMore(false);
327
- } else {
328
- posts([...posts(), ...data.posts]);
329
- page(p => p + 1);
330
- }
331
- } else {
332
- error('Failed to load posts');
333
- }
334
- };
335
-
336
- // Intersection Observer for infinite scroll
337
- $.effect(() => {
338
- const observer = new IntersectionObserver(
339
- (entries) => {
340
- if (entries[0].isIntersecting) {
341
- loadMore();
342
- }
343
- },
344
- { threshold: 0.1 }
345
- );
346
-
347
- const sentinel = document.getElementById('sentinel');
348
- if (sentinel) observer.observe(sentinel);
349
-
350
- return () => observer.disconnect();
351
- });
352
-
353
- // Initial load
354
- loadMore();
355
-
356
- return html`
357
- <div class="infinite-scroll">
358
- <h1>Posts</h1>
359
-
360
- <div class="posts">
361
- ${posts().map(post => html`
362
- <article class="post">
363
- <h2>${post.title}</h2>
364
- <p>${post.body}</p>
365
- <small>By ${post.author}</small>
366
- </article>
367
- `)}
368
- </div>
369
-
370
- <div id="sentinel" class="sentinel">
371
- ${() => {
372
- if (loading()) {
373
- return html`<div class="spinner">Loading more...</div>`;
374
- }
375
- if (error()) {
376
- return html`<div class="error">${error()}</div>`;
377
- }
378
- if (!hasMore()) {
379
- return html`<div class="end">No more posts</div>`;
380
- }
381
- return '';
382
- }}
383
- </div>
384
- </div>
385
- `;
386
- };
387
- ```
388
-
389
- ### Search with Debounce
390
-
391
- ```javascript
392
- import { $, html } from 'sigpro';
393
-
394
- const SearchComponent = () => {
395
- const query = $('');
396
- const results = $([]);
397
- const loading = $(false);
398
- const error = $(null);
399
- let searchTimeout;
400
-
401
- const performSearch = async (searchQuery) => {
402
- if (!searchQuery.trim()) {
403
- results([]);
404
- return;
405
- }
406
-
407
- const data = await $.fetch('/api/search', {
408
- q: searchQuery
409
- }, loading);
410
-
411
- if (data) {
412
- results(data);
413
- } else {
414
- error('Search failed');
415
- }
416
- };
417
-
418
- // Debounced search
419
- $.effect(() => {
420
- const searchQuery = query();
421
-
422
- clearTimeout(searchTimeout);
423
-
424
- if (searchQuery.length < 2) {
425
- results([]);
426
- return;
427
- }
428
-
429
- searchTimeout = setTimeout(() => {
430
- performSearch(searchQuery);
431
- }, 300);
432
-
433
- return () => clearTimeout(searchTimeout);
434
- });
435
-
436
- return html`
437
- <div class="search">
438
- <div class="search-box">
439
- <input
440
- type="search"
441
- :value=${query}
442
- placeholder="Search..."
443
- class="search-input"
444
- />
445
- ${() => loading() ? html`
446
- <span class="spinner-small">⌛</span>
447
- ) : ''}
448
- </div>
449
-
450
- ${() => {
451
- if (error()) {
452
- return html`<div class="error">${error()}</div>`;
453
- }
454
-
455
- if (results().length > 0) {
456
- return html`
457
- <ul class="results">
458
- ${results().map(item => html`
459
- <li class="result-item">
460
- <h3>${item.title}</h3>
461
- <p>${item.description}</p>
462
- </li>
463
- `)}
464
- </ul>
465
- `;
466
- }
467
-
468
- if (query().length >= 2 && !loading()) {
469
- return html`<p class="no-results">No results found</p>`;
470
- }
471
-
472
- return '';
473
- }}
474
- </div>
475
- `;
476
- };
477
- ```
478
-
479
- ### Form Submission
480
-
481
- ```javascript
482
- import { $, html } from 'sigpro';
483
-
484
- const ContactForm = () => {
485
- const formData = $({
486
- name: '',
487
- email: '',
488
- message: ''
489
- });
490
-
491
- const submitting = $(false);
492
- const submitError = $(null);
493
- const submitSuccess = $(false);
494
-
495
- const handleSubmit = async (e) => {
496
- e.preventDefault();
497
-
498
- submitError(null);
499
- submitSuccess(false);
500
-
501
- const result = await $.fetch('/api/contact', formData(), submitting);
502
-
503
- if (result) {
504
- submitSuccess(true);
505
- formData({ name: '', email: '', message: '' });
506
- } else {
507
- submitError('Failed to send message. Please try again.');
508
- }
509
- };
510
-
511
- const updateField = (field, value) => {
512
- formData({
513
- ...formData(),
514
- [field]: value
515
- });
516
- };
517
-
518
- return html`
519
- <form class="contact-form" @submit=${handleSubmit}>
520
- <h2>Contact Us</h2>
521
-
522
- <div class="form-group">
523
- <label for="name">Name:</label>
524
- <input
525
- type="text"
526
- id="name"
527
- :value=${() => formData().name}
528
- @input=${(e) => updateField('name', e.target.value)}
529
- required
530
- ?disabled=${submitting}
531
- />
532
- </div>
533
-
534
- <div class="form-group">
535
- <label for="email">Email:</label>
536
- <input
537
- type="email"
538
- id="email"
539
- :value=${() => formData().email}
540
- @input=${(e) => updateField('email', e.target.value)}
541
- required
542
- ?disabled=${submitting}
543
- />
544
- </div>
545
-
546
- <div class="form-group">
547
- <label for="message">Message:</label>
548
- <textarea
549
- id="message"
550
- :value=${() => formData().message}
551
- @input=${(e) => updateField('message', e.target.value)}
552
- required
553
- rows="5"
554
- ?disabled=${submitting}
555
- ></textarea>
556
- </div>
557
-
558
- ${() => {
559
- if (submitting()) {
560
- return html`<div class="submitting">Sending...</div>`;
561
- }
562
-
563
- if (submitError()) {
564
- return html`<div class="error">${submitError()}</div>`;
565
- }
566
-
567
- if (submitSuccess()) {
568
- return html`<div class="success">Message sent successfully!</div>`;
569
- }
570
-
571
- return '';
572
- }}
573
-
574
- <button
575
- type="submit"
576
- ?disabled=${submitting}
577
- >
578
- Send Message
579
- </button>
580
- </form>
581
- `;
582
- };
583
- ```
584
-
585
- ### Real-time Dashboard with Multiple Endpoints
586
-
587
- ```javascript
588
- import { $, html } from 'sigpro';
589
-
590
- const Dashboard = () => {
591
- // Multiple data streams
592
- const metrics = $({});
593
- const alerts = $([]);
594
- const logs = $([]);
595
-
596
- const loading = $({
597
- metrics: false,
598
- alerts: false,
599
- logs: false
600
- });
601
-
602
- const refreshInterval = $(5000); // 5 seconds
603
-
604
- const fetchMetrics = async () => {
605
- const data = await $.fetch('/api/metrics', {}, loading().metrics);
606
- if (data) metrics(data);
607
- };
608
-
609
- const fetchAlerts = async () => {
610
- const data = await $.fetch('/api/alerts', {}, loading().alerts);
611
- if (data) alerts(data);
612
- };
613
-
614
- const fetchLogs = async () => {
615
- const data = await $.fetch('/api/logs', {
616
- limit: 50
617
- }, loading().logs);
618
- if (data) logs(data);
619
- };
620
-
621
- // Auto-refresh all data
622
- $.effect(() => {
623
- fetchMetrics();
624
- fetchAlerts();
625
- fetchLogs();
626
-
627
- const interval = setInterval(() => {
628
- fetchMetrics();
629
- fetchAlerts();
630
- }, refreshInterval());
631
-
632
- return () => clearInterval(interval);
633
- });
634
-
635
- return html`
636
- <div class="dashboard">
637
- <header>
638
- <h1>System Dashboard</h1>
639
- <div class="refresh-control">
640
- <label>
641
- Refresh interval:
642
- <select :value=${refreshInterval} @change=${(e) => refreshInterval(parseInt(e.target.value))}>
643
- <option value="2000">2 seconds</option>
644
- <option value="5000">5 seconds</option>
645
- <option value="10000">10 seconds</option>
646
- <option value="30000">30 seconds</option>
647
- </select>
648
- </label>
649
- </div>
650
- </header>
651
-
652
- <div class="dashboard-grid">
653
- <!-- Metrics Panel -->
654
- <div class="panel metrics">
655
- <h2>System Metrics</h2>
656
- ${() => loading().metrics ? html`
657
- <div class="spinner">Loading metrics...</div>
658
- ) : html`
659
- <div class="metrics-grid">
660
- <div class="metric">
661
- <label>CPU</label>
662
- <span>${metrics().cpu || 0}%</span>
663
- </div>
664
- <div class="metric">
665
- <label>Memory</label>
666
- <span>${metrics().memory || 0}%</span>
667
- </div>
668
- <div class="metric">
669
- <label>Requests</label>
670
- <span>${metrics().requests || 0}/s</span>
671
- </div>
672
- </div>
673
- `}
674
- </div>
675
-
676
- <!-- Alerts Panel -->
677
- <div class="panel alerts">
678
- <h2>Active Alerts</h2>
679
- ${() => loading().alerts ? html`
680
- <div class="spinner">Loading alerts...</div>
681
- ) : alerts().length > 0 ? html`
682
- <ul>
683
- ${alerts().map(alert => html`
684
- <li class="alert ${alert.severity}">
685
- <strong>${alert.type}</strong>
686
- <p>${alert.message}</p>
687
- <small>${new Date(alert.timestamp).toLocaleTimeString()}</small>
688
- </li>
689
- `)}
690
- </ul>
691
- ) : html`
692
- <p class="no-data">No active alerts</p>
693
- `}
694
- </div>
695
-
696
- <!-- Logs Panel -->
697
- <div class="panel logs">
698
- <h2>Recent Logs</h2>
699
- ${() => loading().logs ? html`
700
- <div class="spinner">Loading logs...</div>
701
- ) : html`
702
- <ul>
703
- ${logs().map(log => html`
704
- <li class="log ${log.level}">
705
- <span class="timestamp">${new Date(log.timestamp).toLocaleTimeString()}</span>
706
- <span class="message">${log.message}</span>
707
- </li>
708
- `)}
709
- </ul>
710
- `}
711
- </div>
712
- </div>
713
- </div>
714
- `;
715
- };
716
- ```
717
-
718
- ### File Upload
719
-
720
- ```javascript
721
- import { $, html } from 'sigpro';
722
-
723
- const FileUploader = () => {
724
- const files = $([]);
725
- const uploading = $(false);
726
- const uploadProgress = $({});
727
- const uploadResults = $([]);
728
-
729
- const handleFileSelect = (e) => {
730
- files([...e.target.files]);
731
- };
732
-
733
- const uploadFiles = async () => {
734
- if (files().length === 0) return;
735
-
736
- uploading(true);
737
- uploadResults([]);
738
-
739
- for (const file of files()) {
740
- const formData = new FormData();
741
- formData.append('file', file);
742
-
743
- // Track progress for this file
744
- uploadProgress({
745
- ...uploadProgress(),
746
- [file.name]: 0
747
- });
748
-
749
- try {
750
- // Custom fetch for FormData
751
- const response = await fetch('/api/upload', {
752
- method: 'POST',
753
- body: formData
754
- });
755
-
756
- const result = await response.json();
757
-
758
- uploadResults([
759
- ...uploadResults(),
760
- { file: file.name, success: true, result }
761
- ]);
762
- } catch (error) {
763
- uploadResults([
764
- ...uploadResults(),
765
- { file: file.name, success: false, error: error.message }
766
- ]);
767
- }
768
-
769
- uploadProgress({
770
- ...uploadProgress(),
771
- [file.name]: 100
772
- });
773
- }
774
-
775
- uploading(false);
776
- };
777
-
778
- return html`
779
- <div class="file-uploader">
780
- <h2>Upload Files</h2>
781
-
782
- <input
783
- type="file"
784
- multiple
785
- @change=${handleFileSelect}
786
- ?disabled=${uploading}
787
- />
788
-
789
- ${() => files().length > 0 ? html`
790
- <div class="file-list">
791
- <h3>Selected Files:</h3>
792
- <ul>
793
- ${files().map(file => html`
794
- <li>
795
- ${file.name} (${(file.size / 1024).toFixed(2)} KB)
796
- ${() => uploadProgress()[file.name] ? html`
797
- <progress value="${uploadProgress()[file.name]}" max="100"></progress>
798
- ) : ''}
799
- </li>
800
- `)}
801
- </ul>
802
-
803
- <button
804
- @click=${uploadFiles}
805
- ?disabled=${uploading}
806
- >
807
- ${() => uploading() ? 'Uploading...' : 'Upload Files'}
808
- </button>
809
- </div>
810
- ` : ''}
811
-
812
- ${() => uploadResults().length > 0 ? html`
813
- <div class="upload-results">
814
- <h3>Upload Results:</h3>
815
- <ul>
816
- ${uploadResults().map(result => html`
817
- <li class="${result.success ? 'success' : 'error'}">
818
- ${result.file}:
819
- ${result.success ? 'Uploaded successfully' : `Failed: ${result.error}`}
820
- </li>
821
- `)}
822
- </ul>
823
- </div>
824
- ` : ''}
825
- </div>
826
- `;
827
- };
828
- ```
829
-
830
- ### Retry Logic
831
-
832
- ```javascript
833
- import { $ } from 'sigpro';
834
-
835
- // Enhanced fetch with retry
836
- const fetchWithRetry = async (url, data, loading, maxRetries = 3) => {
837
- let lastError;
838
-
839
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
840
- try {
841
- if (loading) loading(true);
842
-
843
- const result = await $.fetch(url, data);
844
- if (result !== null) {
845
- return result;
846
- }
847
-
848
- // If we get null but no error, wait and retry
849
- if (attempt < maxRetries) {
850
- await new Promise(resolve =>
851
- setTimeout(resolve, Math.pow(2, attempt) * 1000) // Exponential backoff
852
- );
853
- }
854
- } catch (error) {
855
- lastError = error;
856
- console.warn(`Attempt ${attempt} failed:`, error);
857
-
858
- if (attempt < maxRetries) {
859
- await new Promise(resolve =>
860
- setTimeout(resolve, Math.pow(2, attempt) * 1000)
861
- );
862
- }
863
- } finally {
864
- if (attempt === maxRetries && loading) {
865
- loading(false);
866
- }
867
- }
868
- }
869
-
870
- console.error('All retry attempts failed:', lastError);
871
- return null;
872
- };
873
-
874
- // Usage
875
- const loading = $(false);
876
- const data = await fetchWithRetry('/api/unreliable-endpoint', {}, loading, 5);
877
- ```
878
-
879
- ## 🎯 Best Practices
880
-
881
- ### 1. Always Handle Null Responses
882
-
883
- ```javascript
884
- // ❌ Don't assume success
885
- const data = await $.fetch('/api/data');
886
- console.log(data.property); // Might throw if data is null
887
-
888
- // ✅ Check for null
889
- const data = await $.fetch('/api/data');
890
- if (data) {
891
- console.log(data.property);
892
- } else {
893
- showError('Failed to load data');
894
- }
895
- ```
896
-
897
- ### 2. Use with Effects for Reactivity
898
-
899
- ```javascript
900
- // ❌ Manual fetching
901
- button.addEventListener('click', async () => {
902
- const data = await $.fetch('/api/data');
903
- updateUI(data);
904
- });
905
-
906
- // ✅ Reactive fetching
907
- const trigger = $(false);
908
-
909
- $.effect(() => {
910
- if (trigger()) {
911
- $.fetch('/api/data').then(data => {
912
- if (data) updateUI(data);
913
- });
914
- }
915
- });
916
-
917
- trigger(true); // Triggers fetch
918
- ```
919
-
920
- ### 3. Combine with Loading Signals
921
-
922
- ```javascript
923
- // ✅ Always show loading state
924
- const loading = $(false);
925
- const data = $(null);
926
-
927
- async function load() {
928
- const result = await $.fetch('/api/data', {}, loading);
929
- if (result) data(result);
930
- }
931
-
932
- // In template
933
- html`
934
- <div>
935
- ${() => loading() ? '<Spinner />' :
936
- data() ? '<Data />' :
937
- '<Empty />'}
938
- </div>
939
- `;
940
- ```
941
-
942
- ### 4. Cancel In-flight Requests
943
-
944
- ```javascript
945
- // ✅ Use AbortController with effects
946
- let controller;
947
-
948
- $.effect(() => {
949
- if (controller) {
950
- controller.abort();
951
- }
952
-
953
- controller = new AbortController();
954
-
955
- fetch(url, { signal: controller.signal })
956
- .then(res => res.json())
957
- .then(data => {
958
- if (!controller.signal.aborted) {
959
- updateData(data);
960
- }
961
- });
962
-
963
- return () => controller.abort();
964
- });
965
- ```
966
-
967
- ## 📊 Error Handling
968
-
969
- ### Basic Error Handling
970
-
971
- ```javascript
972
- const data = await $.fetch('/api/data');
973
- if (!data) {
974
- // Handle error (show message, retry, etc.)
975
- }
976
- ```
977
-
978
- ### With Error Signal
979
-
980
- ```javascript
981
- const data = $(null);
982
- const error = $(null);
983
- const loading = $(false);
984
-
985
- async function loadData() {
986
- error(null);
987
- const result = await $.fetch('/api/data', {}, loading);
988
-
989
- if (result) {
990
- data(result);
991
- } else {
992
- error('Failed to load data');
993
- }
994
- }
995
- ```
996
- ---
997
-
998
- > **Pro Tip:** Combine `$.fetch` with `$.effect` and loading signals for a complete reactive data fetching solution. The loading signal integration makes it trivial to show loading states in your UI.