@typecad/ui 1.0.0-alpha.13 → 1.0.0-alpha.15
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 +1457 -1469
- package/dist/cli.js +22 -19
- package/dist/preview/host-ui-runtime.js +0 -1
- package/dist/ui-engine/model.js +7 -1
- package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -115
- package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -182
- package/dist/ui-engine/runtime-header/forward-decls.js +227 -216
- package/dist/ui-engine/runtime-header/init-press-input.js +138 -138
- package/dist/ui-engine/runtime-header/keyboard.js +676 -676
- package/dist/ui-engine/runtime-header/node-draw-body.js +1658 -1656
- package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -259
- package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -736
- package/dist/ui-engine/runtime-header/scroll-physics.js +4 -4
- package/dist/ui-engine/runtime-header/structs.js +228 -228
- package/dist/ui-engine/runtime-header/text-rendering.js +888 -888
- package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -118
- package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +896 -896
- package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -24
- package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +11 -11
- package/dist/ui-engine/runtime-header/types-defines.js +110 -110
- package/dist/ui-engine/shadcn-kit.d.ts +1 -1
- package/dist/ui-engine/shadcn-kit.js +4 -0
- package/dist/ui-engine/ui-lowering.d.ts +1 -1
- package/dist/ui-engine/ui-lowering.js +14 -5
- package/dist/ui-engine/ui-registry.js +9 -1
- package/dist/wizard/display-catalog.d.ts +0 -3
- package/dist/wizard/display-catalog.js +3 -12
- package/dist/wizard/index.d.ts +2 -0
- package/dist/wizard/index.js +1 -0
- package/dist/wizard/integration-wizard.d.ts +3 -0
- package/dist/wizard/integration-wizard.js +69 -20
- package/dist/wizard/package-writer.d.ts +20 -0
- package/dist/wizard/package-writer.js +79 -0
- package/package.json +4 -4
- package/src/cli.ts +90 -87
- package/src/preview/host-ui-runtime.ts +0 -1
- package/src/ui-engine/model.ts +6 -1
- package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -121
- package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -188
- package/src/ui-engine/runtime-header/forward-decls.ts +238 -227
- package/src/ui-engine/runtime-header/init-press-input.ts +144 -144
- package/src/ui-engine/runtime-header/keyboard.ts +689 -689
- package/src/ui-engine/runtime-header/node-draw-body.ts +1683 -1681
- package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -265
- package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -742
- package/src/ui-engine/runtime-header/scroll-physics.ts +4 -4
- package/src/ui-engine/runtime-header/structs.ts +234 -234
- package/src/ui-engine/runtime-header/text-rendering.ts +894 -894
- package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -124
- package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -902
- package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -30
- package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +11 -11
- package/src/ui-engine/runtime-header/types-defines.ts +116 -116
- package/src/ui-engine/shadcn-kit.ts +4 -0
- package/src/ui-engine/ui-lowering.ts +12 -4
- package/src/ui-engine/ui-registry.ts +9 -1
- package/src/wizard/display-catalog.ts +261 -273
- package/src/wizard/index.ts +46 -38
- package/src/wizard/integration-wizard.ts +679 -619
- package/src/wizard/package-writer.ts +98 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// Display + touch hardware catalog for the @typecad/ui integration wizard.
|
|
3
3
|
//
|
|
4
|
-
// Mirrors the built-in display profiles exported by
|
|
5
|
-
// (
|
|
4
|
+
// Mirrors the built-in display profiles exported by the active framework
|
|
5
|
+
// (BUILT_IN_PROFILES) and the TouchLibrary union in
|
|
6
6
|
// @typecad/cuttlefish's display-profile.ts. The wizard asks questions and
|
|
7
7
|
// fills defaults from these entries; the resulting object literal is written
|
|
8
8
|
// into the project's cuttlefish.config.ts `display` section.
|
|
@@ -32,7 +32,6 @@ export const DISPLAY_CATALOG = [
|
|
|
32
32
|
colorOrder: "rgb",
|
|
33
33
|
antialias: true,
|
|
34
34
|
},
|
|
35
|
-
arduinoLibraries: ["Adafruit GFX Library", "Adafruit ILI9341"],
|
|
36
35
|
wiringNote: "Defaults assume ESP32 VSPI: SCK=18, MOSI=23, MISO=19.",
|
|
37
36
|
},
|
|
38
37
|
{
|
|
@@ -57,10 +56,9 @@ export const DISPLAY_CATALOG = [
|
|
|
57
56
|
invert: false,
|
|
58
57
|
antialias: true,
|
|
59
58
|
},
|
|
60
|
-
arduinoLibraries: ["Adafruit GFX Library", "Adafruit ST7735 and ST7789 Library"],
|
|
61
59
|
wiringNote: "Defaults assume ESP32 VSPI: SCK=18, MOSI=23, MISO=19. Adafruit_ST7796S.h comes from the" +
|
|
62
60
|
" Adafruit_ST7735_and_ST7789 fork bundled in the typecode demos (demos/demo-st/lib) —" +
|
|
63
|
-
" copy it into your
|
|
61
|
+
" copy it into your libraries/ folder if your Library Manager copy lacks the class.",
|
|
64
62
|
},
|
|
65
63
|
{
|
|
66
64
|
id: "ssd1309-i2c",
|
|
@@ -75,7 +73,6 @@ export const DISPLAY_CATALOG = [
|
|
|
75
73
|
rotation: 0,
|
|
76
74
|
antialias: false,
|
|
77
75
|
},
|
|
78
|
-
arduinoLibraries: ["Adafruit GFX Library", "Adafruit SSD1306"],
|
|
79
76
|
},
|
|
80
77
|
{
|
|
81
78
|
id: "sdl",
|
|
@@ -116,7 +113,6 @@ export const TOUCH_CATALOG = [
|
|
|
116
113
|
// Typical raw-ADC range for 320×240 modules; refine per panel.
|
|
117
114
|
calibration: { xMin: 375, xMax: 3950, yMin: 200, yMax: 3750 },
|
|
118
115
|
},
|
|
119
|
-
arduinoLibrary: "XPT2046_Touchscreen",
|
|
120
116
|
},
|
|
121
117
|
{
|
|
122
118
|
id: "FT6336U",
|
|
@@ -128,7 +124,6 @@ export const TOUCH_CATALOG = [
|
|
|
128
124
|
i2cFrequency: 400_000,
|
|
129
125
|
},
|
|
130
126
|
// The framework's FT6336U adapter includes RAK14014_FT6336U.h.
|
|
131
|
-
arduinoLibrary: "RAK14014-FT6336U",
|
|
132
127
|
},
|
|
133
128
|
{
|
|
134
129
|
id: "GT911",
|
|
@@ -139,7 +134,6 @@ export const TOUCH_CATALOG = [
|
|
|
139
134
|
i2cAddress: 0x5d,
|
|
140
135
|
i2cFrequency: 400_000,
|
|
141
136
|
},
|
|
142
|
-
arduinoLibrary: "GT911",
|
|
143
137
|
},
|
|
144
138
|
{
|
|
145
139
|
id: "CST816S",
|
|
@@ -150,7 +144,6 @@ export const TOUCH_CATALOG = [
|
|
|
150
144
|
i2cAddress: 0x15,
|
|
151
145
|
i2cFrequency: 400_000,
|
|
152
146
|
},
|
|
153
|
-
arduinoLibrary: "CST816S",
|
|
154
147
|
},
|
|
155
148
|
{
|
|
156
149
|
id: "Adafruit_TouchScreen",
|
|
@@ -161,7 +154,6 @@ export const TOUCH_CATALOG = [
|
|
|
161
154
|
analogPins: { xp: 24, yp: 25, xm: 26, ym: 27, rx: 300 },
|
|
162
155
|
calibration: { xMin: 100, xMax: 900, yMin: 120, yMax: 900 },
|
|
163
156
|
},
|
|
164
|
-
arduinoLibrary: "Adafruit TouchScreen",
|
|
165
157
|
},
|
|
166
158
|
{
|
|
167
159
|
id: "Adafruit_STMPE610",
|
|
@@ -172,7 +164,6 @@ export const TOUCH_CATALOG = [
|
|
|
172
164
|
cs: 14,
|
|
173
165
|
calibration: { xMin: 375, xMax: 3950, yMin: 200, yMax: 3750 },
|
|
174
166
|
},
|
|
175
|
-
arduinoLibrary: "Adafruit STMPE610",
|
|
176
167
|
},
|
|
177
168
|
{
|
|
178
169
|
id: "adapter",
|
package/dist/wizard/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { DISPLAY_CATALOG, TOUCH_CATALOG, findPinConflicts, } from "./display-cat
|
|
|
2
2
|
export type { DisplayCatalogEntry, TouchCatalogEntry, WizardBus, WizardTouchKind, } from "./display-catalog.js";
|
|
3
3
|
export { findCuttlefishConfig, findSyntaxError, readConfigSection, readEntryPath, renderDisplayBody, renderDisplayProperty, upsertDisplaySection, } from "./config-writer.js";
|
|
4
4
|
export type { ConfigRecord, RenderDisplayOptions, UpsertDisplayResult, } from "./config-writer.js";
|
|
5
|
+
export { findPackageJson, previewScriptCommand, readPackageScript, upsertPackageScript, } from "./package-writer.js";
|
|
6
|
+
export type { UpsertScriptResult } from "./package-writer.js";
|
|
5
7
|
export { renderStarterUi } from "./starter-ui.js";
|
|
6
8
|
export { runIntegrationWizard } from "./integration-wizard.js";
|
|
7
9
|
export type { WizardRunResult, WizardStreams } from "./integration-wizard.js";
|
package/dist/wizard/index.js
CHANGED
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
export { DISPLAY_CATALOG, TOUCH_CATALOG, findPinConflicts, } from "./display-catalog.js";
|
|
8
8
|
export { findCuttlefishConfig, findSyntaxError, readConfigSection, readEntryPath, renderDisplayBody, renderDisplayProperty, upsertDisplaySection, } from "./config-writer.js";
|
|
9
|
+
export { findPackageJson, previewScriptCommand, readPackageScript, upsertPackageScript, } from "./package-writer.js";
|
|
9
10
|
export { renderStarterUi } from "./starter-ui.js";
|
|
10
11
|
export { runIntegrationWizard } from "./integration-wizard.js";
|
|
@@ -2,6 +2,9 @@ export interface WizardRunResult {
|
|
|
2
2
|
exitCode: number;
|
|
3
3
|
/** Absolute path of the config that was written, when a write happened. */
|
|
4
4
|
configPath?: string;
|
|
5
|
+
/** Absolute path of the package.json holding the preview script, when the
|
|
6
|
+
* script was added or already present. */
|
|
7
|
+
packageJsonPath?: string;
|
|
5
8
|
}
|
|
6
9
|
/** Optional stream overrides so tests can drive the interactive flow. */
|
|
7
10
|
export interface WizardStreams {
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// into their project's cuttlefish.config.ts: pick a display, answer bus/pin/
|
|
6
6
|
// speed questions with hardware-aware defaults, then splice the resulting
|
|
7
7
|
// `display` section into the config without touching any other section.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
8
|
+
// Also adds a `preview` npm script to package.json so the desktop preview
|
|
9
|
+
// renderer starts via `npm run preview`. Offers to create a starter .ui entry
|
|
10
|
+
// file when none exists and prints the next build/flash/library steps at the
|
|
11
|
+
// end.
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
11
13
|
import fs from "node:fs";
|
|
12
14
|
import path from "node:path";
|
|
@@ -14,6 +16,7 @@ import chalk from "chalk";
|
|
|
14
16
|
import { createPromptInterface, promptConfirm, promptInt, promptSelect, promptText, } from "./prompts.js";
|
|
15
17
|
import { DISPLAY_CATALOG, TOUCH_CATALOG, findPinConflicts, } from "./display-catalog.js";
|
|
16
18
|
import { findCuttlefishConfig, findSyntaxError, readConfigSection, readEntryPath, renderDisplayProperty, upsertDisplaySection, } from "./config-writer.js";
|
|
19
|
+
import { findPackageJson, previewScriptCommand, readPackageScript, upsertPackageScript, } from "./package-writer.js";
|
|
17
20
|
import { renderStarterUi } from "./starter-ui.js";
|
|
18
21
|
/** Keys the wizard manages — anything else found in an existing display
|
|
19
22
|
* section is carried over untouched instead of being silently dropped. */
|
|
@@ -405,7 +408,35 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
|
|
|
405
408
|
return { exitCode: 1 };
|
|
406
409
|
}
|
|
407
410
|
}
|
|
408
|
-
// --- 8.
|
|
411
|
+
// --- 8. package.json preview script ------------------------------------------
|
|
412
|
+
// `npm run preview` should start the desktop preview renderer, pointing at
|
|
413
|
+
// the config the wizard just verified. An existing identical script is
|
|
414
|
+
// left untouched; a differing one is only replaced with consent.
|
|
415
|
+
let packageJsonPath;
|
|
416
|
+
let packageJsonText;
|
|
417
|
+
let previewCommand;
|
|
418
|
+
let existingPreview;
|
|
419
|
+
let replacePreview = false;
|
|
420
|
+
const foundPackageJson = findPackageJson(path.dirname(configPath));
|
|
421
|
+
if (foundPackageJson) {
|
|
422
|
+
try {
|
|
423
|
+
packageJsonText = fs.readFileSync(foundPackageJson, "utf-8");
|
|
424
|
+
existingPreview = readPackageScript(packageJsonText, "preview");
|
|
425
|
+
packageJsonPath = foundPackageJson;
|
|
426
|
+
previewCommand = previewScriptCommand(foundPackageJson, configPath);
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
console.log(` ${chalk.yellow("!")} ${err instanceof Error ? err.message : String(err)} — skipping the npm preview script.`);
|
|
430
|
+
}
|
|
431
|
+
if (previewCommand && existingPreview !== undefined && existingPreview !== previewCommand) {
|
|
432
|
+
console.log(` ${chalk.yellow("!")} package.json already has a preview script: ${chalk.white(existingPreview)}`);
|
|
433
|
+
replacePreview = await promptConfirm(rl, "Replace it with the wizard's preview command?", true);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
console.log(`${chalk.yellow("!")} No package.json found next to ${path.relative(cwd, configPath)} — skipping the npm preview script.`);
|
|
438
|
+
}
|
|
439
|
+
// --- 9. Summary + write -------------------------------------------------------
|
|
409
440
|
console.log();
|
|
410
441
|
console.log(chalk.dim(" The following display section will be written to"));
|
|
411
442
|
console.log(chalk.dim(` ${path.relative(cwd, configPath)}:`));
|
|
@@ -421,6 +452,17 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
|
|
|
421
452
|
if (carried.length > 0) {
|
|
422
453
|
console.log(` ${chalk.dim(`Kept existing display settings the wizard does not manage: ${carried.join(", ")}`)}`);
|
|
423
454
|
}
|
|
455
|
+
if (previewCommand && packageJsonPath) {
|
|
456
|
+
if (existingPreview === previewCommand) {
|
|
457
|
+
console.log(` ${chalk.dim(`npm script already configured: preview — "${previewCommand}"`)}`);
|
|
458
|
+
}
|
|
459
|
+
else if (existingPreview === undefined || replacePreview) {
|
|
460
|
+
console.log(` ${chalk.dim("Also writes npm script:")} ${chalk.white("preview")}${chalk.dim(` — "${previewCommand}"`)}`);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.log(` ${chalk.dim(`Kept existing preview script: "${existingPreview}"`)}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
424
466
|
console.log();
|
|
425
467
|
if (!(await promptConfirm(rl, "Write this configuration?", true))) {
|
|
426
468
|
console.log(chalk.dim(" Aborted — nothing was written."));
|
|
@@ -434,7 +476,21 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
|
|
|
434
476
|
}
|
|
435
477
|
fs.writeFileSync(configPath, updated.text, "utf-8");
|
|
436
478
|
console.log(`${chalk.green("✓")} ${updated.mode === "replaced" ? "Updated" : "Added"} the display section in ${chalk.white(path.relative(cwd, configPath))}`);
|
|
437
|
-
//
|
|
479
|
+
// The preview script rides along with the confirmed write: added when
|
|
480
|
+
// missing, confirmed verbatim when already the wizard's command, and only
|
|
481
|
+
// replaced when the user consented above.
|
|
482
|
+
if (packageJsonPath && packageJsonText !== undefined && previewCommand
|
|
483
|
+
&& (existingPreview === undefined || existingPreview === previewCommand || replacePreview)) {
|
|
484
|
+
const scriptUpsert = upsertPackageScript(packageJsonText, "preview", previewCommand);
|
|
485
|
+
if (scriptUpsert.changed) {
|
|
486
|
+
fs.writeFileSync(packageJsonPath, scriptUpsert.text, "utf-8");
|
|
487
|
+
console.log(`${chalk.green("✓")} ${scriptUpsert.previous !== undefined ? "Updated" : "Added"} npm script ${chalk.white("preview")} in ${chalk.white(path.relative(cwd, packageJsonPath))}`);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
console.log(`${chalk.green("✓")} npm script ${chalk.white("preview")} already configured in ${chalk.white(path.relative(cwd, packageJsonPath))}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// --- 10. Starter entry file ------------------------------------------------------
|
|
438
494
|
if (entryPath) {
|
|
439
495
|
const entryAbs = path.resolve(path.dirname(configPath), entryPath);
|
|
440
496
|
if (!fs.existsSync(entryAbs) && entryAbs.endsWith(".ui")) {
|
|
@@ -445,31 +501,20 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
|
|
|
445
501
|
}
|
|
446
502
|
}
|
|
447
503
|
}
|
|
448
|
-
// ---
|
|
504
|
+
// --- 11. Next steps ---------------------------------------------------------------
|
|
449
505
|
console.log();
|
|
450
506
|
console.log(chalk.cyan("Next steps"));
|
|
451
507
|
console.log();
|
|
452
|
-
const
|
|
453
|
-
if (displayEntry.arduinoLibraries)
|
|
454
|
-
libraries.push(...displayEntry.arduinoLibraries);
|
|
455
|
-
if (touchEntry?.arduinoLibrary && touchRecord?.library)
|
|
456
|
-
libraries.push(touchEntry.arduinoLibrary);
|
|
457
|
-
if (libraries.length > 0) {
|
|
458
|
-
console.log(" 1. Install the display/touch Arduino libraries (once per machine):");
|
|
459
|
-
console.log();
|
|
460
|
-
console.log(` ${chalk.cyan(`arduino-cli lib install ${libraries.map((lib) => `"${lib}"`).join(" ")}`)}`);
|
|
461
|
-
console.log();
|
|
462
|
-
}
|
|
463
|
-
const firstStep = libraries.length > 0 ? 2 : 1;
|
|
508
|
+
const firstStep = 1;
|
|
464
509
|
console.log(` ${firstStep}. Preview your UI on the desktop:`);
|
|
465
510
|
console.log();
|
|
466
|
-
console.log(` ${chalk.cyan("npx @typecad/cuttlefish preview")}`);
|
|
511
|
+
console.log(` ${chalk.cyan(previewCommand ? "npm run preview" : "npx @typecad/cuttlefish preview")}`);
|
|
467
512
|
console.log();
|
|
468
513
|
console.log(` ${firstStep + 1}. Compile for your board:`);
|
|
469
514
|
console.log();
|
|
470
515
|
console.log(` ${chalk.cyan("npx @typecad/cuttlefish build --compile")}`);
|
|
471
516
|
console.log();
|
|
472
|
-
console.log(` ${firstStep + 2}. Flash it (
|
|
517
|
+
console.log(` ${firstStep + 2}. Flash it (pass --port, or set CUTTLEFISH_PORT):`);
|
|
473
518
|
console.log();
|
|
474
519
|
console.log(` ${chalk.cyan("npx @typecad/cuttlefish build --compile --upload")}`);
|
|
475
520
|
console.log();
|
|
@@ -478,7 +523,11 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
|
|
|
478
523
|
console.log(chalk.dim(" off-target, calibrate your panel and update display.touch.calibration."));
|
|
479
524
|
console.log();
|
|
480
525
|
}
|
|
481
|
-
return {
|
|
526
|
+
return {
|
|
527
|
+
exitCode: 0,
|
|
528
|
+
configPath,
|
|
529
|
+
...(packageJsonPath && previewCommand ? { packageJsonPath } : {}),
|
|
530
|
+
};
|
|
482
531
|
}
|
|
483
532
|
finally {
|
|
484
533
|
rl.close();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Walk up from `startDir` looking for package.json. */
|
|
2
|
+
export declare function findPackageJson(startDir: string): string | undefined;
|
|
3
|
+
/** Read a single npm script; undefined when missing. Throws on invalid JSON. */
|
|
4
|
+
export declare function readPackageScript(sourceText: string, name: string): string | undefined;
|
|
5
|
+
export interface UpsertScriptResult {
|
|
6
|
+
text: string;
|
|
7
|
+
/** false — the script already had this exact command; text is unchanged. */
|
|
8
|
+
changed: boolean;
|
|
9
|
+
/** The command that was replaced, when a different script existed. */
|
|
10
|
+
previous?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Insert or update one npm script, preserving every other key and their order. */
|
|
13
|
+
export declare function upsertPackageScript(sourceText: string, name: string, command: string): UpsertScriptResult;
|
|
14
|
+
/**
|
|
15
|
+
* The `cuttlefish preview` command the wizard writes as the `preview` npm
|
|
16
|
+
* script. The --config path is made relative to the package.json directory
|
|
17
|
+
* (npm scripts run there), posix-separated, and explicitly ./-prefixed so
|
|
18
|
+
* sibling configs render as `./cuttlefish.config.ts`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function previewScriptCommand(packageJsonPath: string, configPath: string): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// package.json script writer for the @typecad/ui integration wizard.
|
|
3
|
+
//
|
|
4
|
+
// package.json is plain JSON (no comments, no expressions), so unlike
|
|
5
|
+
// cuttlefish.config.ts (see config-writer.ts) this needs no AST surgery —
|
|
6
|
+
// parse, compare, set, and re-serialize with the 2-space formatting npm and
|
|
7
|
+
// `cuttlefish create` both write. An unchanged script is a no-op: the
|
|
8
|
+
// original text is returned verbatim so a re-run never reformats the file.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
const PACKAGE_FILENAME = "package.json";
|
|
13
|
+
/** Walk up from `startDir` looking for package.json. */
|
|
14
|
+
export function findPackageJson(startDir) {
|
|
15
|
+
let dir = path.resolve(startDir);
|
|
16
|
+
for (;;) {
|
|
17
|
+
const candidate = path.join(dir, PACKAGE_FILENAME);
|
|
18
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
const parent = path.dirname(dir);
|
|
22
|
+
if (parent === dir)
|
|
23
|
+
return undefined;
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function parsePackageJson(sourceText) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(sourceText);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
throw new Error(`package.json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function scriptsOf(pkg) {
|
|
36
|
+
const scripts = pkg.scripts;
|
|
37
|
+
return typeof scripts === "object" && scripts !== null && !Array.isArray(scripts)
|
|
38
|
+
? scripts
|
|
39
|
+
: undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Read a single npm script; undefined when missing. Throws on invalid JSON. */
|
|
42
|
+
export function readPackageScript(sourceText, name) {
|
|
43
|
+
const scripts = scriptsOf(parsePackageJson(sourceText));
|
|
44
|
+
const value = scripts?.[name];
|
|
45
|
+
return typeof value === "string" ? value : undefined;
|
|
46
|
+
}
|
|
47
|
+
/** Insert or update one npm script, preserving every other key and their order. */
|
|
48
|
+
export function upsertPackageScript(sourceText, name, command) {
|
|
49
|
+
const pkg = parsePackageJson(sourceText);
|
|
50
|
+
const scripts = scriptsOf(pkg);
|
|
51
|
+
const previous = scripts?.[name];
|
|
52
|
+
if (previous === command) {
|
|
53
|
+
return { text: sourceText, changed: false };
|
|
54
|
+
}
|
|
55
|
+
if (!scripts) {
|
|
56
|
+
pkg.scripts = {};
|
|
57
|
+
}
|
|
58
|
+
pkg.scripts[name] = command;
|
|
59
|
+
// Keep the file's trailing-newline convention (npm writes LF; a missing
|
|
60
|
+
// final newline stays missing so the diff is one line, not two).
|
|
61
|
+
const eol = sourceText.endsWith("\r\n") ? "\r\n" : sourceText.endsWith("\n") ? "\n" : "";
|
|
62
|
+
return {
|
|
63
|
+
text: `${JSON.stringify(pkg, null, 2)}${eol}`,
|
|
64
|
+
changed: true,
|
|
65
|
+
...(previous !== undefined ? { previous } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The `cuttlefish preview` command the wizard writes as the `preview` npm
|
|
70
|
+
* script. The --config path is made relative to the package.json directory
|
|
71
|
+
* (npm scripts run there), posix-separated, and explicitly ./-prefixed so
|
|
72
|
+
* sibling configs render as `./cuttlefish.config.ts`.
|
|
73
|
+
*/
|
|
74
|
+
export function previewScriptCommand(packageJsonPath, configPath) {
|
|
75
|
+
let rel = path.relative(path.dirname(packageJsonPath), configPath).split(path.sep).join("/");
|
|
76
|
+
if (!rel.startsWith("."))
|
|
77
|
+
rel = `./${rel}`;
|
|
78
|
+
return `cuttlefish preview --config ${rel}`;
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typecad/ui",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.15",
|
|
4
4
|
"description": "TypeCAD UI authoring library — HTML/CSS-driven graphics for microcontrollers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"assets"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@typecad/cuttlefish": "1.0.0-alpha.
|
|
48
|
-
"chalk": "^
|
|
47
|
+
"@typecad/cuttlefish": "1.0.0-alpha.15",
|
|
48
|
+
"chalk": "^6.0.0",
|
|
49
49
|
"css-tree": "^3.2.1",
|
|
50
50
|
"decode-ico": "^0.4.1",
|
|
51
51
|
"linkedom": "^0.18.12",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"firmware"
|
|
87
87
|
],
|
|
88
88
|
"engines": {
|
|
89
|
-
"node": ">=
|
|
89
|
+
"node": ">=22.11.0"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@types/node": "^22.10.7",
|
package/src/cli.ts
CHANGED
|
@@ -1,87 +1,90 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// ---------------------------------------------------------------------------
|
|
3
|
-
// @typecad/ui CLI — display integration wizard.
|
|
4
|
-
//
|
|
5
|
-
// npx @typecad/ui --config Run the interactive integration wizard
|
|
6
|
-
// npx @typecad/ui --help Show usage
|
|
7
|
-
// npx @typecad/ui --version Show the installed version
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
import { runIntegrationWizard } from "./wizard/integration-wizard.js";
|
|
11
|
-
|
|
12
|
-
const USAGE = `
|
|
13
|
-
@typecad/ui — HTML/CSS-driven graphics for microcontrollers
|
|
14
|
-
|
|
15
|
-
Usage:
|
|
16
|
-
npx @typecad/ui --config Configure a display (+ touch) for your
|
|
17
|
-
cuttlefish project interactively. Writes the
|
|
18
|
-
\`display\` section of cuttlefish.config.ts
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log("unknown");
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.error("
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// @typecad/ui CLI — display integration wizard.
|
|
4
|
+
//
|
|
5
|
+
// npx @typecad/ui --config Run the interactive integration wizard
|
|
6
|
+
// npx @typecad/ui --help Show usage
|
|
7
|
+
// npx @typecad/ui --version Show the installed version
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { runIntegrationWizard } from "./wizard/integration-wizard.js";
|
|
11
|
+
|
|
12
|
+
const USAGE = `
|
|
13
|
+
@typecad/ui — HTML/CSS-driven graphics for microcontrollers
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
npx @typecad/ui --config Configure a display (+ touch) for your
|
|
17
|
+
cuttlefish project interactively. Writes the
|
|
18
|
+
\`display\` section of cuttlefish.config.ts
|
|
19
|
+
and a \`preview\` npm script.
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--config Run the integration wizard
|
|
23
|
+
--help, -h Show this help
|
|
24
|
+
--version, -v Print the installed @typecad/ui version
|
|
25
|
+
|
|
26
|
+
The wizard asks which display module you are using (ILI9341 / ST7796S SPI TFT,
|
|
27
|
+
SSD1309 I2C OLED, desktop simulator, or custom), then walks through bus pins,
|
|
28
|
+
SPI/I2C speed, rotation, and touch controller wiring with hardware-aware
|
|
29
|
+
defaults. It never touches the rest of your cuttlefish.config.ts, and adds a
|
|
30
|
+
\`preview\` script to package.json so the desktop preview renderer starts with
|
|
31
|
+
\`npm run preview\`.
|
|
32
|
+
|
|
33
|
+
Docs: https://github.com/justind000/typecode/tree/main/packages/ui
|
|
34
|
+
`.trim();
|
|
35
|
+
|
|
36
|
+
function printUsage(): void {
|
|
37
|
+
console.log(USAGE);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main(argv: string[]): Promise<number> {
|
|
41
|
+
const args = argv.slice(2);
|
|
42
|
+
|
|
43
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
44
|
+
printUsage();
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
49
|
+
// Lazily read the version so importing this module stays side-effect free.
|
|
50
|
+
const { readFile } = await import("node:fs/promises");
|
|
51
|
+
const { fileURLToPath } = await import("node:url");
|
|
52
|
+
const { dirname, join } = await import("node:path");
|
|
53
|
+
try {
|
|
54
|
+
const packageJson = JSON.parse(
|
|
55
|
+
await readFile(join(dirname(dirname(fileURLToPath(import.meta.url))), "package.json"), "utf-8"),
|
|
56
|
+
) as { version?: string };
|
|
57
|
+
console.log(packageJson.version ?? "unknown");
|
|
58
|
+
return 0;
|
|
59
|
+
} catch {
|
|
60
|
+
console.log("unknown");
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (args.length === 0 || (args.length === 1 && (args[0] === "--config" || args[0] === "config"))) {
|
|
66
|
+
if (!process.stdin.isTTY) {
|
|
67
|
+
console.error("The integration wizard is interactive and needs a terminal.");
|
|
68
|
+
console.error("Run `npx @typecad/ui --config` from your project directory, or configure");
|
|
69
|
+
console.error("the `display` section of cuttlefish.config.ts manually:");
|
|
70
|
+
console.error("https://github.com/justind000/typecode/tree/main/packages/ui#display-configuration");
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
const result = await runIntegrationWizard(process.cwd());
|
|
74
|
+
return result.exitCode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.error(`Unknown option${args.length > 1 ? "s" : ""}: ${args.join(" ")}\n`);
|
|
78
|
+
printUsage();
|
|
79
|
+
return 2;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main(process.argv).then(
|
|
83
|
+
(code) => {
|
|
84
|
+
process.exitCode = code;
|
|
85
|
+
},
|
|
86
|
+
(error) => {
|
|
87
|
+
console.error(error instanceof Error ? error.message : error);
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
},
|
|
90
|
+
);
|
|
@@ -665,7 +665,6 @@ export class PreviewUIRuntime {
|
|
|
665
665
|
}
|
|
666
666
|
|
|
667
667
|
private navigate(screenIdx: number): void {
|
|
668
|
-
this.onDiagnostics?.(`NAV -> screen ${screenIdx}`);
|
|
669
668
|
const next = Math.trunc(Number(screenIdx));
|
|
670
669
|
if (!Number.isFinite(next) || next < 0 || next >= this.screenCount || next === this.activeScreen) return;
|
|
671
670
|
this.activeScreen = next;
|
package/src/ui-engine/model.ts
CHANGED
|
@@ -322,9 +322,14 @@ function borderStyle(style: CSSProperty): 0 | 1 | 2 {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
function borderWidthOf(style: CSSProperty): number {
|
|
325
|
+
// A none-style border renders nothing and must consume no space either:
|
|
326
|
+
// the draw path subtracts borderWidth*2 from the text max width, so a
|
|
327
|
+
// residual UA width on a borderless kit button wrapped its last glyph
|
|
328
|
+
// ("Primar" / "y"). Style none wins over any declared width.
|
|
329
|
+
if (borderStyle(style) === 0) return 0;
|
|
325
330
|
const px = cssPx(style.borderWidth);
|
|
326
331
|
if (px > 0) return Math.max(1, Math.min(8, px));
|
|
327
|
-
return
|
|
332
|
+
return 1;
|
|
328
333
|
}
|
|
329
334
|
|
|
330
335
|
/** Per-side border width: the per-side field wins when set, else falls back to
|