@nan0web/ui 1.0.2 → 1.0.4
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 +19 -54
- package/package.json +21 -17
- package/src/App/Core/CoreApp.js +14 -19
- package/src/App/Core/UI.js +4 -50
- package/src/App/Core/Widget.js +4 -6
- package/src/App/User/Command/Message.js +23 -37
- package/src/App/User/Command/index.js +3 -8
- package/src/App/User/UserApp.js +32 -27
- package/src/App/User/UserUI.js +4 -4
- package/src/App/User/index.js +0 -6
- package/src/App/index.js +0 -3
- package/src/README.md.js +33 -57
- package/src/StdIn.js +12 -15
- package/src/View/View.js +5 -5
- package/src/core/Error/index.js +9 -0
- package/src/core/Form/Form.js +43 -23
- package/src/core/Form/Input.js +16 -7
- package/src/core/Form/Message.js +6 -8
- package/src/core/InputAdapter.js +6 -2
- package/src/core/Message/Message.js +109 -19
- package/src/core/Message/OutputMessage.js +8 -8
- package/src/core/Message/index.js +3 -4
- package/src/core/Stream.js +10 -10
- package/src/core/UiAdapter.js +189 -0
- package/src/core/index.js +5 -6
- package/src/index.js +5 -4
- package/types/App/Core/CoreApp.d.ts +9 -9
- package/types/App/Core/UI.d.ts +5 -15
- package/types/App/Core/Widget.d.ts +7 -8
- package/types/App/User/Command/Message.d.ts +15 -29
- package/types/App/User/Command/Options.d.ts +9 -2
- package/types/App/User/Command/index.d.ts +3 -7
- package/types/App/User/UserApp.d.ts +19 -9
- package/types/App/User/UserUI.d.ts +0 -9
- package/types/App/User/index.d.ts +0 -4
- package/types/App/index.d.ts +1 -3
- package/types/Frame/Frame.d.ts +5 -5
- package/types/StdIn.d.ts +13 -13
- package/types/View/View.d.ts +9 -9
- package/types/core/Error/index.d.ts +6 -0
- package/types/core/Form/Form.d.ts +14 -11
- package/types/core/Form/Input.d.ts +20 -7
- package/types/core/Form/Message.d.ts +5 -4
- package/types/core/InputAdapter.d.ts +2 -0
- package/types/core/Intent.d.ts +91 -0
- package/types/core/Message/InputMessage.d.ts +5 -5
- package/types/core/Message/Message.d.ts +58 -15
- package/types/core/Message/OutputMessage.d.ts +4 -4
- package/types/core/Message/index.d.ts +3 -4
- package/types/core/Stream.d.ts +5 -4
- package/types/core/StreamEntry.d.ts +1 -1
- package/types/core/UiAdapter.d.ts +104 -0
- package/types/core/index.d.ts +3 -3
- package/types/index.d.ts +5 -4
- package/src/App/Command/Options.js +0 -78
- package/src/App/Command/index.js +0 -9
- package/src/App/User/Command/Options.js +0 -48
- package/src/core/Message/InputMessage.js +0 -119
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @nan0web/ui
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|---|---|---|---|---|
|
|
5
|
-
|🟢 `96.8%` |🧪 [English 🏴](https://github.com/nan0web/ui/blob/main/README.md)<br />[Українською 🇺🇦](https://github.com/nan0web/ui/blob/main/docs/uk/README.md) |🟡 `81.1%` |✅ d.ts 📜 system.md 🕹️ playground |1.0.1 |
|
|
3
|
+
<!-- %PACKAGE_STATUS% -->
|
|
6
4
|
|
|
7
5
|
A lightweight, agnostic UI framework designed with the **nan0web philosophy**
|
|
8
6
|
— one application logic, many UI implementations.
|
|
@@ -43,8 +41,7 @@ yarn add @nan0web/ui
|
|
|
43
41
|
|
|
44
42
|
UI communication is built around messages:
|
|
45
43
|
|
|
46
|
-
- **`
|
|
47
|
-
- **`InputMessage`** – user input message (value, options)
|
|
44
|
+
- **`UiMessage`** – abstract message base class
|
|
48
45
|
- **`OutputMessage`** – system output (content, error, priority)
|
|
49
46
|
|
|
50
47
|
Messages are simple, serializable data containers. They help build
|
|
@@ -53,15 +50,14 @@ decoupled communication systems between UI components.
|
|
|
53
50
|
How to create input and output messages?
|
|
54
51
|
```js
|
|
55
52
|
import { InputMessage, OutputMessage } from '@nan0web/ui'
|
|
56
|
-
|
|
57
|
-
const input = InputMessage.from({ value: 'Hello User' })
|
|
53
|
+
const input = UiMessage.from({ body: 'Hello User' })
|
|
58
54
|
const output = OutputMessage.from({ content: ['Welcome to @nan0web/ui'] })
|
|
59
|
-
console.info(input
|
|
60
|
-
console.info(output
|
|
55
|
+
console.info(input) // ← Message { body: "Hello User", head: {}, id: "....", type: "" }
|
|
56
|
+
console.info(String(output)) // ← Welcome to @nan0web/ui
|
|
61
57
|
```
|
|
62
58
|
### Forms
|
|
63
59
|
|
|
64
|
-
`
|
|
60
|
+
`UiForm` supports field definitions, data management, and schema validation.
|
|
65
61
|
Every form includes a title, fields, and current state.
|
|
66
62
|
|
|
67
63
|
Field types include:
|
|
@@ -73,11 +69,10 @@ Field types include:
|
|
|
73
69
|
- `checkbox`
|
|
74
70
|
- `textarea`
|
|
75
71
|
|
|
76
|
-
How to define and validate a
|
|
72
|
+
How to define and validate a UiForm?
|
|
77
73
|
```js
|
|
78
|
-
import {
|
|
79
|
-
|
|
80
|
-
const form = new UIForm({
|
|
74
|
+
import { UiForm } from '@nan0web/ui'
|
|
75
|
+
const form = new UiForm({
|
|
81
76
|
title: "Contact Form",
|
|
82
77
|
fields: [
|
|
83
78
|
FormInput.from({ name: "email", label: "Email Address", type: "email", required: true }),
|
|
@@ -88,11 +83,9 @@ const form = new UIForm({
|
|
|
88
83
|
message: "Hello!"
|
|
89
84
|
}
|
|
90
85
|
})
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
console.info(
|
|
94
|
-
console.info(result.errors.email) // ← Invalid email format
|
|
95
|
-
|
|
86
|
+
const errors = form.validate()
|
|
87
|
+
console.info(errors.size) // ← 1
|
|
88
|
+
console.info(errors.get("email")) // ← Invalid email format
|
|
96
89
|
```
|
|
97
90
|
### Components
|
|
98
91
|
|
|
@@ -104,7 +97,6 @@ Components render data as frame-ready output.
|
|
|
104
97
|
How to render the Welcome component?
|
|
105
98
|
```js
|
|
106
99
|
import { Welcome } from '@nan0web/ui'
|
|
107
|
-
|
|
108
100
|
const frame = Welcome({ user: { name: "Alice" } })
|
|
109
101
|
const firstLine = frame[0].join("")
|
|
110
102
|
console.info(firstLine) // ← Welcome Alice!
|
|
@@ -122,7 +114,6 @@ Every view has:
|
|
|
122
114
|
How to render frame with View?
|
|
123
115
|
```js
|
|
124
116
|
import { View } from '@nan0web/ui'
|
|
125
|
-
|
|
126
117
|
const view = new View()
|
|
127
118
|
view.render(1)(["Hello, world"])
|
|
128
119
|
console.info(String(view.frame)) // ← "\rHello, world"
|
|
@@ -141,38 +132,15 @@ Render methods:
|
|
|
141
132
|
How to create a Frame with fixed size?
|
|
142
133
|
```js
|
|
143
134
|
import { Frame } from '@nan0web/ui'
|
|
144
|
-
|
|
145
135
|
const frame = new Frame({
|
|
146
136
|
value: [["Frame content"]],
|
|
147
137
|
width: 20,
|
|
148
138
|
height: 5,
|
|
149
139
|
renderMethod: Frame.RenderMethod.APPEND,
|
|
150
140
|
})
|
|
151
|
-
|
|
152
141
|
const rendered = frame.render()
|
|
153
142
|
console.info(rendered.includes("Frame content")) // ← true
|
|
154
143
|
```
|
|
155
|
-
### App Architecture
|
|
156
|
-
|
|
157
|
-
`App` provides the main application logic.
|
|
158
|
-
|
|
159
|
-
- Core – minimal UI layer
|
|
160
|
-
- User – user-specific UI commands
|
|
161
|
-
|
|
162
|
-
Each app registers commands and binds them to UI actions.
|
|
163
|
-
|
|
164
|
-
How to create a basic user app that greets?
|
|
165
|
-
```js
|
|
166
|
-
import { App, View } from '@nan0web/ui'
|
|
167
|
-
|
|
168
|
-
const app = new App.User.App({ name: "GreetApp" })
|
|
169
|
-
const view = new View()
|
|
170
|
-
view.register("Welcome", Welcome)
|
|
171
|
-
|
|
172
|
-
const cmd = App.Command.Message.parse("welcome --user Bob")
|
|
173
|
-
const result = await app.processCommand(cmd, new App.User.UI(app, view))
|
|
174
|
-
console.info(String(result)) // ← Welcome Bob!
|
|
175
|
-
```
|
|
176
144
|
### Models
|
|
177
145
|
|
|
178
146
|
UI models are plain data objects managed by `Model` classes.
|
|
@@ -182,7 +150,6 @@ UI models are plain data objects managed by `Model` classes.
|
|
|
182
150
|
How to use a User model?
|
|
183
151
|
```js
|
|
184
152
|
import { Model } from '@nan0web/ui'
|
|
185
|
-
|
|
186
153
|
const user = new Model.User({ name: "Charlie", email: "charlie@example.com" })
|
|
187
154
|
console.info(user.name) // ← Charlie
|
|
188
155
|
console.info(user.email) // ← charlie@example.com
|
|
@@ -196,20 +163,18 @@ with minimal setup.
|
|
|
196
163
|
|
|
197
164
|
How to test UI components with assertions?
|
|
198
165
|
```js
|
|
199
|
-
import { Welcome
|
|
200
|
-
|
|
166
|
+
import { Welcome } from '@nan0web/ui'
|
|
201
167
|
const output = Welcome({ user: { name: "Test" } })
|
|
202
|
-
|
|
203
|
-
console.log(output[0].join("")) // ← Welcome Test!
|
|
168
|
+
console.info(output) // ← Welcome Test!
|
|
204
169
|
```
|
|
205
170
|
## Playground Demos
|
|
206
171
|
|
|
207
172
|
The library includes rich playground demos:
|
|
208
173
|
|
|
209
|
-
- [Registration Form](./
|
|
210
|
-
- [Currency Exchange](./
|
|
211
|
-
- [Mobile Top-up](./
|
|
212
|
-
- [Language Selector](./
|
|
174
|
+
- [Registration Form](./play/registration.form.js)
|
|
175
|
+
- [Currency Exchange](./play/currency.exchange.js)
|
|
176
|
+
- [Mobile Top-up](./play/topup.telephone.js)
|
|
177
|
+
- [Language Selector](./play/language.form.js)
|
|
213
178
|
|
|
214
179
|
Run to explore live functionality:
|
|
215
180
|
|
|
@@ -219,7 +184,7 @@ How to run the playground?
|
|
|
219
184
|
git clone https://github.com/nan0web/ui.git
|
|
220
185
|
cd ui
|
|
221
186
|
npm install
|
|
222
|
-
npm run
|
|
187
|
+
npm run play
|
|
223
188
|
```
|
|
224
189
|
|
|
225
190
|
## API Documentation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nan0web/ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "NaN•Web UI. One application logic (algorithm) and many UI.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -12,13 +12,16 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
|
-
"
|
|
16
|
-
"test": "node --test --test-timeout=3333 \"src/**/*.test.js\"
|
|
15
|
+
"play": "node play/main.js",
|
|
16
|
+
"test": "node --test --test-timeout=3333 \"src/**/*.test.js\"",
|
|
17
|
+
"test:nan0test": "node --test --test-timeout=3333 \"src/**/*.test.js\" | nan0test parse --fail",
|
|
17
18
|
"test:coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**/*.js\" --test-coverage-exclude=\"src/**/*.test.js\" --test \"src/**/*.test.js\"",
|
|
18
19
|
"test:coverage:collect": "nan0test coverage",
|
|
19
20
|
"test:docs": "node --test src/README.md.js",
|
|
20
21
|
"test:release": "node --test \"releases/**/*.test.js\"",
|
|
21
22
|
"test:status": "nan0test status --hide-name",
|
|
23
|
+
"test:play": "node --test --test-timeout=3333 \"play/**/*.test.js\"",
|
|
24
|
+
"test:all": "npm run test && npm run test:docs && npm run test:play && npm run build",
|
|
22
25
|
"precommit": "npm test",
|
|
23
26
|
"prepush": "npm test",
|
|
24
27
|
"prepare": "husky",
|
|
@@ -35,6 +38,10 @@
|
|
|
35
38
|
"import": "./src/Component/index.js",
|
|
36
39
|
"types": "./types/Component/index.d.ts"
|
|
37
40
|
},
|
|
41
|
+
"./core": {
|
|
42
|
+
"import": "./src/core/index.js",
|
|
43
|
+
"types": "./types/core/index.d.ts"
|
|
44
|
+
},
|
|
38
45
|
"./cli-app": "./apps/cli/src/index.js",
|
|
39
46
|
"./mobile-app": "./apps/mobile/src/index.js",
|
|
40
47
|
"./web-app": "./apps/web/src/App.jsx"
|
|
@@ -47,23 +54,20 @@
|
|
|
47
54
|
"license": "ISC",
|
|
48
55
|
"packageManager": "pnpm@10.11.0",
|
|
49
56
|
"devDependencies": {
|
|
50
|
-
"@nan0web/
|
|
51
|
-
"@nan0web/
|
|
52
|
-
"@nan0web/
|
|
53
|
-
"@nan0web/
|
|
54
|
-
"@nan0web/
|
|
55
|
-
"@nan0web/types": "workspace:*",
|
|
56
|
-
"@nan0web/ui-cli": "workspace:*",
|
|
57
|
+
"@nan0web/event": "^1.0.0",
|
|
58
|
+
"@nan0web/i18n": "^1.0.1",
|
|
59
|
+
"@nan0web/release": "1.0.1",
|
|
60
|
+
"@nan0web/test": "1.1.0",
|
|
61
|
+
"@nan0web/ui-cli": "1.0.2",
|
|
57
62
|
"@vitest/coverage-v8": "^3.2.4",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
63
|
+
"husky": "^9.1.7",
|
|
64
|
+
"vitest": "^3.2.4"
|
|
60
65
|
},
|
|
61
66
|
"dependencies": {
|
|
67
|
+
"@nan0web/co": "^2.0.0",
|
|
68
|
+
"@nan0web/event": "^1.0.0",
|
|
69
|
+
"@nan0web/types": "^1.2.0",
|
|
62
70
|
"string-width": "^7.2.0"
|
|
63
71
|
},
|
|
64
|
-
"peerDependencies": {
|
|
65
|
-
"@nan0web/co": "^1.0.0",
|
|
66
|
-
"@nan0web/event": "^1.0.0",
|
|
67
|
-
"@nan0web/types": "^1.0.0"
|
|
68
|
-
}
|
|
72
|
+
"peerDependencies": {}
|
|
69
73
|
}
|
package/src/App/Core/CoreApp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Message } from "@nan0web/co"
|
|
1
2
|
import { typeOf } from "@nan0web/types"
|
|
2
|
-
import { CommandMessage } from "../Command/index.js"
|
|
3
3
|
import UI from "./UI.js"
|
|
4
4
|
|
|
5
5
|
/** @typedef {Function} CommandFn */
|
|
@@ -18,7 +18,7 @@ export default class CoreApp {
|
|
|
18
18
|
/** @type {object} App state */
|
|
19
19
|
state
|
|
20
20
|
|
|
21
|
-
/** @type {
|
|
21
|
+
/** @type {Message} Starting command parsed from argv */
|
|
22
22
|
startCommand
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -26,18 +26,19 @@ export default class CoreApp {
|
|
|
26
26
|
* @param {object} props - CoreApp properties
|
|
27
27
|
* @param {string} [props.name="CoreApp"] - App name
|
|
28
28
|
* @param {object} [props.state={}] - Initial state object
|
|
29
|
-
* @param {
|
|
29
|
+
* @param {Message} [props.startCommand=new Message()] - Command line arguments to parse
|
|
30
30
|
*/
|
|
31
31
|
constructor(props = {}) {
|
|
32
32
|
const {
|
|
33
33
|
name = "CoreApp",
|
|
34
34
|
state = {},
|
|
35
|
-
|
|
35
|
+
startCommand = new Message(),
|
|
36
36
|
} = props
|
|
37
37
|
this.name = String(name)
|
|
38
38
|
this.state = state
|
|
39
39
|
this.commands = new Map()
|
|
40
|
-
|
|
40
|
+
// @deprecated @todo fix the argv by moving to ui-cli.
|
|
41
|
+
this.startCommand = Message.from(startCommand ?? {})
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -78,39 +79,33 @@ export default class CoreApp {
|
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
81
|
* Process a command message.
|
|
81
|
-
* @param {
|
|
82
|
+
* @param {Message} msg - Command to process
|
|
82
83
|
* @param {UI} ui - UI instance to use for rendering
|
|
83
84
|
* @returns {Promise<any>} Output of the command
|
|
84
85
|
* @throws {Error} If the command is not registered
|
|
85
86
|
*/
|
|
86
|
-
async processCommand(
|
|
87
|
-
const
|
|
88
|
-
const handler = this.commands.get(cmd)
|
|
87
|
+
async processCommand(msg, ui) {
|
|
88
|
+
const handler = this.commands.get(msg.constructor.name)
|
|
89
89
|
if (!handler) {
|
|
90
90
|
throw new Error([
|
|
91
91
|
"Unknown command", ": ",
|
|
92
|
-
|
|
92
|
+
msg.constructor.name, "\n",
|
|
93
93
|
"Available commands", ": ",
|
|
94
94
|
[...this.commands.keys()].join(", "),
|
|
95
95
|
].join(""))
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
const command = Class.from({
|
|
99
|
-
args: commandMessage.args.slice(1),
|
|
100
|
-
opts: commandMessage.opts,
|
|
101
|
-
})
|
|
102
|
-
return await handler.apply(this, [command, ui])
|
|
97
|
+
return await handler.apply(this, [msg, ui])
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
/**
|
|
106
101
|
* Process an array of command messages sequentially.
|
|
107
|
-
* @param {
|
|
102
|
+
* @param {Message[]} Messages - Array of commands to process
|
|
108
103
|
* @param {UI} ui - UI instance to use for rendering
|
|
109
104
|
* @returns {Promise<any[]>} Array of command outputs
|
|
110
105
|
*/
|
|
111
|
-
async processCommands(
|
|
106
|
+
async processCommands(Messages, ui) {
|
|
112
107
|
const results = []
|
|
113
|
-
for (const cmdMsg of
|
|
108
|
+
for (const cmdMsg of Messages) {
|
|
114
109
|
const result = await this.processCommand(cmdMsg, ui)
|
|
115
110
|
results.push(result)
|
|
116
111
|
}
|
package/src/App/Core/UI.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { Message } from "@nan0web/co"
|
|
2
|
+
import { notEmpty } from "@nan0web/types"
|
|
1
3
|
import View from "../../View/View.js"
|
|
2
4
|
import CoreApp from "./CoreApp.js"
|
|
3
5
|
import Widget from "./Widget.js"
|
|
4
|
-
import { notEmpty } from "@nan0web/types"
|
|
5
|
-
import { CommandMessage } from "../Command/index.js"
|
|
6
6
|
|
|
7
7
|
/** @typedef {import("../../View/View.js").ComponentFn} ComponentFn */
|
|
8
8
|
|
|
@@ -25,62 +25,16 @@ class UI extends Widget {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Convert raw input to
|
|
28
|
+
* Convert raw input to Message array.
|
|
29
29
|
* Must be implemented by subclasses.
|
|
30
30
|
* @param {any} rawInput - Raw input to convert
|
|
31
|
-
* @returns {
|
|
31
|
+
* @returns {Message[]} Array of command messages
|
|
32
32
|
* @throws {Error} Always thrown as this method must be implemented by subclasses
|
|
33
33
|
*/
|
|
34
34
|
convertInput(rawInput) {
|
|
35
35
|
throw new Error("convertInput must be implemented by subclass")
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Process input, run commands on app, and output results.
|
|
40
|
-
* Supports progress callback.
|
|
41
|
-
* @emits {start} Emitted when processing begins
|
|
42
|
-
* @emits {data} Emitted for each command being processed
|
|
43
|
-
* @emits {end} Emitted when all commands have been processed
|
|
44
|
-
* @param {any} rawInput - Raw input to process
|
|
45
|
-
* @returns {Promise<any[]>} Results of command processing
|
|
46
|
-
*/
|
|
47
|
-
async process(rawInput) {
|
|
48
|
-
const commands = this.convertInput(rawInput).filter(notEmpty)
|
|
49
|
-
const results = []
|
|
50
|
-
let count = commands.length
|
|
51
|
-
|
|
52
|
-
const proc = this.view.get("UIProcess")
|
|
53
|
-
if (proc) {
|
|
54
|
-
this.show(proc)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (0 === count && this.app.selectCommand) {
|
|
58
|
-
const answer = await this.app.selectCommand(this)
|
|
59
|
-
if (answer) {
|
|
60
|
-
const [input] = this.convertInput(rawInput)
|
|
61
|
-
const cmd = new CommandMessage({
|
|
62
|
-
argv: [answer],
|
|
63
|
-
opts: input?.opts ?? {},
|
|
64
|
-
})
|
|
65
|
-
commands.push(cmd)
|
|
66
|
-
++count
|
|
67
|
-
} else {
|
|
68
|
-
this.emit("end", { commands, results })
|
|
69
|
-
return results
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
this.emit("start", { commands })
|
|
73
|
-
for (let i = 0; i < count; i++) {
|
|
74
|
-
const command = commands[i]
|
|
75
|
-
this.emit("data", { i, count, command })
|
|
76
|
-
const result = await this.app.processCommand(command, this)
|
|
77
|
-
results.push(result)
|
|
78
|
-
}
|
|
79
|
-
this.emit("end", { commands, results })
|
|
80
|
-
// this.output(results)
|
|
81
|
-
return results
|
|
82
|
-
}
|
|
83
|
-
|
|
84
38
|
/**
|
|
85
39
|
* Sets up event handlers for UI process events.
|
|
86
40
|
* @param {ComponentFn} UIProcess - Process view component
|
package/src/App/Core/Widget.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import EventProcessor from "@nan0web/event/oop"
|
|
2
2
|
import View from "../../View/View.js"
|
|
3
|
-
import InputMessage from "../../core/Message/InputMessage.js"
|
|
4
3
|
import { StreamEntry } from "@nan0web/db"
|
|
4
|
+
import { UiMessage } from "../../core/index.js"
|
|
5
5
|
|
|
6
6
|
/** @typedef {import("./UI.js").ComponentFn} ComponentFn */
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ import { StreamEntry } from "@nan0web/db"
|
|
|
10
10
|
* Widget is a view with ability to input data in a specific format.
|
|
11
11
|
* Input and output data are typed classes.
|
|
12
12
|
*/
|
|
13
|
-
class Widget extends EventProcessor {
|
|
13
|
+
export default class Widget extends EventProcessor {
|
|
14
14
|
/** @type {View} The view associated with this widget */
|
|
15
15
|
view
|
|
16
16
|
|
|
@@ -25,8 +25,8 @@ class Widget extends EventProcessor {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Ask user for input data of specific class.
|
|
28
|
-
* @param {
|
|
29
|
-
* @returns {Promise<
|
|
28
|
+
* @param {UiMessage} input - instance of UiMessage or similar
|
|
29
|
+
* @returns {Promise<UiMessage | null>} instance of UiMessage or null
|
|
30
30
|
*/
|
|
31
31
|
async ask(input) {
|
|
32
32
|
return await this.view.ask(input)
|
|
@@ -63,5 +63,3 @@ class Widget extends EventProcessor {
|
|
|
63
63
|
return this.view.render(viewFn)(outputData)
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
export default Widget
|
|
@@ -1,44 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import Message from "@nan0web/co"
|
|
2
|
+
import UIMessage from "../../../core/Message/Message.js"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Creates a new UserAppCommandMessage instance.
|
|
10
|
-
* @param {object} props - Command message properties
|
|
11
|
-
* @param {string[]} [props.args=[]] - Command arguments
|
|
12
|
-
* @param {Partial<UserAppCommandOptions>} [props.opts={}] - User-specific options
|
|
13
|
-
*/
|
|
14
|
-
constructor(props = {}) {
|
|
15
|
-
super(props)
|
|
4
|
+
class DepsCommandParams {
|
|
5
|
+
fix = false
|
|
6
|
+
static fix = {
|
|
7
|
+
help: "Fix dependencies",
|
|
8
|
+
defaultValue: false
|
|
16
9
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {Partial<UserAppCommandOptions>} value
|
|
25
|
-
*/
|
|
26
|
-
set opts(value) {
|
|
27
|
-
super.opts = UserAppCommandOptions.from(value)
|
|
10
|
+
constructor(input = {}) {
|
|
11
|
+
const {
|
|
12
|
+
fix = this.fix
|
|
13
|
+
} = input
|
|
28
14
|
}
|
|
15
|
+
}
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return new this(result)
|
|
17
|
+
export class DepsCommand extends UIMessage {
|
|
18
|
+
static Body = DepsCommandParams
|
|
19
|
+
/** @type {DepsCommandParams} */
|
|
20
|
+
body
|
|
21
|
+
constructor(input = {}) {
|
|
22
|
+
const {
|
|
23
|
+
body = new DepsCommandParams()
|
|
24
|
+
} = UIMessage.parseBody(input, DepsCommandParams)
|
|
25
|
+
super(input)
|
|
26
|
+
this.body = body
|
|
41
27
|
}
|
|
42
28
|
}
|
|
43
29
|
|
|
44
|
-
export default
|
|
30
|
+
export default DepsCommand
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import Command from "../../Command/index.js"
|
|
2
1
|
import CommandMessage from "./Message.js"
|
|
3
|
-
import
|
|
2
|
+
import DepsCommand from "./Message.js"
|
|
4
3
|
|
|
5
|
-
export { CommandMessage,
|
|
4
|
+
export { CommandMessage, DepsCommand }
|
|
6
5
|
|
|
7
|
-
export default
|
|
8
|
-
...Command,
|
|
9
|
-
Message: CommandMessage,
|
|
10
|
-
Options: CommandOptions,
|
|
11
|
-
}
|
|
6
|
+
export default CommandMessage
|
package/src/App/User/UserApp.js
CHANGED
|
@@ -1,49 +1,56 @@
|
|
|
1
|
+
import { Message } from "@nan0web/co"
|
|
1
2
|
import { notEmpty } from "@nan0web/types"
|
|
2
3
|
import CoreApp from "../Core/CoreApp.js"
|
|
3
|
-
import Command, { CommandMessage } from "./Command/index.js"
|
|
4
4
|
import User from "../../Model/User/User.js"
|
|
5
5
|
import UserUI from "./UserUI.js"
|
|
6
|
-
import
|
|
6
|
+
import UserAppCommandMessage from "./Command/Message.js"
|
|
7
|
+
import DepsCommand from "./Command/Message.js"
|
|
8
|
+
import UIStream from "../../core/Stream.js"
|
|
9
|
+
import { UiMessage } from "../../core/index.js"
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* UserApp requires user name and shows Welcome view.
|
|
10
13
|
* If user.name is provided in command input, ignores user input.
|
|
11
14
|
* User can change user data to see another Welcome view.
|
|
12
15
|
*/
|
|
13
|
-
class UserApp extends CoreApp {
|
|
14
|
-
/** @type {CommandMessage} Starting command parsed from argv */
|
|
15
|
-
startCommand
|
|
16
|
-
|
|
17
|
-
/** @type {object} App state */
|
|
18
|
-
state
|
|
19
|
-
|
|
16
|
+
export default class UserApp extends CoreApp {
|
|
20
17
|
/**
|
|
21
18
|
* Creates a new UserApp instance.
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {string} [props.name="UserApp"] - App name
|
|
24
|
-
* @param {object} [props.state={}] - Initial state object
|
|
25
|
-
* @param {string[]} [props.argv=[]] - Command line arguments to parse
|
|
19
|
+
* @param {Partial<CoreApp>} [props={}] - UserApp properties
|
|
26
20
|
*/
|
|
27
21
|
constructor(props = {}) {
|
|
28
22
|
super(props)
|
|
29
|
-
const {
|
|
30
|
-
argv = [],
|
|
31
|
-
state = {},
|
|
32
|
-
} = props
|
|
33
|
-
this.state = state
|
|
34
|
-
this.startCommand = CommandMessage.parse(argv)
|
|
35
23
|
this.registerCommand("setUser", this.setUser.bind(this))
|
|
36
24
|
this.registerCommand("welcome", this.welcome.bind(this))
|
|
25
|
+
this.registerCommand("deps", this.handleDeps.bind(this)) // Register new command
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Handle deps command with async generator for stream processing.
|
|
30
|
+
* @param {DepsCommand} cmd - Command message with deps parameters
|
|
31
|
+
* @param {UserUI} ui - UI instance
|
|
32
|
+
* @returns {Promise<Object>} Command output
|
|
33
|
+
*/
|
|
34
|
+
async handleDeps(cmd, ui) {
|
|
35
|
+
// Example: Use async generator to stream deps processing
|
|
36
|
+
const processorFn = async () => new UIStream.StreamEntry({ value: { message: `Deps command executed with fix: ${cmd.body.fix}` }, done: true })
|
|
37
|
+
const generatorFn = UIStream.createProcessor(new AbortController().signal, processorFn)
|
|
38
|
+
await UIStream.process(new AbortController().signal, generatorFn,
|
|
39
|
+
(progress, item) => ui.output && ui.output(item.value), // Fix to output the value
|
|
40
|
+
(error) => ui.output && ui.output({ error }), // Assume ui has output method
|
|
41
|
+
(item) => ui.output && ui.output(item.value) // Fix complete callback
|
|
42
|
+
)
|
|
43
|
+
return { completed: true }
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
/**
|
|
40
47
|
* Set user data from params.
|
|
41
|
-
* @param {
|
|
48
|
+
* @param {UserAppCommandMessage} cmd - Command message with user data
|
|
42
49
|
* @param {UserUI} ui - UI instance
|
|
43
50
|
* @returns {Promise<{ message: string }>} Welcome message
|
|
44
51
|
*/
|
|
45
52
|
async setUser(cmd, ui) {
|
|
46
|
-
this.state.user = User.from(cmd.
|
|
53
|
+
this.state.user = User.from(cmd.body.user) // cmd is UserAppCommandMessage, has user
|
|
47
54
|
const frame = await this.welcome(cmd, ui)
|
|
48
55
|
return {
|
|
49
56
|
message: String(frame)
|
|
@@ -52,22 +59,20 @@ class UserApp extends CoreApp {
|
|
|
52
59
|
|
|
53
60
|
/**
|
|
54
61
|
* Show welcome message for current user.
|
|
55
|
-
* @param {
|
|
62
|
+
* @param {UserAppCommandMessage} cmd - Command message
|
|
56
63
|
* @param {UserUI} ui - UI instance
|
|
57
64
|
* @returns {Promise<string[][]>} Welcome view output
|
|
58
65
|
*/
|
|
59
66
|
async welcome(cmd, ui) {
|
|
60
|
-
if (cmd.
|
|
61
|
-
const user = User.from(cmd.
|
|
67
|
+
if (cmd.body.user) { // cmd is UserAppCommandMessage, has user
|
|
68
|
+
const user = User.from(cmd.body.user)
|
|
62
69
|
return ui.render("Welcome", { user })
|
|
63
70
|
}
|
|
64
71
|
if (notEmpty(this.user)) {
|
|
65
72
|
return ui.render("Welcome", { user: this.user })
|
|
66
73
|
}
|
|
67
|
-
const answer = await ui.ask(
|
|
74
|
+
const answer = await ui.ask(UiMessage.from("What is your name?"))
|
|
68
75
|
this.user = User.from(answer?.value)
|
|
69
76
|
return ui.render("Welcome", { user: this.user })
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
export default UserApp
|
package/src/App/User/UserUI.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Message } from "@nan0web/co"
|
|
1
2
|
import App from "../Core/index.js"
|
|
2
|
-
import { CommandMessage } from "./Command/index.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* UserUI connects UserApp and View.
|
|
@@ -8,13 +8,13 @@ import { CommandMessage } from "./Command/index.js"
|
|
|
8
8
|
*/
|
|
9
9
|
export default class UserUI extends App.UI {
|
|
10
10
|
/**
|
|
11
|
-
* Convert raw input to
|
|
11
|
+
* Convert raw input to Message array.
|
|
12
12
|
* If user.name provided in rawInput, use it directly.
|
|
13
13
|
* Otherwise ask user for name.
|
|
14
14
|
* @param {any} rawInput - Raw input to convert
|
|
15
|
-
* @returns {
|
|
15
|
+
* @returns {Message[]} Array of command messages
|
|
16
16
|
*/
|
|
17
17
|
convertInput(rawInput) {
|
|
18
|
-
return [
|
|
18
|
+
return [new Message({ body: rawInput })]
|
|
19
19
|
}
|
|
20
20
|
}
|
package/src/App/User/index.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import UserApp from "./UserApp.js"
|
|
2
2
|
import UserUI from "./UserUI.js"
|
|
3
|
-
import UserCommand from "./Command/index.js"
|
|
4
|
-
import Command from "../Command/index.js"
|
|
5
3
|
|
|
6
4
|
export { UserApp, UserUI }
|
|
7
5
|
|
|
8
6
|
export default {
|
|
9
7
|
App: UserApp,
|
|
10
8
|
UI: UserUI,
|
|
11
|
-
Command: {
|
|
12
|
-
...Command,
|
|
13
|
-
...UserCommand,
|
|
14
|
-
},
|
|
15
9
|
}
|