@nan0web/ui 1.1.0 → 1.5.2
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 +48 -14
- package/package.json +25 -13
- package/src/App/Command/DepsCommand.js +5 -9
- package/src/App/Core/CoreApp.js +18 -17
- package/src/App/Core/UI.js +11 -15
- package/src/App/Core/Widget.js +6 -10
- package/src/App/Core/index.js +3 -3
- package/src/App/Scenario.js +4 -4
- package/src/App/User/Command/Message.js +1 -1
- package/src/App/User/Command/index.js +1 -1
- package/src/App/User/UserApp.js +28 -21
- package/src/App/User/UserUI.js +2 -2
- package/src/App/User/index.js +2 -2
- package/src/App/index.js +5 -10
- package/src/Component/Process/Input.js +10 -17
- package/src/Component/Process/Process.js +3 -5
- package/src/Component/Process/index.js +2 -2
- package/src/Component/SortableList/SortableList.js +100 -0
- package/src/Component/SortableList/index.js +3 -0
- package/src/Component/Welcome/Input.js +2 -4
- package/src/Component/Welcome/Welcome.js +5 -9
- package/src/Component/Welcome/index.js +2 -2
- package/src/Component/index.js +5 -3
- package/src/Frame/Frame.js +163 -146
- package/src/Frame/Props.js +20 -20
- package/src/Locale.js +17 -18
- package/src/Model/User/User.js +3 -6
- package/src/Model/index.js +1 -1
- package/src/README.md.js +119 -94
- package/src/StdIn.js +8 -12
- package/src/StdOut.js +23 -27
- package/src/View/RenderOptions.js +1 -1
- package/src/View/View.js +40 -36
- package/src/core/Error/CancelError.js +2 -2
- package/src/core/Error/index.js +3 -5
- package/src/core/Flow.js +347 -0
- package/src/core/Form/Form.js +35 -33
- package/src/core/Form/Input.js +21 -6
- package/src/core/Form/Message.js +3 -6
- package/src/core/Form/index.js +4 -8
- package/src/core/InputAdapter.js +4 -6
- package/src/core/Message/Message.js +9 -12
- package/src/core/Message/OutputMessage.js +19 -17
- package/src/core/Message/index.js +2 -2
- package/src/core/OutputAdapter.js +12 -10
- package/src/core/Stream.js +1 -1
- package/src/core/StreamEntry.js +2 -2
- package/src/core/UiAdapter.js +31 -30
- package/src/core/index.js +33 -10
- package/src/functions.js +8 -15
- package/src/index.js +21 -32
- package/types/App/Command/DepsCommand.d.ts +1 -1
- package/types/App/Command/Options.d.ts +37 -40
- package/types/App/Command/index.d.ts +6 -6
- package/types/App/Core/CoreApp.d.ts +2 -2
- package/types/App/Core/UI.d.ts +4 -4
- package/types/App/Core/Widget.d.ts +4 -4
- package/types/App/Core/index.d.ts +3 -3
- package/types/App/Scenario.d.ts +1 -1
- package/types/App/User/Command/Message.d.ts +1 -1
- package/types/App/User/Command/Options.d.ts +29 -29
- package/types/App/User/Command/index.d.ts +1 -1
- package/types/App/User/UserApp.d.ts +5 -5
- package/types/App/User/index.d.ts +2 -2
- package/types/App/index.d.ts +4 -4
- package/types/Component/Process/Process.d.ts +2 -2
- package/types/Component/Process/index.d.ts +2 -2
- package/types/Component/SortableList/SortableList.d.ts +58 -0
- package/types/Component/SortableList/index.d.ts +2 -0
- package/types/Component/Welcome/Input.d.ts +1 -1
- package/types/Component/Welcome/Welcome.d.ts +1 -1
- package/types/Component/Welcome/index.d.ts +2 -2
- package/types/Component/index.d.ts +5 -3
- package/types/Frame/Frame.d.ts +1 -1
- package/types/Frame/Props.d.ts +1 -1
- package/types/Model/index.d.ts +1 -1
- package/types/StdIn.d.ts +2 -2
- package/types/StdOut.d.ts +1 -1
- package/types/View/View.d.ts +7 -7
- package/types/core/Error/index.d.ts +1 -1
- package/types/core/Flow.d.ts +320 -0
- package/types/core/Form/Form.d.ts +2 -2
- package/types/core/Form/Input.d.ts +11 -0
- package/types/core/Form/Message.d.ts +1 -1
- package/types/core/Form/index.d.ts +3 -3
- package/types/core/InputAdapter.d.ts +2 -2
- package/types/core/Intent.d.ts +65 -68
- package/types/core/Message/InputMessage.d.ts +65 -65
- package/types/core/Message/Message.d.ts +1 -1
- package/types/core/Message/OutputMessage.d.ts +1 -1
- package/types/core/Message/index.d.ts +2 -2
- package/types/core/Stream.d.ts +1 -1
- package/types/core/UiAdapter.d.ts +5 -5
- package/types/core/index.d.ts +4 -3
- package/types/index.d.ts +10 -10
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @nan0web/ui
|
|
2
2
|
|
|
3
|
+
🏴 [English](./README.md) | 🇺🇦 [Українською](./docs/uk/README.md)
|
|
4
|
+
|
|
3
5
|
<!-- %PACKAGE_STATUS% -->
|
|
4
6
|
|
|
5
7
|
A lightweight, agnostic UI framework designed with the **nan0web philosophy**
|
|
@@ -73,19 +75,24 @@ How to define and validate a UiForm?
|
|
|
73
75
|
```js
|
|
74
76
|
import { UiForm } from '@nan0web/ui'
|
|
75
77
|
const form = new UiForm({
|
|
76
|
-
title:
|
|
78
|
+
title: 'Contact Form',
|
|
77
79
|
fields: [
|
|
78
|
-
FormInput.from({ name:
|
|
79
|
-
FormInput.from({
|
|
80
|
+
FormInput.from({ name: 'email', label: 'Email Address', type: 'email', required: true }),
|
|
81
|
+
FormInput.from({
|
|
82
|
+
name: 'message',
|
|
83
|
+
label: 'Your Message',
|
|
84
|
+
type: 'textarea',
|
|
85
|
+
required: true,
|
|
86
|
+
}),
|
|
80
87
|
],
|
|
81
88
|
state: {
|
|
82
|
-
email:
|
|
83
|
-
message:
|
|
84
|
-
}
|
|
89
|
+
email: 'invalid-email',
|
|
90
|
+
message: 'Hello!',
|
|
91
|
+
},
|
|
85
92
|
})
|
|
86
93
|
const errors = form.validate()
|
|
87
94
|
console.info(errors.size) // ← 1
|
|
88
|
-
console.info(errors.get(
|
|
95
|
+
console.info(errors.get('email')) // ← Invalid email format
|
|
89
96
|
```
|
|
90
97
|
### Components
|
|
91
98
|
|
|
@@ -97,8 +104,8 @@ Components render data as frame-ready output.
|
|
|
97
104
|
How to render the Welcome component?
|
|
98
105
|
```js
|
|
99
106
|
import { Welcome } from '@nan0web/ui'
|
|
100
|
-
const frame = Welcome({ user: { name:
|
|
101
|
-
const firstLine = frame[0].join(
|
|
107
|
+
const frame = Welcome({ user: { name: 'Alice' } })
|
|
108
|
+
const firstLine = frame[0].join('')
|
|
102
109
|
console.info(firstLine) // ← Welcome Alice!
|
|
103
110
|
```
|
|
104
111
|
### View Manager
|
|
@@ -115,7 +122,7 @@ How to render frame with View?
|
|
|
115
122
|
```js
|
|
116
123
|
import { View } from '@nan0web/ui'
|
|
117
124
|
const view = new View()
|
|
118
|
-
view.render(1)([
|
|
125
|
+
view.render(1)(['Hello, world'])
|
|
119
126
|
console.info(String(view.frame)) // ← "\rHello, world"
|
|
120
127
|
```
|
|
121
128
|
### Frame Rendering
|
|
@@ -133,13 +140,13 @@ How to create a Frame with fixed size?
|
|
|
133
140
|
```js
|
|
134
141
|
import { Frame } from '@nan0web/ui'
|
|
135
142
|
const frame = new Frame({
|
|
136
|
-
value: [[
|
|
143
|
+
value: [['Frame content']],
|
|
137
144
|
width: 20,
|
|
138
145
|
height: 5,
|
|
139
146
|
renderMethod: Frame.RenderMethod.APPEND,
|
|
140
147
|
})
|
|
141
148
|
const rendered = frame.render()
|
|
142
|
-
console.info(rendered.includes(
|
|
149
|
+
console.info(rendered.includes('Frame content')) // ← true
|
|
143
150
|
```
|
|
144
151
|
### Models
|
|
145
152
|
|
|
@@ -150,7 +157,7 @@ UI models are plain data objects managed by `Model` classes.
|
|
|
150
157
|
How to use a User model?
|
|
151
158
|
```js
|
|
152
159
|
import { Model } from '@nan0web/ui'
|
|
153
|
-
const user = new Model.User({ name:
|
|
160
|
+
const user = new Model.User({ name: 'Charlie', email: 'charlie@example.com' })
|
|
154
161
|
console.info(user.name) // ← Charlie
|
|
155
162
|
console.info(user.email) // ← charlie@example.com
|
|
156
163
|
```
|
|
@@ -164,9 +171,32 @@ with minimal setup.
|
|
|
164
171
|
How to test UI components with assertions?
|
|
165
172
|
```js
|
|
166
173
|
import { Welcome } from '@nan0web/ui'
|
|
167
|
-
const output = Welcome({ user: { name:
|
|
174
|
+
const output = Welcome({ user: { name: 'Test' } })
|
|
168
175
|
console.info(output) // ← Welcome Test!
|
|
169
176
|
```
|
|
177
|
+
### Master IDE (Component Sandbox)
|
|
178
|
+
|
|
179
|
+
The Master IDE (OlmuiInspector) provides a unified environment for testing and documenting
|
|
180
|
+
web components across platforms. It supports:
|
|
181
|
+
|
|
182
|
+
- **NaN0 Spec** — a concise YAML-based shorthand for declaring component variations.
|
|
183
|
+
- **OlmuiInspector** — unified UI for exploring component models and props.
|
|
184
|
+
- **Live Preview** — real-time rendering of component states.
|
|
185
|
+
- **i18n UI** — fully localized interface for global developers.
|
|
186
|
+
|
|
187
|
+
It follows the **Olmui** core pattern: *One Logic — Many UI* (same manifest powers both CLI and Web).
|
|
188
|
+
|
|
189
|
+
#### NaN0 Spec (YAML)
|
|
190
|
+
|
|
191
|
+
Concise format for defining variations:
|
|
192
|
+
|
|
193
|
+
How to define a component variation using NaN0 Spec?
|
|
194
|
+
```yaml
|
|
195
|
+
- Button: Primary
|
|
196
|
+
$variant: brand
|
|
197
|
+
$outline: true
|
|
198
|
+
```
|
|
199
|
+
|
|
170
200
|
## Playground Demos
|
|
171
201
|
|
|
172
202
|
The library includes rich playground demos:
|
|
@@ -200,6 +230,10 @@ Explore:
|
|
|
200
230
|
- [App](./src/App/)
|
|
201
231
|
- [Models](./src/Model/)
|
|
202
232
|
|
|
233
|
+
## Project Architecture & Specs
|
|
234
|
+
|
|
235
|
+
How the universal block spec is designed? - [check Universal Blocks Spec (`project.md`)](./project.md)
|
|
236
|
+
|
|
203
237
|
## Contributing
|
|
204
238
|
|
|
205
239
|
How to contribute? - [check here](./CONTRIBUTING.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nan0web/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.2",
|
|
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",
|
|
@@ -18,16 +18,21 @@
|
|
|
18
18
|
"test:coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**/*.js\" --test-coverage-exclude=\"src/**/*.test.js\" --test \"src/**/*.test.js\"",
|
|
19
19
|
"test:coverage:collect": "nan0test coverage",
|
|
20
20
|
"test:docs": "node --test src/README.md.js",
|
|
21
|
+
"release:spec": "node --test \"releases/**/*.spec.js\"",
|
|
21
22
|
"test:release": "node --test \"releases/**/*.test.js\"",
|
|
22
23
|
"test:status": "nan0test status --hide-name",
|
|
23
24
|
"test:play": "node --test --test-timeout=3333 \"play/**/*.test.js\"",
|
|
24
|
-
"test:
|
|
25
|
+
"test:e2e": "playwright test",
|
|
26
|
+
"test:all": "npm run test && npm run test:docs && npm run test:play && npm run test:e2e && npm run build && npm run knip",
|
|
27
|
+
"knip": "knip --production",
|
|
25
28
|
"precommit": "npm test",
|
|
26
29
|
"prepush": "npm test",
|
|
27
30
|
"prepare": "husky",
|
|
28
31
|
"release": "nan0release publish",
|
|
29
32
|
"clean": "rm -rf .cache/ && rm -rf dist/",
|
|
30
|
-
"clean:modules": "rm -rf node_modules"
|
|
33
|
+
"clean:modules": "rm -rf node_modules",
|
|
34
|
+
"docs:dev": "node docs/site/scripts/generate-pages.js && vite docs/site",
|
|
35
|
+
"docs:build": "node docs/site/scripts/generate-pages.js && vite build docs/site"
|
|
31
36
|
},
|
|
32
37
|
"exports": {
|
|
33
38
|
".": {
|
|
@@ -54,20 +59,27 @@
|
|
|
54
59
|
"license": "ISC",
|
|
55
60
|
"packageManager": "pnpm@10.11.0",
|
|
56
61
|
"devDependencies": {
|
|
57
|
-
"@nan0web/event": "
|
|
58
|
-
"@nan0web/i18n": "
|
|
59
|
-
"@nan0web/release": "
|
|
60
|
-
"@nan0web/test": "
|
|
61
|
-
"@nan0web/ui-cli": "
|
|
62
|
+
"@nan0web/event": "*",
|
|
63
|
+
"@nan0web/i18n": "*",
|
|
64
|
+
"@nan0web/release": "*",
|
|
65
|
+
"@nan0web/test": "*",
|
|
66
|
+
"@nan0web/ui-cli": "*",
|
|
67
|
+
"@nan0web/ui-lit": "workspace:*",
|
|
68
|
+
"@playwright/test": "^1.58.2",
|
|
69
|
+
"@rollup/plugin-yaml": "^4.1.2",
|
|
62
70
|
"@vitest/coverage-v8": "^3.2.4",
|
|
63
71
|
"husky": "^9.1.7",
|
|
72
|
+
"js-yaml": "^4.1.1",
|
|
73
|
+
"knip": "^5.83.1",
|
|
74
|
+
"lit": "^3.3.2",
|
|
75
|
+
"vite": "^6.0.0",
|
|
64
76
|
"vitest": "^3.2.4"
|
|
65
77
|
},
|
|
66
78
|
"dependencies": {
|
|
67
|
-
"@nan0web/co": "
|
|
68
|
-
"@nan0web/event": "
|
|
69
|
-
"@nan0web/
|
|
79
|
+
"@nan0web/co": "*",
|
|
80
|
+
"@nan0web/event": "*",
|
|
81
|
+
"@nan0web/log": "*",
|
|
82
|
+
"@nan0web/types": "*",
|
|
70
83
|
"string-width": "^7.2.0"
|
|
71
|
-
}
|
|
72
|
-
"peerDependencies": {}
|
|
84
|
+
}
|
|
73
85
|
}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import UiMessage from
|
|
1
|
+
import UiMessage from '../../core/Message/Message.js'
|
|
2
2
|
|
|
3
3
|
class DepsCommandBody {
|
|
4
4
|
fix = false
|
|
5
5
|
static fix = {
|
|
6
|
-
help:
|
|
7
|
-
defaultValue: false
|
|
6
|
+
help: 'Fix dependencies',
|
|
7
|
+
defaultValue: false,
|
|
8
8
|
}
|
|
9
9
|
constructor(input = {}) {
|
|
10
|
-
const {
|
|
11
|
-
fix = this.fix
|
|
12
|
-
} = input
|
|
10
|
+
const { fix = this.fix } = input
|
|
13
11
|
}
|
|
14
12
|
}
|
|
15
13
|
|
|
@@ -18,9 +16,7 @@ export class DepsCommand extends UiMessage {
|
|
|
18
16
|
/** @type {DepsCommandBody} */
|
|
19
17
|
body
|
|
20
18
|
constructor(input = {}) {
|
|
21
|
-
const {
|
|
22
|
-
body = new DepsCommandBody()
|
|
23
|
-
} = UiMessage.parseBody(input, DepsCommandBody)
|
|
19
|
+
const { body = new DepsCommandBody() } = UiMessage.parseBody(input, DepsCommandBody)
|
|
24
20
|
super(input)
|
|
25
21
|
this.body = body
|
|
26
22
|
}
|
package/src/App/Core/CoreApp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Message } from
|
|
2
|
-
import { typeOf } from
|
|
3
|
-
import UI from
|
|
1
|
+
import { Message } from '@nan0web/co'
|
|
2
|
+
import { typeOf } from '@nan0web/types'
|
|
3
|
+
import UI from './UI.js'
|
|
4
4
|
|
|
5
5
|
/** @typedef {Function} CommandFn */
|
|
6
6
|
|
|
@@ -29,11 +29,7 @@ export default class CoreApp {
|
|
|
29
29
|
* @param {Message} [props.startCommand=new Message()] - Command line arguments to parse
|
|
30
30
|
*/
|
|
31
31
|
constructor(props = {}) {
|
|
32
|
-
const {
|
|
33
|
-
name = "CoreApp",
|
|
34
|
-
state = {},
|
|
35
|
-
startCommand = new Message(),
|
|
36
|
-
} = props
|
|
32
|
+
const { name = 'CoreApp', state = {}, startCommand = new Message() } = props
|
|
37
33
|
this.name = String(name)
|
|
38
34
|
this.state = state
|
|
39
35
|
this.commands = new Map()
|
|
@@ -48,7 +44,7 @@ export default class CoreApp {
|
|
|
48
44
|
* @returns {object} Updated state
|
|
49
45
|
*/
|
|
50
46
|
set(state, value) {
|
|
51
|
-
if (
|
|
47
|
+
if ('string' === typeof state) {
|
|
52
48
|
this.state[state] = value
|
|
53
49
|
} else {
|
|
54
50
|
Object.assign(this.state, state)
|
|
@@ -64,7 +60,7 @@ export default class CoreApp {
|
|
|
64
60
|
*/
|
|
65
61
|
registerCommand(commandName, handler) {
|
|
66
62
|
if (!typeOf(Function)(handler)) {
|
|
67
|
-
throw new TypeError(
|
|
63
|
+
throw new TypeError('Handler must be a function')
|
|
68
64
|
}
|
|
69
65
|
this.commands.set(commandName, handler)
|
|
70
66
|
}
|
|
@@ -87,12 +83,17 @@ export default class CoreApp {
|
|
|
87
83
|
async processCommand(msg, ui) {
|
|
88
84
|
const handler = this.commands.get(msg.constructor.name)
|
|
89
85
|
if (!handler) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
throw new Error(
|
|
87
|
+
[
|
|
88
|
+
'Unknown command',
|
|
89
|
+
': ',
|
|
90
|
+
msg.constructor.name,
|
|
91
|
+
'\n',
|
|
92
|
+
'Available commands',
|
|
93
|
+
': ',
|
|
94
|
+
[...this.commands.keys()].join(', '),
|
|
95
|
+
].join(''),
|
|
96
|
+
)
|
|
96
97
|
}
|
|
97
98
|
return await handler.apply(this, [msg, ui])
|
|
98
99
|
}
|
|
@@ -119,6 +120,6 @@ export default class CoreApp {
|
|
|
119
120
|
* @throws {Error} Always thrown as this method must be implemented by subclasses
|
|
120
121
|
*/
|
|
121
122
|
async selectCommand(ui) {
|
|
122
|
-
throw new Error(
|
|
123
|
+
throw new Error('Not implemented, must be implemented by subclass')
|
|
123
124
|
}
|
|
124
125
|
}
|
package/src/App/Core/UI.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Message } from
|
|
2
|
-
import View from
|
|
3
|
-
import CoreApp from
|
|
4
|
-
import Widget from
|
|
1
|
+
import { Message } from '@nan0web/co'
|
|
2
|
+
import View from '../../View/View.js'
|
|
3
|
+
import CoreApp from './CoreApp.js'
|
|
4
|
+
import Widget from './Widget.js'
|
|
5
5
|
|
|
6
6
|
/** @typedef {import("../../View/View.js").ComponentFn} ComponentFn */
|
|
7
7
|
|
|
@@ -31,7 +31,7 @@ export default class UI extends Widget {
|
|
|
31
31
|
* @throws {Error} Always thrown as this method must be implemented by subclasses
|
|
32
32
|
*/
|
|
33
33
|
convertInput(rawInput) {
|
|
34
|
-
throw new Error(
|
|
34
|
+
throw new Error('convertInput must be implemented by subclass')
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -43,16 +43,12 @@ export default class UI extends Widget {
|
|
|
43
43
|
const onStart = () => {
|
|
44
44
|
// this.view.render(UIProcess)
|
|
45
45
|
}
|
|
46
|
-
const onData = () => {
|
|
46
|
+
const onData = () => {}
|
|
47
|
+
const onEnd = () => {}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
this.on("start", onStart)
|
|
54
|
-
this.on("data", onData)
|
|
55
|
-
this.on("end", onEnd)
|
|
49
|
+
this.on('start', onStart)
|
|
50
|
+
this.on('data', onData)
|
|
51
|
+
this.on('end', onEnd)
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
/**
|
|
@@ -60,7 +56,7 @@ export default class UI extends Widget {
|
|
|
60
56
|
* @param {any[]} results - Results to output
|
|
61
57
|
*/
|
|
62
58
|
output(results) {
|
|
63
|
-
results.forEach(result => {
|
|
59
|
+
results.forEach((result) => {
|
|
64
60
|
this.view.info(JSON.stringify(result))
|
|
65
61
|
})
|
|
66
62
|
}
|
package/src/App/Core/Widget.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import EventProcessor from
|
|
2
|
-
import View from
|
|
3
|
-
import { StreamEntry } from
|
|
4
|
-
import { UiMessage } from
|
|
1
|
+
import EventProcessor from '@nan0web/event/oop'
|
|
2
|
+
import View from '../../View/View.js'
|
|
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
|
|
|
@@ -51,14 +51,10 @@ export default class Widget extends EventProcessor {
|
|
|
51
51
|
*/
|
|
52
52
|
render(viewFnOrName, outputData) {
|
|
53
53
|
/** @type {Function | ComponentFn | undefined} */
|
|
54
|
-
const viewFn = typeof viewFnOrName ===
|
|
55
|
-
? this.view.get(viewFnOrName)
|
|
56
|
-
: viewFnOrName
|
|
54
|
+
const viewFn = typeof viewFnOrName === 'string' ? this.view.get(viewFnOrName) : viewFnOrName
|
|
57
55
|
|
|
58
56
|
if (!viewFn) {
|
|
59
|
-
throw new Error([
|
|
60
|
-
"View component not found", ": ", viewFnOrName
|
|
61
|
-
].join(""))
|
|
57
|
+
throw new Error(['View component not found', ': ', viewFnOrName].join(''))
|
|
62
58
|
}
|
|
63
59
|
return this.view.render(viewFn)(outputData)
|
|
64
60
|
}
|
package/src/App/Core/index.js
CHANGED
package/src/App/Scenario.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import App from
|
|
2
|
-
import UI from
|
|
1
|
+
import App from './index.js'
|
|
2
|
+
import UI from './Core/UI.js'
|
|
3
3
|
|
|
4
4
|
/** @typedef {import("./Core/CoreApp.js").default} CoreApp */
|
|
5
5
|
|
|
@@ -19,7 +19,7 @@ export default class Scenario {
|
|
|
19
19
|
*/
|
|
20
20
|
constructor(app, ui) {
|
|
21
21
|
if (!(app instanceof App.Core.App)) {
|
|
22
|
-
throw new TypeError(
|
|
22
|
+
throw new TypeError('Scenario requires a App.Core.App instance')
|
|
23
23
|
}
|
|
24
24
|
this.app = app
|
|
25
25
|
this.ui = ui
|
|
@@ -32,7 +32,7 @@ export default class Scenario {
|
|
|
32
32
|
* @returns {Promise<boolean>} True if all outputs match expected
|
|
33
33
|
*/
|
|
34
34
|
async run(inputCommands, expectedOutputs) {
|
|
35
|
-
const commandMessages = inputCommands.map(arr => App.Command.Message.parse(arr))
|
|
35
|
+
const commandMessages = inputCommands.map((arr) => App.Command.Message.parse(arr))
|
|
36
36
|
const outputs = await this.app.processCommands(commandMessages, this.ui)
|
|
37
37
|
if (outputs.length !== expectedOutputs.length) return false
|
|
38
38
|
for (let i = 0; i < outputs.length; i++) {
|
package/src/App/User/UserApp.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Message } from
|
|
2
|
-
import { notEmpty } from
|
|
3
|
-
import CoreApp from
|
|
4
|
-
import User from
|
|
5
|
-
import UserUI from
|
|
6
|
-
import UserAppCommandMessage from
|
|
7
|
-
import DepsCommand from
|
|
8
|
-
import UIStream from
|
|
9
|
-
import { StreamEntry, UiMessage } from
|
|
1
|
+
import { Message } from '@nan0web/co'
|
|
2
|
+
import { notEmpty } from '@nan0web/types'
|
|
3
|
+
import CoreApp from '../Core/CoreApp.js'
|
|
4
|
+
import User from '../../Model/User/User.js'
|
|
5
|
+
import UserUI from './UserUI.js'
|
|
6
|
+
import UserAppCommandMessage from './Command/Message.js'
|
|
7
|
+
import DepsCommand from './Command/Message.js'
|
|
8
|
+
import UIStream from '../../core/Stream.js'
|
|
9
|
+
import { StreamEntry, UiMessage } from '../../core/index.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* UserApp requires user name and shows Welcome view.
|
|
@@ -20,9 +20,9 @@ export default class UserApp extends CoreApp {
|
|
|
20
20
|
*/
|
|
21
21
|
constructor(props = {}) {
|
|
22
22
|
super(props)
|
|
23
|
-
this.registerCommand(
|
|
24
|
-
this.registerCommand(
|
|
25
|
-
this.registerCommand(
|
|
23
|
+
this.registerCommand('setUser', this.setUser.bind(this))
|
|
24
|
+
this.registerCommand('welcome', this.welcome.bind(this))
|
|
25
|
+
this.registerCommand('deps', this.handleDeps.bind(this)) // Register new command
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -33,12 +33,18 @@ export default class UserApp extends CoreApp {
|
|
|
33
33
|
*/
|
|
34
34
|
async handleDeps(cmd, ui) {
|
|
35
35
|
// Example: Use async generator to stream deps processing
|
|
36
|
-
const processorFn = async () =>
|
|
36
|
+
const processorFn = async () =>
|
|
37
|
+
new StreamEntry({
|
|
38
|
+
value: { message: `Deps command executed with fix: ${cmd.body.fix}` },
|
|
39
|
+
done: true,
|
|
40
|
+
})
|
|
37
41
|
const generatorFn = UIStream.createProcessor(new AbortController().signal, processorFn)
|
|
38
|
-
await UIStream.process(
|
|
42
|
+
await UIStream.process(
|
|
43
|
+
new AbortController().signal,
|
|
44
|
+
generatorFn,
|
|
39
45
|
(progress, item) => ui.output && ui.output(item.value), // Fix to output the value
|
|
40
46
|
(error) => ui.output && ui.output([error]), // Assume ui has output method
|
|
41
|
-
(item) => ui.output && ui.output(item.value) // Fix complete callback
|
|
47
|
+
(item) => ui.output && ui.output(item.value), // Fix complete callback
|
|
42
48
|
)
|
|
43
49
|
return { completed: true }
|
|
44
50
|
}
|
|
@@ -53,7 +59,7 @@ export default class UserApp extends CoreApp {
|
|
|
53
59
|
this.state.user = User.from(cmd.body.user) // cmd is UserAppCommandMessage, has user
|
|
54
60
|
const frame = await this.welcome(cmd, ui)
|
|
55
61
|
return {
|
|
56
|
-
message: String(frame)
|
|
62
|
+
message: String(frame),
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -64,15 +70,16 @@ export default class UserApp extends CoreApp {
|
|
|
64
70
|
* @returns {Promise<string[][]>} Welcome view output
|
|
65
71
|
*/
|
|
66
72
|
async welcome(cmd, ui) {
|
|
67
|
-
if (cmd.body.user) {
|
|
73
|
+
if (cmd.body.user) {
|
|
74
|
+
// cmd is UserAppCommandMessage, has user
|
|
68
75
|
const user = User.from(cmd.body.user)
|
|
69
|
-
return ui.render(
|
|
76
|
+
return ui.render('Welcome', { user })
|
|
70
77
|
}
|
|
71
78
|
if (notEmpty(this.user)) {
|
|
72
|
-
return ui.render(
|
|
79
|
+
return ui.render('Welcome', { user: this.user })
|
|
73
80
|
}
|
|
74
|
-
const answer = await ui.ask(UiMessage.from(
|
|
81
|
+
const answer = await ui.ask(UiMessage.from('What is your name?'))
|
|
75
82
|
this.user = User.from(answer?.body)
|
|
76
|
-
return ui.render(
|
|
83
|
+
return ui.render('Welcome', { user: this.user })
|
|
77
84
|
}
|
|
78
85
|
}
|
package/src/App/User/UserUI.js
CHANGED
package/src/App/User/index.js
CHANGED
package/src/App/index.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import Scenario from
|
|
2
|
-
import UI from
|
|
1
|
+
import Scenario from './Scenario.js'
|
|
2
|
+
import UI from './Core/UI.js'
|
|
3
3
|
|
|
4
|
-
import Core from
|
|
5
|
-
import User from
|
|
4
|
+
import Core from './Core/index.js'
|
|
5
|
+
import User from './User/index.js'
|
|
6
6
|
|
|
7
|
-
export {
|
|
8
|
-
Core,
|
|
9
|
-
User,
|
|
10
|
-
Scenario,
|
|
11
|
-
UI,
|
|
12
|
-
}
|
|
7
|
+
export { Core, User, Scenario, UI }
|
|
13
8
|
|
|
14
9
|
export default {
|
|
15
10
|
Core,
|
|
@@ -5,22 +5,22 @@
|
|
|
5
5
|
class ProcessInput {
|
|
6
6
|
/** @type {string} Process name to display */
|
|
7
7
|
name
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
/** @type {number} Current progress index */
|
|
10
10
|
i
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
/** @type {number} Top limit for progress normalization */
|
|
13
13
|
top
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
/** @type {number} Width of the progress bar */
|
|
16
16
|
width
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
/** @type {string} Character to use for empty space */
|
|
19
19
|
space
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
/** @type {string} Character to use for filled progress */
|
|
22
22
|
char
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
/**
|
|
25
25
|
* Creates a new ProcessInput instance.
|
|
26
26
|
* @param {object} props - Process input properties
|
|
@@ -32,14 +32,7 @@ class ProcessInput {
|
|
|
32
32
|
* @param {string} [props.char='*'] - Character for filled progress
|
|
33
33
|
*/
|
|
34
34
|
constructor(props = {}) {
|
|
35
|
-
const {
|
|
36
|
-
name = "NaN•Coding",
|
|
37
|
-
i = 0,
|
|
38
|
-
top = 9,
|
|
39
|
-
width = 9,
|
|
40
|
-
space = '•',
|
|
41
|
-
char = '*'
|
|
42
|
-
} = props
|
|
35
|
+
const { name = 'NaN•Coding', i = 0, top = 9, width = 9, space = '•', char = '*' } = props
|
|
43
36
|
this.name = name
|
|
44
37
|
this.i = i
|
|
45
38
|
this.top = top
|
|
@@ -47,7 +40,7 @@ class ProcessInput {
|
|
|
47
40
|
this.space = space
|
|
48
41
|
this.char = char
|
|
49
42
|
}
|
|
50
|
-
|
|
43
|
+
|
|
51
44
|
/**
|
|
52
45
|
* Converts the input to a string representation.
|
|
53
46
|
* @returns {string} String representation of the ProcessInput
|
|
@@ -55,7 +48,7 @@ class ProcessInput {
|
|
|
55
48
|
toString() {
|
|
56
49
|
return `ProcessInput(name=${this.name}, i=${this.i}, top=${this.top}, width=${this.width}, space=${this.space}, char=${this.char})`
|
|
57
50
|
}
|
|
58
|
-
|
|
51
|
+
|
|
59
52
|
/**
|
|
60
53
|
* Creates a ProcessInput instance from the given props.
|
|
61
54
|
* @param {ProcessInput|object} props - The properties to create from
|
|
@@ -67,4 +60,4 @@ class ProcessInput {
|
|
|
67
60
|
}
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
export default ProcessInput
|
|
63
|
+
export default ProcessInput
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import ProcessInput from
|
|
2
|
-
import View from
|
|
1
|
+
import ProcessInput from './Input.js'
|
|
2
|
+
import View from '../../View/View.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Renders a progress bar based on input configuration.
|
|
@@ -16,9 +16,7 @@ function Process(props = {}) {
|
|
|
16
16
|
// Provide empty options object to satisfy Locale.format signature
|
|
17
17
|
const format = this.locale.format(Number, {})
|
|
18
18
|
const num = format ? format(100 * per) : 100 * per
|
|
19
|
-
return [
|
|
20
|
-
[`I am ${input.name} ${bar} ${num}`]
|
|
21
|
-
]
|
|
19
|
+
return [[`I am ${input.name} ${bar} ${num}`]]
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
Process.Input = ProcessInput
|