jac-client 0.2.6__py3-none-any.whl → 0.2.11__py3-none-any.whl

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 (119) hide show
  1. jac_client/examples/all-in-one/{src/button.jac → button.jac} +4 -3
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +47 -0
  3. jac_client/examples/all-in-one/components/Header.jac +17 -0
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +64 -0
  5. jac_client/examples/all-in-one/components/Summary.jac +76 -0
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +188 -0
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +62 -0
  8. jac_client/examples/all-in-one/components/TransactionList.jac +44 -0
  9. jac_client/examples/all-in-one/components/button.jac +8 -0
  10. jac_client/examples/all-in-one/components/navigation.jac +126 -0
  11. jac_client/examples/all-in-one/constants/categories.jac +36 -0
  12. jac_client/examples/all-in-one/constants/clients.jac +12 -0
  13. jac_client/examples/all-in-one/context/BudgetContext.jac +31 -0
  14. jac_client/examples/all-in-one/hooks/useBudget.jac +122 -0
  15. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +37 -0
  16. jac_client/examples/all-in-one/main.jac +542 -0
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +140 -0
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +157 -0
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +124 -0
  20. jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +65 -0
  21. jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +675 -0
  22. jac_client/examples/all-in-one/pages/loginPage.jac +127 -0
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +54 -0
  24. jac_client/examples/all-in-one/pages/notFound.jac +18 -0
  25. jac_client/examples/all-in-one/pages/signupPage.jac +127 -0
  26. jac_client/examples/all-in-one/utils/formatters.jac +49 -0
  27. jac_client/examples/asset-serving/css-with-image/main.jac +92 -0
  28. jac_client/examples/asset-serving/image-asset/main.jac +56 -0
  29. jac_client/examples/asset-serving/import-alias/main.jac +109 -0
  30. jac_client/examples/basic/main.jac +23 -0
  31. jac_client/examples/basic-auth/main.jac +363 -0
  32. jac_client/examples/basic-auth-with-router/main.jac +451 -0
  33. jac_client/examples/basic-full-stack/main.jac +362 -0
  34. jac_client/examples/css-styling/js-styling/main.jac +63 -0
  35. jac_client/examples/css-styling/material-ui/main.jac +122 -0
  36. jac_client/examples/css-styling/pure-css/main.jac +55 -0
  37. jac_client/examples/css-styling/sass-example/main.jac +55 -0
  38. jac_client/examples/css-styling/styled-components/main.jac +62 -0
  39. jac_client/examples/css-styling/tailwind-example/main.jac +74 -0
  40. jac_client/examples/full-stack-with-auth/main.jac +696 -0
  41. jac_client/examples/little-x/main.jac +681 -0
  42. jac_client/examples/little-x/src/submit-button.jac +15 -14
  43. jac_client/examples/nested-folders/nested-advance/main.jac +26 -0
  44. jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
  45. jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
  46. jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
  47. jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
  48. jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +7 -5
  49. jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
  50. jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
  51. jac_client/examples/ts-support/main.jac +35 -0
  52. jac_client/examples/with-router/main.jac +286 -0
  53. jac_client/plugin/cli.jac +507 -470
  54. jac_client/plugin/client.jac +30 -12
  55. jac_client/plugin/client_runtime.cl.jac +25 -15
  56. jac_client/plugin/impl/client.impl.jac +126 -26
  57. jac_client/plugin/impl/client_runtime.impl.jac +182 -10
  58. jac_client/plugin/plugin_config.jac +216 -34
  59. jac_client/plugin/src/__init__.jac +0 -2
  60. jac_client/plugin/src/compiler.jac +2 -2
  61. jac_client/plugin/src/config_loader.jac +1 -0
  62. jac_client/plugin/src/desktop_config.jac +31 -0
  63. jac_client/plugin/src/impl/compiler.impl.jac +99 -30
  64. jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
  65. jac_client/plugin/src/impl/desktop_config.impl.jac +191 -0
  66. jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
  67. jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
  68. jac_client/plugin/src/impl/vite_bundler.impl.jac +384 -144
  69. jac_client/plugin/src/package_installer.jac +1 -1
  70. jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
  71. jac_client/plugin/src/targets/desktop_target.jac +37 -0
  72. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2347 -0
  73. jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
  74. jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
  75. jac_client/plugin/src/targets/register.jac +21 -0
  76. jac_client/plugin/src/targets/registry.jac +87 -0
  77. jac_client/plugin/src/targets/web_target.jac +35 -0
  78. jac_client/plugin/src/vite_bundler.jac +15 -1
  79. jac_client/plugin/utils/__init__.jac +3 -0
  80. jac_client/plugin/utils/bun_installer.jac +16 -0
  81. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  82. jac_client/templates/client.jacpack +72 -0
  83. jac_client/templates/fullstack.jacpack +61 -0
  84. jac_client/tests/conftest.py +110 -52
  85. jac_client/tests/fixtures/spawn_test/app.jac +64 -70
  86. jac_client/tests/fixtures/with-ts/app.jac +28 -28
  87. jac_client/tests/test_cli.py +280 -113
  88. jac_client/tests/test_e2e.py +232 -0
  89. jac_client/tests/test_helpers.py +58 -0
  90. jac_client/tests/test_it.py +325 -154
  91. jac_client/tests/test_it_desktop.py +891 -0
  92. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/METADATA +20 -11
  93. jac_client-0.2.11.dist-info/RECORD +113 -0
  94. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/WHEEL +1 -1
  95. jac_client/examples/all-in-one/src/app.jac +0 -841
  96. jac_client/examples/all-in-one/src/components/button.jac +0 -7
  97. jac_client/examples/asset-serving/css-with-image/src/app.jac +0 -88
  98. jac_client/examples/asset-serving/image-asset/src/app.jac +0 -55
  99. jac_client/examples/asset-serving/import-alias/src/app.jac +0 -111
  100. jac_client/examples/basic/src/app.jac +0 -21
  101. jac_client/examples/basic-auth/src/app.jac +0 -377
  102. jac_client/examples/basic-auth-with-router/src/app.jac +0 -464
  103. jac_client/examples/basic-full-stack/src/app.jac +0 -365
  104. jac_client/examples/css-styling/js-styling/src/app.jac +0 -84
  105. jac_client/examples/css-styling/material-ui/src/app.jac +0 -122
  106. jac_client/examples/css-styling/pure-css/src/app.jac +0 -64
  107. jac_client/examples/css-styling/sass-example/src/app.jac +0 -64
  108. jac_client/examples/css-styling/styled-components/src/app.jac +0 -71
  109. jac_client/examples/css-styling/tailwind-example/src/app.jac +0 -63
  110. jac_client/examples/full-stack-with-auth/src/app.jac +0 -722
  111. jac_client/examples/little-x/src/app.jac +0 -719
  112. jac_client/examples/nested-folders/nested-advance/src/app.jac +0 -35
  113. jac_client/examples/ts-support/src/app.jac +0 -35
  114. jac_client/examples/with-router/src/app.jac +0 -323
  115. jac_client/plugin/src/babel_processor.jac +0 -18
  116. jac_client/plugin/src/impl/babel_processor.impl.jac +0 -84
  117. jac_client-0.2.6.dist-info/RECORD +0 -74
  118. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/entry_points.txt +0 -0
  119. {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/top_level.txt +0 -0
@@ -1,722 +0,0 @@
1
- # Full Stack Todo App with Auth and React Router
2
- cl import from react {
3
- useState,
4
- useEffect
5
- }
6
- cl import from "@jac-client/utils" {
7
- Router,
8
- Routes,
9
- Route,
10
- Link,
11
- Navigate,
12
- useNavigate,
13
- useLocation,
14
- jacSignup,
15
- jacLogin,
16
- jacLogout,
17
- jacIsLoggedIn
18
- }
19
-
20
- # Backend - Todo Node
21
- node Todo {
22
- has text: str,
23
- done: bool = False;
24
- }
25
-
26
- # Backend - Walkers
27
- walker create_todo {
28
- has text: str;
29
-
30
- can create with `root entry {
31
- new_todo = here ++> Todo(text=self.text);
32
- report new_todo ;
33
- }
34
- }
35
-
36
- walker read_todos {
37
- can read with `root entry {
38
- visit [-->(`?Todo)];
39
- }
40
-
41
- can report_todos with Todo entry {
42
- report here ;
43
- }
44
- }
45
-
46
- walker toggle_todo {
47
- can toggle with Todo entry {
48
- here.done = not here.done;
49
- report here ;
50
- }
51
- }
52
-
53
- # Frontend Components
54
- cl {
55
- # Navigation
56
- def Navigation -> any {
57
- isLoggedIn = jacIsLoggedIn();
58
- navigate = useNavigate();
59
-
60
- def handleLogout(e: any) -> None {
61
- e.preventDefault();
62
- jacLogout();
63
- navigate("/login");
64
- }
65
-
66
- if isLoggedIn {
67
- return <nav
68
- style={{
69
- "padding": "12px 24px",
70
- "background": "#3b82f6",
71
- "color": "#ffffff",
72
- "display": "flex",
73
- "justifyContent": "space-between"
74
- }}
75
- >
76
- <div
77
- style={{"fontWeight": "600"}}
78
- >
79
- Todo App
80
- </div>
81
- <div
82
- style={{"display": "flex", "gap": "16px"}}
83
- >
84
- <Link
85
- to="/todos"
86
- style={{"color": "#ffffff", "textDecoration": "none"}}
87
- >
88
- Todos
89
- </Link>
90
- <button
91
- onClick={handleLogout}
92
- style={{
93
- "background": "none",
94
- "color": "#ffffff",
95
- "border": "1px solid #ffffff",
96
- "padding": "2px 10px",
97
- "borderRadius": "4px",
98
- "cursor": "pointer"
99
- }}
100
- >
101
- Logout
102
- </button>
103
- </div>
104
- </nav>;
105
- }
106
-
107
- return <nav
108
- style={{
109
- "padding": "12px 24px",
110
- "background": "#3b82f6",
111
- "color": "#ffffff",
112
- "display": "flex",
113
- "justifyContent": "space-between"
114
- }}
115
- >
116
- <div
117
- style={{"fontWeight": "600"}}
118
- >
119
- Todo App
120
- </div>
121
- <div
122
- style={{"display": "flex", "gap": "16px"}}
123
- >
124
- <Link
125
- to="/login"
126
- style={{"color": "#ffffff", "textDecoration": "none"}}
127
- >
128
- Login
129
- </Link>
130
- <Link
131
- to="/signup"
132
- style={{"color": "#ffffff", "textDecoration": "none"}}
133
- >
134
- Sign Up
135
- </Link>
136
- </div>
137
- </nav>;
138
- }
139
-
140
- # Login Page
141
- def LoginPage -> any {
142
- [username, setUsername] = useState("");
143
- [password, setPassword] = useState("");
144
- [error, setError] = useState("");
145
- navigate = useNavigate();
146
-
147
- async def handleLogin(e: any) -> None {
148
- e.preventDefault();
149
- setError("");
150
- if not username or not password {
151
- setError("Please fill in all fields");
152
- return;
153
- }
154
- success = await jacLogin(username, password);
155
- if success {
156
- navigate("/todos");
157
- } else {
158
- setError("Invalid credentials");
159
- }
160
- }
161
-
162
- def handleUsernameChange(e: any) -> None {
163
- setUsername(e.target.value);
164
- }
165
-
166
- def handlePasswordChange(e: any) -> None {
167
- setPassword(e.target.value);
168
- }
169
-
170
- errorDisplay = None;
171
- if error {
172
- errorDisplay = <div
173
- style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "10px"}}
174
- >
175
- {error}
176
- </div>;
177
- }
178
-
179
- return <div
180
- style={{
181
- "minHeight": "calc(100vh - 48px)",
182
- "display": "flex",
183
- "alignItems": "center",
184
- "justifyContent": "center",
185
- "background": "#f5f5f5"
186
- }}
187
- >
188
- <div
189
- style={{
190
- "background": "#ffffff",
191
- "padding": "30px",
192
- "borderRadius": "8px",
193
- "width": "280px",
194
- "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
195
- }}
196
- >
197
- <h2
198
- style={{"marginBottom": "20px"}}
199
- >
200
- Login
201
- </h2>
202
- <form
203
- onSubmit={handleLogin}
204
- >
205
- <input
206
- type="text"
207
- value={username}
208
- onChange={handleUsernameChange}
209
- placeholder="Username"
210
- style={{
211
- "width": "100%",
212
- "padding": "8px",
213
- "marginBottom": "10px",
214
- "border": "1px solid #ddd",
215
- "borderRadius": "4px",
216
- "boxSizing": "border-box"
217
- }}
218
- />
219
- <input
220
- type="password"
221
- value={password}
222
- onChange={handlePasswordChange}
223
- placeholder="Password"
224
- style={{
225
- "width": "100%",
226
- "padding": "8px",
227
- "marginBottom": "10px",
228
- "border": "1px solid #ddd",
229
- "borderRadius": "4px",
230
- "boxSizing": "border-box"
231
- }}
232
- />
233
- {errorDisplay}
234
- <button
235
- type="submit"
236
- style={{
237
- "width": "100%",
238
- "padding": "8px",
239
- "background": "#3b82f6",
240
- "color": "#ffffff",
241
- "border": "none",
242
- "borderRadius": "4px",
243
- "cursor": "pointer",
244
- "fontWeight": "600"
245
- }}
246
- >
247
- Login
248
- </button>
249
- </form>
250
- <p
251
- style={{
252
- "textAlign": "center",
253
- "marginTop": "12px",
254
- "fontSize": "14px"
255
- }}
256
- >
257
- Need an account?
258
- <Link to="/signup">
259
- Sign up
260
- </Link>
261
- </p>
262
- </div>
263
- </div>;
264
- }
265
-
266
- # Signup Page
267
- def SignupPage -> any {
268
- [username, setUsername] = useState("");
269
- [password, setPassword] = useState("");
270
- [error, setError] = useState("");
271
- navigate = useNavigate();
272
-
273
- async def handleSignup(e: any) -> None {
274
- e.preventDefault();
275
- setError("");
276
- if not username or not password {
277
- setError("Please fill in all fields");
278
- return;
279
- }
280
- result = await jacSignup(username, password);
281
- if result["success"] {
282
- navigate("/todos");
283
- } else {
284
- setError(result["error"] if result["error"] else "Signup failed");
285
- }
286
- }
287
-
288
- def handleUsernameChange(e: any) -> None {
289
- setUsername(e.target.value);
290
- }
291
-
292
- def handlePasswordChange(e: any) -> None {
293
- setPassword(e.target.value);
294
- }
295
-
296
- errorDisplay = None;
297
- if error {
298
- errorDisplay = <div
299
- style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "10px"}}
300
- >
301
- {error}
302
- </div>;
303
- }
304
-
305
- return <div
306
- style={{
307
- "minHeight": "calc(100vh - 48px)",
308
- "display": "flex",
309
- "alignItems": "center",
310
- "justifyContent": "center",
311
- "background": "#f5f5f5"
312
- }}
313
- >
314
- <div
315
- style={{
316
- "background": "#ffffff",
317
- "padding": "30px",
318
- "borderRadius": "8px",
319
- "width": "280px",
320
- "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
321
- }}
322
- >
323
- <h2
324
- style={{"marginBottom": "20px"}}
325
- >
326
- Sign Up
327
- </h2>
328
- <form
329
- onSubmit={handleSignup}
330
- >
331
- <input
332
- type="text"
333
- value={username}
334
- onChange={handleUsernameChange}
335
- placeholder="Username"
336
- style={{
337
- "width": "100%",
338
- "padding": "8px",
339
- "marginBottom": "10px",
340
- "border": "1px solid #ddd",
341
- "borderRadius": "4px",
342
- "boxSizing": "border-box"
343
- }}
344
- />
345
- <input
346
- type="password"
347
- value={password}
348
- onChange={handlePasswordChange}
349
- placeholder="Password"
350
- style={{
351
- "width": "100%",
352
- "padding": "8px",
353
- "marginBottom": "10px",
354
- "border": "1px solid #ddd",
355
- "borderRadius": "4px",
356
- "boxSizing": "border-box"
357
- }}
358
- />
359
- {errorDisplay}
360
- <button
361
- type="submit"
362
- style={{
363
- "width": "100%",
364
- "padding": "8px",
365
- "background": "#3b82f6",
366
- "color": "#ffffff",
367
- "border": "none",
368
- "borderRadius": "4px",
369
- "cursor": "pointer",
370
- "fontWeight": "600"
371
- }}
372
- >
373
- Sign Up
374
- </button>
375
- </form>
376
- <p
377
- style={{
378
- "textAlign": "center",
379
- "marginTop": "12px",
380
- "fontSize": "14px"
381
- }}
382
- >
383
- Have an account?
384
- <Link to="/login">
385
- Login
386
- </Link>
387
- </p>
388
- </div>
389
- </div>;
390
- }
391
-
392
- # TodoInput Component
393
- def TodoInput(props: any) -> any {
394
- input = props.input;
395
- setInput = props.setInput;
396
- addTodo = props.addTodo;
397
-
398
- return <div
399
- style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}
400
- >
401
- <input
402
- type="text"
403
- value={input}
404
- onChange={lambda e: any -> None{ setInput(e.target.value);} }
405
- onKeyPress={lambda e: any -> None{ if e.key == "Enter" {
406
- addTodo();
407
- }} }
408
- placeholder="What needs to be done?"
409
- style={{
410
- "flex": "1",
411
- "padding": "8px",
412
- "border": "1px solid #ddd",
413
- "borderRadius": "4px"
414
- }}
415
- />
416
- <button
417
- onClick={addTodo}
418
- style={{
419
- "padding": "8px 16px",
420
- "background": "#3b82f6",
421
- "color": "#ffffff",
422
- "border": "none",
423
- "borderRadius": "4px",
424
- "cursor": "pointer",
425
- "fontWeight": "600"
426
- }}
427
- >
428
- Add
429
- </button>
430
- </div>;
431
- }
432
-
433
- # TodoFilters Component
434
- def TodoFilters(props: any) -> any {
435
- filter = props.filter;
436
- setFilter = props.setFilter;
437
-
438
- return <div
439
- style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}
440
- >
441
- <button
442
- onClick={lambda -> None{ setFilter("all");} }
443
- style={{
444
- "padding": "6px 12px",
445
- "background": ("#3b82f6" if filter == "all" else "#e5e7eb"),
446
- "color": ("#ffffff" if filter == "all" else "#000000"),
447
- "border": "none",
448
- "borderRadius": "4px",
449
- "cursor": "pointer",
450
- "fontSize": "14px"
451
- }}
452
- >
453
- All
454
- </button>
455
- <button
456
- onClick={lambda -> None{ setFilter("active");} }
457
- style={{
458
- "padding": "6px 12px",
459
- "background": ("#3b82f6" if filter == "active" else "#e5e7eb"),
460
- "color": ("#ffffff" if filter == "active" else "#000000"),
461
- "border": "none",
462
- "borderRadius": "4px",
463
- "cursor": "pointer",
464
- "fontSize": "14px"
465
- }}
466
- >
467
- Active
468
- </button>
469
- <button
470
- onClick={lambda -> None{ setFilter("completed");} }
471
- style={{
472
- "padding": "6px 12px",
473
- "background": ("#3b82f6" if filter == "completed" else "#e5e7eb"),
474
- "color": ("#ffffff" if filter == "completed" else "#000000"),
475
- "border": "none",
476
- "borderRadius": "4px",
477
- "cursor": "pointer",
478
- "fontSize": "14px"
479
- }}
480
- >
481
- Completed
482
- </button>
483
- </div>;
484
- }
485
-
486
- # TodoItem Component
487
- def TodoItem(props: any) -> any {
488
- todo = props.todo;
489
- toggleTodo = props.toggleTodo;
490
- deleteTodo = props.deleteTodo;
491
-
492
- return <div
493
- key={todo._jac_id}
494
- style={{
495
- "display": "flex",
496
- "alignItems": "center",
497
- "gap": "10px",
498
- "padding": "10px",
499
- "borderBottom": "1px solid #e5e7eb"
500
- }}
501
- >
502
- <input
503
- type="checkbox"
504
- checked={todo.done}
505
- onChange={lambda -> None{ toggleTodo(todo._jac_id);} }
506
- style={{"cursor": "pointer"}}
507
- />
508
- <span
509
- style={{
510
- "flex": "1",
511
- "textDecoration": ("line-through" if todo.done else "none"),
512
- "color": ("#999" if todo.done else "#000")
513
- }}
514
- >
515
- {todo.text}
516
- </span>
517
- <button
518
- onClick={lambda -> None{ deleteTodo(todo._jac_id);} }
519
- style={{
520
- "padding": "4px 8px",
521
- "background": "#ef4444",
522
- "color": "#ffffff",
523
- "border": "none",
524
- "borderRadius": "4px",
525
- "cursor": "pointer",
526
- "fontSize": "12px"
527
- }}
528
- >
529
- Delete
530
- </button>
531
- </div>;
532
- }
533
-
534
- # TodoList Component
535
- def TodoList(props: any) -> any {
536
- filteredTodos = props.filteredTodos;
537
- toggleTodo = props.toggleTodo;
538
- deleteTodo = props.deleteTodo;
539
-
540
- if filteredTodos.length == 0 {
541
- return <div
542
- style={{"padding": "20px", "textAlign": "center", "color": "#999"}}
543
- >
544
- No todos yet. Add one above!
545
- </div>;
546
- }
547
-
548
- return <div>
549
- {filteredTodos.map(
550
- lambda todo: any -> any{ return <TodoItem
551
- key={todo._jac_id}
552
- todo={todo}
553
- toggleTodo={toggleTodo}
554
- deleteTodo={deleteTodo}
555
- />; }
556
- )}
557
- </div>;
558
- }
559
-
560
- # Todos Page (Protected)
561
- def TodosPage -> any {
562
- # Check if user is logged in, redirect if not
563
- if not jacIsLoggedIn() {
564
- return <Navigate to="/login" />;
565
- }
566
-
567
- [todos, setTodos] = useState([]);
568
- [input, setInput] = useState("");
569
- [filter, setFilter] = useState("all");
570
-
571
- # Load todos on mount
572
- useEffect(
573
- lambda -> None{ async def loadTodos -> None {
574
- result = root spawn read_todos();
575
- setTodos(result.reports if result.reports else []);
576
- } loadTodos();} ,
577
- []
578
- );
579
-
580
- # Add todo
581
- async def addTodo -> None {
582
- if not input.trim() {
583
- return;
584
- }
585
- result = root spawn create_todo(text=input.trim());
586
- setTodos(todos.concat([result.reports[0][0]]));
587
- setInput("");
588
- }
589
-
590
- # Toggle todo
591
- async def toggleTodo(id: any) -> None {
592
- id spawn toggle_todo();
593
- setTodos(
594
- todos.map(
595
- lambda todo: any -> any{ if todo._jac_id == id {
596
- return {
597
- "_jac_id": todo._jac_id,
598
- "text": todo.text,
599
- "done": not todo.done
600
- };
601
- }return todo; }
602
- )
603
- );
604
- }
605
-
606
- # Delete todo
607
- async def deleteTodo(id: any) -> None {
608
- #id spawn delete_todo();
609
- setTodos(
610
- todos.filter(lambda todo: any -> bool{ return todo._jac_id != id; } )
611
- );
612
- }
613
-
614
- # Filter todos
615
- def getFilteredTodos -> list {
616
- if filter == "active" {
617
- return todos.filter(
618
- lambda todo: any -> bool{ return not todo.done; }
619
- );
620
- } elif filter == "completed" {
621
- return todos.filter(lambda todo: any -> bool{ return todo.done; } );
622
- }
623
- return todos;
624
- }
625
-
626
- filteredTodos = getFilteredTodos();
627
- activeCount = todos.filter(
628
- lambda todo: any -> bool{ return not todo.done; }
629
- ).length;
630
-
631
- return <div
632
- style={{
633
- "maxWidth": "600px",
634
- "margin": "20px auto",
635
- "padding": "20px",
636
- "background": "#ffffff",
637
- "borderRadius": "8px",
638
- "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
639
- }}
640
- >
641
- <h1
642
- style={{"marginBottom": "20px"}}
643
- >
644
- My Todos
645
- </h1>
646
- <TodoInput
647
- input={input}
648
- setInput={setInput}
649
- addTodo={addTodo}
650
- />
651
- <TodoFilters
652
- filter={filter}
653
- setFilter={setFilter}
654
- />
655
- <TodoList
656
- filteredTodos={filteredTodos}
657
- toggleTodo={toggleTodo}
658
- deleteTodo={deleteTodo}
659
- />
660
- {(
661
- <div
662
- style={{
663
- "marginTop": "16px",
664
- "padding": "10px",
665
- "background": "#f9fafb",
666
- "borderRadius": "4px",
667
- "fontSize": "14px",
668
- "color": "#666"
669
- }}
670
- >
671
- {activeCount} {"item" if activeCount == 1 else "items"} left
672
- </div>
673
- )
674
- if todos.length > 0
675
- else None}
676
- </div>;
677
- }
678
-
679
- # Home/Landing Page - auto-redirect
680
- def HomePage -> any {
681
- if jacIsLoggedIn() {
682
- return <Navigate to="/todos" />;
683
- }
684
- return <Navigate to="/login" />;
685
- }
686
-
687
- # Main App with React Router
688
- def:pub app -> any {
689
- return <Router>
690
- <div
691
- style={{"fontFamily": "system-ui, sans-serif"}}
692
- >
693
- <Navigation />
694
- <Routes>
695
- <Route
696
- path="/"
697
- element={<HomePage />}
698
- />
699
- <Route
700
- path="/login"
701
- element={<LoginPage />}
702
- />
703
- <Route
704
- path="/signup"
705
- element={<SignupPage />}
706
- />
707
- <Route
708
- path="/todos"
709
- element={<TodosPage />}
710
- />
711
- </Routes>
712
- </div>
713
- </Router>;
714
- }
715
- }
716
- # Add todo input
717
-
718
- # Filter buttons
719
-
720
- # Todo list
721
-
722
- # Stats