create-mithril-lynx 0.1.1 → 2.0.1
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/README.md +23 -25
- package/package.json +5 -13
- package/src/index.js +25 -48
- package/templates/_shared/common/README.md +5 -5
- package/templates/_shared/ts/lynx.config.ts +7 -1
- package/templates/_shared/ts/package.json +1 -1
- package/templates/_shared/ts/src/background.ts +35 -0
- package/templates/_shared/ts/src/main-thread.ts +7 -18
- package/templates/_shared/ts/src/mithril-runtime.d.ts +10 -0
- package/templates/_shared/ts/tsconfig.json +0 -3
- package/templates/basic-activity/ts/src/app-bar.ts +13 -0
- package/templates/basic-activity/ts/src/background.ts +40 -0
- package/templates/basic-activity/ts/src/screens/detail.ts +12 -0
- package/templates/basic-activity/ts/src/screens/home.ts +17 -0
- package/templates/blank/ts/src/index.ts +6 -10
- package/templates/hello-world/ts/src/index.ts +34 -40
- package/LICENSE +0 -21
- package/templates/_shared/js/lynx.config.js +0 -37
- package/templates/_shared/js/package.json +0 -26
- package/templates/_shared/js/src/main-thread.js +0 -20
- package/templates/basic-activity/js/src/index.js +0 -43
- package/templates/basic-activity/ts/src/index.ts +0 -48
- package/templates/blank/js/src/index.js +0 -11
- package/templates/hello-world/js/src/index.js +0 -47
package/README.md
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# create-mithril-lynx
|
|
2
2
|
|
|
3
|
-
Scaffolds a new [`mithril-lynx`](https://github.com/carlos-sweb/mithril-lynx) app. Three templates, matching the "Hello World" / "Blank" / "Basic Activity" naming most native app scaffolds already use
|
|
3
|
+
Scaffolds a new [`mithril-lynx`](https://github.com/carlos-sweb/mithril-lynx) app. Three templates, matching the "Hello World" / "Blank" / "Basic Activity" naming most native app scaffolds already use.
|
|
4
4
|
|
|
5
5
|
It can also generate the **native Android host** — the whole Gradle project that wraps the bundle into an installable APK — so you never hand-write the ~20 Kotlin/Gradle/XML files that takes. That path automates [`mithril-lynx`'s ANDROID_APK_GUIDE.md](https://github.com/carlos-sweb/mithril-lynx/blob/main/ANDROID_APK_GUIDE.md).
|
|
6
6
|
|
|
7
|
+
> **Note:** this is a complete rewrite of the previous `create-mithril-lynx`, matching [`mithril-lynx`'s own rewrite](https://github.com/carlos-sweb/mithril-lynx). The old tool's **Basic Activity** template used `mithril-lynx/navigation` (a stack-based, in-memory navigator) — that module doesn't exist in the new `mithril-lynx`, so this template was rewritten from scratch around [`mithril-lynx/route`](https://github.com/carlos-sweb/mithril-lynx/blob/main/ROUTE.md) instead. Everything else (Hello World, Blank, the Android host) ports over unchanged, since none of it depended on the part of the framework that got rewritten.
|
|
8
|
+
|
|
7
9
|
## Usage
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
npm create mithril-lynx@latest
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
Prompts for a project name
|
|
15
|
+
Prompts for a project name and a template, scaffolds it, and offers to install dependencies for you.
|
|
14
16
|
|
|
15
17
|
### Non-interactive
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
npx create-mithril-lynx my-app --hello-world
|
|
19
|
-
npx create-mithril-lynx my-app --basic-activity --
|
|
20
|
+
npx create-mithril-lynx my-app --hello-world
|
|
21
|
+
npx create-mithril-lynx my-app --basic-activity --no-install
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
### With the Android host
|
|
23
25
|
|
|
24
26
|
```bash
|
|
25
27
|
# Scaffold the JS app plus a sibling my-app-android/ Gradle project:
|
|
26
|
-
npx create-mithril-lynx my-app --
|
|
28
|
+
npx create-mithril-lynx my-app --blank --android
|
|
27
29
|
|
|
28
30
|
cd my-app
|
|
29
31
|
npm install
|
|
@@ -42,7 +44,6 @@ Every flag, in one table — `npx create-mithril-lynx --help` prints the same li
|
|
|
42
44
|
|---|---|
|
|
43
45
|
| `<name>` (positional) | Project name, and the directory to create. Omit it and you're prompted. |
|
|
44
46
|
| `--hello-world` / `--blank` / `--basic-activity` | Template to scaffold. Omit it and you're prompted. |
|
|
45
|
-
| `--ts` / `--js` | TypeScript or JavaScript variant. Omit it and you're prompted. |
|
|
46
47
|
| `--no-install` | Don't install dependencies (also skips that prompt). |
|
|
47
48
|
| `-h`, `--help` | Print usage and exit. |
|
|
48
49
|
| `--android` / `--target android` / `--target=android` / `target=android` | Also scaffold the sibling `<name>-android/` Gradle project. |
|
|
@@ -56,11 +57,11 @@ Anything not listed — in particular the four Android flags — requires `--and
|
|
|
56
57
|
|
|
57
58
|
### What the generated Android host gives you
|
|
58
59
|
|
|
59
|
-
The sibling `<name>-android/` project is a complete, no-Android-Studio Gradle CLI project
|
|
60
|
+
The sibling `<name>-android/` project is a complete, no-Android-Studio Gradle CLI project, including:
|
|
60
61
|
|
|
61
|
-
- the Gradle wrapper (`gradlew`, `gradlew.bat`, `gradle-wrapper.jar`), so nothing needs to be installed besides a JDK
|
|
62
|
+
- the Gradle wrapper (`gradlew`, `gradlew.bat`, `gradle-wrapper.jar`), so nothing needs to be installed besides a JDK and the Android SDK;
|
|
62
63
|
- `local.properties` with `sdk.dir` auto-detected from `ANDROID_HOME`/`ANDROID_SDK_ROOT` (with a written warning if it can't be found);
|
|
63
|
-
- the Kotlin host: the `Application
|
|
64
|
+
- the Kotlin host: the `Application`, `MainActivity` (async `AssetTemplateProvider`, splash screen), and the font loader when `--with-font` is used;
|
|
64
65
|
- resource files that compile as-is (theme, splash theme, adaptive launcher icon);
|
|
65
66
|
- an opt-in release signing setup driven by a gitignored `keystore.properties`.
|
|
66
67
|
|
|
@@ -74,20 +75,15 @@ npm run android:sync # build + sync bundle into app/src/main/assets/, no G
|
|
|
74
75
|
npm run android:keystore # generate release.keystore + keystore.properties (KEYSTORE_PASSWORD=...)
|
|
75
76
|
```
|
|
76
77
|
|
|
78
|
+
This part of the tool is framework-agnostic — it just wraps whatever bundle `rspeedy build` produces into a native host — so it needed no changes for the `mithril-lynx` rewrite beyond what's inherited automatically through the generated JS project.
|
|
79
|
+
|
|
77
80
|
## Templates
|
|
78
81
|
|
|
79
82
|
| Template | What it is |
|
|
80
83
|
|---|---|
|
|
81
84
|
| **Hello World** (recommended) | The Mithril.js analog of Lynx's official React hello-world ([`lynx-examples/examples/hello-world`](https://github.com/lynx-family/lynx-examples/tree/main/examples/hello-world)) — same layout, same "tap the logo" interaction, running through Mithril hyperscript instead of JSX. |
|
|
82
85
|
| **Blank** | A single centered line of text. Nothing else — the starting point when you don't want any of Hello World's styling/assets in your way. |
|
|
83
|
-
| **Basic Activity** | Two screens (Main → Details) wired with [`mithril-lynx/
|
|
84
|
-
|
|
85
|
-
## Variants
|
|
86
|
-
|
|
87
|
-
| Variant | Adds over the base template |
|
|
88
|
-
|---|---|
|
|
89
|
-
| **TypeScript** (recommended) | `.ts` source files, `tsconfig.json` (project references, matching `@lynx-js/rspeedy`'s own generated config), `@rsbuild/plugin-type-check`, `@types/mithril` for real editor autocomplete on `m()` calls. |
|
|
90
|
-
| **JavaScript** | Same app, plain `.js` with ES module `import`/`export` — no type-checking, no `tsconfig.json`. |
|
|
86
|
+
| **Basic Activity** | Two screens (Main → Details) wired with [`mithril-lynx/route`](https://github.com/carlos-sweb/mithril-lynx/blob/main/ROUTE.md) — an in-memory router (`m.route`, reimplemented for an environment with no URL bar). Includes a reusable app-bar-with-back-button pattern, using `route.back()` since Lynx has no hardware back button for a route to hook into automatically. |
|
|
91
87
|
|
|
92
88
|
## Package structure
|
|
93
89
|
|
|
@@ -97,31 +93,33 @@ create-mithril-lynx/
|
|
|
97
93
|
templates/
|
|
98
94
|
_shared/
|
|
99
95
|
common/ gitignore, project README — shared by every template
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
ts/ lynx.config.ts, tsconfig*, package.json, main-thread.ts,
|
|
97
|
+
background.ts (single-view mount) — shared by every
|
|
98
|
+
template that doesn't override background.ts itself
|
|
102
99
|
hello-world/
|
|
103
100
|
common/src/ logo/arrow assets, style.css
|
|
104
|
-
js/src/index.js
|
|
105
101
|
ts/src/index.ts
|
|
106
102
|
blank/
|
|
107
103
|
common/src/style.css
|
|
108
|
-
js/src/index.js
|
|
109
104
|
ts/src/index.ts
|
|
110
105
|
basic-activity/
|
|
111
106
|
common/src/style.css
|
|
112
|
-
|
|
113
|
-
ts/src/index.ts
|
|
107
|
+
ts/src/{app-bar.ts, background.ts, screens/{home,detail}.ts}
|
|
114
108
|
android/ only used with --android
|
|
115
109
|
host/ the Gradle project -> <name>-android/
|
|
116
110
|
font/AssetFontFaceLoader.kt copied only when --with-font is used
|
|
117
111
|
app-scripts/android.mjs the bridge -> <name>/scripts/android.mjs
|
|
118
112
|
```
|
|
119
113
|
|
|
120
|
-
`src/index.js` copies, in order, `templates/_shared/common` → `templates/_shared
|
|
114
|
+
`src/index.js` copies, in order, `templates/_shared/common` → `templates/_shared/ts` → `templates/<template>/common` → `templates/<template>/ts` into the target directory — later copies overwrite same-named files from earlier ones, which is exactly how **Basic Activity**'s own routing-based `src/background.ts` replaces `_shared/ts`'s generic single-view one (Hello World and Blank don't ship their own, so they keep the shared file). It then renames `gitignore` to `.gitignore` (npm doesn't publish dotfiles reliably otherwise) and replaces `{{PROJECT_NAME}}`/`{{MITHRIL_LYNX_VERSION}}` placeholders in `package.json` and `README.md`.
|
|
121
115
|
|
|
122
116
|
With `--android` it then copies `templates/android/host` into a sibling `<name>-android/` (renaming `package-path` to the real package directory and `App.kt` to the Application class), substitutes `{{PACKAGE_NAME}}`, `{{APP_CLASS}}`, `{{APP_NAME}}`, `{{ANDROID_DIR}}` and `{{SDK_DIR}}` across the tree, and fills in the four font-hack hooks (`{{FONT_LOADER_IMPORT}}`, `{{FONT_LOADER_REGISTRATION}}`, `{{FONT_IMPORT}}`, `{{FONT_PREFETCH}}`) with either the real code or nothing at all. Binary files (the Gradle wrapper jar, the `.ttf`) are never text-substituted.
|
|
123
117
|
|
|
124
|
-
|
|
118
|
+
**Not carried over from v1 (yet)**: the JavaScript variant. The old tool generated either TypeScript or JavaScript for every template; this rewrite ships TypeScript only for now — doubling every template for a parallel JS copy wasn't part of this pass.
|
|
119
|
+
|
|
120
|
+
## Testing
|
|
121
|
+
|
|
122
|
+
All three templates are verified end to end: scaffolded non-interactively, `npm install`ed against the real published `mithril-lynx`/`mithril-runtime` packages, and type-checked with `tsc -b` — not just copied and assumed correct.
|
|
125
123
|
|
|
126
124
|
## License
|
|
127
125
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-mithril-lynx",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Scaffolds a new mithril-lynx app
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Scaffolds a new mithril-lynx app (and, optionally, its Android host).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/carlos-sweb/create-mithril-lynx.git"
|
|
10
|
-
},
|
|
11
|
-
"homepage": "https://github.com/carlos-sweb/create-mithril-lynx#readme",
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/carlos-sweb/create-mithril-lynx/issues"
|
|
14
|
-
},
|
|
15
7
|
"bin": {
|
|
16
8
|
"create-mithril-lynx": "src/index.js"
|
|
17
9
|
},
|
|
@@ -19,10 +11,10 @@
|
|
|
19
11
|
"src",
|
|
20
12
|
"templates"
|
|
21
13
|
],
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": "^20.19.0 || >=22.12.0"
|
|
24
|
-
},
|
|
25
14
|
"dependencies": {
|
|
26
15
|
"@clack/prompts": "^1.8.0"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
27
19
|
}
|
|
28
20
|
}
|
package/src/index.js
CHANGED
|
@@ -10,19 +10,14 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const packageRoot = path.join(scriptDir, "..");
|
|
11
11
|
const cwd = process.cwd();
|
|
12
12
|
|
|
13
|
-
// The mithril-lynx version pinned in the generated package.json
|
|
14
|
-
//
|
|
15
|
-
// This used to be "0.0.2" (what was needed when the "basic-activity" template
|
|
16
|
-
// was added, since that one needs mithril-lynx/navigation). It now tracks the
|
|
17
|
-
// current release: a fresh scaffold shouldn't start five releases behind.
|
|
18
|
-
// Verified by generating with --ts --blank and --js --basic-activity,
|
|
19
|
-
// installing, and building the APK (debug and release).
|
|
13
|
+
// The mithril-lynx version pinned in the generated package.json — tracks
|
|
14
|
+
// the current release, same policy as this tool's own v1.
|
|
20
15
|
const MITHRIL_LYNX_VERSION = "latest";
|
|
21
16
|
|
|
22
17
|
const TEMPLATES = [
|
|
23
18
|
{ value: "hello-world", label: "Hello World", hint: "recommended" },
|
|
24
19
|
{ value: "blank", label: "Blank" },
|
|
25
|
-
{ value: "basic-activity", label: "Basic Activity", hint: "multi-screen
|
|
20
|
+
{ value: "basic-activity", label: "Basic Activity", hint: "multi-screen routing" },
|
|
26
21
|
];
|
|
27
22
|
const TEMPLATE_VALUES = TEMPLATES.map((t) => t.value);
|
|
28
23
|
|
|
@@ -74,11 +69,10 @@ function replaceInFile(filePath, replacements) {
|
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
/** Applies substitutions across a whole tree, skipping binaries. */
|
|
77
|
-
function replaceInTree(dir, replacements
|
|
72
|
+
function replaceInTree(dir, replacements) {
|
|
78
73
|
for (const file of walkFiles(dir)) {
|
|
79
74
|
const ext = path.extname(file).toLowerCase();
|
|
80
75
|
if (BINARY_EXTENSIONS.has(ext)) continue;
|
|
81
|
-
if (onlyExtensions && !onlyExtensions.has(path.basename(file)) && !onlyExtensions.has(ext)) continue;
|
|
82
76
|
replaceInFile(file, replacements);
|
|
83
77
|
}
|
|
84
78
|
}
|
|
@@ -157,7 +151,7 @@ function findAndroidSdk() {
|
|
|
157
151
|
}
|
|
158
152
|
|
|
159
153
|
// ---------------------------------------------------------------------------
|
|
160
|
-
//
|
|
154
|
+
// Argument parsing
|
|
161
155
|
// ---------------------------------------------------------------------------
|
|
162
156
|
|
|
163
157
|
function readOption(args, name) {
|
|
@@ -303,8 +297,8 @@ function scaffoldAndroid({ targetDir, rawName, options }) {
|
|
|
303
297
|
// best-effort
|
|
304
298
|
}
|
|
305
299
|
|
|
306
|
-
// 2. The .ttf
|
|
307
|
-
//
|
|
300
|
+
// 2. The .ttf is opt-in: without it neither the loader nor the prefetch is
|
|
301
|
+
// generated, so no dead code is left behind.
|
|
308
302
|
if (font != null) {
|
|
309
303
|
const fontsDir = path.join(androidDir, "app", "src", "main", "assets", "fonts");
|
|
310
304
|
fs.mkdirSync(fontsDir, { recursive: true });
|
|
@@ -357,8 +351,7 @@ function scaffoldAndroid({ targetDir, rawName, options }) {
|
|
|
357
351
|
"// renderTemplateUrl() gives the bundle's CSS a chance to resolve",
|
|
358
352
|
"// @font-face during the first layout. FontFaceManager caches by the",
|
|
359
353
|
"// exact src string, so this URI has to be identical to the",
|
|
360
|
-
'// url("...") of the @font-face in src/style.css
|
|
361
|
-
"// ANDROID_APK_GUIDE.md.",
|
|
354
|
+
'// url("...") of the @font-face in src/style.css.',
|
|
362
355
|
"FontFaceManager.getInstance().prefetchFont(",
|
|
363
356
|
" lynxView.lynxContext,",
|
|
364
357
|
` "${sourceUri}",`,
|
|
@@ -417,8 +410,8 @@ function patchJsProject({ targetDir, android }) {
|
|
|
417
410
|
if (android.font != null) {
|
|
418
411
|
const cssPath = path.join(targetDir, "src", "style.css");
|
|
419
412
|
const block = [
|
|
420
|
-
"/*
|
|
421
|
-
"
|
|
413
|
+
"/* The font lives in the Android host's assets",
|
|
414
|
+
" (app/src/main/assets/fonts/), not in the bundle. The asset:///",
|
|
422
415
|
" string has to be identical to the one in MainActivity.kt's prefetchFont()",
|
|
423
416
|
" or Lynx won't find the warmed Typeface. */",
|
|
424
417
|
"@font-face {",
|
|
@@ -453,12 +446,9 @@ function patchJsProject({ targetDir, android }) {
|
|
|
453
446
|
`- Application ID: \`${android.androidId}\``,
|
|
454
447
|
`- Application class: \`${android.appClass}\` · Activity: \`MainActivity\``,
|
|
455
448
|
...(android.font != null
|
|
456
|
-
? [`- Font \`${android.font.family}\` prefetched from \`asset:///fonts/${android.font.file}\` (the cold-start hack
|
|
449
|
+
? [`- Font \`${android.font.family}\` prefetched from \`asset:///fonts/${android.font.file}\` (the cold-start hack).`]
|
|
457
450
|
: []),
|
|
458
451
|
"",
|
|
459
|
-
"The full procedure — including why each Gradle dependency is there and how to measure",
|
|
460
|
-
"cold start — is in [`mithril-lynx`'s ANDROID_APK_GUIDE.md](https://github.com/carlos-sweb/mithril-lynx/blob/main/ANDROID_APK_GUIDE.md).",
|
|
461
|
-
"",
|
|
462
452
|
].join("\n");
|
|
463
453
|
fs.appendFileSync(readmePath, section);
|
|
464
454
|
}
|
|
@@ -471,15 +461,12 @@ create-mithril-lynx — scaffold a mithril-lynx app (and, optionally, its Androi
|
|
|
471
461
|
|
|
472
462
|
Usage:
|
|
473
463
|
npm create mithril-lynx@latest [name] [options]
|
|
474
|
-
npx create-mithril-lynx <name> --
|
|
464
|
+
npx create-mithril-lynx <name> --blank --android
|
|
475
465
|
|
|
476
466
|
Template (prompted for if omitted):
|
|
477
467
|
--hello-world | --blank | --basic-activity
|
|
478
468
|
|
|
479
|
-
|
|
480
|
-
--ts | --js
|
|
481
|
-
|
|
482
|
-
Android host (see mithril-lynx's ANDROID_APK_GUIDE.md):
|
|
469
|
+
Android host:
|
|
483
470
|
--android, --target android, target=android
|
|
484
471
|
scaffold the sibling Gradle project <name>-android/
|
|
485
472
|
--android-id <id> applicationId / namespace (default com.example.<name>)
|
|
@@ -496,10 +483,10 @@ Other:
|
|
|
496
483
|
|
|
497
484
|
async function main() {
|
|
498
485
|
// Non-interactive escape hatch for scripting/CI:
|
|
499
|
-
// create-mithril-lynx my-app --
|
|
500
|
-
// create-mithril-lynx my-app --
|
|
501
|
-
// create-mithril-lynx my-app --
|
|
502
|
-
// create-mithril-lynx my-app --
|
|
486
|
+
// create-mithril-lynx my-app --hello-world
|
|
487
|
+
// create-mithril-lynx my-app --basic-activity --no-install
|
|
488
|
+
// create-mithril-lynx my-app --blank --android --android-id com.acme.miapp
|
|
489
|
+
// create-mithril-lynx my-app --blank --with-font ./UbuntuMono-Regular.ttf
|
|
503
490
|
const args = process.argv.slice(2);
|
|
504
491
|
|
|
505
492
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -508,10 +495,9 @@ async function main() {
|
|
|
508
495
|
}
|
|
509
496
|
|
|
510
497
|
const positional = findPositional(args);
|
|
511
|
-
const variantFlag = args.includes("--ts") ? "ts" : args.includes("--js") ? "js" : undefined;
|
|
512
498
|
const templateFlag = TEMPLATE_VALUES.find((t) => args.includes(`--${t}`));
|
|
513
499
|
const noInstall = args.includes("--no-install");
|
|
514
|
-
const nonInteractive = positional != null &&
|
|
500
|
+
const nonInteractive = positional != null && templateFlag != null;
|
|
515
501
|
const android = parseAndroidArgs(args);
|
|
516
502
|
|
|
517
503
|
// --with-font/--font-family only make sense with an Android host: imply it
|
|
@@ -554,18 +540,6 @@ async function main() {
|
|
|
554
540
|
if (isCancel(template)) return bail();
|
|
555
541
|
}
|
|
556
542
|
|
|
557
|
-
let variant = variantFlag;
|
|
558
|
-
if (variant == null) {
|
|
559
|
-
variant = await select({
|
|
560
|
-
message: "Select a variant",
|
|
561
|
-
options: [
|
|
562
|
-
{ value: "ts", label: "TypeScript", hint: "recommended" },
|
|
563
|
-
{ value: "js", label: "JavaScript" },
|
|
564
|
-
],
|
|
565
|
-
});
|
|
566
|
-
if (isCancel(variant)) return bail();
|
|
567
|
-
}
|
|
568
|
-
|
|
569
543
|
let withAndroid = android.requested;
|
|
570
544
|
if (!withAndroid && !nonInteractive) {
|
|
571
545
|
withAndroid = await confirm({
|
|
@@ -585,12 +559,15 @@ async function main() {
|
|
|
585
559
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
586
560
|
|
|
587
561
|
// Layered copy: shared build plumbing (gitignore/README, then
|
|
588
|
-
// lynx.config/tsconfig/main-thread
|
|
589
|
-
//
|
|
562
|
+
// lynx.config/tsconfig/main-thread/background), then the chosen
|
|
563
|
+
// template's own app content (style.css/assets, then src). A template's
|
|
564
|
+
// own src/background.ts (basic-activity's — routing-based) overwrites
|
|
565
|
+
// the shared single-view one; hello-world/blank don't ship one, so the
|
|
566
|
+
// shared file is what they get.
|
|
590
567
|
copyDir(path.join(packageRoot, "templates/_shared/common"), targetDir);
|
|
591
|
-
copyDir(path.join(packageRoot, "templates/_shared"
|
|
568
|
+
copyDir(path.join(packageRoot, "templates/_shared/ts"), targetDir);
|
|
592
569
|
copyDir(path.join(packageRoot, "templates", template, "common"), targetDir);
|
|
593
|
-
copyDir(path.join(packageRoot, "templates", template,
|
|
570
|
+
copyDir(path.join(packageRoot, "templates", template, "ts"), targetDir);
|
|
594
571
|
|
|
595
572
|
const gitignorePath = path.join(targetDir, "gitignore");
|
|
596
573
|
if (fs.existsSync(gitignorePath)) {
|
|
@@ -6,12 +6,12 @@ A [Lynx](https://lynxjs.org) app built with [Mithril.js](https://mithril.js.org)
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install
|
|
9
|
-
npm run dev # scan the printed QR code with LynxExplorer
|
|
10
|
-
npm run build
|
|
9
|
+
npm run dev # scan the printed QR code with LynxExplorer, or open it in Lynx Go
|
|
10
|
+
npm run build # production bundle, in dist/
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Learn more
|
|
14
14
|
|
|
15
|
-
- `src/main-thread.
|
|
16
|
-
- `src/
|
|
17
|
-
- Everything about the framework — the three
|
|
15
|
+
- `src/main-thread.ts` starts the main-thread patch-replay runtime (`setupRenderer()`) — mithril-lynx has exactly one rendering mode, so no app code lives here.
|
|
16
|
+
- `src/background.ts` is where the app actually mounts (`renderApp()`), running the real Mithril view tree in the background thread.
|
|
17
|
+
- Everything about the framework — the three reload modes, routing, networking — is documented in [`mithril-lynx`'s own README](https://github.com/carlos-sweb/mithril-lynx#readme).
|
|
@@ -27,7 +27,13 @@ export default defineConfig({
|
|
|
27
27
|
},
|
|
28
28
|
plugins: [
|
|
29
29
|
pluginMithrilLynx(),
|
|
30
|
-
|
|
30
|
+
// enableNewGesture: the native gesture arena (mithril-lynx/gesture, once
|
|
31
|
+
// it exists, needs this on — off by default, and __SetGestureDetector
|
|
32
|
+
// calls are silently inert without it).
|
|
33
|
+
// enableCSSRule: CSS selector rules beyond a single class/type match —
|
|
34
|
+
// confirmed needed for a plain `:root { ... }` rule (used by every
|
|
35
|
+
// template's style.css) to actually apply on a real device.
|
|
36
|
+
pluginLynxConfig({ enableNewGesture: true, enableCSSRule: true }),
|
|
31
37
|
pluginQRCode({
|
|
32
38
|
schema(url) {
|
|
33
39
|
// Opens the page in LynxExplorer in full screen mode.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/background.ts
|
|
2
|
+
//
|
|
3
|
+
// The stable-host pattern: `host` never changes identity across a
|
|
4
|
+
// hot-update, so Mithril never unmounts/remounts it; `currentView` is a
|
|
5
|
+
// live binding re-pointed by `module.hot.accept`. This is what lets
|
|
6
|
+
// editing `index.ts` hot-reload in place without losing an `<input>`'s
|
|
7
|
+
// focus or in-progress text.
|
|
8
|
+
|
|
9
|
+
import m from "mithril-runtime";
|
|
10
|
+
import { renderApp } from "mithril-lynx/background";
|
|
11
|
+
import * as indexModule from "./index.js";
|
|
12
|
+
|
|
13
|
+
let currentView: typeof indexModule = indexModule;
|
|
14
|
+
|
|
15
|
+
const host: m.Component = {
|
|
16
|
+
view() {
|
|
17
|
+
return currentView.view();
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const app = renderApp({ root: () => m(host) });
|
|
22
|
+
|
|
23
|
+
declare const module: {
|
|
24
|
+
hot?: {
|
|
25
|
+
accept(path: string, callback: () => void): void;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
declare const require: (id: string) => typeof indexModule;
|
|
29
|
+
|
|
30
|
+
if (module.hot) {
|
|
31
|
+
module.hot.accept("./index.js", () => {
|
|
32
|
+
currentView = require("./index.js");
|
|
33
|
+
app.redraw();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// src/main-thread.ts
|
|
2
|
+
//
|
|
3
|
+
// No app code here — mithril-lynx has exactly one rendering mode: all
|
|
4
|
+
// view logic runs in background.ts, this just starts the main-thread
|
|
5
|
+
// patch-replay runtime.
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
// hook on every __RenderPage/__UpdatePage — install a pass-through default.
|
|
6
|
-
// (mithril-lynx/main-thread's setupApp() does this for you in data-channel
|
|
7
|
-
// mode; this bare main-thread-owned pattern doesn't go through that module.)
|
|
8
|
-
Object.assign(globalThis, {
|
|
9
|
-
processData: (data: unknown) => data,
|
|
10
|
-
});
|
|
7
|
+
import { setupRenderer } from "mithril-lynx/main-thread";
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
// modes: no background.ts, no dual-bundle build. The app renders directly
|
|
14
|
-
// on the main thread, synchronously, on first paint; every later update
|
|
15
|
-
// flows through shim.redraw(), called from event handlers bound via
|
|
16
|
-
// Mithril's own on* attrs (see src/index.ts).
|
|
17
|
-
lynx.getEngine().addEventListener("__RenderPage", () => {
|
|
18
|
-
const page = __CreatePage("0", 0);
|
|
19
|
-
shim.renderToPage(page, app.root);
|
|
20
|
-
});
|
|
9
|
+
setupRenderer();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// "mithril-runtime" (https://github.com/carlos-sweb/mithril-runtime) has no
|
|
2
|
+
// types of its own yet — it's a same-shaped subset of real Mithril (m.route/
|
|
3
|
+
// m.trust/m.request removed, see that repo's README and tests). Reusing
|
|
4
|
+
// @types/mithril here is a deliberate approximation: it still types
|
|
5
|
+
// m.route/m.trust/m.request as present, which mithril-runtime doesn't have
|
|
6
|
+
// — don't reference those from app code; nothing in this app does.
|
|
7
|
+
declare module "mithril-runtime" {
|
|
8
|
+
import m from "mithril";
|
|
9
|
+
export = m;
|
|
10
|
+
}
|
|
@@ -7,9 +7,6 @@
|
|
|
7
7
|
"esModuleInterop": true,
|
|
8
8
|
"skipLibCheck": true,
|
|
9
9
|
|
|
10
|
-
// Explicit, not left to the TS-version-dependent default (which can be
|
|
11
|
-
// as old as ES3 and lacks e.g. Object.assign) — confirmed necessary
|
|
12
|
-
// by a real build failure with a newer/older typescript version.
|
|
13
10
|
"target": "ES2022",
|
|
14
11
|
"lib": ["ES2023"]
|
|
15
12
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import m from "mithril-runtime";
|
|
2
|
+
|
|
3
|
+
// A reusable app bar: a title, plus a back affordance when `onBack` is
|
|
4
|
+
// given. No m.route.Link involved for the back button — see
|
|
5
|
+
// mithril-lynx/route.js's README section on route.back(): Lynx has no
|
|
6
|
+
// hardware back button, so a screen's own back affordance has to call
|
|
7
|
+
// route.back() explicitly.
|
|
8
|
+
export function AppBar(title: string, onBack?: () => void) {
|
|
9
|
+
return m("view", { class: "AppBar" }, [
|
|
10
|
+
onBack != null ? m("text", { class: "AppBar-back", ontap: onBack }, "←") : null,
|
|
11
|
+
m("text", { class: "AppBar-title" }, title),
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/background.ts
|
|
2
|
+
//
|
|
3
|
+
// A stable-host wrapper PER SCREEN, so a hot-update never unmounts/
|
|
4
|
+
// remounts either screen — `current*` is a live binding re-pointed by
|
|
5
|
+
// `module.hot.accept`, read fresh on every redraw. route(...) (mithril-lynx/
|
|
6
|
+
// route) is what actually mounts the app — it calls renderApp() internally
|
|
7
|
+
// the first time a route resolves, so this file never calls it directly.
|
|
8
|
+
|
|
9
|
+
import route from "mithril-lynx/route";
|
|
10
|
+
import * as homeModule from "./screens/home.js";
|
|
11
|
+
import * as detailModule from "./screens/detail.js";
|
|
12
|
+
|
|
13
|
+
let currentHome: typeof homeModule = homeModule;
|
|
14
|
+
let currentDetail: typeof detailModule = detailModule;
|
|
15
|
+
|
|
16
|
+
const HomeHost = { view: () => currentHome.view() };
|
|
17
|
+
const DetailHost = { view: () => currentDetail.view() };
|
|
18
|
+
|
|
19
|
+
route("/", {
|
|
20
|
+
"/": HomeHost,
|
|
21
|
+
"/detail": DetailHost,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
declare const module: {
|
|
25
|
+
hot?: {
|
|
26
|
+
accept(path: string, callback: () => void): void;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
declare const require: (id: string) => unknown;
|
|
30
|
+
|
|
31
|
+
if (module.hot) {
|
|
32
|
+
module.hot.accept("./screens/home.js", () => {
|
|
33
|
+
currentHome = require("./screens/home.js") as typeof homeModule;
|
|
34
|
+
route.set(route.get() ?? "/", null, { replace: true });
|
|
35
|
+
});
|
|
36
|
+
module.hot.accept("./screens/detail.js", () => {
|
|
37
|
+
currentDetail = require("./screens/detail.js") as typeof detailModule;
|
|
38
|
+
route.set(route.get() ?? "/", null, { replace: true });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import m from "mithril-runtime";
|
|
2
|
+
import route from "mithril-lynx/route";
|
|
3
|
+
import { AppBar } from "../app-bar.js";
|
|
4
|
+
|
|
5
|
+
export function view() {
|
|
6
|
+
return m("view", { class: "Page" }, [
|
|
7
|
+
AppBar("Details", () => route.back()),
|
|
8
|
+
m("view", { class: "Content" }, [
|
|
9
|
+
m("text", { class: "Description" }, String(route.param("message") ?? "")),
|
|
10
|
+
]),
|
|
11
|
+
]);
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import m from "mithril-runtime";
|
|
2
|
+
import route from "mithril-lynx/route";
|
|
3
|
+
import { AppBar } from "../app-bar.js";
|
|
4
|
+
|
|
5
|
+
export function view() {
|
|
6
|
+
return m("view", { class: "Page" }, [
|
|
7
|
+
AppBar("Basic Activity"),
|
|
8
|
+
m("view", { class: "Content" }, [
|
|
9
|
+
m("text", { class: "Description" }, "This is the main screen."),
|
|
10
|
+
m(
|
|
11
|
+
route.Link,
|
|
12
|
+
{ href: "/detail", params: { message: "Hello from the main screen!" }, selector: "view", class: "Button" },
|
|
13
|
+
[m("text", { class: "Button-label" }, "Next")],
|
|
14
|
+
),
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import m from "mithril";
|
|
1
|
+
import m from "mithril-runtime";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export default { App, root: m(App) };
|
|
3
|
+
export function view() {
|
|
4
|
+
return m("view", { class: "Page" }, [
|
|
5
|
+
m("text", { class: "Title" }, "Hello, mithril-lynx!"),
|
|
6
|
+
]);
|
|
7
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import m from "mithril";
|
|
1
|
+
import m from "mithril-runtime";
|
|
3
2
|
|
|
4
3
|
import lynxLogo from "./assets/lynx-logo.png";
|
|
5
4
|
import mithrilLogo from "./assets/mithril-logo.png";
|
|
@@ -7,41 +6,36 @@ import arrow from "./assets/arrow.png";
|
|
|
7
6
|
|
|
8
7
|
let alterLogo = false;
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
];
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export default { App, root: m(App) };
|
|
9
|
+
export function view() {
|
|
10
|
+
return [
|
|
11
|
+
m("view", { class: "Background" }),
|
|
12
|
+
m("view", { class: "App" }, [
|
|
13
|
+
m("view", { class: "Banner" }, [
|
|
14
|
+
m("view", {
|
|
15
|
+
class: "Logo",
|
|
16
|
+
// No explicit redraw() call here — mithril-lynx repaints
|
|
17
|
+
// automatically after any event, so mutating state is enough.
|
|
18
|
+
ontap: () => {
|
|
19
|
+
alterLogo = !alterLogo;
|
|
20
|
+
},
|
|
21
|
+
}, [
|
|
22
|
+
alterLogo
|
|
23
|
+
? m("image", { src: mithrilLogo, class: "Logo--mithril" })
|
|
24
|
+
: m("image", { src: lynxLogo, class: "Logo--lynx" }),
|
|
25
|
+
]),
|
|
26
|
+
m("text", { class: "Title" }, "Mithril"),
|
|
27
|
+
m("text", { class: "Subtitle" }, "on Lynx"),
|
|
28
|
+
]),
|
|
29
|
+
m("view", { class: "Content" }, [
|
|
30
|
+
m("image", { src: arrow, class: "Arrow" }),
|
|
31
|
+
m("text", { class: "Description" }, "Tap the logo and have fun!"),
|
|
32
|
+
m("text", { class: "Hint" }, [
|
|
33
|
+
"Edit ",
|
|
34
|
+
m("text", { style: { fontStyle: "italic", color: "rgba(255, 255, 255, 0.85)" } }, "src/index.ts"),
|
|
35
|
+
" to see updates!",
|
|
36
|
+
]),
|
|
37
|
+
]),
|
|
38
|
+
m("view", { style: { flex: 1 } }),
|
|
39
|
+
]),
|
|
40
|
+
];
|
|
41
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 carlos-sweb
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
|
|
4
|
-
import { pluginLynxConfig } from "@lynx-js/config-rsbuild-plugin";
|
|
5
|
-
import { pluginQRCode } from "@lynx-js/qrcode-rsbuild-plugin";
|
|
6
|
-
import { defineConfig } from "@lynx-js/rspeedy";
|
|
7
|
-
|
|
8
|
-
import { pluginMithrilLynx } from "mithril-lynx/plugin";
|
|
9
|
-
|
|
10
|
-
const projectRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
|
-
export default defineConfig({
|
|
13
|
-
source: {
|
|
14
|
-
entry: {
|
|
15
|
-
"main-thread": path.join(projectRoot, "src/main-thread.js"),
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
output: {
|
|
19
|
-
distPath: {
|
|
20
|
-
root: path.join(projectRoot, "dist"),
|
|
21
|
-
},
|
|
22
|
-
filename: "[name].bundle",
|
|
23
|
-
// Lynx bundles are self-contained — image imports must be inlined as
|
|
24
|
-
// data URIs rather than left as separate file references.
|
|
25
|
-
dataUriLimit: Infinity,
|
|
26
|
-
},
|
|
27
|
-
plugins: [
|
|
28
|
-
pluginMithrilLynx(),
|
|
29
|
-
pluginLynxConfig({}),
|
|
30
|
-
pluginQRCode({
|
|
31
|
-
schema(url) {
|
|
32
|
-
// Opens the page in LynxExplorer in full screen mode.
|
|
33
|
-
return `${url}?fullscreen=true`;
|
|
34
|
-
},
|
|
35
|
-
}),
|
|
36
|
-
],
|
|
37
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{PROJECT_NAME}}",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "rspeedy dev",
|
|
8
|
-
"build": "rspeedy build",
|
|
9
|
-
"preview": "rspeedy preview"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"mithril": "2.3.8",
|
|
13
|
-
"mithril-lynx": "{{MITHRIL_LYNX_VERSION}}"
|
|
14
|
-
},
|
|
15
|
-
"devDependencies": {
|
|
16
|
-
"@lynx-js/config-rsbuild-plugin": "^0.2.0",
|
|
17
|
-
"@lynx-js/qrcode-rsbuild-plugin": "^0.7.0",
|
|
18
|
-
"@lynx-js/rspeedy": "^0.17.0",
|
|
19
|
-
"@lynx-js/template-webpack-plugin": "^0.16.0",
|
|
20
|
-
"@lynx-js/type-element-api": "0.0.9",
|
|
21
|
-
"@lynx-js/types": "4.1.0"
|
|
22
|
-
},
|
|
23
|
-
"engines": {
|
|
24
|
-
"node": "^20.19.0 || >=22.12.0"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import shim from "mithril-lynx";
|
|
2
|
-
import app from "./index.js";
|
|
3
|
-
|
|
4
|
-
// The native engine unconditionally invokes a global processData(initData)
|
|
5
|
-
// hook on every __RenderPage/__UpdatePage — install a pass-through default.
|
|
6
|
-
// (mithril-lynx/main-thread's setupApp() does this for you in data-channel
|
|
7
|
-
// mode; this bare main-thread-owned pattern doesn't go through that module.)
|
|
8
|
-
Object.assign(globalThis, {
|
|
9
|
-
processData: (data) => data,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
// Main-thread-owned mode — the simplest of mithril-lynx's three rendering
|
|
13
|
-
// modes: no background.js, no dual-bundle build. The app renders directly
|
|
14
|
-
// on the main thread, synchronously, on first paint; every later update
|
|
15
|
-
// flows through shim.redraw(), called from event handlers bound via
|
|
16
|
-
// Mithril's own on* attrs (see src/index.js).
|
|
17
|
-
lynx.getEngine().addEventListener("__RenderPage", () => {
|
|
18
|
-
const page = __CreatePage("0", 0);
|
|
19
|
-
shim.renderToPage(page, app.root);
|
|
20
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import m from "mithril";
|
|
2
|
-
import { createNavigator } from "mithril-lynx/navigation";
|
|
3
|
-
|
|
4
|
-
// A reusable app bar: a title, plus a back affordance when `onBack` is
|
|
5
|
-
// given. No m.route involved anywhere in this file — see
|
|
6
|
-
// mithril-lynx/navigation's own docs for why (Lynx pages have no URL to
|
|
7
|
-
// route to). Navigation here is a plain in-memory stack.
|
|
8
|
-
function AppBar(title, onBack) {
|
|
9
|
-
return m("view", { class: "AppBar" }, [
|
|
10
|
-
onBack != null ? m("text", { class: "AppBar-back", ontap: onBack }, "←") : null,
|
|
11
|
-
m("text", { class: "AppBar-title" }, title),
|
|
12
|
-
]);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const MainScreen = {
|
|
16
|
-
view(vnode) {
|
|
17
|
-
return m("view", { class: "Page" }, [
|
|
18
|
-
AppBar("Basic Activity"),
|
|
19
|
-
m("view", { class: "Content" }, [
|
|
20
|
-
m("text", { class: "Description" }, "This is the main screen."),
|
|
21
|
-
m("view", {
|
|
22
|
-
class: "Button",
|
|
23
|
-
ontap: () => vnode.attrs.nav.push(DetailsScreen, { message: "Hello from MainScreen!" }),
|
|
24
|
-
}, [m("text", { class: "Button-label" }, "Next")]),
|
|
25
|
-
]),
|
|
26
|
-
]);
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const DetailsScreen = {
|
|
31
|
-
view(vnode) {
|
|
32
|
-
return m("view", { class: "Page" }, [
|
|
33
|
-
AppBar("Details", () => vnode.attrs.nav.pop()),
|
|
34
|
-
m("view", { class: "Content" }, [
|
|
35
|
-
m("text", { class: "Description" }, vnode.attrs.message),
|
|
36
|
-
]),
|
|
37
|
-
]);
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const nav = createNavigator({ initial: MainScreen });
|
|
42
|
-
|
|
43
|
-
export default { root: m(nav.Navigator) };
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import m from "mithril";
|
|
2
|
-
import { createNavigator, type Nav } from "mithril-lynx/navigation";
|
|
3
|
-
|
|
4
|
-
interface DetailsAttrs {
|
|
5
|
-
nav: Nav;
|
|
6
|
-
message: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// A reusable app bar: a title, plus a back affordance when `onBack` is
|
|
10
|
-
// given. No m.route involved anywhere in this file — see
|
|
11
|
-
// mithril-lynx/navigation's own docs for why (Lynx pages have no URL to
|
|
12
|
-
// route to). Navigation here is a plain in-memory stack.
|
|
13
|
-
function AppBar(title: string, onBack?: () => void) {
|
|
14
|
-
return m("view", { class: "AppBar" }, [
|
|
15
|
-
onBack != null ? m("text", { class: "AppBar-back", ontap: onBack }, "←") : null,
|
|
16
|
-
m("text", { class: "AppBar-title" }, title),
|
|
17
|
-
]);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const MainScreen: m.Component<{ nav: Nav }> = {
|
|
21
|
-
view(vnode) {
|
|
22
|
-
return m("view", { class: "Page" }, [
|
|
23
|
-
AppBar("Basic Activity"),
|
|
24
|
-
m("view", { class: "Content" }, [
|
|
25
|
-
m("text", { class: "Description" }, "This is the main screen."),
|
|
26
|
-
m("view", {
|
|
27
|
-
class: "Button",
|
|
28
|
-
ontap: () => vnode.attrs.nav.push(DetailsScreen, { message: "Hello from MainScreen!" }),
|
|
29
|
-
}, [m("text", { class: "Button-label" }, "Next")]),
|
|
30
|
-
]),
|
|
31
|
-
]);
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const DetailsScreen: m.Component<DetailsAttrs> = {
|
|
36
|
-
view(vnode) {
|
|
37
|
-
return m("view", { class: "Page" }, [
|
|
38
|
-
AppBar("Details", () => vnode.attrs.nav.pop()),
|
|
39
|
-
m("view", { class: "Content" }, [
|
|
40
|
-
m("text", { class: "Description" }, vnode.attrs.message),
|
|
41
|
-
]),
|
|
42
|
-
]);
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const nav = createNavigator({ initial: MainScreen });
|
|
47
|
-
|
|
48
|
-
export default { root: m(nav.Navigator) };
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import shim from "mithril-lynx";
|
|
2
|
-
import m from "mithril";
|
|
3
|
-
|
|
4
|
-
import lynxLogo from "./assets/lynx-logo.png";
|
|
5
|
-
import mithrilLogo from "./assets/mithril-logo.png";
|
|
6
|
-
import arrow from "./assets/arrow.png";
|
|
7
|
-
|
|
8
|
-
let alterLogo = false;
|
|
9
|
-
|
|
10
|
-
const App = {
|
|
11
|
-
view() {
|
|
12
|
-
return [
|
|
13
|
-
m("view", { class: "Background" }),
|
|
14
|
-
m("view", { class: "App" }, [
|
|
15
|
-
m("view", { class: "Banner" }, [
|
|
16
|
-
m("view", {
|
|
17
|
-
class: "Logo",
|
|
18
|
-
// shim.redraw() — NOT mithril's own m.redraw(), which is a
|
|
19
|
-
// no-op in this shim-based architecture.
|
|
20
|
-
ontap: () => {
|
|
21
|
-
alterLogo = !alterLogo;
|
|
22
|
-
shim.redraw();
|
|
23
|
-
},
|
|
24
|
-
}, [
|
|
25
|
-
alterLogo
|
|
26
|
-
? m("image", { src: mithrilLogo, class: "Logo--mithril" })
|
|
27
|
-
: m("image", { src: lynxLogo, class: "Logo--lynx" }),
|
|
28
|
-
]),
|
|
29
|
-
m("text", { class: "Title" }, "Mithril"),
|
|
30
|
-
m("text", { class: "Subtitle" }, "on Lynx"),
|
|
31
|
-
]),
|
|
32
|
-
m("view", { class: "Content" }, [
|
|
33
|
-
m("image", { src: arrow, class: "Arrow" }),
|
|
34
|
-
m("text", { class: "Description" }, "Tap the logo and have fun!"),
|
|
35
|
-
m("text", { class: "Hint" }, [
|
|
36
|
-
"Edit ",
|
|
37
|
-
m("text", { style: { fontStyle: "italic", color: "rgba(255, 255, 255, 0.85)" } }, "src/index.js"),
|
|
38
|
-
" to see updates!",
|
|
39
|
-
]),
|
|
40
|
-
]),
|
|
41
|
-
m("view", { style: { flex: 1 } }),
|
|
42
|
-
]),
|
|
43
|
-
];
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export default { App, root: m(App) };
|