jac-client 0.1.0__py3-none-any.whl → 0.2.1__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/docs/README.md +232 -172
- jac_client/docs/advanced-state.md +1012 -452
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/intro.md +90 -0
- jac_client/docs/guide-example/intro.md +117 -0
- jac_client/docs/guide-example/step-01-setup.md +260 -0
- jac_client/docs/guide-example/step-02-components.md +416 -0
- jac_client/docs/guide-example/step-03-styling.md +478 -0
- jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
- jac_client/docs/guide-example/step-05-local-state.md +530 -0
- jac_client/docs/guide-example/step-06-events.md +750 -0
- jac_client/docs/guide-example/step-07-effects.md +469 -0
- jac_client/docs/guide-example/step-08-walkers.md +534 -0
- jac_client/docs/guide-example/step-09-authentication.md +586 -0
- jac_client/docs/guide-example/step-10-routing.md +540 -0
- jac_client/docs/guide-example/step-11-final.md +964 -0
- jac_client/docs/imports.md +538 -46
- jac_client/docs/lifecycle-hooks.md +517 -297
- jac_client/docs/routing.md +487 -357
- jac_client/docs/styling/intro.md +250 -0
- jac_client/docs/styling/js-styling.md +373 -0
- jac_client/docs/styling/material-ui.md +346 -0
- jac_client/docs/styling/pure-css.md +305 -0
- jac_client/docs/styling/sass.md +409 -0
- jac_client/docs/styling/styled-components.md +401 -0
- jac_client/docs/styling/tailwind.md +303 -0
- jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
- jac_client/examples/asset-serving/css-with-image/README.md +91 -0
- jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +28 -0
- jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
- jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
- jac_client/examples/asset-serving/image-asset/README.md +119 -0
- jac_client/examples/asset-serving/image-asset/app.jac +43 -0
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +28 -0
- jac_client/examples/asset-serving/image-asset/styles.css +27 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
- jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
- jac_client/examples/asset-serving/import-alias/README.md +83 -0
- jac_client/examples/asset-serving/import-alias/app.jac +57 -0
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +28 -0
- jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
- jac_client/examples/basic/.babelrc +9 -0
- jac_client/examples/basic/README.md +16 -0
- jac_client/examples/basic/app.jac +16 -0
- jac_client/examples/basic/package.json +27 -0
- jac_client/examples/basic/vite.config.js +28 -0
- jac_client/examples/basic-auth/.babelrc +9 -0
- jac_client/examples/basic-auth/README.md +16 -0
- jac_client/examples/basic-auth/app.jac +308 -0
- jac_client/examples/basic-auth/package.json +27 -0
- jac_client/examples/basic-auth/vite.config.js +28 -0
- jac_client/examples/basic-auth-with-router/.babelrc +9 -0
- jac_client/examples/basic-auth-with-router/README.md +60 -0
- jac_client/examples/basic-auth-with-router/app.jac +464 -0
- jac_client/examples/basic-auth-with-router/package.json +28 -0
- jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
- jac_client/examples/basic-full-stack/.babelrc +9 -0
- jac_client/examples/basic-full-stack/README.md +18 -0
- jac_client/examples/basic-full-stack/app.jac +320 -0
- jac_client/examples/basic-full-stack/package.json +28 -0
- jac_client/examples/basic-full-stack/vite.config.js +28 -0
- jac_client/examples/css-styling/js-styling/.babelrc +9 -0
- jac_client/examples/css-styling/js-styling/README.md +183 -0
- jac_client/examples/css-styling/js-styling/app.jac +63 -0
- jac_client/examples/css-styling/js-styling/package.json +28 -0
- jac_client/examples/css-styling/js-styling/styles.js +100 -0
- jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
- jac_client/examples/css-styling/material-ui/.babelrc +9 -0
- jac_client/examples/css-styling/material-ui/README.md +16 -0
- jac_client/examples/css-styling/material-ui/app.jac +82 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
- jac_client/examples/css-styling/pure-css/.babelrc +9 -0
- jac_client/examples/css-styling/pure-css/README.md +16 -0
- jac_client/examples/css-styling/pure-css/app.jac +63 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +112 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
- jac_client/examples/css-styling/sass-example/.babelrc +9 -0
- jac_client/examples/css-styling/sass-example/README.md +16 -0
- jac_client/examples/css-styling/sass-example/app.jac +63 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +158 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
- jac_client/examples/css-styling/styled-components/.babelrc +9 -0
- jac_client/examples/css-styling/styled-components/README.md +16 -0
- jac_client/examples/css-styling/styled-components/app.jac +66 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +91 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
- jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
- jac_client/examples/css-styling/tailwind-example/README.md +16 -0
- jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
- jac_client/examples/css-styling/tailwind-example/global.css +1 -0
- jac_client/examples/css-styling/tailwind-example/package.json +30 -0
- jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
- jac_client/examples/full-stack-with-auth/.babelrc +9 -0
- jac_client/examples/full-stack-with-auth/README.md +16 -0
- jac_client/examples/full-stack-with-auth/app.jac +735 -0
- jac_client/examples/full-stack-with-auth/package.json +28 -0
- jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
- jac_client/examples/with-router/.babelrc +9 -0
- jac_client/examples/with-router/README.md +17 -0
- jac_client/examples/with-router/app.jac +323 -0
- jac_client/examples/with-router/package.json +28 -0
- jac_client/examples/with-router/vite.config.js +28 -0
- jac_client/plugin/cli.py +95 -179
- jac_client/plugin/client.py +111 -2
- jac_client/plugin/client_runtime.jac +183 -890
- jac_client/plugin/vite_client_bundle.py +185 -205
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
- jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
- jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
- jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
- jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
- jac_client/tests/test_asset_examples.py +339 -0
- jac_client/tests/test_cl.py +345 -151
- jac_client/tests/test_create_jac_app.py +41 -45
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
- jac_client-0.2.1.dist-info/RECORD +140 -0
- jac_client/examples/little-x/package-lock.json +0 -2840
- jac_client/examples/todo-app/README.md +0 -82
- jac_client/examples/todo-app/app.jac +0 -683
- jac_client/examples/todo-app/package-lock.json +0 -999
- jac_client/examples/todo-app/package.json +0 -22
- jac_client-0.1.0.dist-info/RECORD +0 -33
- /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,941 +1,234 @@
|
|
|
1
1
|
"""Client-side runtime for Jac JSX and walker interactions."""
|
|
2
2
|
|
|
3
|
-
cl import from 'react' {* as React}
|
|
4
|
-
cl import from 'react-dom/client' {* as ReactDOM}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
childrenArray = [];
|
|
15
|
-
if children != None {
|
|
16
|
-
if Array.isArray(children) {
|
|
17
|
-
childrenArray = children;
|
|
18
|
-
} else {
|
|
19
|
-
childrenArray = [children];
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
# Filter out null/undefined children
|
|
24
|
-
reactChildren = [];
|
|
25
|
-
for child in childrenArray {
|
|
26
|
-
if child != None {
|
|
27
|
-
reactChildren.push(child);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if reactChildren.length > 0 {
|
|
32
|
-
args = [tag, props];
|
|
33
|
-
for child in reactChildren {
|
|
34
|
-
args.push(child);
|
|
35
|
-
}
|
|
36
|
-
return React.createElement.apply(React, args);
|
|
37
|
-
} else {
|
|
38
|
-
return React.createElement(tag, props);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
def renderJsxTree(node: any, container: any) -> None {
|
|
43
|
-
try {
|
|
44
|
-
ReactDOM.createRoot(container).render(node);
|
|
45
|
-
} except Exception as err {
|
|
46
|
-
console.error("[Jac] Error in renderJsxTree:", err);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
# ============================================================================
|
|
51
|
-
# Reactive State Management System
|
|
52
|
-
# ============================================================================
|
|
53
|
-
|
|
54
|
-
# Global reactive context for managing signals, effects, and re-renders
|
|
55
|
-
let __jacReactiveContext = {
|
|
56
|
-
"signals": [], # Global signal storage (enables closures)
|
|
57
|
-
"pendingRenders": [], # Batched re-renders queue
|
|
58
|
-
"flushScheduled": False, # Debounce flag for batching
|
|
59
|
-
"rootComponent": None, # Root function to re-render
|
|
60
|
-
"reactRoot": None, # Store the React 18 root instance
|
|
61
|
-
"currentComponent": None, # Current component ID being rendered
|
|
62
|
-
"currentEffect": None, # Current effect for dependency tracking
|
|
63
|
-
"router": None, # Global router instance
|
|
64
|
-
"mountedComponents": {} # Track which components have mounted (for onMount)
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
# Create a reactive signal (primitive value)
|
|
68
|
-
# Returns [getter, setter] tuple for reactive primitive value
|
|
69
|
-
def createSignal(initialValue: any) -> list {
|
|
70
|
-
signalId = __jacReactiveContext.signals.length;
|
|
71
|
-
signalData = {"value": initialValue, "subscribers": []};
|
|
72
|
-
__jacReactiveContext.signals.push(signalData);
|
|
73
|
-
|
|
74
|
-
def getter() -> any {
|
|
75
|
-
__jacTrackDependency(signalData.subscribers);
|
|
76
|
-
return signalData.value;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
def setter(newValue: any) -> None {
|
|
80
|
-
if newValue != signalData.value {
|
|
81
|
-
signalData.value = newValue;
|
|
82
|
-
__jacNotifySubscribers(signalData.subscribers);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return [getter, setter];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Create reactive state (object/dict)
|
|
90
|
-
# Returns [getter, setter] tuple for reactive object
|
|
91
|
-
def createState(initialState: dict) -> list {
|
|
92
|
-
signalId = __jacReactiveContext.signals.length;
|
|
93
|
-
signalData = {"value": initialState, "subscribers": []};
|
|
94
|
-
__jacReactiveContext.signals.push(signalData);
|
|
95
|
-
|
|
96
|
-
def getter() -> dict {
|
|
97
|
-
__jacTrackDependency(signalData.subscribers);
|
|
98
|
-
return signalData.value;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
def setter(updates: dict) -> None {
|
|
102
|
-
# Shallow merge
|
|
103
|
-
newState = {};
|
|
104
|
-
stateValue = signalData.value;
|
|
105
|
-
for key in __objectKeys(stateValue) {
|
|
106
|
-
newState[key] = stateValue[key];
|
|
107
|
-
}
|
|
108
|
-
for key in __objectKeys(updates) {
|
|
109
|
-
newState[key] = updates[key];
|
|
110
|
-
}
|
|
111
|
-
signalData.value = newState;
|
|
112
|
-
__jacNotifySubscribers(signalData.subscribers);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return [getter, setter];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
# Run effect when dependencies change
|
|
119
|
-
# Executes effectFn and re-runs when tracked signals change
|
|
120
|
-
def createEffect(effectFn: any) -> None {
|
|
121
|
-
__jacRunEffect(effectFn);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
# Run effect once when component mounts (similar to useEffect with empty deps)
|
|
125
|
-
# Executes mountFn once when the component first renders
|
|
126
|
-
def onMount(mountFn: any) -> None {
|
|
127
|
-
currentComponent = __jacReactiveContext.currentComponent;
|
|
128
|
-
if not currentComponent {
|
|
129
|
-
# Not in a component context, run immediately
|
|
130
|
-
__jacRunEffect(mountFn);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
# Track mounted components
|
|
135
|
-
if not __jacReactiveContext.mountedComponents {
|
|
136
|
-
__jacReactiveContext.mountedComponents = {};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
# Check if this component has already mounted
|
|
140
|
-
componentId = f"{currentComponent}";
|
|
141
|
-
if not __jacHasOwn(__jacReactiveContext.mountedComponents, componentId) {
|
|
142
|
-
# Mark as mounted and run the effect
|
|
143
|
-
__jacReactiveContext.mountedComponents[componentId] = True;
|
|
144
|
-
|
|
145
|
-
# Run in next tick to ensure component is fully rendered
|
|
146
|
-
try {
|
|
147
|
-
setTimeout(lambda -> None {
|
|
148
|
-
__jacRunEffect(mountFn);
|
|
149
|
-
}, 0);
|
|
150
|
-
} except Exception {
|
|
151
|
-
# Fallback if setTimeout not available
|
|
152
|
-
__jacRunEffect(mountFn);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
3
|
+
cl import from 'react' { * as React }
|
|
4
|
+
cl import from 'react-dom/client' { * as ReactDOM }
|
|
5
|
+
cl import from 'react-router-dom' {
|
|
6
|
+
HashRouter as ReactRouterHashRouter,
|
|
7
|
+
Routes as ReactRouterRoutes,
|
|
8
|
+
Route as ReactRouterRoute,
|
|
9
|
+
Link as ReactRouterLink,
|
|
10
|
+
Navigate as ReactRouterNavigate,
|
|
11
|
+
useNavigate as reactRouterUseNavigate,
|
|
12
|
+
useLocation as reactRouterUseLocation,
|
|
13
|
+
useParams as reactRouterUseParams
|
|
155
14
|
}
|
|
156
15
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
16
|
+
cl {
|
|
17
|
+
# JSX factory function - uses React.createElement
|
|
18
|
+
def __jacJsx(tag: any, props: dict = {}, children: any = []) -> any {
|
|
19
|
+
# Handle fragments: when tag is None/null, use React.Fragment
|
|
20
|
+
if tag == None {
|
|
21
|
+
tag = React.Fragment;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
childrenArray = [];
|
|
25
|
+
if children != None {
|
|
26
|
+
if Array.isArray(children) {
|
|
27
|
+
childrenArray = children;
|
|
28
|
+
} else {
|
|
29
|
+
childrenArray = [children];
|
|
165
30
|
}
|
|
166
31
|
}
|
|
167
|
-
if not alreadySubscribed {
|
|
168
|
-
subscribers.push(currentEffect);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
32
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
alreadySubscribed = True;
|
|
33
|
+
# Filter out null/undefined children
|
|
34
|
+
reactChildren = [];
|
|
35
|
+
for child in childrenArray {
|
|
36
|
+
if child != None {
|
|
37
|
+
reactChildren.push(child);
|
|
178
38
|
}
|
|
179
39
|
}
|
|
180
|
-
if not alreadySubscribed {
|
|
181
|
-
subscribers.push(currentComponent);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
# Internal: Notify subscribers of state change
|
|
187
|
-
def __jacNotifySubscribers(subscribers: list) -> None {
|
|
188
|
-
for subscriber in subscribers {
|
|
189
|
-
if __isFunction(subscriber) {
|
|
190
|
-
# It's an effect function - re-run it
|
|
191
|
-
__jacRunEffect(subscriber);
|
|
192
|
-
} else {
|
|
193
|
-
# It's a component ID - schedule re-render
|
|
194
|
-
__jacScheduleRerender(subscriber);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
# Internal: Run an effect function with dependency tracking
|
|
200
|
-
def __jacRunEffect(effectFn: any) -> None {
|
|
201
|
-
previousEffect = __jacReactiveContext.currentEffect;
|
|
202
|
-
__jacReactiveContext.currentEffect = effectFn;
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
effectFn();
|
|
206
|
-
} except Exception as err {
|
|
207
|
-
console.error("[Jac] Error in effect:", err);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
__jacReactiveContext.currentEffect = previousEffect;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
# Schedule a re-render (batched)
|
|
214
|
-
def __jacScheduleRerender(componentId: any) -> None {
|
|
215
|
-
pending = __jacReactiveContext.pendingRenders;
|
|
216
|
-
|
|
217
|
-
# Check if already scheduled
|
|
218
|
-
alreadyScheduled = False;
|
|
219
|
-
for item in pending {
|
|
220
|
-
if item == componentId {
|
|
221
|
-
alreadyScheduled = True;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if not alreadyScheduled {
|
|
226
|
-
pending.push(componentId);
|
|
227
|
-
__jacScheduleFlush();
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
# Schedule a flush of pending renders
|
|
232
|
-
def __jacScheduleFlush() -> None {
|
|
233
|
-
if not __jacReactiveContext.flushScheduled {
|
|
234
|
-
__jacReactiveContext.flushScheduled = True;
|
|
235
|
-
|
|
236
|
-
# Use requestAnimationFrame for batching, fallback to setTimeout
|
|
237
|
-
try {
|
|
238
|
-
requestAnimationFrame(__jacFlushRenders);
|
|
239
|
-
} except Exception {
|
|
240
|
-
setTimeout(__jacFlushRenders, 0);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
# Flush all pending renders
|
|
246
|
-
def __jacFlushRenders() -> None {
|
|
247
|
-
pending = __jacReactiveContext.pendingRenders;
|
|
248
|
-
__jacReactiveContext.pendingRenders = [];
|
|
249
|
-
__jacReactiveContext.flushScheduled = False;
|
|
250
|
-
|
|
251
|
-
for componentId in pending {
|
|
252
|
-
__jacRerenderComponent(componentId);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
# Re-render the root component
|
|
257
|
-
def __jacRerenderComponent(componentId: any) -> None {
|
|
258
|
-
# Use the stored React 18 root for re-rendering
|
|
259
|
-
reactRoot = __jacReactiveContext.reactRoot;
|
|
260
|
-
if not reactRoot {
|
|
261
|
-
console.error("[Jac] React root not initialized. Cannot re-render.");
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
rootComponent = __jacReactiveContext.rootComponent;
|
|
266
|
-
if not rootComponent {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
previousComponent = __jacReactiveContext.currentComponent;
|
|
272
|
-
__jacReactiveContext.currentComponent = componentId;
|
|
273
|
-
|
|
274
|
-
component = rootComponent();
|
|
275
|
-
|
|
276
|
-
# FIXED: Use the stored React 18 root's render method
|
|
277
|
-
reactRoot.render(component);
|
|
278
|
-
|
|
279
|
-
__jacReactiveContext.currentComponent = previousComponent;
|
|
280
|
-
} except Exception as err {
|
|
281
|
-
console.error("[Jac] Error re-rendering component:", err);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
# ============================================================================
|
|
286
|
-
# Declarative Routing System
|
|
287
|
-
# ============================================================================
|
|
288
|
-
|
|
289
|
-
# Route configuration object
|
|
290
|
-
obj RouteConfig {
|
|
291
|
-
has path: str;
|
|
292
|
-
has component: any;
|
|
293
|
-
has guard: any = None;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
# Create a router instance
|
|
297
|
-
def initRouter(routes: list, defaultRoute: str = "/") -> dict {
|
|
298
|
-
# Get initial path from hash or use default
|
|
299
|
-
initialPath = __jacGetHashPath();
|
|
300
|
-
if not initialPath {
|
|
301
|
-
initialPath = defaultRoute;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
# Create reactive signal for current path
|
|
305
|
-
[currentPath, setCurrentPath] = createSignal(initialPath);
|
|
306
|
-
|
|
307
|
-
# Listen to hash changes
|
|
308
|
-
window.addEventListener("hashchange", lambda event: any -> None {
|
|
309
|
-
newPath = __jacGetHashPath();
|
|
310
|
-
if not newPath {
|
|
311
|
-
newPath = defaultRoute;
|
|
312
|
-
}
|
|
313
|
-
setCurrentPath(newPath);
|
|
314
|
-
});
|
|
315
40
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
newPath = defaultRoute;
|
|
321
|
-
}
|
|
322
|
-
setCurrentPath(newPath);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
# Render method - returns component for current route
|
|
326
|
-
def render() -> any {
|
|
327
|
-
path = currentPath(); # Track dependency!
|
|
328
|
-
|
|
329
|
-
# Find matching route
|
|
330
|
-
for route in routes {
|
|
331
|
-
if route.path == path {
|
|
332
|
-
# Check guard if present
|
|
333
|
-
if route.guard and not route.guard() {
|
|
334
|
-
return __jacJsx("div", {}, ["Access Denied"]);
|
|
335
|
-
}
|
|
336
|
-
return route.component();
|
|
41
|
+
if reactChildren.length > 0 {
|
|
42
|
+
args = [tag, props];
|
|
43
|
+
for child in reactChildren {
|
|
44
|
+
args.push(child);
|
|
337
45
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
# No match - show 404
|
|
341
|
-
return __jacJsx("div", {}, ["404 - Route not found: ", path]);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
# Navigate method
|
|
345
|
-
def navigateTo(path: str) -> None {
|
|
346
|
-
window.location.hash = "#" + path;
|
|
347
|
-
setCurrentPath(path);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
# Store router in global context
|
|
351
|
-
router = {
|
|
352
|
-
"path": currentPath,
|
|
353
|
-
"render": render,
|
|
354
|
-
"navigate": navigateTo
|
|
355
|
-
};
|
|
356
|
-
__jacReactiveContext.router = router;
|
|
357
|
-
|
|
358
|
-
return router;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
# Route config factory
|
|
362
|
-
def Route(path: str, component: any, guard: any = None) -> dict {
|
|
363
|
-
return {"path": path, "component": component, "guard": guard};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
# Link component for declarative navigation
|
|
367
|
-
def Link(props: dict) -> any {
|
|
368
|
-
href = props["href"] if "href" in props else "/";
|
|
369
|
-
children = props["children"] if "children" in props else [];
|
|
370
|
-
|
|
371
|
-
def handleClick(event: any) -> None {
|
|
372
|
-
console.log("Link clicked, navigating to:", href);
|
|
373
|
-
event.preventDefault();
|
|
374
|
-
navigate(href);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
# Ensure children is properly handled - convert to array if it's not already
|
|
378
|
-
childrenArray = [];
|
|
379
|
-
if children != None {
|
|
380
|
-
if Array.isArray(children) {
|
|
381
|
-
childrenArray = children;
|
|
46
|
+
return React.createElement.apply(React, args);
|
|
382
47
|
} else {
|
|
383
|
-
|
|
384
|
-
}
|
|
48
|
+
return React.createElement(tag, props);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ============================================================================
|
|
53
|
+
# React Router Integration (using react-router-dom v6)
|
|
54
|
+
# ============================================================================
|
|
55
|
+
# Direct re-exports of React Router components for seamless integration
|
|
56
|
+
# Router uses HashRouter for hash-based routing (#/path)
|
|
57
|
+
# See: https://reactrouter.com/6.30.1/router-components/hash-router
|
|
58
|
+
let Router = ReactRouterHashRouter;
|
|
59
|
+
let Routes = ReactRouterRoutes;
|
|
60
|
+
let Route = ReactRouterRoute;
|
|
61
|
+
let Link = ReactRouterLink;
|
|
62
|
+
let Navigate = ReactRouterNavigate;
|
|
63
|
+
|
|
64
|
+
# React Router Hooks - wrapped for Jac compatibility
|
|
65
|
+
let useNavigate = reactRouterUseNavigate;
|
|
66
|
+
let useLocation = reactRouterUseLocation;
|
|
67
|
+
let useParams = reactRouterUseParams;
|
|
68
|
+
|
|
69
|
+
# useRouter Hook - convenience hook that combines common router utilities
|
|
70
|
+
def useRouter() -> dict {
|
|
71
|
+
navigate = reactRouterUseNavigate();
|
|
72
|
+
location = reactRouterUseLocation();
|
|
73
|
+
params = reactRouterUseParams();
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"navigate": navigate,
|
|
77
|
+
"location": location,
|
|
78
|
+
"params": params,
|
|
79
|
+
"pathname": location.pathname,
|
|
80
|
+
"search": location.search,
|
|
81
|
+
"hash": location.hash
|
|
82
|
+
};
|
|
385
83
|
}
|
|
386
84
|
|
|
387
|
-
#
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
# Navigate programmatically
|
|
392
|
-
def navigate(path: str) -> None {
|
|
393
|
-
console.log("navigate() called with path:", path);
|
|
394
|
-
router = __jacReactiveContext.router;
|
|
395
|
-
if router {
|
|
396
|
-
console.log("Router found, calling router.navigate()");
|
|
397
|
-
router.navigate(path);
|
|
398
|
-
} else {
|
|
399
|
-
console.log("No router, setting hash directly");
|
|
85
|
+
# navigate function - programmatic navigation (backward compatibility)
|
|
86
|
+
# Note: Use useNavigate() hook inside components instead
|
|
87
|
+
def navigate(path: str) -> None {
|
|
400
88
|
window.location.hash = "#" + path;
|
|
401
89
|
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
# Hook to access router in components
|
|
405
|
-
def useRouter() -> dict {
|
|
406
|
-
return __jacReactiveContext.router;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
# Internal: Get current hash path
|
|
410
|
-
def __jacGetHashPath() -> str {
|
|
411
|
-
hash = window.location.hash;
|
|
412
|
-
if hash {
|
|
413
|
-
return hash[1:]; # Remove '#'
|
|
414
|
-
}
|
|
415
|
-
return "";
|
|
416
|
-
}
|
|
417
90
|
|
|
418
|
-
# ============================================================================
|
|
419
|
-
# Walker spawn function
|
|
420
|
-
# ============================================================================
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# Walker spawn function
|
|
93
|
+
# ============================================================================
|
|
94
|
+
async def __jacSpawn(left: str, right: str = "", fields: dict = {}) -> any {
|
|
95
|
+
token = __getLocalStorage("jac_token");
|
|
96
|
+
url = f"/walker/{left}";
|
|
97
|
+
if right != "" {
|
|
98
|
+
url = f"/walker/{left}/{right}";
|
|
99
|
+
}
|
|
100
|
+
response = await fetch(
|
|
101
|
+
url,
|
|
102
|
+
{
|
|
103
|
+
"method": "POST",
|
|
104
|
+
"accept": "application/json",
|
|
105
|
+
"headers": {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
"Authorization": f"Bearer {token}" if token else ""
|
|
108
|
+
},
|
|
109
|
+
"body": JSON.stringify({"fields": fields})
|
|
110
|
+
}
|
|
111
|
+
);
|
|
424
112
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
"method": "POST",
|
|
429
|
-
"headers": {
|
|
430
|
-
"Content-Type": "application/json",
|
|
431
|
-
"Authorization": f"Bearer {token}" if token else ""
|
|
432
|
-
},
|
|
433
|
-
"body": JSON.stringify({"nd": nd if nd else "root", **fields})
|
|
113
|
+
if not response.ok {
|
|
114
|
+
error_text = await response.json();
|
|
115
|
+
raise Exception(f"Walker {walker} failed: {error_text}") ;
|
|
434
116
|
}
|
|
435
|
-
);
|
|
436
117
|
|
|
437
|
-
|
|
438
|
-
error_text = await response.text();
|
|
439
|
-
raise Exception(f"Walker {walker} failed: {error_text}");
|
|
118
|
+
return await response.json();
|
|
440
119
|
}
|
|
441
120
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
# Function call function - calls server-side functions from client
|
|
446
|
-
async def __jacCallFunction(function_name: str, args: dict = {}) -> any {
|
|
447
|
-
token = __getLocalStorage("jac_token");
|
|
448
|
-
|
|
449
|
-
response = await fetch(
|
|
450
|
-
f"/function/{function_name}",
|
|
451
|
-
{
|
|
452
|
-
"method": "POST",
|
|
453
|
-
"headers": {
|
|
454
|
-
"Content-Type": "application/json",
|
|
455
|
-
"Authorization": f"Bearer {token}" if token else ""
|
|
456
|
-
},
|
|
457
|
-
"body": JSON.stringify({"args": args})
|
|
458
|
-
}
|
|
459
|
-
);
|
|
460
|
-
|
|
461
|
-
if not response.ok {
|
|
462
|
-
error_text = await response.text();
|
|
463
|
-
raise Exception(f"Function {function_name} failed: {error_text}");
|
|
121
|
+
def jacSpawn(left: str, right: str = "", fields: dict = {}) -> any {
|
|
122
|
+
return __jacSpawn(left, right, fields);
|
|
464
123
|
}
|
|
465
124
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
# Authentication helpers
|
|
471
|
-
async def jacSignup(username: str, password: str) -> dict {
|
|
472
|
-
response = await fetch(
|
|
473
|
-
"/user/create",
|
|
474
|
-
{
|
|
475
|
-
"method": "POST",
|
|
476
|
-
"headers": {"Content-Type": "application/json"},
|
|
477
|
-
"body": JSON.stringify({"username": username, "password": password})
|
|
478
|
-
}
|
|
479
|
-
);
|
|
125
|
+
# Function call function - calls server-side functions from client
|
|
126
|
+
async def __jacCallFunction(function_name: str, args: dict = {}) -> any {
|
|
127
|
+
token = __getLocalStorage("jac_token");
|
|
480
128
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
error_data = JSON.parse(error_text);
|
|
493
|
-
return {"success": False, "error": error_data["error"] if error_data["error"] != None else "Signup failed"};
|
|
494
|
-
} except Exception {
|
|
495
|
-
return {"success": False, "error": error_text};
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
129
|
+
response = await fetch(
|
|
130
|
+
f"/function/{function_name}",
|
|
131
|
+
{
|
|
132
|
+
"method": "POST",
|
|
133
|
+
"headers": {
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
"Authorization": f"Bearer {token}" if token else ""
|
|
136
|
+
},
|
|
137
|
+
"body": JSON.stringify({"args": args})
|
|
138
|
+
}
|
|
139
|
+
);
|
|
499
140
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
{
|
|
504
|
-
"method": "POST",
|
|
505
|
-
"headers": {"Content-Type": "application/json"},
|
|
506
|
-
"body": JSON.stringify({"username": username, "password": password})
|
|
141
|
+
if not response.ok {
|
|
142
|
+
error_text = await response.text();
|
|
143
|
+
raise Exception(f"Function {function_name} failed: {error_text}") ;
|
|
507
144
|
}
|
|
508
|
-
);
|
|
509
145
|
|
|
510
|
-
if response.ok {
|
|
511
146
|
data = JSON.parse(await response.text());
|
|
512
|
-
|
|
513
|
-
if token {
|
|
514
|
-
__setLocalStorage("jac_token", token);
|
|
515
|
-
return True;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return False;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
def jacLogout() -> None {
|
|
522
|
-
__removeLocalStorage("jac_token");
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
def jacIsLoggedIn() -> bool {
|
|
526
|
-
token = __getLocalStorage("jac_token");
|
|
527
|
-
return token != None and token != "";
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
# Browser API shims
|
|
531
|
-
def __getLocalStorage(key: str) -> str {
|
|
532
|
-
storage = globalThis.localStorage;
|
|
533
|
-
return storage.getItem(key) if storage else "";
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
def __setLocalStorage(key: str, value: str) -> None {
|
|
537
|
-
storage = globalThis.localStorage;
|
|
538
|
-
if storage {
|
|
539
|
-
storage.setItem(key, value);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
def __removeLocalStorage(key: str) -> None {
|
|
544
|
-
storage = globalThis.localStorage;
|
|
545
|
-
if storage {
|
|
546
|
-
storage.removeItem(key);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
def __isObject(value: any) -> bool {
|
|
551
|
-
if value == None {
|
|
552
|
-
return False;
|
|
553
|
-
}
|
|
554
|
-
return Object.prototype.toString.call(value) == "[object Object]";
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
def __isFunction(value: any) -> bool {
|
|
558
|
-
return Object.prototype.toString.call(value) == "[object Function]";
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
def __objectKeys(obj: any) -> list {
|
|
562
|
-
if obj == None {
|
|
563
|
-
return [];
|
|
564
|
-
}
|
|
565
|
-
return Object.keys(obj);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
# Low-level helpers
|
|
569
|
-
def __jacHasOwn(obj: any, key: any) -> bool {
|
|
570
|
-
try {
|
|
571
|
-
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
572
|
-
} except Exception {
|
|
573
|
-
return False;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
# Internal polyfill for Python-style dict.get on plain JS objects
|
|
578
|
-
def __jacEnsureObjectGetPolyfill() -> None {
|
|
579
|
-
# No longer needed - we use standard JavaScript object access instead of .get() method
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
# Common utility helpers
|
|
584
|
-
def __jacGetDocument(scope: any) -> any {
|
|
585
|
-
try {
|
|
586
|
-
return scope.document;
|
|
587
|
-
} except Exception {}
|
|
588
|
-
try {
|
|
589
|
-
return window.document;
|
|
590
|
-
} except Exception {}
|
|
591
|
-
return None;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
def __jacParseJsonObject(text: str) -> any {
|
|
595
|
-
try {
|
|
596
|
-
parsed = JSON.parse(text);
|
|
597
|
-
if __isObject(parsed) {
|
|
598
|
-
return parsed;
|
|
599
|
-
}
|
|
600
|
-
console.error("[Jac] Hydration payload is not an object");
|
|
601
|
-
return None;
|
|
602
|
-
} except Exception as err {
|
|
603
|
-
console.error("[Jac] Failed to parse hydration payload", err);
|
|
604
|
-
return None;
|
|
147
|
+
return data["result"];
|
|
605
148
|
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
def __jacBuildOrderedArgs(order: list, argsDict: dict) -> list {
|
|
609
|
-
result = [];
|
|
610
|
-
if not order {
|
|
611
|
-
return result;
|
|
612
|
-
}
|
|
613
|
-
values = argsDict if __isObject(argsDict) else {};
|
|
614
|
-
for name in order {
|
|
615
|
-
result.push(values[name]);
|
|
616
|
-
}
|
|
617
|
-
return result;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
def __jacResolveRenderer(scope: any) -> any {
|
|
621
|
-
if scope.renderJsxTree {
|
|
622
|
-
return scope.renderJsxTree;
|
|
623
|
-
}
|
|
624
|
-
if __isFunction(renderJsxTree) {
|
|
625
|
-
return renderJsxTree;
|
|
626
|
-
}
|
|
627
|
-
return None;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
def __jacResolveTarget(
|
|
631
|
-
moduleRecord: dict,
|
|
632
|
-
registry: dict,
|
|
633
|
-
name: str
|
|
634
|
-
) -> any {
|
|
635
|
-
moduleFunctions = (
|
|
636
|
-
moduleRecord.moduleFunctions
|
|
637
|
-
if moduleRecord and moduleRecord.moduleFunctions
|
|
638
|
-
else {}
|
|
639
|
-
);
|
|
640
|
-
if __jacHasOwn(moduleFunctions, name) {
|
|
641
|
-
return moduleFunctions[name];
|
|
642
|
-
}
|
|
643
|
-
registryFunctions = (
|
|
644
|
-
registry.functions
|
|
645
|
-
if registry and registry.functions
|
|
646
|
-
else {}
|
|
647
|
-
);
|
|
648
|
-
if __jacHasOwn(registryFunctions, name) {
|
|
649
|
-
return registryFunctions[name];
|
|
650
|
-
}
|
|
651
|
-
return None;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
def __jacSafeCallTarget(
|
|
655
|
-
target: any,
|
|
656
|
-
scope: any,
|
|
657
|
-
orderedArgs: list,
|
|
658
|
-
targetName: str
|
|
659
|
-
) -> dict {
|
|
660
|
-
try {
|
|
661
|
-
result = target.apply(scope, orderedArgs);
|
|
662
|
-
return {"ok": True, "value": result};
|
|
663
|
-
} except Exception as err {
|
|
664
|
-
console.error("[Jac] Error executing client function " + targetName, err);
|
|
665
|
-
return {"ok": False, "value": None};
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
149
|
|
|
669
|
-
#
|
|
670
|
-
def
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
} except Exception {}
|
|
680
|
-
return {};
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
def __jacEnsureRegistry() -> dict {
|
|
684
|
-
scope = __jacGlobalScope();
|
|
685
|
-
registry = scope.__jacClient;
|
|
686
|
-
if not registry {
|
|
687
|
-
registry = {
|
|
688
|
-
"functions": {},
|
|
689
|
-
"globals": {},
|
|
690
|
-
"modules": {},
|
|
691
|
-
"state": {"globals": {}},
|
|
692
|
-
"__hydration": {"registered": False},
|
|
693
|
-
"lastModule": None
|
|
694
|
-
};
|
|
695
|
-
scope.__jacClient = registry;
|
|
696
|
-
return registry;
|
|
697
|
-
}
|
|
698
|
-
if not registry.functions {
|
|
699
|
-
registry.functions = {};
|
|
700
|
-
}
|
|
701
|
-
if not registry.globals {
|
|
702
|
-
registry.globals = {};
|
|
703
|
-
}
|
|
704
|
-
if not registry.modules {
|
|
705
|
-
registry.modules = {};
|
|
706
|
-
}
|
|
707
|
-
if not registry.state {
|
|
708
|
-
registry.state = {"globals": {}};
|
|
709
|
-
} elif not registry.state.globals {
|
|
710
|
-
registry.state.globals = {};
|
|
711
|
-
}
|
|
712
|
-
if not registry.__hydration {
|
|
713
|
-
registry.__hydration = {"registered": False};
|
|
714
|
-
}
|
|
715
|
-
return registry;
|
|
716
|
-
}
|
|
150
|
+
# Authentication helpers
|
|
151
|
+
async def jacSignup(username: str, password: str) -> dict {
|
|
152
|
+
response = await fetch(
|
|
153
|
+
"/user/create",
|
|
154
|
+
{
|
|
155
|
+
"method": "POST",
|
|
156
|
+
"headers": {"Content-Type": "application/json"},
|
|
157
|
+
"body": JSON.stringify({"username": username, "password": password})
|
|
158
|
+
}
|
|
159
|
+
);
|
|
717
160
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
renderer(node, container);
|
|
161
|
+
if response.ok {
|
|
162
|
+
data = JSON.parse(await response.text());
|
|
163
|
+
token = data["token"];
|
|
164
|
+
if token {
|
|
165
|
+
__setLocalStorage("jac_token", token);
|
|
166
|
+
return {"success": True, "token": token, "username": username};
|
|
167
|
+
}
|
|
168
|
+
return {"success": False, "error": "No token received"};
|
|
727
169
|
} else {
|
|
728
|
-
|
|
170
|
+
error_text = await response.text();
|
|
171
|
+
try {
|
|
172
|
+
error_data = JSON.parse(error_text);
|
|
173
|
+
return {
|
|
174
|
+
"success": False,
|
|
175
|
+
"error": error_data["error"]
|
|
176
|
+
if error_data["error"] != None
|
|
177
|
+
else "Signup failed"
|
|
178
|
+
};
|
|
179
|
+
} except Exception {
|
|
180
|
+
return {"success": False, "error": error_text};
|
|
181
|
+
}
|
|
729
182
|
}
|
|
730
|
-
} except Exception as err {
|
|
731
|
-
console.error("[Jac] Failed to render JSX tree (fallback path)", err);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
def __jacHydrateFromDom(defaultModuleName: str) -> None {
|
|
736
|
-
__jacEnsureObjectGetPolyfill();
|
|
737
|
-
scope = __jacGlobalScope();
|
|
738
|
-
documentRef = __jacGetDocument(scope);
|
|
739
|
-
if not documentRef {
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
initEl = documentRef.getElementById("__jac_init__");
|
|
744
|
-
rootEl = documentRef.getElementById("__jac_root");
|
|
745
|
-
if not initEl or not rootEl {
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
dataset = initEl.dataset if initEl.dataset else None;
|
|
750
|
-
if dataset and dataset.jacHydrated == "true" {
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
if dataset {
|
|
754
|
-
dataset.jacHydrated = "true";
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
payloadText = initEl.textContent if initEl.textContent else "{}";
|
|
758
|
-
payload = __jacParseJsonObject(payloadText);
|
|
759
|
-
if not payload {
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
targetName = payload["function"];
|
|
764
|
-
if not targetName {
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
fallbackModule = defaultModuleName if defaultModuleName else "";
|
|
769
|
-
moduleCandidate = payload["module"];
|
|
770
|
-
moduleName = moduleCandidate if moduleCandidate else fallbackModule;
|
|
771
|
-
|
|
772
|
-
registry = __jacEnsureRegistry();
|
|
773
|
-
modulesStore = registry.modules if registry.modules else {};
|
|
774
|
-
moduleRecord = modulesStore[moduleName] if __jacHasOwn(modulesStore, moduleName) else None;
|
|
775
|
-
if not moduleRecord {
|
|
776
|
-
console.error("[Jac] Client module not registered: " + moduleName);
|
|
777
|
-
return;
|
|
778
183
|
}
|
|
779
184
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
payloadGlobals = payloadGlobalsRaw if __isObject(payloadGlobalsRaw) else {};
|
|
788
|
-
registry.state.globals[moduleName] = payloadGlobals;
|
|
789
|
-
for gName in __objectKeys(payloadGlobals) {
|
|
790
|
-
gValue = payloadGlobals[gName];
|
|
791
|
-
scope[gName] = gValue;
|
|
792
|
-
registry.globals[gName] = gValue;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
target = __jacResolveTarget(moduleRecord, registry, targetName);
|
|
796
|
-
if not target {
|
|
797
|
-
console.error("[Jac] Client function not found: " + targetName);
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
# Set up reactive root component for automatic re-rendering
|
|
802
|
-
__jacReactiveContext.rootComponent = lambda -> any {
|
|
803
|
-
__jacReactiveContext.currentComponent = "__root__";
|
|
804
|
-
result = target.apply(scope, orderedArgs);
|
|
805
|
-
__jacReactiveContext.currentComponent = None;
|
|
806
|
-
return result;
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
renderer = __jacResolveRenderer(scope);
|
|
810
|
-
if not renderer {
|
|
811
|
-
console.warn("[Jac] renderJsxTree is not available in client bundle");
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
# FIX: Declare reactRoot in the function scope
|
|
815
|
-
let reactRoot = None;
|
|
816
|
-
|
|
817
|
-
# 1. Create and store the React 18 root instance
|
|
818
|
-
try {
|
|
819
|
-
reactRoot = ReactDOM.createRoot(rootEl);
|
|
820
|
-
__jacReactiveContext.reactRoot = reactRoot; # Store the root instance
|
|
821
|
-
} except Exception as err {
|
|
822
|
-
console.error("[Jac] Failed to create React root for hydration:", err);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
# 2. Initial render - call the root component
|
|
827
|
-
value = __jacReactiveContext.rootComponent();
|
|
828
|
-
|
|
829
|
-
# 3. Use the stored root for the initial render (hydration)
|
|
830
|
-
if value and __isObject(value) and __isFunction(value.then) {
|
|
831
|
-
value.then(
|
|
832
|
-
lambda node: any -> None {
|
|
833
|
-
reactRoot.render(node);
|
|
834
|
-
}
|
|
835
|
-
).catch(
|
|
836
|
-
lambda err: any -> None {
|
|
837
|
-
console.error("[Jac] Error resolving client function promise", err);
|
|
185
|
+
async def jacLogin(username: str, password: str) -> bool {
|
|
186
|
+
response = await fetch(
|
|
187
|
+
"/user/login",
|
|
188
|
+
{
|
|
189
|
+
"method": "POST",
|
|
190
|
+
"headers": {"Content-Type": "application/json"},
|
|
191
|
+
"body": JSON.stringify({"username": username, "password": password})
|
|
838
192
|
}
|
|
839
193
|
);
|
|
840
|
-
} else {
|
|
841
|
-
reactRoot.render(value);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
def __jacExecuteHydration() -> None {
|
|
846
|
-
registry = __jacEnsureRegistry();
|
|
847
|
-
defaultModule = registry.lastModule if registry.lastModule else "";
|
|
848
|
-
__jacHydrateFromDom(defaultModule);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
def __jacEnsureHydration(moduleName: str) -> None {
|
|
852
|
-
__jacEnsureObjectGetPolyfill();
|
|
853
|
-
registry = __jacEnsureRegistry();
|
|
854
|
-
registry.lastModule = moduleName;
|
|
855
194
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
195
|
+
if response.ok {
|
|
196
|
+
data = JSON.parse(await response.text());
|
|
197
|
+
token = data["token"];
|
|
198
|
+
if token {
|
|
199
|
+
__setLocalStorage("jac_token", token);
|
|
200
|
+
return True;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return False;
|
|
864
204
|
}
|
|
865
205
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
hydration.registered = True;
|
|
869
|
-
documentRef.addEventListener(
|
|
870
|
-
"DOMContentLoaded",
|
|
871
|
-
lambda _event: any -> None {
|
|
872
|
-
__jacExecuteHydration();
|
|
873
|
-
},
|
|
874
|
-
{"once": True}
|
|
875
|
-
);
|
|
206
|
+
def jacLogout() -> None {
|
|
207
|
+
__removeLocalStorage("jac_token");
|
|
876
208
|
}
|
|
877
|
-
}
|
|
878
209
|
|
|
879
|
-
def
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
) -> None {
|
|
884
|
-
__jacEnsureObjectGetPolyfill();
|
|
885
|
-
scope = __jacGlobalScope();
|
|
886
|
-
registry = __jacEnsureRegistry();
|
|
210
|
+
def jacIsLoggedIn() -> bool {
|
|
211
|
+
token = __getLocalStorage("jac_token");
|
|
212
|
+
return token != None and token != "";
|
|
213
|
+
}
|
|
887
214
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
funcRef = scope[funcName];
|
|
893
|
-
if not funcRef {
|
|
894
|
-
console.error("[Jac] Client function not found during registration: " + funcName);
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
moduleFunctions[funcName] = funcRef;
|
|
898
|
-
registry.functions[funcName] = funcRef;
|
|
899
|
-
scope[funcName] = funcRef;
|
|
900
|
-
registeredFunctions.push(funcName);
|
|
901
|
-
}
|
|
215
|
+
# Browser API shims
|
|
216
|
+
def __getLocalStorage(key: str) -> str {
|
|
217
|
+
storage = globalThis.localStorage;
|
|
218
|
+
return storage.getItem(key) if storage else "";
|
|
902
219
|
}
|
|
903
220
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
globalNames.push(gName);
|
|
909
|
-
defaultValue = globalsMap[gName];
|
|
910
|
-
existing = scope[gName];
|
|
911
|
-
if existing == None {
|
|
912
|
-
scope[gName] = defaultValue;
|
|
913
|
-
moduleGlobals[gName] = defaultValue;
|
|
914
|
-
} else {
|
|
915
|
-
moduleGlobals[gName] = existing;
|
|
221
|
+
def __setLocalStorage(key: str, value: str) -> None {
|
|
222
|
+
storage = globalThis.localStorage;
|
|
223
|
+
if storage {
|
|
224
|
+
storage.setItem(key, value);
|
|
916
225
|
}
|
|
917
|
-
registry.globals[gName] = scope[gName];
|
|
918
226
|
}
|
|
919
227
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
moduleRecord.functions = registeredFunctions;
|
|
926
|
-
moduleRecord.globals = globalNames;
|
|
927
|
-
moduleRecord.defaults = globalsMap;
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
# 1. Ensure the module is written to the registry FIRST.
|
|
931
|
-
registry.modules[moduleName] = moduleRecord;
|
|
932
|
-
|
|
933
|
-
stateGlobals = registry.state.globals;
|
|
934
|
-
if not __jacHasOwn(stateGlobals, moduleName) {
|
|
935
|
-
stateGlobals[moduleName] = {};
|
|
228
|
+
def __removeLocalStorage(key: str) -> None {
|
|
229
|
+
storage = globalThis.localStorage;
|
|
230
|
+
if storage {
|
|
231
|
+
storage.removeItem(key);
|
|
232
|
+
}
|
|
936
233
|
}
|
|
937
|
-
|
|
938
|
-
# 2. ONLY THEN, trigger the hydration/lookup process.
|
|
939
|
-
__jacEnsureHydration(moduleName);
|
|
940
234
|
}
|
|
941
|
-
}
|