jac-client 0.2.6__py3-none-any.whl → 0.2.8__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.
- jac_client/examples/all-in-one/app.jac +573 -0
- jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
- jac_client/examples/all-in-one/components/Header.jac +13 -0
- jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
- jac_client/examples/all-in-one/components/Summary.jac +53 -0
- jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
- jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
- jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
- jac_client/examples/all-in-one/components/navigation.jac +132 -0
- jac_client/examples/all-in-one/constants/categories.jac +37 -0
- jac_client/examples/all-in-one/constants/clients.jac +13 -0
- jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
- jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
- jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
- jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
- jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
- jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
- jac_client/examples/all-in-one/pages/notFound.jac +24 -0
- jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
- jac_client/examples/all-in-one/utils/formatters.jac +52 -0
- jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
- jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
- jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
- jac_client/examples/basic/src/app.jac +3 -3
- jac_client/examples/basic-auth/src/app.jac +31 -37
- jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
- jac_client/examples/basic-full-stack/src/app.jac +24 -30
- jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
- jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
- jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
- jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
- jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
- jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
- jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
- jac_client/examples/ts-support/src/app.jac +4 -4
- jac_client/examples/with-router/src/app.jac +4 -4
- jac_client/plugin/cli.jac +160 -203
- jac_client/plugin/client.jac +8 -15
- jac_client/plugin/client_runtime.cl.jac +18 -14
- jac_client/plugin/impl/client.impl.jac +85 -26
- jac_client/plugin/impl/client_runtime.impl.jac +27 -9
- jac_client/plugin/plugin_config.jac +11 -11
- jac_client/plugin/src/compiler.jac +2 -1
- jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
- jac_client/plugin/src/impl/compiler.impl.jac +55 -18
- jac_client/plugin/src/impl/vite_bundler.impl.jac +215 -102
- jac_client/plugin/src/package_installer.jac +1 -1
- jac_client/plugin/src/vite_bundler.jac +9 -1
- jac_client/tests/conftest.py +10 -8
- jac_client/tests/fixtures/spawn_test/app.jac +15 -18
- jac_client/tests/fixtures/with-ts/app.jac +4 -4
- jac_client/tests/test_cli.py +105 -49
- jac_client/tests/test_it.py +297 -82
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/METADATA +16 -7
- jac_client-0.2.8.dist-info/RECORD +97 -0
- jac_client/examples/all-in-one/src/app.jac +0 -841
- jac_client-0.2.6.dist-info/RECORD +0 -74
- /jac_client/examples/all-in-one/{src/button.jac → button.jac} +0 -0
- /jac_client/examples/all-in-one/{src/components → components}/button.jac +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/WHEEL +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/entry_points.txt +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import from datetime { datetime, timedelta }
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Basic backend walkers
|
|
5
|
+
#
|
|
6
|
+
node Todo {
|
|
7
|
+
has text: str,
|
|
8
|
+
done: bool = False;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
walker create_todo {
|
|
12
|
+
has text: str;
|
|
13
|
+
|
|
14
|
+
can create with `root entry {
|
|
15
|
+
new_todo = here ++> Todo(text=self.text);
|
|
16
|
+
report new_todo ;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
walker ping_server {
|
|
21
|
+
can ping with `root entry {
|
|
22
|
+
report "pong from backend!" ;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
walker get_server_message {
|
|
27
|
+
can info with `root entry {
|
|
28
|
+
report "hello from a basic walker!" ;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Features Test Page - Backend Logic
|
|
35
|
+
# This file is intentionally kept empty to demonstrate the separation of concerns
|
|
36
|
+
# where UI logic resides in .cl.jac files and backend walker logic in app.jac
|
|
37
|
+
|
|
38
|
+
# All walker implementations for this feature are in src/app.jac
|
|
39
|
+
# This demonstrates that you can have separate .jac files for different purposes
|
|
40
|
+
# Features Test - Backend Walkers
|
|
41
|
+
# This file demonstrates walker functionality for testing various JAC features
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Node definition for storing test data
|
|
45
|
+
node TestData {
|
|
46
|
+
has message: str;
|
|
47
|
+
has count: int = 0;
|
|
48
|
+
has created_at: str = "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Walker: Create test data
|
|
52
|
+
walker create_test_data {
|
|
53
|
+
has message: str;
|
|
54
|
+
|
|
55
|
+
can create with `root entry {
|
|
56
|
+
new_data = here ++> TestData(
|
|
57
|
+
message=self.message,
|
|
58
|
+
count=1,
|
|
59
|
+
created_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
60
|
+
);
|
|
61
|
+
report new_data;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Walker: Read all test data
|
|
66
|
+
walker read_test_data {
|
|
67
|
+
can read with `root entry {
|
|
68
|
+
visit [-->(`?TestData)];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
can report_data with exit {
|
|
72
|
+
report here;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Walker: Update test data
|
|
77
|
+
walker update_test_data {
|
|
78
|
+
has new_message: str;
|
|
79
|
+
|
|
80
|
+
can update with TestData entry {
|
|
81
|
+
here.message = self.new_message;
|
|
82
|
+
here.count = here.count + 1;
|
|
83
|
+
report here;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Walker: Delete test data
|
|
88
|
+
walker delete_test_data {
|
|
89
|
+
can delete with TestData entry {
|
|
90
|
+
del here;
|
|
91
|
+
report {"status": "deleted"};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Walker: String manipulation test
|
|
96
|
+
walker test_string_methods {
|
|
97
|
+
has input_text: str;
|
|
98
|
+
|
|
99
|
+
can process with `root entry {
|
|
100
|
+
result = {
|
|
101
|
+
"original": self.input_text,
|
|
102
|
+
"uppercase": self.input_text.upper(),
|
|
103
|
+
"lowercase": self.input_text.lower(),
|
|
104
|
+
"capitalized": self.input_text.capitalize(),
|
|
105
|
+
"reversed": self.input_text[::-1],
|
|
106
|
+
"length": len(self.input_text),
|
|
107
|
+
"words": self.input_text.split(),
|
|
108
|
+
"trimmed": self.input_text.strip(),
|
|
109
|
+
"replaced": self.input_text.replace("test", "demo")
|
|
110
|
+
};
|
|
111
|
+
report result;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Walker: Array/List operations test
|
|
116
|
+
walker test_list_operations {
|
|
117
|
+
has numbers: list;
|
|
118
|
+
|
|
119
|
+
can process with `root entry {
|
|
120
|
+
result = {
|
|
121
|
+
"original": self.numbers,
|
|
122
|
+
"sorted": sorted(self.numbers),
|
|
123
|
+
"reversed": list(reversed(self.numbers)),
|
|
124
|
+
"sum": sum(self.numbers),
|
|
125
|
+
"max": max(self.numbers) if len(self.numbers) > 0 else 0,
|
|
126
|
+
"min": min(self.numbers) if len(self.numbers) > 0 else 0,
|
|
127
|
+
"length": len(self.numbers),
|
|
128
|
+
"doubled": [x * 2 for x in self.numbers]
|
|
129
|
+
};
|
|
130
|
+
report result;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Walker: Complex data processing
|
|
135
|
+
walker process_complex_data {
|
|
136
|
+
has items: list;
|
|
137
|
+
|
|
138
|
+
can process with `root entry {
|
|
139
|
+
processed = [];
|
|
140
|
+
for item in self.items {
|
|
141
|
+
processed.append({
|
|
142
|
+
"id": item.get("id", 0),
|
|
143
|
+
"name": item.get("name", "").upper(),
|
|
144
|
+
"value": item.get("value", 0) * 2,
|
|
145
|
+
"processed_at": datetime.now().strftime("%H:%M:%S")
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
result = {
|
|
150
|
+
"original_count": len(self.items),
|
|
151
|
+
"processed_count": len(processed),
|
|
152
|
+
"items": processed,
|
|
153
|
+
"total_value": sum([p["value"] for p in processed])
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
report result;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# Combined example: auth + routing + CSS styling + asset serving + nested folder imports
|
|
163
|
+
#
|
|
164
|
+
cl import from react { useEffect, useRef }
|
|
165
|
+
cl import from "@jac-client/utils" {
|
|
166
|
+
Router,
|
|
167
|
+
Routes,
|
|
168
|
+
Route,
|
|
169
|
+
Link,
|
|
170
|
+
useNavigate,
|
|
171
|
+
Navigate,
|
|
172
|
+
jacIsLoggedIn
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Pure CSS + asset-in-CSS example
|
|
177
|
+
cl import ".styles.css";
|
|
178
|
+
|
|
179
|
+
# Login Page
|
|
180
|
+
cl import from .pages.loginPage { LoginPage }
|
|
181
|
+
|
|
182
|
+
# Signup Page
|
|
183
|
+
cl import from .pages.signupPage { SignupPage }
|
|
184
|
+
|
|
185
|
+
# Simple 404 page
|
|
186
|
+
cl import from .pages.notFound { NotFound }
|
|
187
|
+
|
|
188
|
+
# Navigation component with active link styling and auth
|
|
189
|
+
cl import from .components.navigation { Navigation }
|
|
190
|
+
|
|
191
|
+
# Page showing nested imports from different folders
|
|
192
|
+
cl import from .pages.nestedDemo { NestedImportsDemo }
|
|
193
|
+
|
|
194
|
+
cl import from .pages.FeaturesTest { FeaturesTest }
|
|
195
|
+
|
|
196
|
+
cl import from .pages.LandingPage { LandingPage }
|
|
197
|
+
|
|
198
|
+
cl import from .pages.BudgetPlanner { BudgetPlanner }
|
|
199
|
+
|
|
200
|
+
# Context provider
|
|
201
|
+
cl import from .context.BudgetContext { BudgetProvider }
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# TypeScript component import
|
|
206
|
+
cl import from ".components/Card.tsx" { Card }
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# Main app wrapped in Router (same API as with-router/ example)
|
|
210
|
+
cl {
|
|
211
|
+
def:pub HomePage -> any {
|
|
212
|
+
# Check if user is logged in, redirect if not
|
|
213
|
+
if not jacIsLoggedIn() {
|
|
214
|
+
return <Navigate to="/login" />;
|
|
215
|
+
}
|
|
216
|
+
has count: int = 0;
|
|
217
|
+
has pingResult: str = "";
|
|
218
|
+
has serverMessage: str = "";
|
|
219
|
+
has lastTodoMessage: str = "";
|
|
220
|
+
|
|
221
|
+
useEffect(
|
|
222
|
+
lambda -> None{ console.log("Home count changed: ", count);} , [count]
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
# Call simple backend walkers
|
|
226
|
+
async def handlePing -> None {
|
|
227
|
+
result = root spawn ping_server();
|
|
228
|
+
if result.reports and result.reports.length > 0 {
|
|
229
|
+
pingResult = result.reports[0][0];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async def loadServerMessage -> None {
|
|
234
|
+
result = root spawn get_server_message();
|
|
235
|
+
if result.reports and result.reports.length > 0 {
|
|
236
|
+
serverMessage = result.reports[0][0];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Create a sample Todo node in the graph with a hardcoded payload
|
|
241
|
+
async def handleCreateSampleTodo -> None {
|
|
242
|
+
result = root spawn create_todo(text="Sample todo from all-in-one app");
|
|
243
|
+
if result.reports and result.reports.length > 0 {
|
|
244
|
+
todo = result.reports[0][0];
|
|
245
|
+
lastTodoMessage = "Created Todo: " + todo.text;
|
|
246
|
+
console.log("Created Todo node:", todo);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
useEffect(lambda -> None{ loadServerMessage();} , []);
|
|
251
|
+
|
|
252
|
+
# Initialize a Web Worker and handle message-based communication
|
|
253
|
+
workerRef = useRef(None);
|
|
254
|
+
has message: str = "";
|
|
255
|
+
|
|
256
|
+
useEffect(lambda -> None {
|
|
257
|
+
workerRef.current = Reflect.construct(Worker, ["/workers/worker.js"]);
|
|
258
|
+
workerRef.current.onmessage = lambda event: any -> None {
|
|
259
|
+
console.log("Message received from worker:", event.data);
|
|
260
|
+
message = event.data;
|
|
261
|
+
};
|
|
262
|
+
return (lambda -> None {
|
|
263
|
+
workerRef.current.terminate();
|
|
264
|
+
});
|
|
265
|
+
}, []);
|
|
266
|
+
handleClick = lambda -> None {
|
|
267
|
+
workerRef.current.postMessage("");
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return <div
|
|
271
|
+
style={{
|
|
272
|
+
"padding": "2rem",
|
|
273
|
+
"fontFamily": "system-ui, -apple-system, sans-serif"
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<h1>
|
|
277
|
+
🍔 Router + Styling + Assets Demo
|
|
278
|
+
</h1>
|
|
279
|
+
<p>
|
|
280
|
+
This home page combines
|
|
281
|
+
{" "}
|
|
282
|
+
<strong>
|
|
283
|
+
React Router,
|
|
284
|
+
</strong>
|
|
285
|
+
{" "}
|
|
286
|
+
<strong>
|
|
287
|
+
pure CSS styling,
|
|
288
|
+
</strong>
|
|
289
|
+
{" "}
|
|
290
|
+
<strong>
|
|
291
|
+
static assets
|
|
292
|
+
</strong>
|
|
293
|
+
{" "}
|
|
294
|
+
and
|
|
295
|
+
{" "}
|
|
296
|
+
<strong>
|
|
297
|
+
nested folder imports
|
|
298
|
+
</strong>
|
|
299
|
+
</p>
|
|
300
|
+
<div className="container">
|
|
301
|
+
<h2
|
|
302
|
+
style={{
|
|
303
|
+
"color": "white",
|
|
304
|
+
"textShadow": "2px 2px 4px rgba(0,0,0,0.6)"
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
CSS Background Image
|
|
308
|
+
</h2>
|
|
309
|
+
<p
|
|
310
|
+
style={{
|
|
311
|
+
"color": "white",
|
|
312
|
+
"maxWidth": "480px",
|
|
313
|
+
"textShadow": "1px 1px 3px rgba(0,0,0,0.7)"
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
This section uses the burger image as a background via CSS, just like the
|
|
317
|
+
{" "}
|
|
318
|
+
<code>
|
|
319
|
+
asset-serving/css-with-image
|
|
320
|
+
</code>
|
|
321
|
+
{" "}
|
|
322
|
+
example.
|
|
323
|
+
</p>
|
|
324
|
+
</div>
|
|
325
|
+
<Card
|
|
326
|
+
title="TypeScript Card Component"
|
|
327
|
+
description="This card is built with TypeScript and demonstrates type-safe component usage in Jac"
|
|
328
|
+
variant="highlighted"
|
|
329
|
+
>
|
|
330
|
+
<p
|
|
331
|
+
style={{"margin": "0.5rem 0", "color": "#374151"}}
|
|
332
|
+
>
|
|
333
|
+
This is a TypeScript component imported and used in Jac code!
|
|
334
|
+
</p>
|
|
335
|
+
</Card>
|
|
336
|
+
<div className="card">
|
|
337
|
+
<h3>
|
|
338
|
+
Direct <img> asset
|
|
339
|
+
</h3>
|
|
340
|
+
<img
|
|
341
|
+
src="/static/assets/burger.png"
|
|
342
|
+
alt="Burger asset served by Jac"
|
|
343
|
+
className="burgerImage"
|
|
344
|
+
/>
|
|
345
|
+
<p
|
|
346
|
+
style={{"marginTop": "0.75rem", "color": "#555"}}
|
|
347
|
+
>
|
|
348
|
+
This image is served from the project
|
|
349
|
+
{" "}
|
|
350
|
+
<code>
|
|
351
|
+
assets/
|
|
352
|
+
</code>
|
|
353
|
+
{" "}
|
|
354
|
+
folder using the
|
|
355
|
+
{" "}
|
|
356
|
+
<code>
|
|
357
|
+
/static/assets/
|
|
358
|
+
</code>
|
|
359
|
+
{" "}
|
|
360
|
+
path.
|
|
361
|
+
</p>
|
|
362
|
+
</div>
|
|
363
|
+
<div
|
|
364
|
+
style={{"marginTop": "2rem"}}
|
|
365
|
+
>
|
|
366
|
+
<h3>
|
|
367
|
+
Counter with pure CSS classes
|
|
368
|
+
</h3>
|
|
369
|
+
<p>
|
|
370
|
+
You've clicked the burger
|
|
371
|
+
{" "}
|
|
372
|
+
<strong>
|
|
373
|
+
{count}
|
|
374
|
+
</strong>
|
|
375
|
+
{" "}
|
|
376
|
+
times.
|
|
377
|
+
</p>
|
|
378
|
+
<button
|
|
379
|
+
onClick={lambda e: any -> None{ count = count + 1;} }
|
|
380
|
+
style={{
|
|
381
|
+
"padding": "0.6rem 1.4rem",
|
|
382
|
+
"fontSize": "1rem",
|
|
383
|
+
"backgroundColor": "#ff6b35",
|
|
384
|
+
"color": "white",
|
|
385
|
+
"border": "none",
|
|
386
|
+
"borderRadius": "6px",
|
|
387
|
+
"cursor": "pointer",
|
|
388
|
+
"boxShadow": "0 2px 4px rgba(0,0,0,0.2)"
|
|
389
|
+
}}
|
|
390
|
+
>
|
|
391
|
+
Click the Burger! 🍔
|
|
392
|
+
</button>
|
|
393
|
+
</div>
|
|
394
|
+
<div
|
|
395
|
+
style={{"marginTop": "2rem"}}
|
|
396
|
+
>
|
|
397
|
+
<h3>
|
|
398
|
+
Backend Walkers
|
|
399
|
+
</h3>
|
|
400
|
+
<p>
|
|
401
|
+
Basic example walkers:
|
|
402
|
+
{" "}
|
|
403
|
+
<code>
|
|
404
|
+
ping_server
|
|
405
|
+
</code>
|
|
406
|
+
{" "}
|
|
407
|
+
and
|
|
408
|
+
{" "}
|
|
409
|
+
<code>
|
|
410
|
+
get_server_message
|
|
411
|
+
</code>
|
|
412
|
+
</p>
|
|
413
|
+
<button
|
|
414
|
+
onClick={lambda e: any -> None{ handlePing();} }
|
|
415
|
+
style={{
|
|
416
|
+
"padding": "0.5rem 1.2rem",
|
|
417
|
+
"marginRight": "0.75rem",
|
|
418
|
+
"backgroundColor": "#3b82f6",
|
|
419
|
+
"color": "white",
|
|
420
|
+
"border": "none",
|
|
421
|
+
"borderRadius": "6px",
|
|
422
|
+
"cursor": "pointer"
|
|
423
|
+
}}
|
|
424
|
+
>
|
|
425
|
+
Ping Backend
|
|
426
|
+
</button>
|
|
427
|
+
<button
|
|
428
|
+
onClick={lambda e: any -> None{ handleCreateSampleTodo();} }
|
|
429
|
+
style={{
|
|
430
|
+
"padding": "0.5rem 1.2rem",
|
|
431
|
+
"backgroundColor": "#10b981",
|
|
432
|
+
"color": "white",
|
|
433
|
+
"border": "none",
|
|
434
|
+
"borderRadius": "6px",
|
|
435
|
+
"cursor": "pointer"
|
|
436
|
+
}}
|
|
437
|
+
>
|
|
438
|
+
Create Sample Todo
|
|
439
|
+
</button>
|
|
440
|
+
{pingResult
|
|
441
|
+
and (
|
|
442
|
+
<span
|
|
443
|
+
style={{"marginLeft": "0.5rem", "color": "#374151"}}
|
|
444
|
+
>
|
|
445
|
+
Result:
|
|
446
|
+
{" "}
|
|
447
|
+
<code>
|
|
448
|
+
{pingResult}
|
|
449
|
+
</code>
|
|
450
|
+
</span>
|
|
451
|
+
)}
|
|
452
|
+
{serverMessage
|
|
453
|
+
and (
|
|
454
|
+
<p
|
|
455
|
+
style={{"marginTop": "0.75rem", "color": "#374151"}}
|
|
456
|
+
>
|
|
457
|
+
Message:
|
|
458
|
+
{" "}
|
|
459
|
+
<code>
|
|
460
|
+
{serverMessage}
|
|
461
|
+
</code>
|
|
462
|
+
</p>
|
|
463
|
+
)}
|
|
464
|
+
{lastTodoMessage
|
|
465
|
+
and (
|
|
466
|
+
<p
|
|
467
|
+
style={{"marginTop": "0.5rem", "color": "#111827"}}
|
|
468
|
+
>
|
|
469
|
+
{lastTodoMessage}
|
|
470
|
+
</p>
|
|
471
|
+
)}
|
|
472
|
+
</div>
|
|
473
|
+
<div
|
|
474
|
+
style={{"marginTop": "2rem"}}
|
|
475
|
+
>
|
|
476
|
+
<h3>
|
|
477
|
+
Web Worker
|
|
478
|
+
</h3>
|
|
479
|
+
<p>
|
|
480
|
+
This demonstrates how to communicate with a
|
|
481
|
+
{" "}
|
|
482
|
+
<strong>Python backend worker</strong>
|
|
483
|
+
{" "}
|
|
484
|
+
using Web Workers for asynchronous processing.
|
|
485
|
+
</p>
|
|
486
|
+
<p
|
|
487
|
+
style={{"fontSize": "0.9rem", "color": "#666", "marginTop": "0.5rem"}}
|
|
488
|
+
>
|
|
489
|
+
File:
|
|
490
|
+
{" "}
|
|
491
|
+
<code>
|
|
492
|
+
worker.py
|
|
493
|
+
</code>
|
|
494
|
+
{" "}
|
|
495
|
+
— Runs in a separate thread to avoid blocking the UI.
|
|
496
|
+
</p>
|
|
497
|
+
<button
|
|
498
|
+
onClick={lambda -> None { handleClick(); }}
|
|
499
|
+
style={{
|
|
500
|
+
"padding": "0.5rem 1.2rem",
|
|
501
|
+
"marginRight": "0.75rem",
|
|
502
|
+
"backgroundColor": "#d73bf6ff",
|
|
503
|
+
"color": "white",
|
|
504
|
+
"border": "none",
|
|
505
|
+
"borderRadius": "6px",
|
|
506
|
+
"cursor": "pointer"
|
|
507
|
+
}}
|
|
508
|
+
>
|
|
509
|
+
Call Python Worker
|
|
510
|
+
</button>
|
|
511
|
+
{message && (
|
|
512
|
+
<p style={{ marginTop: "1rem", fontWeight: "bold" }}>
|
|
513
|
+
{message}
|
|
514
|
+
</p>
|
|
515
|
+
)}
|
|
516
|
+
</div>
|
|
517
|
+
</div>;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
def:pub app -> any {
|
|
521
|
+
return <Router>
|
|
522
|
+
<div
|
|
523
|
+
style={{"fontFamily": "system-ui, -apple-system, sans-serif"}}
|
|
524
|
+
>
|
|
525
|
+
<Navigation />
|
|
526
|
+
<div
|
|
527
|
+
style={{
|
|
528
|
+
"maxWidth": "960px",
|
|
529
|
+
"margin": "0 auto",
|
|
530
|
+
"padding": "0 1rem 3rem 1rem"
|
|
531
|
+
}}
|
|
532
|
+
>
|
|
533
|
+
<Routes>
|
|
534
|
+
<Route
|
|
535
|
+
path="/"
|
|
536
|
+
element={<HomePage />}
|
|
537
|
+
/>
|
|
538
|
+
<Route
|
|
539
|
+
path="/login"
|
|
540
|
+
element={<LoginPage />}
|
|
541
|
+
/>
|
|
542
|
+
<Route
|
|
543
|
+
path="/signup"
|
|
544
|
+
element={<SignupPage />}
|
|
545
|
+
/>
|
|
546
|
+
<Route
|
|
547
|
+
path="/nested"
|
|
548
|
+
element={<NestedImportsDemo />}
|
|
549
|
+
/>
|
|
550
|
+
<Route
|
|
551
|
+
path="/features-test"
|
|
552
|
+
element={<FeaturesTest />}
|
|
553
|
+
/>
|
|
554
|
+
<Route
|
|
555
|
+
path="/landing"
|
|
556
|
+
element={<LandingPage />}
|
|
557
|
+
/>
|
|
558
|
+
<Route
|
|
559
|
+
path="/budget-planner"
|
|
560
|
+
element={<BudgetProvider>
|
|
561
|
+
<BudgetPlanner />
|
|
562
|
+
</BudgetProvider>}
|
|
563
|
+
/>
|
|
564
|
+
<Route
|
|
565
|
+
path="*"
|
|
566
|
+
element={<NotFound />}
|
|
567
|
+
/>
|
|
568
|
+
</Routes>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
</Router>;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Category filter component
|
|
2
|
+
# Demonstrates: iteration with map, conditional classes, events
|
|
3
|
+
|
|
4
|
+
cl import from ..constants.categories { CATEGORIES, CATEGORY_LABELS, CATEGORY_COLORS }
|
|
5
|
+
|
|
6
|
+
cl {
|
|
7
|
+
# Filter buttons for categories
|
|
8
|
+
def:pub CategoryFilter(selectedCategory: str, onSelect: any) -> any {
|
|
9
|
+
# Add "ALL" to the beginning of categories
|
|
10
|
+
allCategories = ["ALL"].concat(CATEGORIES);
|
|
11
|
+
|
|
12
|
+
return <div className="category-filter">
|
|
13
|
+
<h3 className="filter-title">Filter by Category</h3>
|
|
14
|
+
<div className="filter-buttons">
|
|
15
|
+
{allCategories.map(lambda cat: str -> any {
|
|
16
|
+
isActive = selectedCategory == cat;
|
|
17
|
+
color = CATEGORY_COLORS[cat] if cat != "ALL" else "#374151";
|
|
18
|
+
|
|
19
|
+
return <button
|
|
20
|
+
key={cat}
|
|
21
|
+
className={("filter-btn active") if isActive else ("filter-btn")}
|
|
22
|
+
style={{
|
|
23
|
+
"borderColor": color,
|
|
24
|
+
"backgroundColor": (color) if isActive else ("transparent"),
|
|
25
|
+
"color": ("#fff") if isActive else (color)
|
|
26
|
+
}}
|
|
27
|
+
onClick={lambda: onSelect(cat)}
|
|
28
|
+
>
|
|
29
|
+
{(cat) if cat == "ALL" else (CATEGORY_LABELS[cat])}
|
|
30
|
+
</button>;
|
|
31
|
+
})}
|
|
32
|
+
</div>
|
|
33
|
+
</div>;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Header component
|
|
2
|
+
# Demonstrates: simple component, CSS classes
|
|
3
|
+
|
|
4
|
+
cl {
|
|
5
|
+
def:pub Header() -> any {
|
|
6
|
+
return <header className="header">
|
|
7
|
+
<div className="header-content">
|
|
8
|
+
<h1 className="header-title">Budget Planner</h1>
|
|
9
|
+
<p className="header-subtitle">Track your income and expenses</p>
|
|
10
|
+
</div>
|
|
11
|
+
</header>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Profit Overview component - monthly profit snapshot
|
|
2
|
+
# Demonstrates: context usage, calculated displays, formatting
|
|
3
|
+
|
|
4
|
+
cl import from ..context.BudgetContext { useBudgetContext }
|
|
5
|
+
cl import from ..utils.formatters { formatCurrency }
|
|
6
|
+
|
|
7
|
+
cl {
|
|
8
|
+
def:pub ProfitOverview() -> any {
|
|
9
|
+
budget = useBudgetContext();
|
|
10
|
+
|
|
11
|
+
businessIncome = budget["businessIncome"];
|
|
12
|
+
businessExpenses = budget["businessExpenses"];
|
|
13
|
+
taxReserve = budget["taxReserve"];
|
|
14
|
+
netProfit = budget["netProfit"];
|
|
15
|
+
|
|
16
|
+
return <div className="profit-overview">
|
|
17
|
+
<h3 className="profit-title">Monthly Profit Snapshot</h3>
|
|
18
|
+
|
|
19
|
+
<div className="profit-breakdown">
|
|
20
|
+
<div className="profit-row income">
|
|
21
|
+
<span className="profit-label">Business Income</span>
|
|
22
|
+
<span className="profit-value positive">
|
|
23
|
+
+{formatCurrency(businessIncome)}
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="profit-row expense">
|
|
28
|
+
<span className="profit-label">Business Expenses</span>
|
|
29
|
+
<span className="profit-value negative">
|
|
30
|
+
-{formatCurrency(businessExpenses)}
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div className="profit-row tax">
|
|
35
|
+
<span className="profit-label">Tax Reserve (20%)</span>
|
|
36
|
+
<span className="profit-value negative">
|
|
37
|
+
-{formatCurrency(taxReserve)}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="profit-row total">
|
|
42
|
+
<span className="profit-label">Net Profit</span>
|
|
43
|
+
<span className={("profit-value bold positive") if netProfit >= 0 else ("profit-value bold negative")}>
|
|
44
|
+
{("+") if netProfit > 0 else ("")}{formatCurrency(netProfit)}
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>;
|
|
49
|
+
}
|
|
50
|
+
}
|