@jetstart/core 2.0.1 β 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/hot-reload-service.js +3 -3
- package/dist/build/js-compiler-service.js +73 -37
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +12 -9
- package/dist/server/middleware.js +28 -1
- package/dist/server/routes.js +2 -5
- package/dist/websocket/handler.d.ts +1 -1
- package/dist/websocket/handler.js +4 -4
- package/dist/websocket/manager.d.ts +0 -5
- package/dist/websocket/manager.js +0 -8
- package/package.json +3 -3
- package/src/build/hot-reload-service.ts +3 -3
- package/src/build/js-compiler-service.ts +136 -63
- package/src/server/index.ts +6 -6
- package/src/server/middleware.ts +29 -1
- package/src/server/routes.ts +2 -3
- package/src/utils/session.ts +0 -1
- package/src/websocket/handler.ts +4 -4
- package/src/websocket/manager.ts +0 -8
|
@@ -63,7 +63,7 @@ class HotReloadService {
|
|
|
63
63
|
async hotReload(filePath) {
|
|
64
64
|
const startTime = Date.now();
|
|
65
65
|
(0, logger_1.log)(`π₯ Hot reload starting for: ${path.basename(filePath)}`);
|
|
66
|
-
//
|
|
66
|
+
// Compile Kotlin to .class
|
|
67
67
|
const compileStart = Date.now();
|
|
68
68
|
const compileResult = await this.kotlinCompiler.compileFile(filePath);
|
|
69
69
|
const compileTime = Date.now() - compileStart;
|
|
@@ -79,7 +79,7 @@ class HotReloadService {
|
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
(0, logger_1.log)(`Compilation completed in ${compileTime}ms (${compileResult.classFiles.length} classes)`);
|
|
82
|
-
//
|
|
82
|
+
// Generate $Override classes (Phase 2)
|
|
83
83
|
const overrideDir = path.join(os.tmpdir(), 'jetstart-overrides', Date.now().toString());
|
|
84
84
|
fs.mkdirSync(overrideDir, { recursive: true });
|
|
85
85
|
const overrideResult = await this.overrideGenerator.generateOverrides(compileResult.classFiles, filePath, overrideDir);
|
|
@@ -106,7 +106,7 @@ class HotReloadService {
|
|
|
106
106
|
compileResult.classFiles.push(...allOverrideClassFiles);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
//
|
|
109
|
+
// Convert .class to .dex
|
|
110
110
|
const dexStart = Date.now();
|
|
111
111
|
const dexResult = await this.dexGenerator.generateDex(compileResult.classFiles);
|
|
112
112
|
const dexTime = Date.now() - dexStart;
|
|
@@ -106,17 +106,24 @@ class JsCompilerService {
|
|
|
106
106
|
const outKlib = path.join(this.workDir, 'compose_stubs.klib');
|
|
107
107
|
const argFile = path.join(this.workDir, 'stubs-args.txt');
|
|
108
108
|
fs.writeFileSync(argFile, [
|
|
109
|
-
'-ir-output-name',
|
|
110
|
-
'
|
|
111
|
-
'-
|
|
112
|
-
|
|
113
|
-
'-
|
|
109
|
+
'-ir-output-name',
|
|
110
|
+
'compose_stubs',
|
|
111
|
+
'-ir-output-dir',
|
|
112
|
+
this.workDir,
|
|
113
|
+
'-libraries',
|
|
114
|
+
this.stdlibKlib,
|
|
115
|
+
'-module-kind',
|
|
116
|
+
'es',
|
|
117
|
+
'-target',
|
|
118
|
+
'es2015',
|
|
114
119
|
'-Xir-produce-klib-file',
|
|
115
120
|
src,
|
|
116
121
|
].join('\n'), 'utf8');
|
|
117
122
|
(0, logger_1.log)('[JsCompiler] Building Compose shims klib (one-time, ~30s)...');
|
|
118
123
|
const r = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${argFile}`], {
|
|
119
|
-
shell: true,
|
|
124
|
+
shell: true,
|
|
125
|
+
encoding: 'utf8',
|
|
126
|
+
timeout: 120000,
|
|
120
127
|
});
|
|
121
128
|
if ((r.status === 0 || !r.stderr?.includes('error:')) && fs.existsSync(outKlib)) {
|
|
122
129
|
this.stubsKlib = outKlib;
|
|
@@ -209,7 +216,7 @@ class JsCompilerService {
|
|
|
209
216
|
*/
|
|
210
217
|
removeElseBlockIf(text, predicate) {
|
|
211
218
|
const re = /\}\s*else\s*\{([^{}]|\{[^{}]*\})*\}/gs;
|
|
212
|
-
return text.replace(re, (match) => predicate(match) ? '}' : match);
|
|
219
|
+
return text.replace(re, (match) => (predicate(match) ? '}' : match));
|
|
213
220
|
}
|
|
214
221
|
/**
|
|
215
222
|
* Preprocess a Kotlin Compose source file for kotlinc-js compilation.
|
|
@@ -221,7 +228,7 @@ class JsCompilerService {
|
|
|
221
228
|
*/
|
|
222
229
|
preprocessFile(ktFilePath, runDir) {
|
|
223
230
|
let text = fs.readFileSync(ktFilePath, 'utf8');
|
|
224
|
-
//
|
|
231
|
+
// Pass 1: Whole-file transformations
|
|
225
232
|
// Imports / package
|
|
226
233
|
text = text.replace(/^import\s+.*/gm, '');
|
|
227
234
|
text = text.replace(/^package\s+.*/gm, '');
|
|
@@ -261,10 +268,14 @@ class JsCompilerService {
|
|
|
261
268
|
text = text.replace(/\bval\s+searchResults\b[^\n]*/g, 'val searchResults: List<String> = listOf("Note 1", "Note 2", "Note 3")');
|
|
262
269
|
// Named M3 colors
|
|
263
270
|
const colorMap = {
|
|
264
|
-
onSurfaceVariant: '"#49454F"',
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
271
|
+
onSurfaceVariant: '"#49454F"',
|
|
272
|
+
onBackground: '"#1C1B1F"',
|
|
273
|
+
outline: '"#79747E"',
|
|
274
|
+
surfaceVariant: '"#E7E0EC"',
|
|
275
|
+
onPrimaryContainer: '"#21005D"',
|
|
276
|
+
inverseOnSurface: '"#F4EFF4"',
|
|
277
|
+
inverseSurface: '"#313033"',
|
|
278
|
+
tertiaryContainer: '"#FFD8E4"',
|
|
268
279
|
};
|
|
269
280
|
for (const [k, v] of Object.entries(colorMap)) {
|
|
270
281
|
text = text.replace(new RegExp(`MaterialTheme\\.colorScheme\\.${k}\\b`, 'g'), v);
|
|
@@ -310,7 +321,7 @@ class JsCompilerService {
|
|
|
310
321
|
// Fix extra ) from removed args in grid calls
|
|
311
322
|
text = text.replace(/StaggeredGridCells\.Fixed\((\d+)\)\),/g, 'StaggeredGridCells.Fixed($1),');
|
|
312
323
|
text = text.replace(/modifier\s*=\s*Modifier\(\)\),/g, 'modifier = Modifier(),');
|
|
313
|
-
//
|
|
324
|
+
// Pass 2: Brace-balanced block removal
|
|
314
325
|
// LaunchedEffect / SideEffect / DisposableEffect
|
|
315
326
|
for (const kw of ['LaunchedEffect', 'DisposableEffect', 'SideEffect']) {
|
|
316
327
|
text = this.removeBalancedBlock(text, new RegExp(`\\b${kw}\\b[^{]*\\{`, 'g'));
|
|
@@ -325,13 +336,12 @@ class JsCompilerService {
|
|
|
325
336
|
text = this.removeBalancedBlock(text, /if\s*\(\s*\w+\.isNotEmpty\(\)\s*\)\s*\{/g);
|
|
326
337
|
// Remove else blocks containing broken/removed suggestedTags code
|
|
327
338
|
text = this.removeElseBlockIf(text, (body) => body.includes('suggestedTags') || body.includes('Modifier()),'));
|
|
328
|
-
//
|
|
339
|
+
// Pass 3: Final cleanup
|
|
329
340
|
text = text.replace(/\n{3,}/g, '\n\n');
|
|
330
341
|
text = text.replace(/,\s*\)/g, ')');
|
|
331
342
|
text = text.replace(/\(\s*,/g, '(');
|
|
332
343
|
// Detect the screen function name to wrap the entry point
|
|
333
|
-
const screenMatch = text.match(/^fun\s+(\w+Screen)/m) ||
|
|
334
|
-
text.match(/^fun\s+(\w+)/m);
|
|
344
|
+
const screenMatch = text.match(/^fun\s+(\w+Screen)/m) || text.match(/^fun\s+(\w+)/m);
|
|
335
345
|
const screenName = screenMatch ? screenMatch[1] : null;
|
|
336
346
|
const renderWrapper = screenName
|
|
337
347
|
? `\n\n@OptIn(kotlin.js.ExperimentalJsExport::class)\n@JsExport\nfun __jetstart_render__(): dynamic = renderScreen { ${screenName}() }\n`
|
|
@@ -352,49 +362,69 @@ class JsCompilerService {
|
|
|
352
362
|
const runDir = path.join(this.workDir, runId);
|
|
353
363
|
fs.mkdirSync(runDir, { recursive: true });
|
|
354
364
|
try {
|
|
355
|
-
//
|
|
365
|
+
// Step 1: .kt β screen.klib
|
|
356
366
|
const screenKlib = path.join(runDir, 'screen.klib');
|
|
357
367
|
const args1 = path.join(runDir, 'step1.txt');
|
|
358
368
|
const libs1 = [this.stdlibKlib, stubsKlib].join(process.platform === 'win32' ? ';' : ':');
|
|
359
369
|
// Preprocess: strip Android imports, annotations, and types not available in JS
|
|
360
370
|
const preprocessedKtPath = this.preprocessFile(ktFilePath, runDir);
|
|
361
371
|
fs.writeFileSync(args1, [
|
|
362
|
-
'-ir-output-name',
|
|
363
|
-
'
|
|
364
|
-
'-
|
|
365
|
-
|
|
366
|
-
'-
|
|
372
|
+
'-ir-output-name',
|
|
373
|
+
'screen',
|
|
374
|
+
'-ir-output-dir',
|
|
375
|
+
runDir,
|
|
376
|
+
'-libraries',
|
|
377
|
+
libs1,
|
|
378
|
+
'-module-kind',
|
|
379
|
+
'es',
|
|
380
|
+
'-target',
|
|
381
|
+
'es2015',
|
|
367
382
|
'-Xir-produce-klib-file',
|
|
368
383
|
preprocessedKtPath,
|
|
369
384
|
].join('\n'), 'utf8');
|
|
370
385
|
const r1 = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${args1}`], {
|
|
371
|
-
shell: true,
|
|
386
|
+
shell: true,
|
|
387
|
+
encoding: 'utf8',
|
|
388
|
+
timeout: 120000,
|
|
372
389
|
});
|
|
373
390
|
if (!fs.existsSync(screenKlib)) {
|
|
374
|
-
const errLines = (r1.stderr || r1.stdout || '')
|
|
375
|
-
.
|
|
391
|
+
const errLines = (r1.stderr || r1.stdout || '')
|
|
392
|
+
.split('\n')
|
|
393
|
+
.filter((l) => l.includes('error:'))
|
|
394
|
+
.slice(0, 8)
|
|
395
|
+
.join('\n');
|
|
376
396
|
return { success: false, error: `Step 1 failed:\n${errLines}` };
|
|
377
397
|
}
|
|
378
|
-
//
|
|
398
|
+
// Step 2: screen.klib β screen.mjs
|
|
379
399
|
const outDir = path.join(runDir, 'out');
|
|
380
400
|
fs.mkdirSync(outDir, { recursive: true });
|
|
381
401
|
const args2 = path.join(runDir, 'step2.txt');
|
|
382
402
|
fs.writeFileSync(args2, [
|
|
383
|
-
'-ir-output-name',
|
|
384
|
-
'
|
|
385
|
-
'-
|
|
386
|
-
|
|
387
|
-
'-
|
|
403
|
+
'-ir-output-name',
|
|
404
|
+
'screen',
|
|
405
|
+
'-ir-output-dir',
|
|
406
|
+
outDir,
|
|
407
|
+
'-libraries',
|
|
408
|
+
libs1,
|
|
409
|
+
'-module-kind',
|
|
410
|
+
'es',
|
|
411
|
+
'-target',
|
|
412
|
+
'es2015',
|
|
388
413
|
'-Xir-produce-js',
|
|
389
414
|
`-Xinclude=${screenKlib}`,
|
|
390
415
|
].join('\n'), 'utf8');
|
|
391
416
|
const r2 = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${args2}`], {
|
|
392
|
-
shell: true,
|
|
417
|
+
shell: true,
|
|
418
|
+
encoding: 'utf8',
|
|
419
|
+
timeout: 120000,
|
|
393
420
|
});
|
|
394
421
|
const mjsPath = path.join(outDir, 'screen.mjs');
|
|
395
422
|
if (!fs.existsSync(mjsPath)) {
|
|
396
|
-
const errLines = (r2.stderr || r2.stdout || '')
|
|
397
|
-
.
|
|
423
|
+
const errLines = (r2.stderr || r2.stdout || '')
|
|
424
|
+
.split('\n')
|
|
425
|
+
.filter((l) => !l.startsWith('warning:') && l.trim())
|
|
426
|
+
.slice(0, 8)
|
|
427
|
+
.join('\n');
|
|
398
428
|
return { success: false, error: `Step 2 failed:\n${errLines}` };
|
|
399
429
|
}
|
|
400
430
|
// Detect the screen function name from source
|
|
@@ -405,7 +435,13 @@ class JsCompilerService {
|
|
|
405
435
|
const jsBase64 = mjsBytes.toString('base64');
|
|
406
436
|
const elapsed = Date.now() - t0;
|
|
407
437
|
(0, logger_1.log)(`[JsCompiler] β
${path.basename(ktFilePath)} β JS in ${elapsed}ms (${mjsBytes.length} bytes)`);
|
|
408
|
-
return {
|
|
438
|
+
return {
|
|
439
|
+
success: true,
|
|
440
|
+
jsBase64,
|
|
441
|
+
byteSize: mjsBytes.length,
|
|
442
|
+
compileTimeMs: elapsed,
|
|
443
|
+
screenFunctionName,
|
|
444
|
+
};
|
|
409
445
|
}
|
|
410
446
|
finally {
|
|
411
447
|
try {
|
|
@@ -416,6 +452,6 @@ class JsCompilerService {
|
|
|
416
452
|
}
|
|
417
453
|
}
|
|
418
454
|
exports.JsCompilerService = JsCompilerService;
|
|
419
|
-
//
|
|
420
|
-
const COMPOSE_STUBS = "package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js(\"({type:'root',children:[]})\")\n val stack: dynamic = js(\"([])\")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js(\"({type:'slot',children:[]})\")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js(\"({})\")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = \"top\"; val Bottom = \"bottom\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val SpaceBetween = \"space-between\"; val SpaceEvenly = \"space-evenly\"; val SpaceAround = \"space-around\"\n fun spacedBy(dp: Int) = \"spacedBy(${\"$\"}dp)\"\n fun spacedBy(dp: Float) = \"spacedBy(${\"$\"}{dp.toInt()})\"\n}\nobject Alignment {\n val Top = \"top\"; val Bottom = \"bottom\"; val CenterVertically = \"centerVertically\"\n val CenterHorizontally = \"centerHorizontally\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val TopStart = \"topStart\"; val TopEnd = \"topEnd\"\n val BottomStart = \"bottomStart\"; val BottomEnd = \"bottomEnd\"\n val TopCenter = \"topCenter\"; val BottomCenter = \"bottomCenter\"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = \"#6750A4\"; val onPrimary = \"#FFFFFF\"\n val secondary = \"#625B71\"; val surface = \"#FFFBFE\"\n val background = \"#FFFBFE\"; val error = \"#B3261E\"\n val primaryContainer = \"#EADDFF\"; val secondaryContainer = \"#E8DEF8\"\n val surfaceVariant = \"#E7E0EC\"; val outline = \"#79747E\"\n val onBackground = \"#1C1B1F\"; val onSurface = \"#1C1B1F\"\n val tertiaryContainer = \"#FFD8E4\"; val inverseSurface = \"#313033\"\n val inverseOnSurface = \"#F4EFF4\"; val onPrimaryContainer = \"#21005D\"\n}\nclass Typography {\n val displayLarge = \"displayLarge\"; val displayMedium = \"displayMedium\"; val displaySmall = \"displaySmall\"\n val headlineLarge = \"headlineLarge\"; val headlineMedium = \"headlineMedium\"; val headlineSmall = \"headlineSmall\"\n val titleLarge = \"titleLarge\"; val titleMedium = \"titleMedium\"; val titleSmall = \"titleSmall\"\n val bodyLarge = \"bodyLarge\"; val bodyMedium = \"bodyMedium\"; val bodySmall = \"bodySmall\"\n val labelLarge = \"labelLarge\"; val labelMedium = \"labelMedium\"; val labelSmall = \"labelSmall\"\n}\nclass Shapes { val small=\"small\"; val medium=\"medium\"; val large=\"large\"; val extraLarge=\"extraLarge\" }\nobject FontWeight { val Bold=\"bold\"; val Normal=\"normal\"; val Light=\"light\"; val Medium=\"medium\"; val SemiBold=\"semibold\" }\nobject TextAlign { val Start=\"start\"; val Center=\"center\"; val End=\"end\"; val Justify=\"justify\" }\nobject ContentScale { val Crop=\"crop\"; val Fit=\"fit\"; val FillBounds=\"fillBounds\"; val FillWidth=\"fillWidth\" }\nobject Color {\n val White = \"#FFFFFF\"; val Black = \"#000000\"; val Transparent = \"transparent\"\n val Red = \"#F44336\"; val Blue = \"#2196F3\"; val Green = \"#4CAF50\"\n val Gray = \"#9E9E9E\"; val LightGray = \"#E0E0E0\"; val DarkGray = \"#424242\"\n val Unspecified = \"unspecified\"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = \"string_${\"$\"}id\"\nfun painterResource(id: Int): Any = js(\"({type:'resource',id:${\"$\"}id})\")\n\nobject Icons {\n object Default {\n val Add: Any = js(\"({icon:'add'})\")\n val Search: Any = js(\"({icon:'search'})\")\n val Close: Any = js(\"({icon:'close'})\")\n val Check: Any = js(\"({icon:'check'})\")\n val Delete: Any = js(\"({icon:'delete'})\")\n val Edit: Any = js(\"({icon:'edit'})\")\n val Home: Any = js(\"({icon:'home'})\")\n val Menu: Any = js(\"({icon:'menu'})\")\n val Settings: Any = js(\"({icon:'settings'})\")\n val ArrowBack: Any = js(\"({icon:'arrow_back'})\")\n val MoreVert: Any = js(\"({icon:'more_vert'})\")\n val Favorite: Any = js(\"({icon:'favorite'})\")\n val Share: Any = js(\"({icon:'share'})\")\n val Info: Any = js(\"({icon:'info'})\")\n val Person: Any = js(\"({icon:'person'})\")\n val Star: Any = js(\"({icon:'star'})\")\n val Notifications: Any = js(\"({icon:'notifications'})\")\n val Email: Any = js(\"({icon:'email'})\")\n val Phone: Any = js(\"({icon:'phone'})\")\n val Lock: Any = js(\"({icon:'lock'})\")\n val Visibility: Any = js(\"({icon:'visibility'})\")\n val VisibilityOff: Any = js(\"({icon:'visibility_off'})\")\n val ShoppingCart: Any = js(\"({icon:'shopping_cart'})\")\n val KeyboardArrowDown: Any = js(\"({icon:'keyboard_arrow_down'})\")\n val KeyboardArrowUp: Any = js(\"({icon:'keyboard_arrow_up'})\")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ββ Layout composables ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any=\"\", horizontalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Column',children:[]})\")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any=\"\", verticalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Row',children:[]})\")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any=\"topStart\", content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'Box',children:[]})\")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any=\"\", color: Any=\"\", fontSize: Any=\"\", fontWeight: Any=\"\", textAlign: Any=\"\", maxLines: Int=Int.MAX_VALUE, overflow: Any=\"\") {\n val n: dynamic = js(\"({type:'Text',children:[]})\")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) \"bodyMedium\" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any=\"\", colors: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Button',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'TextButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'FilledTonalButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'FloatingActionButton',children:[]})\")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js(\"({type:'ExtendedFAB',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'IconButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", colors: Any=\"\", elevation: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Card',children:[]})\")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any=\"\", content: (Any)->Unit) {\n val n: dynamic = js(\"({type:'Scaffold',children:[],topBar:[],bottomBar:[],fab:[]})\")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js(\"({})\")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") {\n val n: dynamic = js(\"({type:'TopAppBar',children:[],title:[],actions:[]})\")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'NavigationBar',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'NavigationBarItem',children:[],label:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'OutlinedTextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'TextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'SearchBar',children:[]})\")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", verticalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyColumn',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyRow',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", verticalArrangement: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalStaggeredGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any=\"\") {\n val n: dynamic = js(\"({type:'Icon',children:[]})\")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else \"default\"\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js(\"({type:'Image',children:[]})\")\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'Spacer',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") {\n val n: dynamic = js(\"({type:'Divider',children:[]})\")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Switch',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Checkbox',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any=\"\", strokeWidth: Int=4) {\n val n: dynamic = js(\"({type:'CircularProgressIndicator',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any=\"\") {\n val n: dynamic = js(\"({type:'LinearProgressIndicator',children:[]})\")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'assist',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'filter',children:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'suggestion',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'AlertDialog',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})\")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js(\"({type:'DropdownMenu',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js(\"({type:'DropdownMenuItem',children:[]})\")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js(\"({})\")\nfun rememberScrollState(): Any = js(\"({})\")\nfun SnackbarHostState(): Any = js(\"({})\")\nfun rememberSnackbarHostState(): Any = js(\"({})\")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js(\"({})\")\nobject KeyboardOptions { val Default = js(\"({})\") }\nobject KeyboardActions { val Default = js(\"({})\") }\nobject ImeAction { val Done=\"done\"; val Search=\"search\"; val Go=\"go\"; val Next=\"next\" }\nobject KeyboardType { val Text=\"text\"; val Number=\"number\"; val Email=\"email\"; val Password=\"password\" }\nfun PaddingValues(all: Int=0): Any = js(\"({})\")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js(\"({})\")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js(\"({})\")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ββ Entry point called by the browser ββββββββββββββββββββββββββββββββββββββββ\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}";
|
|
455
|
+
// Compose shim stubs (validated against kotlinc-js 2.3.0)
|
|
456
|
+
const COMPOSE_STUBS = 'package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js("({type:\'root\',children:[]})")\n val stack: dynamic = js("([])")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js("({type:\'slot\',children:[]})")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js("({})")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = "top"; val Bottom = "bottom"; val Center = "center"\n val Start = "start"; val End = "end"\n val SpaceBetween = "space-between"; val SpaceEvenly = "space-evenly"; val SpaceAround = "space-around"\n fun spacedBy(dp: Int) = "spacedBy(${"$"}dp)"\n fun spacedBy(dp: Float) = "spacedBy(${"$"}{dp.toInt()})"\n}\nobject Alignment {\n val Top = "top"; val Bottom = "bottom"; val CenterVertically = "centerVertically"\n val CenterHorizontally = "centerHorizontally"; val Center = "center"\n val Start = "start"; val End = "end"\n val TopStart = "topStart"; val TopEnd = "topEnd"\n val BottomStart = "bottomStart"; val BottomEnd = "bottomEnd"\n val TopCenter = "topCenter"; val BottomCenter = "bottomCenter"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = "#6750A4"; val onPrimary = "#FFFFFF"\n val secondary = "#625B71"; val surface = "#FFFBFE"\n val background = "#FFFBFE"; val error = "#B3261E"\n val primaryContainer = "#EADDFF"; val secondaryContainer = "#E8DEF8"\n val surfaceVariant = "#E7E0EC"; val outline = "#79747E"\n val onBackground = "#1C1B1F"; val onSurface = "#1C1B1F"\n val tertiaryContainer = "#FFD8E4"; val inverseSurface = "#313033"\n val inverseOnSurface = "#F4EFF4"; val onPrimaryContainer = "#21005D"\n}\nclass Typography {\n val displayLarge = "displayLarge"; val displayMedium = "displayMedium"; val displaySmall = "displaySmall"\n val headlineLarge = "headlineLarge"; val headlineMedium = "headlineMedium"; val headlineSmall = "headlineSmall"\n val titleLarge = "titleLarge"; val titleMedium = "titleMedium"; val titleSmall = "titleSmall"\n val bodyLarge = "bodyLarge"; val bodyMedium = "bodyMedium"; val bodySmall = "bodySmall"\n val labelLarge = "labelLarge"; val labelMedium = "labelMedium"; val labelSmall = "labelSmall"\n}\nclass Shapes { val small="small"; val medium="medium"; val large="large"; val extraLarge="extraLarge" }\nobject FontWeight { val Bold="bold"; val Normal="normal"; val Light="light"; val Medium="medium"; val SemiBold="semibold" }\nobject TextAlign { val Start="start"; val Center="center"; val End="end"; val Justify="justify" }\nobject ContentScale { val Crop="crop"; val Fit="fit"; val FillBounds="fillBounds"; val FillWidth="fillWidth" }\nobject Color {\n val White = "#FFFFFF"; val Black = "#000000"; val Transparent = "transparent"\n val Red = "#F44336"; val Blue = "#2196F3"; val Green = "#4CAF50"\n val Gray = "#9E9E9E"; val LightGray = "#E0E0E0"; val DarkGray = "#424242"\n val Unspecified = "unspecified"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = "string_${"$"}id"\nfun painterResource(id: Int): Any = js("({type:\'resource\',id:${"$"}id})")\n\nobject Icons {\n object Default {\n val Add: Any = js("({icon:\'add\'})")\n val Search: Any = js("({icon:\'search\'})")\n val Close: Any = js("({icon:\'close\'})")\n val Check: Any = js("({icon:\'check\'})")\n val Delete: Any = js("({icon:\'delete\'})")\n val Edit: Any = js("({icon:\'edit\'})")\n val Home: Any = js("({icon:\'home\'})")\n val Menu: Any = js("({icon:\'menu\'})")\n val Settings: Any = js("({icon:\'settings\'})")\n val ArrowBack: Any = js("({icon:\'arrow_back\'})")\n val MoreVert: Any = js("({icon:\'more_vert\'})")\n val Favorite: Any = js("({icon:\'favorite\'})")\n val Share: Any = js("({icon:\'share\'})")\n val Info: Any = js("({icon:\'info\'})")\n val Person: Any = js("({icon:\'person\'})")\n val Star: Any = js("({icon:\'star\'})")\n val Notifications: Any = js("({icon:\'notifications\'})")\n val Email: Any = js("({icon:\'email\'})")\n val Phone: Any = js("({icon:\'phone\'})")\n val Lock: Any = js("({icon:\'lock\'})")\n val Visibility: Any = js("({icon:\'visibility\'})")\n val VisibilityOff: Any = js("({icon:\'visibility_off\'})")\n val ShoppingCart: Any = js("({icon:\'shopping_cart\'})")\n val KeyboardArrowDown: Any = js("({icon:\'keyboard_arrow_down\'})")\n val KeyboardArrowUp: Any = js("({icon:\'keyboard_arrow_up\'})")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ββ Layout composables ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any="", horizontalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Column\',children:[]})")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any="", verticalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Row\',children:[]})")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any="topStart", content: ()->Unit={}) {\n val n: dynamic = js("({type:\'Box\',children:[]})")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any="", color: Any="", fontSize: Any="", fontWeight: Any="", textAlign: Any="", maxLines: Int=Int.MAX_VALUE, overflow: Any="") {\n val n: dynamic = js("({type:\'Text\',children:[]})")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) "bodyMedium" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any="", colors: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Button\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'TextButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'FilledTonalButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'FloatingActionButton\',children:[]})")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js("({type:\'ExtendedFAB\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'IconButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", colors: Any="", elevation: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Card\',children:[]})")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any="", content: (Any)->Unit) {\n val n: dynamic = js("({type:\'Scaffold\',children:[],topBar:[],bottomBar:[],fab:[]})")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js("({})")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") {\n val n: dynamic = js("({type:\'TopAppBar\',children:[],title:[],actions:[]})")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'NavigationBar\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'NavigationBarItem\',children:[],label:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'OutlinedTextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'TextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js("({type:\'SearchBar\',children:[]})")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", verticalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyColumn\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyRow\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", verticalArrangement: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalStaggeredGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any="") {\n val n: dynamic = js("({type:\'Icon\',children:[]})")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else "default"\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js("({type:\'Image\',children:[]})")\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'Spacer\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") {\n val n: dynamic = js("({type:\'Divider\',children:[]})")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Switch\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Checkbox\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any="", strokeWidth: Int=4) {\n val n: dynamic = js("({type:\'CircularProgressIndicator\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any="") {\n val n: dynamic = js("({type:\'LinearProgressIndicator\',children:[]})")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'assist\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'filter\',children:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'suggestion\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'AlertDialog\',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js("({type:\'DropdownMenu\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js("({type:\'DropdownMenuItem\',children:[]})")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js("({})")\nfun rememberScrollState(): Any = js("({})")\nfun SnackbarHostState(): Any = js("({})")\nfun rememberSnackbarHostState(): Any = js("({})")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js("({})")\nobject KeyboardOptions { val Default = js("({})") }\nobject KeyboardActions { val Default = js("({})") }\nobject ImeAction { val Done="done"; val Search="search"; val Go="go"; val Next="next" }\nobject KeyboardType { val Text="text"; val Number="number"; val Email="email"; val Password="password" }\nfun PaddingValues(all: Int=0): Any = js("({})")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js("({})")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js("({})")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ββ Entry point called by the browser ββββββββββββββββββββββββββββββββββββββββ\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}';
|
|
421
457
|
//# sourceMappingURL=js-compiler-service.js.map
|
package/dist/server/index.d.ts
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Main Server Entry Point
|
|
4
|
-
* Starts HTTP and WebSocket servers
|
|
5
|
-
*/
|
|
6
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
3
|
if (k2 === undefined) k2 = k;
|
|
8
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -38,6 +34,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
34
|
})();
|
|
39
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
36
|
exports.JetStartServer = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* Main Server Entry Point
|
|
39
|
+
* Starts HTTP and WebSocket servers
|
|
40
|
+
*/
|
|
41
|
+
require("dotenv/config");
|
|
41
42
|
const path = __importStar(require("path"));
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const os = __importStar(require("os"));
|
|
@@ -148,8 +149,8 @@ class JetStartServer extends events_1.EventEmitter {
|
|
|
148
149
|
port: this.config.wsPort,
|
|
149
150
|
logsServer: this.logsServer,
|
|
150
151
|
adbHelper: this.adbHelper, // Enable wireless ADB auto-connect
|
|
151
|
-
expectedSessionId: this.currentSession?.id,
|
|
152
|
-
expectedToken: this.currentSession?.token,
|
|
152
|
+
expectedSessionId: this.currentSession?.id,
|
|
153
|
+
expectedToken: this.currentSession?.token,
|
|
153
154
|
projectName: this.config.projectName,
|
|
154
155
|
onClientConnected: async (sessionId) => {
|
|
155
156
|
(0, logger_1.log)(`Client connected (session: ${sessionId}). Triggering initial build...`);
|
|
@@ -243,7 +244,7 @@ class JetStartServer extends events_1.EventEmitter {
|
|
|
243
244
|
if (this.wsHandler && this.currentSession) {
|
|
244
245
|
// Store the APK path
|
|
245
246
|
this.latestApkPath = result.apkPath || null;
|
|
246
|
-
// Send full download URL to client
|
|
247
|
+
// Send full download URL to client
|
|
247
248
|
const downloadUrl = `http://${this.config.displayHost}:${this.config.httpPort}/download/app.apk`;
|
|
248
249
|
this.wsHandler.sendBuildComplete(this.currentSession.id, downloadUrl);
|
|
249
250
|
(0, logger_1.log)(`APK download URL: ${downloadUrl}`);
|
|
@@ -265,7 +266,9 @@ class JetStartServer extends events_1.EventEmitter {
|
|
|
265
266
|
appPackageName = cfg.packageName || appPackageName;
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
|
-
catch (
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.log("couldnt find package name");
|
|
271
|
+
}
|
|
269
272
|
await this.adbHelper.launchApp(appPackageName, '.MainActivity');
|
|
270
273
|
}
|
|
271
274
|
else {
|
|
@@ -320,7 +323,7 @@ class JetStartServer extends events_1.EventEmitter {
|
|
|
320
323
|
const result = await this.buildService.build({
|
|
321
324
|
projectPath: this.config.projectPath,
|
|
322
325
|
outputPath: path.join(this.config.projectPath, 'build/outputs/apk'),
|
|
323
|
-
buildType: 'debug',
|
|
326
|
+
buildType: 'debug',
|
|
324
327
|
minifyEnabled: false,
|
|
325
328
|
debuggable: true,
|
|
326
329
|
versionCode: 1,
|
|
@@ -13,8 +13,35 @@ const cors_1 = __importDefault(require("cors"));
|
|
|
13
13
|
const logger_1 = require("../utils/logger");
|
|
14
14
|
function setupMiddleware(app) {
|
|
15
15
|
// CORS
|
|
16
|
+
const isAllowedOrigin = (origin) => {
|
|
17
|
+
// Standard allowed origins
|
|
18
|
+
const allowedOrigins = [
|
|
19
|
+
'http://localhost:8000',
|
|
20
|
+
'http://localhost:3000',
|
|
21
|
+
'http://localhost:8765',
|
|
22
|
+
'http://localhost:8766',
|
|
23
|
+
'http://localhost:8767',
|
|
24
|
+
...(process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : [])
|
|
25
|
+
];
|
|
26
|
+
if (allowedOrigins.indexOf(origin) !== -1)
|
|
27
|
+
return true;
|
|
28
|
+
// Allow jetstart.site and any subdomains
|
|
29
|
+
if (origin === 'https://jetstart.site' || origin.endsWith('.jetstart.site'))
|
|
30
|
+
return true;
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
16
33
|
app.use((0, cors_1.default)({
|
|
17
|
-
origin:
|
|
34
|
+
origin: (origin, callback) => {
|
|
35
|
+
// Allow requests with no origin (like mobile apps or curl requests)
|
|
36
|
+
if (!origin)
|
|
37
|
+
return callback(null, true);
|
|
38
|
+
if (isAllowedOrigin(origin) || !process.env.IS_PROD) {
|
|
39
|
+
callback(null, true);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
callback(null, false);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
18
45
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
19
46
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
20
47
|
}));
|
package/dist/server/routes.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* HTTP Routes
|
|
4
|
-
* REST API endpoints
|
|
5
|
-
*/
|
|
6
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
4
|
};
|
|
@@ -16,6 +12,7 @@ const sessionManager = new session_1.SessionManager();
|
|
|
16
12
|
function setupRoutes(app, getLatestApk, getCurrentSession) {
|
|
17
13
|
// Root Redirect -> Web Emulator
|
|
18
14
|
app.get('/', (req, res) => {
|
|
15
|
+
const isProduction = process.env.IS_PROD;
|
|
19
16
|
try {
|
|
20
17
|
const session = getCurrentSession?.();
|
|
21
18
|
// If no session active, just show simple status
|
|
@@ -26,7 +23,7 @@ function setupRoutes(app, getLatestApk, getCurrentSession) {
|
|
|
26
23
|
`);
|
|
27
24
|
}
|
|
28
25
|
// Construct redirect URL
|
|
29
|
-
const webUrl = 'https://web.jetstart.site';
|
|
26
|
+
const webUrl = isProduction ? 'https://web.jetstart.site' : 'http://localhost:8000';
|
|
30
27
|
const host = req.hostname;
|
|
31
28
|
const port = req.socket.localPort || 8765;
|
|
32
29
|
const wsPort = shared_1.DEFAULT_WS_PORT;
|
|
@@ -39,7 +39,7 @@ export declare class WebSocketHandler {
|
|
|
39
39
|
* Send compiled KotlinβJS ES module to web emulator clients.
|
|
40
40
|
* The browser imports it dynamically and renders the Compose UI as HTML.
|
|
41
41
|
*/
|
|
42
|
-
sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number,
|
|
42
|
+
sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number, _screenFunctionName: string): void;
|
|
43
43
|
sendLogBroadcast(sessionId: string, logEntry: any): void;
|
|
44
44
|
}
|
|
45
45
|
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -76,13 +76,13 @@ class WebSocketHandler {
|
|
|
76
76
|
handleConnect(clientId, message) {
|
|
77
77
|
const incomingSession = message.sessionId;
|
|
78
78
|
const incomingToken = message.token;
|
|
79
|
-
//
|
|
79
|
+
// Session + token validation
|
|
80
80
|
// Only validate when the server has an expected session configured.
|
|
81
81
|
if (this.expectedSessionId) {
|
|
82
82
|
if (incomingSession !== this.expectedSessionId) {
|
|
83
83
|
(0, logger_1.error)(`Rejected client ${clientId}: wrong session "${incomingSession}" (expected "${this.expectedSessionId}")`);
|
|
84
84
|
(0, logger_1.error)('This device was built against a different jetstart dev session. Rescan the QR code.');
|
|
85
|
-
// Close the WebSocket immediately β
|
|
85
|
+
// Close the WebSocket immediately β does not accept this client.
|
|
86
86
|
const ws = this.connectionManager.getConnection(clientId);
|
|
87
87
|
if (ws) {
|
|
88
88
|
ws.close(4001, 'Session mismatch β rescan QR code');
|
|
@@ -100,7 +100,7 @@ class WebSocketHandler {
|
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
//
|
|
103
|
+
// Accepted
|
|
104
104
|
(0, logger_1.log)(`Client accepted (session: ${incomingSession})`);
|
|
105
105
|
this.connectionManager.setClientSession(clientId, incomingSession);
|
|
106
106
|
const response = {
|
|
@@ -160,7 +160,7 @@ class WebSocketHandler {
|
|
|
160
160
|
* Send compiled KotlinβJS ES module to web emulator clients.
|
|
161
161
|
* The browser imports it dynamically and renders the Compose UI as HTML.
|
|
162
162
|
*/
|
|
163
|
-
sendJsUpdate(sessionId, jsBase64, sourceFile, byteSize,
|
|
163
|
+
sendJsUpdate(sessionId, jsBase64, sourceFile, byteSize, _screenFunctionName) {
|
|
164
164
|
const message = {
|
|
165
165
|
type: 'core:js-update',
|
|
166
166
|
timestamp: Date.now(),
|
|
@@ -14,11 +14,6 @@ export declare class ConnectionManager {
|
|
|
14
14
|
*/
|
|
15
15
|
setClientSession(clientId: string, sessionId: string): void;
|
|
16
16
|
sendToClient(clientId: string, message: CoreMessage): boolean;
|
|
17
|
-
/**
|
|
18
|
-
* Broadcast to all clients (UNSAFE - use broadcastToSession instead)
|
|
19
|
-
* @deprecated Use broadcastToSession for session isolation
|
|
20
|
-
*/
|
|
21
|
-
broadcast(message: CoreMessage): void;
|
|
22
17
|
/**
|
|
23
18
|
* Broadcast to all clients in a specific session (SECURE)
|
|
24
19
|
*/
|
|
@@ -46,14 +46,6 @@ class ConnectionManager {
|
|
|
46
46
|
return false;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Broadcast to all clients (UNSAFE - use broadcastToSession instead)
|
|
51
|
-
* @deprecated Use broadcastToSession for session isolation
|
|
52
|
-
*/
|
|
53
|
-
broadcast(message) {
|
|
54
|
-
console.warn('[ConnectionManager] WARNING: Using unsafe broadcast() - use broadcastToSession() instead');
|
|
55
|
-
this.broadcastToAll(message);
|
|
56
|
-
}
|
|
57
49
|
/**
|
|
58
50
|
* Broadcast to all clients in a specific session (SECURE)
|
|
59
51
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetstart/core",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Build server and orchestration for JetStart",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://github.com/dev-phantom/jetstart#readme",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@jetstart/shared": "^2.0
|
|
37
|
-
"@jetstart/logs": "^2.0
|
|
36
|
+
"@jetstart/shared": "^2.1.0",
|
|
37
|
+
"@jetstart/logs": "^2.1.0",
|
|
38
38
|
"express": "^4.18.2",
|
|
39
39
|
"ws": "^8.14.2",
|
|
40
40
|
"chokidar": "^3.5.3",
|
|
@@ -42,7 +42,7 @@ export class HotReloadService {
|
|
|
42
42
|
|
|
43
43
|
log(`π₯ Hot reload starting for: ${path.basename(filePath)}`);
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// Compile Kotlin to .class
|
|
46
46
|
const compileStart = Date.now();
|
|
47
47
|
const compileResult = await this.kotlinCompiler.compileFile(filePath);
|
|
48
48
|
const compileTime = Date.now() - compileStart;
|
|
@@ -61,7 +61,7 @@ export class HotReloadService {
|
|
|
61
61
|
|
|
62
62
|
log(`Compilation completed in ${compileTime}ms (${compileResult.classFiles.length} classes)`);
|
|
63
63
|
|
|
64
|
-
//
|
|
64
|
+
// Generate $Override classes (Phase 2)
|
|
65
65
|
const overrideDir = path.join(os.tmpdir(), 'jetstart-overrides', Date.now().toString());
|
|
66
66
|
fs.mkdirSync(overrideDir, { recursive: true });
|
|
67
67
|
|
|
@@ -95,7 +95,7 @@ export class HotReloadService {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
//
|
|
98
|
+
// Convert .class to .dex
|
|
99
99
|
const dexStart = Date.now();
|
|
100
100
|
const dexResult = await this.dexGenerator.generateDex(compileResult.classFiles);
|
|
101
101
|
const dexTime = Date.now() - dexStart;
|
|
@@ -46,7 +46,10 @@ export class JsCompilerService {
|
|
|
46
46
|
path.join(process.env.PATH?.split(isWin ? ';' : ':')[0] || '', `kotlinc-js${ext}`),
|
|
47
47
|
];
|
|
48
48
|
for (const c of candidates) {
|
|
49
|
-
if (c && fs.existsSync(c)) {
|
|
49
|
+
if (c && fs.existsSync(c)) {
|
|
50
|
+
this.kotlincJsPath = c;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
50
53
|
}
|
|
51
54
|
if (!this.kotlincJsPath) {
|
|
52
55
|
warn('[JsCompiler] kotlinc-js not found β web live preview disabled');
|
|
@@ -83,19 +86,30 @@ export class JsCompilerService {
|
|
|
83
86
|
const outKlib = path.join(this.workDir, 'compose_stubs.klib');
|
|
84
87
|
const argFile = path.join(this.workDir, 'stubs-args.txt');
|
|
85
88
|
|
|
86
|
-
fs.writeFileSync(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
fs.writeFileSync(
|
|
90
|
+
argFile,
|
|
91
|
+
[
|
|
92
|
+
'-ir-output-name',
|
|
93
|
+
'compose_stubs',
|
|
94
|
+
'-ir-output-dir',
|
|
95
|
+
this.workDir,
|
|
96
|
+
'-libraries',
|
|
97
|
+
this.stdlibKlib!,
|
|
98
|
+
'-module-kind',
|
|
99
|
+
'es',
|
|
100
|
+
'-target',
|
|
101
|
+
'es2015',
|
|
102
|
+
'-Xir-produce-klib-file',
|
|
103
|
+
src,
|
|
104
|
+
].join('\n'),
|
|
105
|
+
'utf8'
|
|
106
|
+
);
|
|
95
107
|
|
|
96
108
|
log('[JsCompiler] Building Compose shims klib (one-time, ~30s)...');
|
|
97
109
|
const r = spawnSync(this.kotlincJsPath!, [`@${argFile}`], {
|
|
98
|
-
shell: true,
|
|
110
|
+
shell: true,
|
|
111
|
+
encoding: 'utf8',
|
|
112
|
+
timeout: 120000,
|
|
99
113
|
});
|
|
100
114
|
|
|
101
115
|
if ((r.status === 0 || !r.stderr?.includes('error:')) && fs.existsSync(outKlib)) {
|
|
@@ -181,7 +195,7 @@ export class JsCompilerService {
|
|
|
181
195
|
*/
|
|
182
196
|
private removeElseBlockIf(text: string, predicate: (body: string) => boolean): string {
|
|
183
197
|
const re = /\}\s*else\s*\{([^{}]|\{[^{}]*\})*\}/gs;
|
|
184
|
-
return text.replace(re, (match) => predicate(match) ? '}' : match);
|
|
198
|
+
return text.replace(re, (match) => (predicate(match) ? '}' : match));
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
/**
|
|
@@ -195,7 +209,7 @@ export class JsCompilerService {
|
|
|
195
209
|
private preprocessFile(ktFilePath: string, runDir: string): string {
|
|
196
210
|
let text = fs.readFileSync(ktFilePath, 'utf8');
|
|
197
211
|
|
|
198
|
-
//
|
|
212
|
+
// Pass 1: Whole-file transformations
|
|
199
213
|
// Imports / package
|
|
200
214
|
text = text.replace(/^import\s+.*/gm, '');
|
|
201
215
|
text = text.replace(/^package\s+.*/gm, '');
|
|
@@ -222,24 +236,36 @@ export class JsCompilerService {
|
|
|
222
236
|
text = text.replace(/\bviewModel\.\w+\b/g, 'Unit');
|
|
223
237
|
// Delegate by remember
|
|
224
238
|
text = text.replace(/\bval\s+(\w+)\s+by\s+remember\s*\{[^}]+\}/g, 'val $1 = ""');
|
|
225
|
-
text = text.replace(
|
|
239
|
+
text = text.replace(
|
|
240
|
+
/\bvar\s+(\w+)\s+by\s+remember\s*\{\s*mutableStateOf\((false|true)\)\s*\}/g,
|
|
241
|
+
'var $1: Boolean = $2'
|
|
242
|
+
);
|
|
226
243
|
text = text.replace(/\bvar\s+(\w+)\s+by\s+remember\s*\{[^}]+\}/g, 'var $1 = ""');
|
|
227
244
|
// Data class fields
|
|
228
245
|
text = text.replace(/\bnote\.content\b/g, '"Note content"');
|
|
229
246
|
text = text.replace(/\bnote\.\w+\b/g, '"preview"');
|
|
230
247
|
text = text.replace(/\b(\w+)\s*:\s*Note\b/g, '$1: String = "Note"');
|
|
231
248
|
// Preview lists
|
|
232
|
-
text = text.replace(
|
|
249
|
+
text = text.replace(
|
|
250
|
+
/\bval\s+suggestedTags\b[^\n]*/g,
|
|
251
|
+
'val suggestedTags: List<String> = listOf()'
|
|
252
|
+
);
|
|
233
253
|
text = text.replace(/suggestedTags\.isEmpty\(\)/g, 'true');
|
|
234
254
|
text = text.replace(/suggestedTags\.isNotEmpty\(\)/g, 'false');
|
|
235
|
-
text = text.replace(
|
|
236
|
-
|
|
255
|
+
text = text.replace(
|
|
256
|
+
/\bval\s+searchResults\b[^\n]*/g,
|
|
257
|
+
'val searchResults: List<String> = listOf("Note 1", "Note 2", "Note 3")'
|
|
258
|
+
);
|
|
237
259
|
// Named M3 colors
|
|
238
260
|
const colorMap: Record<string, string> = {
|
|
239
|
-
onSurfaceVariant: '"#49454F"',
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
261
|
+
onSurfaceVariant: '"#49454F"',
|
|
262
|
+
onBackground: '"#1C1B1F"',
|
|
263
|
+
outline: '"#79747E"',
|
|
264
|
+
surfaceVariant: '"#E7E0EC"',
|
|
265
|
+
onPrimaryContainer: '"#21005D"',
|
|
266
|
+
inverseOnSurface: '"#F4EFF4"',
|
|
267
|
+
inverseSurface: '"#313033"',
|
|
268
|
+
tertiaryContainer: '"#FFD8E4"',
|
|
243
269
|
};
|
|
244
270
|
for (const [k, v] of Object.entries(colorMap)) {
|
|
245
271
|
text = text.replace(new RegExp(`MaterialTheme\\.colorScheme\\.${k}\\b`, 'g'), v);
|
|
@@ -254,15 +280,24 @@ export class JsCompilerService {
|
|
|
254
280
|
text = text.replace(/PaddingValues\((\d+)\)/g, '$1');
|
|
255
281
|
text = text.replace(/PaddingValues\([^)]*\)/g, '0');
|
|
256
282
|
// Named padding β single value
|
|
257
|
-
text = text.replace(
|
|
283
|
+
text = text.replace(
|
|
284
|
+
/\.padding\(\s*(?:start|left|horizontal)\s*=\s*(\d+)[^)]*\)/g,
|
|
285
|
+
'.padding($1)'
|
|
286
|
+
);
|
|
258
287
|
text = text.replace(/\.padding\(\s*(?:top|vertical)\s*=\s*(\d+)[^)]*\)/g, '.padding($1)');
|
|
259
288
|
text = text.replace(/\.padding\(padding\)/g, '.padding(0)');
|
|
260
289
|
// Shape removal
|
|
261
|
-
text = text.replace(
|
|
290
|
+
text = text.replace(
|
|
291
|
+
/,?\s*shape\s*=\s*(?:MaterialTheme\.shapes\.\w+|RoundedCornerShape\([^)]*\)|CircleShape)/g,
|
|
292
|
+
''
|
|
293
|
+
);
|
|
262
294
|
// Modifier
|
|
263
295
|
text = text.replace(/\bModifier\b(?!\(\)|[\w])/g, 'Modifier()');
|
|
264
296
|
text = text.replace(/\bModifier\(\)\s*\n\s*\./g, 'Modifier().');
|
|
265
|
-
text = text.replace(
|
|
297
|
+
text = text.replace(
|
|
298
|
+
/\)\s*\n\s*\.(fill|padding|height|width|size|weight|background|clip|alpha|wrap)/g,
|
|
299
|
+
').$1'
|
|
300
|
+
);
|
|
266
301
|
// horizontalArrangement not in staggered grid stubs
|
|
267
302
|
text = this.removeHorizontalArrangementOutsideRow(text);
|
|
268
303
|
// Scroll
|
|
@@ -286,7 +321,7 @@ export class JsCompilerService {
|
|
|
286
321
|
text = text.replace(/StaggeredGridCells\.Fixed\((\d+)\)\),/g, 'StaggeredGridCells.Fixed($1),');
|
|
287
322
|
text = text.replace(/modifier\s*=\s*Modifier\(\)\),/g, 'modifier = Modifier(),');
|
|
288
323
|
|
|
289
|
-
//
|
|
324
|
+
// Pass 2: Brace-balanced block removal
|
|
290
325
|
// LaunchedEffect / SideEffect / DisposableEffect
|
|
291
326
|
for (const kw of ['LaunchedEffect', 'DisposableEffect', 'SideEffect']) {
|
|
292
327
|
text = this.removeBalancedBlock(text, new RegExp(`\\b${kw}\\b[^{]*\\{`, 'g'));
|
|
@@ -300,18 +335,18 @@ export class JsCompilerService {
|
|
|
300
335
|
// if (X.isNotEmpty()) { ... } β generic tag guards
|
|
301
336
|
text = this.removeBalancedBlock(text, /if\s*\(\s*\w+\.isNotEmpty\(\)\s*\)\s*\{/g);
|
|
302
337
|
// Remove else blocks containing broken/removed suggestedTags code
|
|
303
|
-
text = this.removeElseBlockIf(
|
|
304
|
-
|
|
338
|
+
text = this.removeElseBlockIf(
|
|
339
|
+
text,
|
|
340
|
+
(body) => body.includes('suggestedTags') || body.includes('Modifier()),')
|
|
305
341
|
);
|
|
306
342
|
|
|
307
|
-
//
|
|
343
|
+
// Pass 3: Final cleanup
|
|
308
344
|
text = text.replace(/\n{3,}/g, '\n\n');
|
|
309
345
|
text = text.replace(/,\s*\)/g, ')');
|
|
310
346
|
text = text.replace(/\(\s*,/g, '(');
|
|
311
347
|
|
|
312
348
|
// Detect the screen function name to wrap the entry point
|
|
313
|
-
const screenMatch = text.match(/^fun\s+(\w+Screen)/m) ||
|
|
314
|
-
text.match(/^fun\s+(\w+)/m);
|
|
349
|
+
const screenMatch = text.match(/^fun\s+(\w+Screen)/m) || text.match(/^fun\s+(\w+)/m);
|
|
315
350
|
const screenName = screenMatch ? screenMatch[1] : null;
|
|
316
351
|
const renderWrapper = screenName
|
|
317
352
|
? `\n\n@OptIn(kotlin.js.ExperimentalJsExport::class)\n@JsExport\nfun __jetstart_render__(): dynamic = renderScreen { ${screenName}() }\n`
|
|
@@ -323,7 +358,6 @@ export class JsCompilerService {
|
|
|
323
358
|
return preprocessedPath;
|
|
324
359
|
}
|
|
325
360
|
|
|
326
|
-
|
|
327
361
|
async compile(ktFilePath: string): Promise<JsCompileResult> {
|
|
328
362
|
if (!this.isAvailable()) return { success: false, error: 'kotlinc-js unavailable' };
|
|
329
363
|
|
|
@@ -336,7 +370,7 @@ export class JsCompilerService {
|
|
|
336
370
|
fs.mkdirSync(runDir, { recursive: true });
|
|
337
371
|
|
|
338
372
|
try {
|
|
339
|
-
//
|
|
373
|
+
// Step 1: .kt β screen.klib
|
|
340
374
|
const screenKlib = path.join(runDir, 'screen.klib');
|
|
341
375
|
const args1 = path.join(runDir, 'step1.txt');
|
|
342
376
|
const libs1 = [this.stdlibKlib!, stubsKlib].join(process.platform === 'win32' ? ';' : ':');
|
|
@@ -344,49 +378,77 @@ export class JsCompilerService {
|
|
|
344
378
|
// Preprocess: strip Android imports, annotations, and types not available in JS
|
|
345
379
|
const preprocessedKtPath = this.preprocessFile(ktFilePath, runDir);
|
|
346
380
|
|
|
347
|
-
fs.writeFileSync(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
381
|
+
fs.writeFileSync(
|
|
382
|
+
args1,
|
|
383
|
+
[
|
|
384
|
+
'-ir-output-name',
|
|
385
|
+
'screen',
|
|
386
|
+
'-ir-output-dir',
|
|
387
|
+
runDir,
|
|
388
|
+
'-libraries',
|
|
389
|
+
libs1,
|
|
390
|
+
'-module-kind',
|
|
391
|
+
'es',
|
|
392
|
+
'-target',
|
|
393
|
+
'es2015',
|
|
394
|
+
'-Xir-produce-klib-file',
|
|
395
|
+
preprocessedKtPath,
|
|
396
|
+
].join('\n'),
|
|
397
|
+
'utf8'
|
|
398
|
+
);
|
|
356
399
|
|
|
357
400
|
const r1 = spawnSync(this.kotlincJsPath!, [`@${args1}`], {
|
|
358
|
-
shell: true,
|
|
401
|
+
shell: true,
|
|
402
|
+
encoding: 'utf8',
|
|
403
|
+
timeout: 120000,
|
|
359
404
|
});
|
|
360
405
|
|
|
361
406
|
if (!fs.existsSync(screenKlib)) {
|
|
362
|
-
const errLines = (r1.stderr || r1.stdout || '')
|
|
363
|
-
.
|
|
407
|
+
const errLines = (r1.stderr || r1.stdout || '')
|
|
408
|
+
.split('\n')
|
|
409
|
+
.filter((l: string) => l.includes('error:'))
|
|
410
|
+
.slice(0, 8)
|
|
411
|
+
.join('\n');
|
|
364
412
|
return { success: false, error: `Step 1 failed:\n${errLines}` };
|
|
365
413
|
}
|
|
366
414
|
|
|
367
|
-
//
|
|
415
|
+
// Step 2: screen.klib β screen.mjs
|
|
368
416
|
const outDir = path.join(runDir, 'out');
|
|
369
417
|
fs.mkdirSync(outDir, { recursive: true });
|
|
370
418
|
const args2 = path.join(runDir, 'step2.txt');
|
|
371
419
|
|
|
372
|
-
fs.writeFileSync(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
420
|
+
fs.writeFileSync(
|
|
421
|
+
args2,
|
|
422
|
+
[
|
|
423
|
+
'-ir-output-name',
|
|
424
|
+
'screen',
|
|
425
|
+
'-ir-output-dir',
|
|
426
|
+
outDir,
|
|
427
|
+
'-libraries',
|
|
428
|
+
libs1,
|
|
429
|
+
'-module-kind',
|
|
430
|
+
'es',
|
|
431
|
+
'-target',
|
|
432
|
+
'es2015',
|
|
433
|
+
'-Xir-produce-js',
|
|
434
|
+
`-Xinclude=${screenKlib}`,
|
|
435
|
+
].join('\n'),
|
|
436
|
+
'utf8'
|
|
437
|
+
);
|
|
381
438
|
|
|
382
439
|
const r2 = spawnSync(this.kotlincJsPath!, [`@${args2}`], {
|
|
383
|
-
shell: true,
|
|
440
|
+
shell: true,
|
|
441
|
+
encoding: 'utf8',
|
|
442
|
+
timeout: 120000,
|
|
384
443
|
});
|
|
385
444
|
|
|
386
445
|
const mjsPath = path.join(outDir, 'screen.mjs');
|
|
387
446
|
if (!fs.existsSync(mjsPath)) {
|
|
388
|
-
const errLines = (r2.stderr || r2.stdout || '')
|
|
389
|
-
.
|
|
447
|
+
const errLines = (r2.stderr || r2.stdout || '')
|
|
448
|
+
.split('\n')
|
|
449
|
+
.filter((l: string) => !l.startsWith('warning:') && l.trim())
|
|
450
|
+
.slice(0, 8)
|
|
451
|
+
.join('\n');
|
|
390
452
|
return { success: false, error: `Step 2 failed:\n${errLines}` };
|
|
391
453
|
}
|
|
392
454
|
|
|
@@ -398,14 +460,25 @@ export class JsCompilerService {
|
|
|
398
460
|
const mjsBytes = fs.readFileSync(mjsPath);
|
|
399
461
|
const jsBase64 = mjsBytes.toString('base64');
|
|
400
462
|
const elapsed = Date.now() - t0;
|
|
401
|
-
log(
|
|
402
|
-
|
|
403
|
-
|
|
463
|
+
log(
|
|
464
|
+
`[JsCompiler] β
${path.basename(ktFilePath)} β JS in ${elapsed}ms (${mjsBytes.length} bytes)`
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
success: true,
|
|
469
|
+
jsBase64,
|
|
470
|
+
byteSize: mjsBytes.length,
|
|
471
|
+
compileTimeMs: elapsed,
|
|
472
|
+
screenFunctionName,
|
|
473
|
+
};
|
|
404
474
|
} finally {
|
|
405
|
-
try {
|
|
475
|
+
try {
|
|
476
|
+
fs.rmSync(runDir, { recursive: true, force: true });
|
|
477
|
+
} catch {}
|
|
406
478
|
}
|
|
407
479
|
}
|
|
408
480
|
}
|
|
409
481
|
|
|
410
|
-
//
|
|
411
|
-
const COMPOSE_STUBS = "package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js(\"({type:'root',children:[]})\")\n val stack: dynamic = js(\"([])\")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js(\"({type:'slot',children:[]})\")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js(\"({})\")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = \"top\"; val Bottom = \"bottom\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val SpaceBetween = \"space-between\"; val SpaceEvenly = \"space-evenly\"; val SpaceAround = \"space-around\"\n fun spacedBy(dp: Int) = \"spacedBy(${\"$\"}dp)\"\n fun spacedBy(dp: Float) = \"spacedBy(${\"$\"}{dp.toInt()})\"\n}\nobject Alignment {\n val Top = \"top\"; val Bottom = \"bottom\"; val CenterVertically = \"centerVertically\"\n val CenterHorizontally = \"centerHorizontally\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val TopStart = \"topStart\"; val TopEnd = \"topEnd\"\n val BottomStart = \"bottomStart\"; val BottomEnd = \"bottomEnd\"\n val TopCenter = \"topCenter\"; val BottomCenter = \"bottomCenter\"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = \"#6750A4\"; val onPrimary = \"#FFFFFF\"\n val secondary = \"#625B71\"; val surface = \"#FFFBFE\"\n val background = \"#FFFBFE\"; val error = \"#B3261E\"\n val primaryContainer = \"#EADDFF\"; val secondaryContainer = \"#E8DEF8\"\n val surfaceVariant = \"#E7E0EC\"; val outline = \"#79747E\"\n val onBackground = \"#1C1B1F\"; val onSurface = \"#1C1B1F\"\n val tertiaryContainer = \"#FFD8E4\"; val inverseSurface = \"#313033\"\n val inverseOnSurface = \"#F4EFF4\"; val onPrimaryContainer = \"#21005D\"\n}\nclass Typography {\n val displayLarge = \"displayLarge\"; val displayMedium = \"displayMedium\"; val displaySmall = \"displaySmall\"\n val headlineLarge = \"headlineLarge\"; val headlineMedium = \"headlineMedium\"; val headlineSmall = \"headlineSmall\"\n val titleLarge = \"titleLarge\"; val titleMedium = \"titleMedium\"; val titleSmall = \"titleSmall\"\n val bodyLarge = \"bodyLarge\"; val bodyMedium = \"bodyMedium\"; val bodySmall = \"bodySmall\"\n val labelLarge = \"labelLarge\"; val labelMedium = \"labelMedium\"; val labelSmall = \"labelSmall\"\n}\nclass Shapes { val small=\"small\"; val medium=\"medium\"; val large=\"large\"; val extraLarge=\"extraLarge\" }\nobject FontWeight { val Bold=\"bold\"; val Normal=\"normal\"; val Light=\"light\"; val Medium=\"medium\"; val SemiBold=\"semibold\" }\nobject TextAlign { val Start=\"start\"; val Center=\"center\"; val End=\"end\"; val Justify=\"justify\" }\nobject ContentScale { val Crop=\"crop\"; val Fit=\"fit\"; val FillBounds=\"fillBounds\"; val FillWidth=\"fillWidth\" }\nobject Color {\n val White = \"#FFFFFF\"; val Black = \"#000000\"; val Transparent = \"transparent\"\n val Red = \"#F44336\"; val Blue = \"#2196F3\"; val Green = \"#4CAF50\"\n val Gray = \"#9E9E9E\"; val LightGray = \"#E0E0E0\"; val DarkGray = \"#424242\"\n val Unspecified = \"unspecified\"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = \"string_${\"$\"}id\"\nfun painterResource(id: Int): Any = js(\"({type:'resource',id:${\"$\"}id})\")\n\nobject Icons {\n object Default {\n val Add: Any = js(\"({icon:'add'})\")\n val Search: Any = js(\"({icon:'search'})\")\n val Close: Any = js(\"({icon:'close'})\")\n val Check: Any = js(\"({icon:'check'})\")\n val Delete: Any = js(\"({icon:'delete'})\")\n val Edit: Any = js(\"({icon:'edit'})\")\n val Home: Any = js(\"({icon:'home'})\")\n val Menu: Any = js(\"({icon:'menu'})\")\n val Settings: Any = js(\"({icon:'settings'})\")\n val ArrowBack: Any = js(\"({icon:'arrow_back'})\")\n val MoreVert: Any = js(\"({icon:'more_vert'})\")\n val Favorite: Any = js(\"({icon:'favorite'})\")\n val Share: Any = js(\"({icon:'share'})\")\n val Info: Any = js(\"({icon:'info'})\")\n val Person: Any = js(\"({icon:'person'})\")\n val Star: Any = js(\"({icon:'star'})\")\n val Notifications: Any = js(\"({icon:'notifications'})\")\n val Email: Any = js(\"({icon:'email'})\")\n val Phone: Any = js(\"({icon:'phone'})\")\n val Lock: Any = js(\"({icon:'lock'})\")\n val Visibility: Any = js(\"({icon:'visibility'})\")\n val VisibilityOff: Any = js(\"({icon:'visibility_off'})\")\n val ShoppingCart: Any = js(\"({icon:'shopping_cart'})\")\n val KeyboardArrowDown: Any = js(\"({icon:'keyboard_arrow_down'})\")\n val KeyboardArrowUp: Any = js(\"({icon:'keyboard_arrow_up'})\")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ββ Layout composables ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any=\"\", horizontalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Column',children:[]})\")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any=\"\", verticalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Row',children:[]})\")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any=\"topStart\", content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'Box',children:[]})\")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any=\"\", color: Any=\"\", fontSize: Any=\"\", fontWeight: Any=\"\", textAlign: Any=\"\", maxLines: Int=Int.MAX_VALUE, overflow: Any=\"\") {\n val n: dynamic = js(\"({type:'Text',children:[]})\")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) \"bodyMedium\" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any=\"\", colors: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Button',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'TextButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'FilledTonalButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'FloatingActionButton',children:[]})\")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js(\"({type:'ExtendedFAB',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'IconButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", colors: Any=\"\", elevation: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Card',children:[]})\")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any=\"\", content: (Any)->Unit) {\n val n: dynamic = js(\"({type:'Scaffold',children:[],topBar:[],bottomBar:[],fab:[]})\")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js(\"({})\")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") {\n val n: dynamic = js(\"({type:'TopAppBar',children:[],title:[],actions:[]})\")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'NavigationBar',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'NavigationBarItem',children:[],label:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'OutlinedTextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'TextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'SearchBar',children:[]})\")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", verticalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyColumn',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyRow',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", verticalArrangement: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalStaggeredGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any=\"\") {\n val n: dynamic = js(\"({type:'Icon',children:[]})\")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else \"default\"\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js(\"({type:'Image',children:[]})\")\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'Spacer',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") {\n val n: dynamic = js(\"({type:'Divider',children:[]})\")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Switch',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Checkbox',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any=\"\", strokeWidth: Int=4) {\n val n: dynamic = js(\"({type:'CircularProgressIndicator',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any=\"\") {\n val n: dynamic = js(\"({type:'LinearProgressIndicator',children:[]})\")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'assist',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'filter',children:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'suggestion',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'AlertDialog',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})\")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js(\"({type:'DropdownMenu',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js(\"({type:'DropdownMenuItem',children:[]})\")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js(\"({})\")\nfun rememberScrollState(): Any = js(\"({})\")\nfun SnackbarHostState(): Any = js(\"({})\")\nfun rememberSnackbarHostState(): Any = js(\"({})\")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js(\"({})\")\nobject KeyboardOptions { val Default = js(\"({})\") }\nobject KeyboardActions { val Default = js(\"({})\") }\nobject ImeAction { val Done=\"done\"; val Search=\"search\"; val Go=\"go\"; val Next=\"next\" }\nobject KeyboardType { val Text=\"text\"; val Number=\"number\"; val Email=\"email\"; val Password=\"password\" }\nfun PaddingValues(all: Int=0): Any = js(\"({})\")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js(\"({})\")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js(\"({})\")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ββ Entry point called by the browser ββββββββββββββββββββββββββββββββββββββββ\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}";
|
|
482
|
+
// Compose shim stubs (validated against kotlinc-js 2.3.0)
|
|
483
|
+
const COMPOSE_STUBS =
|
|
484
|
+
'package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js("({type:\'root\',children:[]})")\n val stack: dynamic = js("([])")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree β prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js("({type:\'slot\',children:[]})")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js("({})")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = "top"; val Bottom = "bottom"; val Center = "center"\n val Start = "start"; val End = "end"\n val SpaceBetween = "space-between"; val SpaceEvenly = "space-evenly"; val SpaceAround = "space-around"\n fun spacedBy(dp: Int) = "spacedBy(${"$"}dp)"\n fun spacedBy(dp: Float) = "spacedBy(${"$"}{dp.toInt()})"\n}\nobject Alignment {\n val Top = "top"; val Bottom = "bottom"; val CenterVertically = "centerVertically"\n val CenterHorizontally = "centerHorizontally"; val Center = "center"\n val Start = "start"; val End = "end"\n val TopStart = "topStart"; val TopEnd = "topEnd"\n val BottomStart = "bottomStart"; val BottomEnd = "bottomEnd"\n val TopCenter = "topCenter"; val BottomCenter = "bottomCenter"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = "#6750A4"; val onPrimary = "#FFFFFF"\n val secondary = "#625B71"; val surface = "#FFFBFE"\n val background = "#FFFBFE"; val error = "#B3261E"\n val primaryContainer = "#EADDFF"; val secondaryContainer = "#E8DEF8"\n val surfaceVariant = "#E7E0EC"; val outline = "#79747E"\n val onBackground = "#1C1B1F"; val onSurface = "#1C1B1F"\n val tertiaryContainer = "#FFD8E4"; val inverseSurface = "#313033"\n val inverseOnSurface = "#F4EFF4"; val onPrimaryContainer = "#21005D"\n}\nclass Typography {\n val displayLarge = "displayLarge"; val displayMedium = "displayMedium"; val displaySmall = "displaySmall"\n val headlineLarge = "headlineLarge"; val headlineMedium = "headlineMedium"; val headlineSmall = "headlineSmall"\n val titleLarge = "titleLarge"; val titleMedium = "titleMedium"; val titleSmall = "titleSmall"\n val bodyLarge = "bodyLarge"; val bodyMedium = "bodyMedium"; val bodySmall = "bodySmall"\n val labelLarge = "labelLarge"; val labelMedium = "labelMedium"; val labelSmall = "labelSmall"\n}\nclass Shapes { val small="small"; val medium="medium"; val large="large"; val extraLarge="extraLarge" }\nobject FontWeight { val Bold="bold"; val Normal="normal"; val Light="light"; val Medium="medium"; val SemiBold="semibold" }\nobject TextAlign { val Start="start"; val Center="center"; val End="end"; val Justify="justify" }\nobject ContentScale { val Crop="crop"; val Fit="fit"; val FillBounds="fillBounds"; val FillWidth="fillWidth" }\nobject Color {\n val White = "#FFFFFF"; val Black = "#000000"; val Transparent = "transparent"\n val Red = "#F44336"; val Blue = "#2196F3"; val Green = "#4CAF50"\n val Gray = "#9E9E9E"; val LightGray = "#E0E0E0"; val DarkGray = "#424242"\n val Unspecified = "unspecified"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = "string_${"$"}id"\nfun painterResource(id: Int): Any = js("({type:\'resource\',id:${"$"}id})")\n\nobject Icons {\n object Default {\n val Add: Any = js("({icon:\'add\'})")\n val Search: Any = js("({icon:\'search\'})")\n val Close: Any = js("({icon:\'close\'})")\n val Check: Any = js("({icon:\'check\'})")\n val Delete: Any = js("({icon:\'delete\'})")\n val Edit: Any = js("({icon:\'edit\'})")\n val Home: Any = js("({icon:\'home\'})")\n val Menu: Any = js("({icon:\'menu\'})")\n val Settings: Any = js("({icon:\'settings\'})")\n val ArrowBack: Any = js("({icon:\'arrow_back\'})")\n val MoreVert: Any = js("({icon:\'more_vert\'})")\n val Favorite: Any = js("({icon:\'favorite\'})")\n val Share: Any = js("({icon:\'share\'})")\n val Info: Any = js("({icon:\'info\'})")\n val Person: Any = js("({icon:\'person\'})")\n val Star: Any = js("({icon:\'star\'})")\n val Notifications: Any = js("({icon:\'notifications\'})")\n val Email: Any = js("({icon:\'email\'})")\n val Phone: Any = js("({icon:\'phone\'})")\n val Lock: Any = js("({icon:\'lock\'})")\n val Visibility: Any = js("({icon:\'visibility\'})")\n val VisibilityOff: Any = js("({icon:\'visibility_off\'})")\n val ShoppingCart: Any = js("({icon:\'shopping_cart\'})")\n val KeyboardArrowDown: Any = js("({icon:\'keyboard_arrow_down\'})")\n val KeyboardArrowUp: Any = js("({icon:\'keyboard_arrow_up\'})")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ββ Layout composables ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any="", horizontalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Column\',children:[]})")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any="", verticalAlignment: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Row\',children:[]})")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any="topStart", content: ()->Unit={}) {\n val n: dynamic = js("({type:\'Box\',children:[]})")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any="", color: Any="", fontSize: Any="", fontWeight: Any="", textAlign: Any="", maxLines: Int=Int.MAX_VALUE, overflow: Any="") {\n val n: dynamic = js("({type:\'Text\',children:[]})")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) "bodyMedium" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any="", colors: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Button\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'TextButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'FilledTonalButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'FloatingActionButton\',children:[]})")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js("({type:\'ExtendedFAB\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js("({type:\'IconButton\',children:[]})")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", colors: Any="", elevation: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'Card\',children:[]})")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'ElevatedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'OutlinedCard\',children:[]})")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any="", content: (Any)->Unit) {\n val n: dynamic = js("({type:\'Scaffold\',children:[],topBar:[],bottomBar:[],fab:[]})")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js("({})")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") {\n val n: dynamic = js("({type:\'TopAppBar\',children:[],title:[],actions:[]})")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any="") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'NavigationBar\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'NavigationBarItem\',children:[],label:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'OutlinedTextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any="", keyboardOptions: Any="", keyboardActions: Any="") {\n val n: dynamic = js("({type:\'TextField\',children:[]})")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js("({type:\'SearchBar\',children:[]})")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", verticalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyColumn\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any="", contentPadding: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyRow\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", verticalArrangement: Any="", horizontalArrangement: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any="", content: ()->Unit) {\n val n: dynamic = js("({type:\'LazyVerticalStaggeredGrid\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any="") {\n val n: dynamic = js("({type:\'Icon\',children:[]})")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else "default"\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js("({type:\'Image\',children:[]})")\n n.contentDescription=contentDescription?:""; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'Spacer\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") {\n val n: dynamic = js("({type:\'Divider\',children:[]})")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any="") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Switch\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Checkbox\',children:[]})")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any="", strokeWidth: Int=4) {\n val n: dynamic = js("({type:\'CircularProgressIndicator\',children:[]})")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any="") {\n val n: dynamic = js("({type:\'LinearProgressIndicator\',children:[]})")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'assist\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'filter\',children:[]})")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js("({type:\'Chip\',chipType:\'suggestion\',children:[]})")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js("({type:\'AlertDialog\',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js("({type:\'DropdownMenu\',children:[]})")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js("({type:\'DropdownMenuItem\',children:[]})")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js("({})")\nfun rememberScrollState(): Any = js("({})")\nfun SnackbarHostState(): Any = js("({})")\nfun rememberSnackbarHostState(): Any = js("({})")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js("({})")\nobject KeyboardOptions { val Default = js("({})") }\nobject KeyboardActions { val Default = js("({})") }\nobject ImeAction { val Done="done"; val Search="search"; val Go="go"; val Next="next" }\nobject KeyboardType { val Text="text"; val Number="number"; val Email="email"; val Password="password" }\nfun PaddingValues(all: Int=0): Any = js("({})")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js("({})")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js("({})")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ββ Entry point called by the browser ββββββββββββββββββββββββββββββββββββββββ\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}';
|
package/src/server/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Main Server Entry Point
|
|
3
3
|
* Starts HTTP and WebSocket servers
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
import 'dotenv/config';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as os from 'os';
|
|
@@ -141,8 +141,8 @@ export class JetStartServer extends EventEmitter {
|
|
|
141
141
|
port: this.config.wsPort,
|
|
142
142
|
logsServer: this.logsServer,
|
|
143
143
|
adbHelper: this.adbHelper, // Enable wireless ADB auto-connect
|
|
144
|
-
expectedSessionId: this.currentSession?.id,
|
|
145
|
-
expectedToken: this.currentSession?.token,
|
|
144
|
+
expectedSessionId: this.currentSession?.id,
|
|
145
|
+
expectedToken: this.currentSession?.token,
|
|
146
146
|
projectName: this.config.projectName,
|
|
147
147
|
onClientConnected: async (sessionId: string) => {
|
|
148
148
|
log(`Client connected (session: ${sessionId}). Triggering initial build...`);
|
|
@@ -250,7 +250,7 @@ export class JetStartServer extends EventEmitter {
|
|
|
250
250
|
// Store the APK path
|
|
251
251
|
this.latestApkPath = result.apkPath || null;
|
|
252
252
|
|
|
253
|
-
// Send full download URL to client
|
|
253
|
+
// Send full download URL to client
|
|
254
254
|
const downloadUrl = `http://${this.config.displayHost}:${this.config.httpPort}/download/app.apk`;
|
|
255
255
|
this.wsHandler.sendBuildComplete(this.currentSession.id, downloadUrl);
|
|
256
256
|
log(`APK download URL: ${downloadUrl}`);
|
|
@@ -273,7 +273,7 @@ export class JetStartServer extends EventEmitter {
|
|
|
273
273
|
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
274
274
|
appPackageName = cfg.packageName || appPackageName;
|
|
275
275
|
}
|
|
276
|
-
} catch (
|
|
276
|
+
} catch (error) { console.log("couldnt find package name") }
|
|
277
277
|
await this.adbHelper.launchApp(appPackageName, '.MainActivity');
|
|
278
278
|
} else {
|
|
279
279
|
error(`Auto-install failed: ${installResult.error}`);
|
|
@@ -331,7 +331,7 @@ export class JetStartServer extends EventEmitter {
|
|
|
331
331
|
const result = await this.buildService.build({
|
|
332
332
|
projectPath: this.config.projectPath,
|
|
333
333
|
outputPath: path.join(this.config.projectPath, 'build/outputs/apk'),
|
|
334
|
-
buildType: 'debug' as any,
|
|
334
|
+
buildType: 'debug' as any,
|
|
335
335
|
minifyEnabled: false,
|
|
336
336
|
debuggable: true,
|
|
337
337
|
versionCode: 1,
|
package/src/server/middleware.ts
CHANGED
|
@@ -10,8 +10,36 @@ import { error as logError } from '../utils/logger';
|
|
|
10
10
|
|
|
11
11
|
export function setupMiddleware(app: Express): void {
|
|
12
12
|
// CORS
|
|
13
|
+
const isAllowedOrigin = (origin: string): boolean => {
|
|
14
|
+
// Standard allowed origins
|
|
15
|
+
const allowedOrigins = [
|
|
16
|
+
'http://localhost:8000',
|
|
17
|
+
'http://localhost:3000',
|
|
18
|
+
'http://localhost:8765',
|
|
19
|
+
'http://localhost:8766',
|
|
20
|
+
'http://localhost:8767',
|
|
21
|
+
...(process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : [])
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
if (allowedOrigins.indexOf(origin) !== -1) return true;
|
|
25
|
+
|
|
26
|
+
// Allow jetstart.site and any subdomains
|
|
27
|
+
if (origin === 'https://jetstart.site' || origin.endsWith('.jetstart.site')) return true;
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
|
|
13
32
|
app.use(cors({
|
|
14
|
-
origin:
|
|
33
|
+
origin: (origin, callback) => {
|
|
34
|
+
// Allow requests with no origin (like mobile apps or curl requests)
|
|
35
|
+
if (!origin) return callback(null, true);
|
|
36
|
+
|
|
37
|
+
if (isAllowedOrigin(origin) || !process.env.IS_PROD) {
|
|
38
|
+
callback(null, true);
|
|
39
|
+
} else {
|
|
40
|
+
callback(null, false);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
15
43
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
16
44
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
17
45
|
}));
|
package/src/server/routes.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* HTTP Routes
|
|
3
3
|
* REST API endpoints
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
import { Express, Request, Response } from 'express';
|
|
7
|
-
import path from 'path';
|
|
8
6
|
import fs from 'fs';
|
|
9
7
|
import { SessionManager } from '../utils/session';
|
|
10
8
|
import { generateQRCode } from '../utils/qr';
|
|
@@ -20,6 +18,7 @@ export function setupRoutes(
|
|
|
20
18
|
): void {
|
|
21
19
|
// Root Redirect -> Web Emulator
|
|
22
20
|
app.get('/', (req: Request, res: Response) => {
|
|
21
|
+
const isProduction = process.env.IS_PROD
|
|
23
22
|
try {
|
|
24
23
|
const session = getCurrentSession?.();
|
|
25
24
|
|
|
@@ -32,7 +31,7 @@ export function setupRoutes(
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
// Construct redirect URL
|
|
35
|
-
const webUrl = 'https://web.jetstart.site';
|
|
34
|
+
const webUrl = isProduction ? 'https://web.jetstart.site': 'http://localhost:8000';
|
|
36
35
|
const host = req.hostname;
|
|
37
36
|
const port = req.socket.localPort || 8765;
|
|
38
37
|
const wsPort = DEFAULT_WS_PORT;
|
package/src/utils/session.ts
CHANGED
package/src/websocket/handler.ts
CHANGED
|
@@ -99,7 +99,7 @@ export class WebSocketHandler {
|
|
|
99
99
|
const incomingSession = message.sessionId;
|
|
100
100
|
const incomingToken = (message as any).token as string | undefined;
|
|
101
101
|
|
|
102
|
-
//
|
|
102
|
+
// Session + token validation
|
|
103
103
|
// Only validate when the server has an expected session configured.
|
|
104
104
|
if (this.expectedSessionId) {
|
|
105
105
|
if (incomingSession !== this.expectedSessionId) {
|
|
@@ -107,7 +107,7 @@ export class WebSocketHandler {
|
|
|
107
107
|
`Rejected client ${clientId}: wrong session "${incomingSession}" (expected "${this.expectedSessionId}")`
|
|
108
108
|
);
|
|
109
109
|
logError('This device was built against a different jetstart dev session. Rescan the QR code.');
|
|
110
|
-
// Close the WebSocket immediately β
|
|
110
|
+
// Close the WebSocket immediately β does not accept this client.
|
|
111
111
|
const ws = this.connectionManager.getConnection(clientId);
|
|
112
112
|
if (ws) {
|
|
113
113
|
ws.close(4001, 'Session mismatch β rescan QR code');
|
|
@@ -129,7 +129,7 @@ export class WebSocketHandler {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// Accepted
|
|
133
133
|
log(`Client accepted (session: ${incomingSession})`);
|
|
134
134
|
this.connectionManager.setClientSession(clientId, incomingSession);
|
|
135
135
|
|
|
@@ -196,7 +196,7 @@ export class WebSocketHandler {
|
|
|
196
196
|
* Send compiled KotlinβJS ES module to web emulator clients.
|
|
197
197
|
* The browser imports it dynamically and renders the Compose UI as HTML.
|
|
198
198
|
*/
|
|
199
|
-
sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number,
|
|
199
|
+
sendJsUpdate(sessionId: string, jsBase64: string, sourceFile: string, byteSize: number, _screenFunctionName: string): void {
|
|
200
200
|
const message: CoreJsUpdateMessage = {
|
|
201
201
|
type: 'core:js-update',
|
|
202
202
|
timestamp: Date.now(),
|
package/src/websocket/manager.ts
CHANGED
|
@@ -58,14 +58,6 @@ export class ConnectionManager {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* Broadcast to all clients (UNSAFE - use broadcastToSession instead)
|
|
63
|
-
* @deprecated Use broadcastToSession for session isolation
|
|
64
|
-
*/
|
|
65
|
-
broadcast(message: CoreMessage): void {
|
|
66
|
-
console.warn('[ConnectionManager] WARNING: Using unsafe broadcast() - use broadcastToSession() instead');
|
|
67
|
-
this.broadcastToAll(message);
|
|
68
|
-
}
|
|
69
61
|
|
|
70
62
|
/**
|
|
71
63
|
* Broadcast to all clients in a specific session (SECURE)
|