@nan0web/ui 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -12
- package/package.json +54 -25
- package/src/App/Command/DepsCommand.js +3 -4
- package/src/Frame/Props.js +12 -18
- package/src/InterfaceTemplate/InterfaceTemplate.js +9 -7
- package/src/Model/index.js +86 -2
- package/src/StdIn.js +2 -6
- package/src/cli.js +1 -0
- package/src/core/Form/Form.js +8 -7
- package/src/core/Form/Message.js +1 -1
- package/src/core/GeneratorRunner.js +77 -7
- package/src/core/InputAdapter.js +3 -1
- package/src/core/Intent.js +214 -16
- package/src/core/IntentErrorModel.js +6 -1
- package/src/core/Message/Message.js +4 -7
- package/src/core/Message/OutputMessage.js +4 -9
- package/src/core/Stream.js +16 -5
- package/src/core/StreamEntry.js +20 -28
- package/src/core/index.js +2 -1
- package/src/domain/Content.js +196 -0
- package/src/domain/Document.js +17 -0
- package/src/domain/FooterModel.js +37 -19
- package/src/domain/HeaderModel.js +47 -21
- package/src/domain/HeroModel.js +24 -22
- package/src/domain/LayoutModel.js +43 -0
- package/src/domain/ModelAsApp.js +46 -0
- package/src/domain/SandboxModel.js +19 -16
- package/src/domain/app/GalleryCommand.js +53 -0
- package/src/domain/app/GalleryRenderIntent.js +77 -0
- package/src/domain/app/SnapshotAuditor.js +401 -0
- package/src/domain/app/SnapshotRunner.js +264 -0
- package/src/domain/app/UIApp.js +78 -0
- package/src/domain/components/BreadcrumbModel.js +10 -6
- package/src/domain/components/FeatureGridModel.js +62 -0
- package/src/domain/components/MarkdownModel.js +24 -0
- package/src/domain/components/ShellModel.js +243 -0
- package/src/domain/components/TableModel.js +10 -6
- package/src/domain/components/ToastModel.js +10 -6
- package/src/domain/components/index.js +3 -1
- package/src/domain/index.js +14 -4
- package/src/index.js +21 -2
- package/src/inspect.js +2 -0
- package/src/test/ScenarioAdapter.js +59 -0
- package/src/test/ScenarioTest.js +51 -0
- package/src/test/ScenarioTest.story.js +56 -0
- package/src/testing/CrashReporter.js +56 -0
- package/src/testing/GalleryGenerator.js +29 -0
- package/src/testing/LogicInspector.js +55 -0
- package/src/testing/SnapshotRunner.js +22 -0
- package/src/testing/SpecAdapter.js +115 -0
- package/src/testing/SpecRunner.js +121 -0
- package/src/testing/VisualAdapter.js +46 -0
- package/src/testing/index.js +7 -0
- package/src/testing/verifySnapshot.js +17 -0
- package/types/App/Command/DepsCommand.d.ts +0 -2
- package/types/Model/index.d.ts +56 -4
- package/types/StdIn.d.ts +3 -3
- package/types/cli.d.ts +1 -0
- package/types/core/Form/Form.d.ts +2 -2
- package/types/core/GeneratorRunner.d.ts +18 -1
- package/types/core/InputAdapter.d.ts +2 -1
- package/types/core/Intent.d.ts +232 -26
- package/types/core/IntentErrorModel.d.ts +4 -0
- package/types/core/Message/Message.d.ts +2 -2
- package/types/core/Message/OutputMessage.d.ts +0 -2
- package/types/core/index.d.ts +2 -1
- package/types/domain/Content.d.ts +340 -0
- package/types/domain/Document.d.ts +21 -0
- package/types/domain/FooterModel.d.ts +22 -12
- package/types/domain/HeaderModel.d.ts +36 -13
- package/types/domain/HeroModel.d.ts +19 -17
- package/types/domain/LayoutModel.d.ts +34 -0
- package/types/domain/ModelAsApp.d.ts +23 -0
- package/types/domain/SandboxModel.d.ts +10 -0
- package/types/domain/app/GalleryCommand.d.ts +55 -0
- package/types/domain/app/GalleryRenderIntent.d.ts +31 -0
- package/types/domain/app/SnapshotAuditor.d.ts +99 -0
- package/types/domain/app/SnapshotRunner.d.ts +45 -0
- package/types/domain/app/UIApp.d.ts +60 -0
- package/types/domain/components/BreadcrumbModel.d.ts +6 -8
- package/types/domain/components/FeatureGridModel.d.ts +50 -0
- package/types/domain/components/MarkdownModel.d.ts +19 -0
- package/types/domain/components/ShellModel.d.ts +56 -0
- package/types/domain/components/TableModel.d.ts +4 -0
- package/types/domain/components/ToastModel.d.ts +4 -0
- package/types/domain/components/index.d.ts +3 -0
- package/types/domain/index.d.ts +10 -4
- package/types/index.d.ts +19 -1
- package/types/inspect.d.ts +2 -0
- package/types/test/ScenarioAdapter.d.ts +43 -0
- package/types/test/ScenarioTest.d.ts +24 -0
- package/types/test/ScenarioTest.story.d.ts +1 -0
- package/types/testing/CrashReporter.d.ts +13 -0
- package/types/testing/GalleryGenerator.d.ts +1 -0
- package/types/testing/LogicInspector.d.ts +22 -0
- package/types/testing/SnapshotRunner.d.ts +7 -0
- package/types/testing/SpecAdapter.d.ts +57 -0
- package/types/testing/SpecRunner.d.ts +41 -0
- package/types/testing/VisualAdapter.d.ts +9 -0
- package/types/testing/index.d.ts +7 -0
- package/types/testing/verifySnapshot.d.ts +14 -0
- package/src/README.md.js +0 -436
- package/types/App/Command/Options.d.ts +0 -43
- package/types/App/Command/index.d.ts +0 -8
- package/types/App/User/Command/Options.d.ts +0 -34
- package/types/core/Message/InputMessage.d.ts +0 -71
- package/types/domain/components/HeroModel.d.ts +0 -24
- package/types/domain/components/ShowcaseAppModel.d.ts +0 -32
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
import { LogicInspector } from '../../testing/LogicInspector.js'
|
|
3
|
+
import { VisualAdapter } from '../../testing/VisualAdapter.js'
|
|
4
|
+
import { result, show, render } from '../../core/Intent.js'
|
|
5
|
+
import SnapshotAuditor from './SnapshotAuditor.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SnapshotRunner — Zero-Hallucination Snapshot Generation & Audit (Model-as-Schema v2).
|
|
9
|
+
* Operates entirely through DB-FS abstraction without raw FS/Path hardcodes.
|
|
10
|
+
*/
|
|
11
|
+
export class SnapshotRunner extends Model {
|
|
12
|
+
static UI = {
|
|
13
|
+
generating: '📸 Generating snapshots for {lang}/{comp}',
|
|
14
|
+
saved: '📸 Saved {file}',
|
|
15
|
+
auditFailed: '🚨 Audit failed for {file}: {errors}',
|
|
16
|
+
rootGallery:
|
|
17
|
+
'# 📸 Core Snapshots Gallery\n\n**Total Snapshots:** {count} | **Total Errors:** {errors}\n\n## Locales\n\n',
|
|
18
|
+
localeTitle: '🌍 Locale: {title}',
|
|
19
|
+
categoryTitle: '📂 Category: {title}',
|
|
20
|
+
backText: 'Back',
|
|
21
|
+
backLink: '[⬅ {text}](../index.md)',
|
|
22
|
+
galleryDescription: 'This gallery contains automatically generated interaction snapshots (Zero-Hallucination UI Core).',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static data = {
|
|
26
|
+
type: 'string',
|
|
27
|
+
help: 'Root directory containing locale folders with data.',
|
|
28
|
+
default: 'docs',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static snapshotsDir = {
|
|
32
|
+
type: 'string',
|
|
33
|
+
help: 'Directory where output text snapshots will be stored.',
|
|
34
|
+
default: 'snapshots/core',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Partial<SnapshotRunner> | Record<string, any>} [data={}]
|
|
39
|
+
* @param {import('@nan0web/types').ModelOptions} [options={}]
|
|
40
|
+
*/
|
|
41
|
+
constructor(data = {}, options = {}) {
|
|
42
|
+
super(data, options)
|
|
43
|
+
/** @type {string} Directory containing snapshots */ this.snapshotsDir
|
|
44
|
+
/** @type {string} Root data directory */ this.data
|
|
45
|
+
/** @type {(compName: string) => string} */ this.getCategory = (comp) => 'Components'
|
|
46
|
+
/** @type {(compName: string, varData: any) => AsyncGenerator<any>} */ this.createModelStream
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get db() {
|
|
50
|
+
return /** @type {import('@nan0web/db').DB} */ (this._.db)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Recursive drop for directories via DB-FS.
|
|
55
|
+
* @param {string} uri
|
|
56
|
+
*/
|
|
57
|
+
async dropRecursive(uri) {
|
|
58
|
+
let entries = []
|
|
59
|
+
try {
|
|
60
|
+
for await (const entry of this.db.readDir(uri)) entries.push(entry)
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return // Dir doesn't exist
|
|
63
|
+
}
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (entry.stat.isDirectory) {
|
|
66
|
+
await this.dropRecursive(entry.path)
|
|
67
|
+
} else {
|
|
68
|
+
await this.db.dropDocument(entry.path)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await this.db.dropDocument(uri)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async *run() {
|
|
75
|
+
const db = this.db
|
|
76
|
+
const t = this._.t || ((k) => k)
|
|
77
|
+
|
|
78
|
+
// Clean before generation
|
|
79
|
+
await this.dropRecursive(this.snapshotsDir)
|
|
80
|
+
|
|
81
|
+
const doc = (await this.db.fetch('index')) ?? {}
|
|
82
|
+
|
|
83
|
+
// Fetch languages
|
|
84
|
+
const langsData = (await this.db.fetch(`${this.data}/_/langs`)) || []
|
|
85
|
+
const langsIndex = {}
|
|
86
|
+
if (Array.isArray(langsData)) {
|
|
87
|
+
langsData.forEach((l) => {
|
|
88
|
+
if (l && l.locale) langsIndex[l.locale] = l.title || l.locale
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const langs = []
|
|
93
|
+
for await (const entry of this.db.readDir(this.data)) {
|
|
94
|
+
if (entry.stat.isDirectory && entry.name !== '_' && entry.name !== 'site') langs.push(entry.name)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const galleryTree = {}
|
|
98
|
+
let globalErrors = 0
|
|
99
|
+
let globalCount = 0
|
|
100
|
+
|
|
101
|
+
for (const lang of langs) {
|
|
102
|
+
galleryTree[lang] = {}
|
|
103
|
+
const componentsBase = `${this.data}/${lang}/components`
|
|
104
|
+
const components = []
|
|
105
|
+
try {
|
|
106
|
+
for await (const entry of this.db.readDir(componentsBase)) {
|
|
107
|
+
if (!entry.isDirectory) components.push(entry.name)
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {}
|
|
110
|
+
|
|
111
|
+
for (const file of components) {
|
|
112
|
+
const compName = file.replace(/\.[^/.]+$/, "")
|
|
113
|
+
const data = (await this.db.fetch(`${componentsBase}/${compName}`)) || {}
|
|
114
|
+
|
|
115
|
+
// Extract variations
|
|
116
|
+
const variations = data.content || []
|
|
117
|
+
const variationsData = []
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < variations.length; i++) {
|
|
120
|
+
const rawVar = variations[i]
|
|
121
|
+
let varData = rawVar[compName] !== undefined ? rawVar[compName] : rawVar
|
|
122
|
+
|
|
123
|
+
// Extract schema defaults
|
|
124
|
+
const schema = data['$' + compName] || {}
|
|
125
|
+
const defaultProps = {}
|
|
126
|
+
for (const [k, v] of Object.entries(schema)) {
|
|
127
|
+
if (v && v.default !== undefined) {
|
|
128
|
+
defaultProps[k] = v.default
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Merge defaults
|
|
133
|
+
if (typeof varData === 'object' && varData !== null) {
|
|
134
|
+
varData = { ...defaultProps, ...varData }
|
|
135
|
+
} else if (varData === true) {
|
|
136
|
+
varData = { ...defaultProps }
|
|
137
|
+
} else if (typeof varData === 'string' || typeof varData === 'number') {
|
|
138
|
+
varData = { ...defaultProps, content: String(varData) }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let varName = rawVar.content || rawVar.title || rawVar.message || `var${i + 1}`
|
|
142
|
+
if (typeof varName !== 'string') {
|
|
143
|
+
if (typeof varData.title === 'string') varName = varData.title
|
|
144
|
+
else if (typeof varData.content === 'string') varName = varData.content
|
|
145
|
+
else varName = `var${i + 1}`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const safeVarName = varName
|
|
149
|
+
.trim()
|
|
150
|
+
.toLowerCase()
|
|
151
|
+
.replace(/[./\\:]/g, '_')
|
|
152
|
+
.replace(/\s+/g, '_')
|
|
153
|
+
.replace(/_{2,}/g, '_')
|
|
154
|
+
.slice(0, 50)
|
|
155
|
+
|
|
156
|
+
// Build model stream
|
|
157
|
+
let intents
|
|
158
|
+
if (this.createModelStream) {
|
|
159
|
+
intents = await LogicInspector.capture(this.createModelStream(compName, varData))
|
|
160
|
+
} else {
|
|
161
|
+
const defaultModelStream = async function* () {
|
|
162
|
+
yield render(`ui-${compName.toLowerCase()}`, varData)
|
|
163
|
+
return result({})
|
|
164
|
+
}
|
|
165
|
+
intents = await LogicInspector.capture(defaultModelStream())
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const snapshot = intents.map((it) => VisualAdapter.render(it)).join('\n')
|
|
169
|
+
|
|
170
|
+
const categoryPath = this.getCategory ? this.getCategory(compName) : 'Components'
|
|
171
|
+
const outPath = `${this.snapshotsDir}/${lang}/${categoryPath}/${compName}`
|
|
172
|
+
|
|
173
|
+
if (!galleryTree[lang][categoryPath]) galleryTree[lang][categoryPath] = {}
|
|
174
|
+
if (!galleryTree[lang][categoryPath][compName])
|
|
175
|
+
galleryTree[lang][categoryPath][compName] = { score: 100, errors: [] }
|
|
176
|
+
|
|
177
|
+
const filePath = `${outPath}/${safeVarName}.nan0`
|
|
178
|
+
await this.db.saveDocument(filePath, snapshot)
|
|
179
|
+
|
|
180
|
+
yield show(t(SnapshotRunner.UI.generating, { lang, comp: `${compName}/${safeVarName}` }))
|
|
181
|
+
variationsData.push({ safeVarName, snapshot })
|
|
182
|
+
|
|
183
|
+
// Instant Audit
|
|
184
|
+
const audit = SnapshotAuditor.inspectText(snapshot, lang, filePath, t)
|
|
185
|
+
if (audit.score < 100) {
|
|
186
|
+
galleryTree[lang][categoryPath][compName].score = Math.min(
|
|
187
|
+
galleryTree[lang][categoryPath][compName].score,
|
|
188
|
+
audit.score,
|
|
189
|
+
)
|
|
190
|
+
galleryTree[lang][categoryPath][compName].errors.push(...audit.errors)
|
|
191
|
+
globalErrors += audit.errors.length
|
|
192
|
+
yield show(
|
|
193
|
+
t(SnapshotRunner.UI.auditFailed, {
|
|
194
|
+
file: filePath,
|
|
195
|
+
errors: audit.errors.join('; '),
|
|
196
|
+
}),
|
|
197
|
+
'error',
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
globalCount++
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Generate index.md for component
|
|
204
|
+
if (variationsData.length > 0) {
|
|
205
|
+
const categoryPath = this.getCategory ? this.getCategory(compName) : 'Components'
|
|
206
|
+
const outPath = `${this.snapshotsDir}/${lang}/${categoryPath}/${compName}`
|
|
207
|
+
const desc = t(SnapshotRunner.UI.galleryDescription, undefined)
|
|
208
|
+
|
|
209
|
+
const backPrefix = t(SnapshotRunner.UI.backLink, { text: t(SnapshotRunner.UI.backText, undefined) })
|
|
210
|
+
let markdown = `${backPrefix}\n\n# ${compName}\n\n> ${desc}\n\n`
|
|
211
|
+
for (const { safeVarName, snapshot } of variationsData) {
|
|
212
|
+
markdown += `## ${safeVarName}\n\n\`\`\`yaml\n${snapshot}\n\`\`\`\n\n`
|
|
213
|
+
}
|
|
214
|
+
await this.db.saveDocument(`${outPath}/index.md`, markdown)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Generate top-level indexes
|
|
220
|
+
let rootMd = t(SnapshotRunner.UI.rootGallery, { count: globalCount, errors: globalErrors })
|
|
221
|
+
|
|
222
|
+
for (const lang of Object.keys(galleryTree)) {
|
|
223
|
+
const langTitle = langsIndex[lang] || lang
|
|
224
|
+
rootMd += `- [${langTitle}](./${lang}/index.md) — ${t(SnapshotRunner.UI.galleryDescription, undefined)}\n`
|
|
225
|
+
|
|
226
|
+
const backPrefix = t(SnapshotRunner.UI.backLink, { text: t(SnapshotRunner.UI.backText, undefined) })
|
|
227
|
+
let langMd = `${backPrefix}\n\n# ${t(SnapshotRunner.UI.localeTitle, {
|
|
228
|
+
title: langTitle,
|
|
229
|
+
})}\n\n`
|
|
230
|
+
|
|
231
|
+
for (const category of Object.keys(galleryTree[lang])) {
|
|
232
|
+
langMd += `## [${category}](./${category}/index.md)\n\n`
|
|
233
|
+
|
|
234
|
+
const backPrefix = t(SnapshotRunner.UI.backLink, { text: t(SnapshotRunner.UI.backText, undefined) })
|
|
235
|
+
let catMd = `${backPrefix}\n\n# ${t(SnapshotRunner.UI.categoryTitle, {
|
|
236
|
+
title: category,
|
|
237
|
+
})}\n\n`
|
|
238
|
+
|
|
239
|
+
for (const compName of Object.keys(galleryTree[lang][category])) {
|
|
240
|
+
const compData = galleryTree[lang][category][compName]
|
|
241
|
+
const status = compData.score === 100 ? '✅' : '❌'
|
|
242
|
+
|
|
243
|
+
langMd += `- [${compName}](./${category}/${compName}/index.md) ${status}\n`
|
|
244
|
+
catMd += `- [${compName}](./${compName}/index.md) ${status}\n`
|
|
245
|
+
if (compData.errors.length) {
|
|
246
|
+
catMd += ` - ${compData.errors.join('\\n - ')}\n`
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
langMd += '\n'
|
|
250
|
+
|
|
251
|
+
const catDir = `${this.snapshotsDir}/${lang}/${category}`
|
|
252
|
+
await this.db.saveDocument(`${catDir}/index.md`, catMd)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const langDir = `${this.snapshotsDir}/${lang}`
|
|
256
|
+
await this.db.saveDocument(`${langDir}/index.md`, langMd)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await this.db.saveDocument(`${this.snapshotsDir}/index.md`, rootMd)
|
|
260
|
+
return result({ success: globalErrors === 0, count: globalCount, errors: globalErrors })
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export default SnapshotRunner
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ModelAsApp } from '../ModelAsApp.js'
|
|
2
|
+
import { resolvePositionalArgs } from '@nan0web/ui-cli'
|
|
3
|
+
import SnapshotAuditor from './SnapshotAuditor.js'
|
|
4
|
+
import GalleryCommand from './GalleryCommand.js'
|
|
5
|
+
import { show, result } from '../../core/Intent.js'
|
|
6
|
+
|
|
7
|
+
export class UIApp extends ModelAsApp {
|
|
8
|
+
static command = {
|
|
9
|
+
type: 'string',
|
|
10
|
+
help: 'Command to run (e.g. gallery)',
|
|
11
|
+
options: [GalleryCommand, SnapshotAuditor],
|
|
12
|
+
default: GalleryCommand.alias || GalleryCommand.name,
|
|
13
|
+
positional: true,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static UI = {
|
|
17
|
+
helpText: [
|
|
18
|
+
'# ✨ NaN•Web UI CLI',
|
|
19
|
+
'',
|
|
20
|
+
'Zero-Hallucination Tools for the NaN•Web ecosystem.',
|
|
21
|
+
'',
|
|
22
|
+
'## Usage',
|
|
23
|
+
'',
|
|
24
|
+
'```bash',
|
|
25
|
+
'',
|
|
26
|
+
'nan0ui gallery audit',
|
|
27
|
+
'',
|
|
28
|
+
'nan0ui gallery render',
|
|
29
|
+
'',
|
|
30
|
+
'```',
|
|
31
|
+
].join('\n'),
|
|
32
|
+
unknownCommand: 'Unknown command: {command}',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static help = {
|
|
36
|
+
help: 'Show help message',
|
|
37
|
+
default: false,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Partial<UIApp> | Record<string, any>} [data={}]
|
|
42
|
+
* @param {import('@nan0web/types').ModelOptions} [options={}]
|
|
43
|
+
*/
|
|
44
|
+
constructor(data = {}, options = {}) {
|
|
45
|
+
super(data, options)
|
|
46
|
+
/** @type {string[]} */ this._positionals = []
|
|
47
|
+
/** @type {string} Type of command to run */ this.command
|
|
48
|
+
/** @type {boolean} Show help message */ this.help
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async *run() {
|
|
52
|
+
const t = this._.t
|
|
53
|
+
if (this.help || this.command === 'help') {
|
|
54
|
+
yield show(t(UIApp.UI.helpText, undefined))
|
|
55
|
+
return result({})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const TargetCommand = UIApp.command.options.find((opt) =>
|
|
59
|
+
[opt.alias, opt.name].includes(this.command),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if (!TargetCommand) {
|
|
63
|
+
yield show(t(UIApp.UI.unknownCommand, { command: this.command }), 'error')
|
|
64
|
+
return result({ status: 'error' })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Pass remaining positionals down to the target action
|
|
68
|
+
const nextData = resolvePositionalArgs(
|
|
69
|
+
/** @type {any} */ (TargetCommand),
|
|
70
|
+
this._positionals || [],
|
|
71
|
+
this
|
|
72
|
+
)
|
|
73
|
+
const intent = new TargetCommand(nextData, this._)
|
|
74
|
+
return yield* intent.run()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default UIApp
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Model } from '@nan0web/types'
|
|
2
|
+
import { show, result } from '../../core/Intent.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @typedef {Object} BreadcrumbItem
|
|
@@ -28,6 +29,12 @@ import { Model } from '@nan0web/types'
|
|
|
28
29
|
* Ctrl+C = always exit (adapter responsibility).
|
|
29
30
|
*/
|
|
30
31
|
export class BreadcrumbModel extends Model {
|
|
32
|
+
static $id = '@nan0web/ui/BreadcrumbModel'
|
|
33
|
+
|
|
34
|
+
static UI = {
|
|
35
|
+
navigated: 'Navigated to Context',
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
// ==========================================
|
|
32
39
|
// 1. MODEL AS SCHEMA (Static Definition)
|
|
33
40
|
// ==========================================
|
|
@@ -243,17 +250,14 @@ export class BreadcrumbModel extends Model {
|
|
|
243
250
|
// ==========================================
|
|
244
251
|
|
|
245
252
|
/**
|
|
246
|
-
* Yields a
|
|
253
|
+
* Yields a show intent with the current breadcrumb path.
|
|
247
254
|
* This is a "display-only" run — it shows the navigation state.
|
|
248
255
|
*/
|
|
249
256
|
async *run() {
|
|
250
|
-
yield {
|
|
251
|
-
type: 'log',
|
|
252
|
-
level: 'info',
|
|
253
|
-
message: this.toString(),
|
|
257
|
+
yield show(this._.t(BreadcrumbModel.UI.navigated, {}), 'info', {
|
|
254
258
|
component: 'Breadcrumbs',
|
|
255
259
|
model: /** @type {BreadcrumbModel} */ (this),
|
|
256
|
-
}
|
|
260
|
+
})
|
|
257
261
|
|
|
258
262
|
return {
|
|
259
263
|
type: 'result',
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FeatureItemModel — OLMUI Component Model
|
|
5
|
+
* Represents a single features block entry with an icon, title, and description.
|
|
6
|
+
*/
|
|
7
|
+
export class FeatureItemModel extends Model {
|
|
8
|
+
static $id = '@nan0web/ui/FeatureItemModel'
|
|
9
|
+
|
|
10
|
+
static icon = {
|
|
11
|
+
help: 'Icon name from @nan0web/icons or SVG path',
|
|
12
|
+
placeholder: 'stars',
|
|
13
|
+
default: '',
|
|
14
|
+
}
|
|
15
|
+
static title = {
|
|
16
|
+
help: 'Feature heading',
|
|
17
|
+
placeholder: 'High Performance',
|
|
18
|
+
default: '',
|
|
19
|
+
required: true,
|
|
20
|
+
}
|
|
21
|
+
static description = {
|
|
22
|
+
help: 'Detailed feature description',
|
|
23
|
+
placeholder: 'Built with the latest technologies...',
|
|
24
|
+
default: '',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {Partial<FeatureItemModel>} data
|
|
29
|
+
*/
|
|
30
|
+
constructor(data = {}) {
|
|
31
|
+
super(data)
|
|
32
|
+
/** @type {string} Icon name */ this.icon
|
|
33
|
+
/** @type {string} Feature heading */ this.title
|
|
34
|
+
/** @type {string} Feature description */ this.description
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* FeatureGridModel — OLMUI Component Model
|
|
40
|
+
* Grid of features with icons and descriptions.
|
|
41
|
+
*/
|
|
42
|
+
export class FeatureGridModel extends Model {
|
|
43
|
+
static $id = '@nan0web/ui/FeatureGridModel'
|
|
44
|
+
|
|
45
|
+
static items = {
|
|
46
|
+
help: 'Array of features to display in a grid',
|
|
47
|
+
type: 'FeatureItemModel[]',
|
|
48
|
+
hint: FeatureItemModel,
|
|
49
|
+
default: [],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Partial<FeatureGridModel>} data
|
|
54
|
+
*/
|
|
55
|
+
constructor(data = {}) {
|
|
56
|
+
super(data)
|
|
57
|
+
/** @type {FeatureItemModel[]} List of features */
|
|
58
|
+
this.items = Array.isArray(this.items)
|
|
59
|
+
? this.items.map((i) => (i instanceof FeatureItemModel ? i : new FeatureItemModel(i)))
|
|
60
|
+
: []
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Model } from '@nan0web/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MarkdownModel — OLMUI Model-as-Schema
|
|
5
|
+
* Represents a rich text block powered by Markdown.
|
|
6
|
+
*/
|
|
7
|
+
export class MarkdownModel extends Model {
|
|
8
|
+
static $id = '@nan0web/ui/MarkdownModel'
|
|
9
|
+
|
|
10
|
+
static content = {
|
|
11
|
+
help: 'Markdown content string',
|
|
12
|
+
type: 'textarea',
|
|
13
|
+
default: '',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Partial<MarkdownModel> | Record<string, any>} data Model input data.
|
|
18
|
+
* @param {object} [options] Extended options
|
|
19
|
+
*/
|
|
20
|
+
constructor(data = {}, options = {}) {
|
|
21
|
+
super(data, options)
|
|
22
|
+
/** @type {string} Markdown content string */ this.content
|
|
23
|
+
}
|
|
24
|
+
}
|