wyreframe 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +34 -4
- package/src/parser/Detector/BoxTracer.mjs +122 -36
- package/src/parser/Detector/BoxTracer.res +172 -27
- package/src/parser/Detector/ShapeDetector.mjs +27 -9
- package/src/parser/Detector/ShapeDetector.res +15 -8
- package/src/parser/Errors/ErrorMessages.mjs +25 -0
- package/src/parser/Errors/ErrorMessages.res +17 -0
- package/src/parser/Errors/ErrorTypes.mjs +99 -0
- package/src/parser/Errors/ErrorTypes.res +66 -1
- package/src/parser/Parser.gen.tsx +3 -2
- package/src/parser/Parser.mjs +30 -15
- package/src/parser/Parser.res +46 -22
- package/src/parser/Semantic/SemanticParser.mjs +22 -4
- package/src/parser/Semantic/SemanticParser.res +55 -3
- package/src/renderer/Renderer.gen.tsx +7 -1
- package/src/renderer/Renderer.mjs +126 -25
- package/src/renderer/Renderer.res +116 -12
package/src/parser/Parser.mjs
CHANGED
|
@@ -69,17 +69,23 @@ function mergeInteractionsIntoAST(ast, sceneInteractions) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function parseSingleScene(sceneContent, sceneMetadata, errors) {
|
|
72
|
+
function parseSingleScene(sceneContent, sceneMetadata, lineOffset, errors) {
|
|
73
73
|
let gridResult = scanGrid(sceneContent);
|
|
74
74
|
if (gridResult.TAG === "Ok") {
|
|
75
75
|
let grid = gridResult._0;
|
|
76
76
|
let shapesResult = ShapeDetector.detect(grid);
|
|
77
77
|
let shapes;
|
|
78
78
|
if (shapesResult.TAG === "Ok") {
|
|
79
|
-
|
|
79
|
+
let match = shapesResult._0;
|
|
80
|
+
match[1].forEach(w => {
|
|
81
|
+
let adjusted = ErrorTypes.adjustLineOffset(w, lineOffset);
|
|
82
|
+
errors.push(adjusted);
|
|
83
|
+
});
|
|
84
|
+
shapes = match[0];
|
|
80
85
|
} else {
|
|
81
86
|
shapesResult._0.forEach(err => {
|
|
82
|
-
|
|
87
|
+
let adjusted = ErrorTypes.adjustLineOffset(err, lineOffset);
|
|
88
|
+
errors.push(adjusted);
|
|
83
89
|
});
|
|
84
90
|
shapes = [];
|
|
85
91
|
}
|
|
@@ -106,28 +112,32 @@ function parseSingleScene(sceneContent, sceneMetadata, errors) {
|
|
|
106
112
|
};
|
|
107
113
|
}
|
|
108
114
|
gridResult._0.forEach(err => {
|
|
109
|
-
|
|
115
|
+
let adjusted = ErrorTypes.adjustLineOffset(err, lineOffset);
|
|
116
|
+
errors.push(adjusted);
|
|
110
117
|
});
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
function parseInternal(wireframe, interactions) {
|
|
114
|
-
let
|
|
121
|
+
let allIssues = [];
|
|
115
122
|
let sceneBlocks = SemanticParser.splitSceneBlocks(wireframe);
|
|
116
123
|
let trimmed = wireframe.trim();
|
|
117
124
|
if (sceneBlocks.length === 0 && trimmed === "") {
|
|
118
125
|
return {
|
|
119
126
|
TAG: "Ok",
|
|
120
|
-
_0:
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
_0: [
|
|
128
|
+
{
|
|
129
|
+
scenes: []
|
|
130
|
+
},
|
|
131
|
+
[]
|
|
132
|
+
]
|
|
123
133
|
};
|
|
124
134
|
}
|
|
125
135
|
let scenes = [];
|
|
126
136
|
sceneBlocks.forEach(block => {
|
|
127
137
|
let lines = block.split("\n");
|
|
128
|
-
let match = SemanticParser.
|
|
129
|
-
let sceneContent = match
|
|
130
|
-
let scene = parseSingleScene(sceneContent, match
|
|
138
|
+
let match = SemanticParser.parseSceneDirectivesWithOffset(lines);
|
|
139
|
+
let sceneContent = match.contentLines.join("\n");
|
|
140
|
+
let scene = parseSingleScene(sceneContent, match.metadata, match.lineOffset, allIssues);
|
|
131
141
|
if (scene !== undefined) {
|
|
132
142
|
scenes.push(scene);
|
|
133
143
|
return;
|
|
@@ -143,23 +153,28 @@ function parseInternal(wireframe, interactions) {
|
|
|
143
153
|
finalAst = mergeInteractionsIntoAST(baseAst, interactionsResult._0);
|
|
144
154
|
} else {
|
|
145
155
|
interactionsResult._0.forEach(err => {
|
|
146
|
-
|
|
156
|
+
allIssues.push(err);
|
|
147
157
|
});
|
|
148
158
|
finalAst = baseAst;
|
|
149
159
|
}
|
|
150
160
|
} else {
|
|
151
161
|
finalAst = baseAst;
|
|
152
162
|
}
|
|
163
|
+
let errors = allIssues.filter(ErrorTypes.isError);
|
|
164
|
+
let warnings = allIssues.filter(ErrorTypes.isWarning);
|
|
153
165
|
let totalElements = Core__Array.reduce(finalAst.scenes, 0, (acc, scene) => acc + scene.elements.length | 0);
|
|
154
|
-
if (
|
|
166
|
+
if (errors.length > 0 && totalElements === 0) {
|
|
155
167
|
return {
|
|
156
168
|
TAG: "Error",
|
|
157
|
-
_0:
|
|
169
|
+
_0: allIssues
|
|
158
170
|
};
|
|
159
171
|
} else {
|
|
160
172
|
return {
|
|
161
173
|
TAG: "Ok",
|
|
162
|
-
_0:
|
|
174
|
+
_0: [
|
|
175
|
+
finalAst,
|
|
176
|
+
warnings
|
|
177
|
+
]
|
|
163
178
|
};
|
|
164
179
|
}
|
|
165
180
|
}
|
package/src/parser/Parser.res
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
// ============================================================================
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Parse result type - either a successful AST or an array of parse errors.
|
|
11
|
+
* Parse result type - either a successful (AST, warnings) tuple or an array of parse errors.
|
|
12
|
+
* Warnings are non-fatal issues like misaligned borders that don't prevent parsing.
|
|
12
13
|
* This Result type is compatible with TypeScript through GenType.
|
|
13
14
|
*/
|
|
14
|
-
type parseResult = result<Types.ast, array<ErrorTypes.t>>
|
|
15
|
+
type parseResult = result<(Types.ast, array<ErrorTypes.t>), array<ErrorTypes.t>>
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Interaction parse result type.
|
|
@@ -65,11 +66,11 @@ let scanGrid = (wireframe: string): result<Grid.t, array<ErrorTypes.t>> => {
|
|
|
65
66
|
* and builds parent-child hierarchy.
|
|
66
67
|
*
|
|
67
68
|
* @param grid The 2D grid from Stage 1
|
|
68
|
-
* @returns Result containing root boxes or errors
|
|
69
|
+
* @returns Result containing (root boxes, warnings) or errors
|
|
69
70
|
*
|
|
70
71
|
* Requirements: REQ-3, REQ-4, REQ-5, REQ-6, REQ-7 (Shape Detection)
|
|
71
72
|
*/
|
|
72
|
-
let detectShapes = (grid: Grid.t): result<array<box>, array<ErrorTypes.t>> => {
|
|
73
|
+
let detectShapes = (grid: Grid.t): result<(array<box>, array<ErrorTypes.t>), array<ErrorTypes.t>> => {
|
|
73
74
|
// Use ShapeDetector to detect all shapes in the grid
|
|
74
75
|
ShapeDetector.detect(grid)
|
|
75
76
|
}
|
|
@@ -190,12 +191,14 @@ let mergeInteractionsIntoAST = (
|
|
|
190
191
|
*
|
|
191
192
|
* @param sceneContent ASCII wireframe content for one scene (without directives)
|
|
192
193
|
* @param sceneMetadata Scene metadata from directives
|
|
194
|
+
* @param lineOffset Number of directive lines stripped (for adjusting error line numbers)
|
|
193
195
|
* @param errors Accumulator for errors
|
|
194
196
|
* @returns Parsed scene or None if parsing failed
|
|
195
197
|
*/
|
|
196
198
|
let parseSingleScene = (
|
|
197
199
|
sceneContent: string,
|
|
198
200
|
sceneMetadata: SemanticParser.sceneMetadata,
|
|
201
|
+
lineOffset: int,
|
|
199
202
|
errors: array<ErrorTypes.t>,
|
|
200
203
|
): option<Types.scene> => {
|
|
201
204
|
// Stage 1: Grid Scanner
|
|
@@ -203,7 +206,11 @@ let parseSingleScene = (
|
|
|
203
206
|
|
|
204
207
|
switch gridResult {
|
|
205
208
|
| Error(gridErrors) => {
|
|
206
|
-
|
|
209
|
+
// Adjust line numbers by offset before adding to errors
|
|
210
|
+
gridErrors->Array.forEach(err => {
|
|
211
|
+
let adjusted = ErrorTypes.adjustLineOffset(err, lineOffset)
|
|
212
|
+
errors->Array.push(adjusted)->ignore
|
|
213
|
+
})
|
|
207
214
|
None
|
|
208
215
|
}
|
|
209
216
|
| Ok(grid) => {
|
|
@@ -212,10 +219,22 @@ let parseSingleScene = (
|
|
|
212
219
|
|
|
213
220
|
let shapes = switch shapesResult {
|
|
214
221
|
| Error(shapeErrors) => {
|
|
215
|
-
|
|
222
|
+
// Adjust line numbers by offset before adding to errors
|
|
223
|
+
shapeErrors->Array.forEach(err => {
|
|
224
|
+
let adjusted = ErrorTypes.adjustLineOffset(err, lineOffset)
|
|
225
|
+
errors->Array.push(adjusted)->ignore
|
|
226
|
+
})
|
|
216
227
|
[]
|
|
217
228
|
}
|
|
218
|
-
| Ok(
|
|
229
|
+
| Ok((boxes, warnings)) => {
|
|
230
|
+
// Collect warnings (non-fatal issues like misaligned borders)
|
|
231
|
+
// Adjust line numbers by offset before adding to errors
|
|
232
|
+
warnings->Array.forEach(w => {
|
|
233
|
+
let adjusted = ErrorTypes.adjustLineOffset(w, lineOffset)
|
|
234
|
+
errors->Array.push(adjusted)->ignore
|
|
235
|
+
})
|
|
236
|
+
boxes
|
|
237
|
+
}
|
|
219
238
|
}
|
|
220
239
|
|
|
221
240
|
// Stage 3: Parse box content into elements
|
|
@@ -273,8 +292,8 @@ let parseSingleScene = (
|
|
|
273
292
|
* @returns Result containing AST or array of parse errors
|
|
274
293
|
*/
|
|
275
294
|
let parseInternal = (wireframe: string, interactions: option<string>): parseResult => {
|
|
276
|
-
// Accumulator for all errors across stages
|
|
277
|
-
let
|
|
295
|
+
// Accumulator for all issues (errors and warnings) across stages
|
|
296
|
+
let allIssues = []
|
|
278
297
|
|
|
279
298
|
// Split wireframe into scene blocks
|
|
280
299
|
let sceneBlocks = SemanticParser.splitSceneBlocks(wireframe)
|
|
@@ -282,22 +301,23 @@ let parseInternal = (wireframe: string, interactions: option<string>): parseResu
|
|
|
282
301
|
// Check if wireframe is empty
|
|
283
302
|
let trimmed = wireframe->String.trim
|
|
284
303
|
if sceneBlocks->Array.length === 0 && trimmed === "" {
|
|
285
|
-
// Empty wireframe - return empty AST
|
|
286
|
-
Ok({scenes: []})
|
|
304
|
+
// Empty wireframe - return empty AST with no warnings
|
|
305
|
+
Ok(({scenes: []}: Types.ast, []))
|
|
287
306
|
} else {
|
|
288
307
|
// Parse each scene block
|
|
289
308
|
let scenes = []
|
|
290
309
|
|
|
291
310
|
sceneBlocks->Array.forEach(block => {
|
|
292
|
-
// Parse scene directives
|
|
311
|
+
// Parse scene directives and get line offset
|
|
293
312
|
let lines = block->String.split("\n")
|
|
294
|
-
let
|
|
313
|
+
let {metadata, contentLines, lineOffset} = SemanticParser.parseSceneDirectivesWithOffset(lines)
|
|
295
314
|
|
|
296
315
|
// Rejoin content lines (without directives)
|
|
297
316
|
let sceneContent = contentLines->Array.join("\n")
|
|
298
317
|
|
|
299
318
|
// Parse this scene through 3-stage pipeline
|
|
300
|
-
|
|
319
|
+
// Pass lineOffset to adjust error/warning line numbers
|
|
320
|
+
switch parseSingleScene(sceneContent, metadata, lineOffset, allIssues) {
|
|
301
321
|
| Some(scene) => scenes->Array.push(scene)->ignore
|
|
302
322
|
| None => () // Scene parsing failed, errors already collected
|
|
303
323
|
}
|
|
@@ -314,7 +334,7 @@ let parseInternal = (wireframe: string, interactions: option<string>): parseResu
|
|
|
314
334
|
|
|
315
335
|
switch interactionsResult {
|
|
316
336
|
| Error(errors) => {
|
|
317
|
-
errors->Array.forEach(err =>
|
|
337
|
+
errors->Array.forEach(err => allIssues->Array.push(err)->ignore)
|
|
318
338
|
baseAst // Return AST without interactions on error
|
|
319
339
|
}
|
|
320
340
|
| Ok(sceneInteractions) => {
|
|
@@ -325,19 +345,23 @@ let parseInternal = (wireframe: string, interactions: option<string>): parseResu
|
|
|
325
345
|
}
|
|
326
346
|
}
|
|
327
347
|
|
|
348
|
+
// Separate errors from warnings
|
|
349
|
+
let errors = allIssues->Array.filter(issue => ErrorTypes.isError(issue))
|
|
350
|
+
let warnings = allIssues->Array.filter(issue => ErrorTypes.isWarning(issue))
|
|
351
|
+
|
|
328
352
|
// Return final result
|
|
329
|
-
// Return Error if all boxes failed to parse and we have errors
|
|
330
|
-
// Return Ok if at least some elements were parsed successfully
|
|
353
|
+
// Return Error if all boxes failed to parse and we have actual errors
|
|
354
|
+
// Return Ok with warnings if at least some elements were parsed successfully
|
|
331
355
|
let totalElements = finalAst.scenes->Array.reduce(0, (acc, scene) => {
|
|
332
356
|
acc + Array.length(scene.elements)
|
|
333
357
|
})
|
|
334
358
|
|
|
335
|
-
if Array.length(
|
|
336
|
-
// No elements parsed and we have errors - return error
|
|
337
|
-
Error(
|
|
359
|
+
if Array.length(errors) > 0 && totalElements === 0 {
|
|
360
|
+
// No elements parsed and we have errors - return error (include warnings too)
|
|
361
|
+
Error(allIssues)
|
|
338
362
|
} else {
|
|
339
|
-
// Either no errors, or some elements were parsed - return Ok
|
|
340
|
-
Ok(finalAst)
|
|
363
|
+
// Either no errors, or some elements were parsed - return Ok with warnings
|
|
364
|
+
Ok((finalAst, warnings))
|
|
341
365
|
}
|
|
342
366
|
}
|
|
343
367
|
}
|
|
@@ -141,7 +141,7 @@ function defaultSceneMetadata() {
|
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
function
|
|
144
|
+
function parseSceneDirectivesWithOffset(lines) {
|
|
145
145
|
let sceneId = {
|
|
146
146
|
contents: undefined
|
|
147
147
|
};
|
|
@@ -155,7 +155,10 @@ function parseSceneDirectives(lines) {
|
|
|
155
155
|
contents: undefined
|
|
156
156
|
};
|
|
157
157
|
let contentLines = [];
|
|
158
|
-
|
|
158
|
+
let firstContentLineIndex = {
|
|
159
|
+
contents: undefined
|
|
160
|
+
};
|
|
161
|
+
lines.forEach((line, lineIndex) => {
|
|
159
162
|
let trimmed = line.trim();
|
|
160
163
|
if (trimmed.startsWith("@scene:")) {
|
|
161
164
|
let id = trimmed.replace("@scene:", "").trim();
|
|
@@ -176,6 +179,9 @@ function parseSceneDirectives(lines) {
|
|
|
176
179
|
if (trimmed.startsWith("@")) {
|
|
177
180
|
return;
|
|
178
181
|
} else {
|
|
182
|
+
if (firstContentLineIndex.contents === undefined) {
|
|
183
|
+
firstContentLineIndex.contents = lineIndex;
|
|
184
|
+
}
|
|
179
185
|
contentLines.push(line);
|
|
180
186
|
return;
|
|
181
187
|
}
|
|
@@ -204,9 +210,20 @@ function parseSceneDirectives(lines) {
|
|
|
204
210
|
transition: finalTransition,
|
|
205
211
|
device: finalDevice
|
|
206
212
|
};
|
|
213
|
+
let idx = firstContentLineIndex.contents;
|
|
214
|
+
let lineOffset = idx !== undefined ? idx : 0;
|
|
215
|
+
return {
|
|
216
|
+
metadata: metadata,
|
|
217
|
+
contentLines: contentLines,
|
|
218
|
+
lineOffset: lineOffset
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseSceneDirectives(lines) {
|
|
223
|
+
let result = parseSceneDirectivesWithOffset(lines);
|
|
207
224
|
return [
|
|
208
|
-
metadata,
|
|
209
|
-
contentLines
|
|
225
|
+
result.metadata,
|
|
226
|
+
result.contentLines
|
|
210
227
|
];
|
|
211
228
|
}
|
|
212
229
|
|
|
@@ -824,6 +841,7 @@ export {
|
|
|
824
841
|
hasContent,
|
|
825
842
|
getContentLineCount,
|
|
826
843
|
defaultSceneMetadata,
|
|
844
|
+
parseSceneDirectivesWithOffset,
|
|
827
845
|
parseSceneDirectives,
|
|
828
846
|
splitSceneBlocks,
|
|
829
847
|
groupContentByScenes,
|
|
@@ -261,7 +261,32 @@ let defaultSceneMetadata = (): sceneMetadata => {
|
|
|
261
261
|
* ```
|
|
262
262
|
* Returns: ({id: "login", title: "Login Page", transition: "slide"}, ["+--Login--+", ...])
|
|
263
263
|
*/
|
|
264
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Result type for parseSceneDirectives that includes line offset information.
|
|
266
|
+
* The lineOffset indicates how many lines were removed from the beginning
|
|
267
|
+
* before the first content line, which is needed to correctly report
|
|
268
|
+
* line numbers in warnings and errors.
|
|
269
|
+
*/
|
|
270
|
+
type directiveParseResult = {
|
|
271
|
+
metadata: sceneMetadata,
|
|
272
|
+
contentLines: array<string>,
|
|
273
|
+
lineOffset: int, // Number of directive lines stripped from the beginning
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Parse scene directives and track line offset.
|
|
278
|
+
* Returns metadata, content lines, and the number of lines stripped from the beginning.
|
|
279
|
+
*
|
|
280
|
+
* The lineOffset is calculated as the index of the first content line in the original
|
|
281
|
+
* input. This offset is needed to convert grid row numbers back to original file line numbers.
|
|
282
|
+
*
|
|
283
|
+
* Example:
|
|
284
|
+
* - Input: ["@scene: login", "", "+---+", ...]
|
|
285
|
+
* - Output: contentLines = ["", "+---+", ...], lineOffset = 1
|
|
286
|
+
*
|
|
287
|
+
* Grid row 0 corresponds to original line (0 + lineOffset + 1) = line 2 (1-indexed)
|
|
288
|
+
*/
|
|
289
|
+
let parseSceneDirectivesWithOffset = (lines: array<string>): directiveParseResult => {
|
|
265
290
|
// Use mutable refs to accumulate directive values
|
|
266
291
|
let sceneId = ref(None)
|
|
267
292
|
let title = ref(None)
|
|
@@ -269,7 +294,10 @@ let parseSceneDirectives = (lines: array<string>): (sceneMetadata, array<string>
|
|
|
269
294
|
let device = ref(None)
|
|
270
295
|
let contentLines = []
|
|
271
296
|
|
|
272
|
-
|
|
297
|
+
// Track the index of the first content line (for line offset calculation)
|
|
298
|
+
let firstContentLineIndex = ref(None)
|
|
299
|
+
|
|
300
|
+
lines->Array.forEachWithIndex((line, lineIndex) => {
|
|
273
301
|
let trimmed = line->String.trim
|
|
274
302
|
|
|
275
303
|
if trimmed->String.startsWith("@scene:") {
|
|
@@ -301,6 +329,10 @@ let parseSceneDirectives = (lines: array<string>): (sceneMetadata, array<string>
|
|
|
301
329
|
()
|
|
302
330
|
} else {
|
|
303
331
|
// Non-directive line - add to content
|
|
332
|
+
// Track the index of the first content line
|
|
333
|
+
if firstContentLineIndex.contents === None {
|
|
334
|
+
firstContentLineIndex := Some(lineIndex)
|
|
335
|
+
}
|
|
304
336
|
contentLines->Array.push(line)
|
|
305
337
|
}
|
|
306
338
|
})
|
|
@@ -334,7 +366,27 @@ let parseSceneDirectives = (lines: array<string>): (sceneMetadata, array<string>
|
|
|
334
366
|
device: finalDevice,
|
|
335
367
|
}
|
|
336
368
|
|
|
337
|
-
|
|
369
|
+
// Calculate line offset: index of first content line
|
|
370
|
+
// If no content lines, offset is 0
|
|
371
|
+
let lineOffset = switch firstContentLineIndex.contents {
|
|
372
|
+
| Some(idx) => idx
|
|
373
|
+
| None => 0
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
{
|
|
377
|
+
metadata,
|
|
378
|
+
contentLines,
|
|
379
|
+
lineOffset,
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Parse scene directives from an array of lines.
|
|
385
|
+
* This is the original API that returns just (metadata, contentLines) for backward compatibility.
|
|
386
|
+
*/
|
|
387
|
+
let parseSceneDirectives = (lines: array<string>): (sceneMetadata, array<string>) => {
|
|
388
|
+
let result = parseSceneDirectivesWithOffset(lines)
|
|
389
|
+
(result.metadata, result.contentLines)
|
|
338
390
|
}
|
|
339
391
|
|
|
340
392
|
/**
|
|
@@ -22,6 +22,11 @@ export type renderOptions = {
|
|
|
22
22
|
/** * Scene management interface returned by render function. */
|
|
23
23
|
export type sceneManager = {
|
|
24
24
|
readonly goto: (_1:string) => void;
|
|
25
|
+
readonly back: () => void;
|
|
26
|
+
readonly forward: () => void;
|
|
27
|
+
readonly refresh: () => void;
|
|
28
|
+
readonly canGoBack: () => boolean;
|
|
29
|
+
readonly canGoForward: () => boolean;
|
|
25
30
|
readonly getCurrentScene: () => (undefined | string);
|
|
26
31
|
readonly getSceneIds: () => string[]
|
|
27
32
|
};
|
|
@@ -34,7 +39,8 @@ export type renderResult = { readonly root: DomBindings_element; readonly sceneM
|
|
|
34
39
|
export type createUISuccessResult = {
|
|
35
40
|
readonly root: DomBindings_element;
|
|
36
41
|
readonly sceneManager: sceneManager;
|
|
37
|
-
readonly ast: Types_ast
|
|
42
|
+
readonly ast: Types_ast;
|
|
43
|
+
readonly warnings: ErrorTypes_t[]
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
export type createUIResult =
|
|
@@ -284,7 +284,13 @@ function createSceneManager(scenes) {
|
|
|
284
284
|
let currentScene = {
|
|
285
285
|
contents: undefined
|
|
286
286
|
};
|
|
287
|
-
let
|
|
287
|
+
let historyStack = {
|
|
288
|
+
contents: []
|
|
289
|
+
};
|
|
290
|
+
let forwardStack = {
|
|
291
|
+
contents: []
|
|
292
|
+
};
|
|
293
|
+
let switchToScene = id => {
|
|
288
294
|
let currentId = currentScene.contents;
|
|
289
295
|
if (currentId !== undefined) {
|
|
290
296
|
let el = scenes.get(currentId);
|
|
@@ -299,10 +305,74 @@ function createSceneManager(scenes) {
|
|
|
299
305
|
return;
|
|
300
306
|
}
|
|
301
307
|
};
|
|
308
|
+
let goto = id => {
|
|
309
|
+
let currentId = currentScene.contents;
|
|
310
|
+
if (currentId !== undefined && currentId !== id) {
|
|
311
|
+
historyStack.contents = historyStack.contents.concat([currentId]);
|
|
312
|
+
forwardStack.contents = [];
|
|
313
|
+
}
|
|
314
|
+
switchToScene(id);
|
|
315
|
+
};
|
|
316
|
+
let back = () => {
|
|
317
|
+
let history = historyStack.contents;
|
|
318
|
+
let len = history.length;
|
|
319
|
+
if (len <= 0) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
let prevId = history[len - 1 | 0];
|
|
323
|
+
if (prevId === undefined) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
let currentId = currentScene.contents;
|
|
327
|
+
if (currentId !== undefined) {
|
|
328
|
+
forwardStack.contents = forwardStack.contents.concat([currentId]);
|
|
329
|
+
}
|
|
330
|
+
historyStack.contents = history.slice(0, len - 1 | 0);
|
|
331
|
+
switchToScene(prevId);
|
|
332
|
+
};
|
|
333
|
+
let forward = () => {
|
|
334
|
+
let fwdStack = forwardStack.contents;
|
|
335
|
+
let len = fwdStack.length;
|
|
336
|
+
if (len <= 0) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
let nextId = fwdStack[len - 1 | 0];
|
|
340
|
+
if (nextId === undefined) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
let currentId = currentScene.contents;
|
|
344
|
+
if (currentId !== undefined) {
|
|
345
|
+
historyStack.contents = historyStack.contents.concat([currentId]);
|
|
346
|
+
}
|
|
347
|
+
forwardStack.contents = fwdStack.slice(0, len - 1 | 0);
|
|
348
|
+
switchToScene(nextId);
|
|
349
|
+
};
|
|
350
|
+
let refresh = () => {
|
|
351
|
+
let id = currentScene.contents;
|
|
352
|
+
if (id === undefined) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
let el = scenes.get(id);
|
|
356
|
+
if (el === undefined) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
let el$1 = Primitive_option.valFromOption(el);
|
|
360
|
+
el$1.classList.remove("active");
|
|
361
|
+
setTimeout(() => {
|
|
362
|
+
el$1.classList.add("active");
|
|
363
|
+
}, 0);
|
|
364
|
+
};
|
|
365
|
+
let canGoBack = () => historyStack.contents.length > 0;
|
|
366
|
+
let canGoForward = () => forwardStack.contents.length > 0;
|
|
302
367
|
let getCurrentScene = () => currentScene.contents;
|
|
303
368
|
let getSceneIds = () => Array.from(scenes.keys());
|
|
304
369
|
return {
|
|
305
370
|
goto: goto,
|
|
371
|
+
back: back,
|
|
372
|
+
forward: forward,
|
|
373
|
+
refresh: refresh,
|
|
374
|
+
canGoBack: canGoBack,
|
|
375
|
+
canGoForward: canGoForward,
|
|
306
376
|
getCurrentScene: getCurrentScene,
|
|
307
377
|
getSceneIds: getSceneIds
|
|
308
378
|
};
|
|
@@ -338,16 +408,41 @@ function render(ast, options) {
|
|
|
338
408
|
let gotoRef = {
|
|
339
409
|
contents: undefined
|
|
340
410
|
};
|
|
411
|
+
let backRef = {
|
|
412
|
+
contents: undefined
|
|
413
|
+
};
|
|
414
|
+
let forwardRef = {
|
|
415
|
+
contents: undefined
|
|
416
|
+
};
|
|
341
417
|
let handleAction = action => {
|
|
342
418
|
if (typeof action !== "object") {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
419
|
+
if (action === "Back") {
|
|
420
|
+
let back = backRef.contents;
|
|
421
|
+
if (back !== undefined) {
|
|
422
|
+
return back();
|
|
423
|
+
} else {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
let forward = forwardRef.contents;
|
|
428
|
+
if (forward !== undefined) {
|
|
429
|
+
return forward();
|
|
430
|
+
} else {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
switch (action.TAG) {
|
|
435
|
+
case "Goto" :
|
|
436
|
+
let goto = gotoRef.contents;
|
|
437
|
+
if (goto !== undefined) {
|
|
438
|
+
return goto(action.target);
|
|
439
|
+
} else {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
case "Validate" :
|
|
443
|
+
case "Call" :
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
351
446
|
}
|
|
352
447
|
};
|
|
353
448
|
let sceneMap = new Map();
|
|
@@ -358,6 +453,8 @@ function render(ast, options) {
|
|
|
358
453
|
});
|
|
359
454
|
let manager = createSceneManager(sceneMap);
|
|
360
455
|
gotoRef.contents = manager.goto;
|
|
456
|
+
backRef.contents = manager.back;
|
|
457
|
+
forwardRef.contents = manager.forward;
|
|
361
458
|
if (ast.scenes.length > 0) {
|
|
362
459
|
let firstScene$1 = ast.scenes[0];
|
|
363
460
|
if (firstScene$1 !== undefined) {
|
|
@@ -375,37 +472,41 @@ function toHTMLString(_ast, _options) {
|
|
|
375
472
|
}
|
|
376
473
|
|
|
377
474
|
function createUI(text, options) {
|
|
378
|
-
let
|
|
379
|
-
if (
|
|
475
|
+
let errors = Parser.parse(text);
|
|
476
|
+
if (errors.TAG !== "Ok") {
|
|
380
477
|
return {
|
|
381
478
|
TAG: "Error",
|
|
382
|
-
_0:
|
|
479
|
+
_0: errors._0
|
|
383
480
|
};
|
|
384
481
|
}
|
|
385
|
-
let
|
|
386
|
-
let
|
|
482
|
+
let match = errors._0;
|
|
483
|
+
let ast = match[0];
|
|
484
|
+
let match$1 = render(ast, options);
|
|
387
485
|
return {
|
|
388
486
|
TAG: "Ok",
|
|
389
487
|
_0: {
|
|
390
|
-
root: match.root,
|
|
391
|
-
sceneManager: match.sceneManager,
|
|
392
|
-
ast: ast
|
|
488
|
+
root: match$1.root,
|
|
489
|
+
sceneManager: match$1.sceneManager,
|
|
490
|
+
ast: ast,
|
|
491
|
+
warnings: match[1]
|
|
393
492
|
}
|
|
394
493
|
};
|
|
395
494
|
}
|
|
396
495
|
|
|
397
496
|
function createUIOrThrow(text, options) {
|
|
398
|
-
let
|
|
399
|
-
if (
|
|
400
|
-
let
|
|
401
|
-
let
|
|
497
|
+
let errors = Parser.parse(text);
|
|
498
|
+
if (errors.TAG === "Ok") {
|
|
499
|
+
let match = errors._0;
|
|
500
|
+
let ast = match[0];
|
|
501
|
+
let match$1 = render(ast, options);
|
|
402
502
|
return {
|
|
403
|
-
root: match.root,
|
|
404
|
-
sceneManager: match.sceneManager,
|
|
405
|
-
ast: ast
|
|
503
|
+
root: match$1.root,
|
|
504
|
+
sceneManager: match$1.sceneManager,
|
|
505
|
+
ast: ast,
|
|
506
|
+
warnings: match[1]
|
|
406
507
|
};
|
|
407
508
|
}
|
|
408
|
-
let messages =
|
|
509
|
+
let messages = errors._0.map(err => ErrorMessages.getTitle(err.code)).join("\n");
|
|
409
510
|
return Stdlib_JsError.throwWithMessage("Parse failed:\n" + messages);
|
|
410
511
|
}
|
|
411
512
|
|