wyreframe 0.1.1 → 0.2.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.
@@ -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
  }
@@ -215,7 +216,11 @@ let parseSingleScene = (
215
216
  shapeErrors->Array.forEach(err => errors->Array.push(err)->ignore)
216
217
  []
217
218
  }
218
- | Ok(shapes) => shapes
219
+ | Ok((boxes, warnings)) => {
220
+ // Collect warnings (non-fatal issues like misaligned borders)
221
+ warnings->Array.forEach(w => errors->Array.push(w)->ignore)
222
+ boxes
223
+ }
219
224
  }
220
225
 
221
226
  // Stage 3: Parse box content into elements
@@ -273,8 +278,8 @@ let parseSingleScene = (
273
278
  * @returns Result containing AST or array of parse errors
274
279
  */
275
280
  let parseInternal = (wireframe: string, interactions: option<string>): parseResult => {
276
- // Accumulator for all errors across stages
277
- let allErrors = []
281
+ // Accumulator for all issues (errors and warnings) across stages
282
+ let allIssues = []
278
283
 
279
284
  // Split wireframe into scene blocks
280
285
  let sceneBlocks = SemanticParser.splitSceneBlocks(wireframe)
@@ -282,8 +287,8 @@ let parseInternal = (wireframe: string, interactions: option<string>): parseResu
282
287
  // Check if wireframe is empty
283
288
  let trimmed = wireframe->String.trim
284
289
  if sceneBlocks->Array.length === 0 && trimmed === "" {
285
- // Empty wireframe - return empty AST
286
- Ok({scenes: []})
290
+ // Empty wireframe - return empty AST with no warnings
291
+ Ok(({scenes: []}: Types.ast, []))
287
292
  } else {
288
293
  // Parse each scene block
289
294
  let scenes = []
@@ -297,7 +302,7 @@ let parseInternal = (wireframe: string, interactions: option<string>): parseResu
297
302
  let sceneContent = contentLines->Array.join("\n")
298
303
 
299
304
  // Parse this scene through 3-stage pipeline
300
- switch parseSingleScene(sceneContent, metadata, allErrors) {
305
+ switch parseSingleScene(sceneContent, metadata, allIssues) {
301
306
  | Some(scene) => scenes->Array.push(scene)->ignore
302
307
  | None => () // Scene parsing failed, errors already collected
303
308
  }
@@ -314,7 +319,7 @@ let parseInternal = (wireframe: string, interactions: option<string>): parseResu
314
319
 
315
320
  switch interactionsResult {
316
321
  | Error(errors) => {
317
- errors->Array.forEach(err => allErrors->Array.push(err)->ignore)
322
+ errors->Array.forEach(err => allIssues->Array.push(err)->ignore)
318
323
  baseAst // Return AST without interactions on error
319
324
  }
320
325
  | Ok(sceneInteractions) => {
@@ -325,19 +330,23 @@ let parseInternal = (wireframe: string, interactions: option<string>): parseResu
325
330
  }
326
331
  }
327
332
 
333
+ // Separate errors from warnings
334
+ let errors = allIssues->Array.filter(issue => ErrorTypes.isError(issue))
335
+ let warnings = allIssues->Array.filter(issue => ErrorTypes.isWarning(issue))
336
+
328
337
  // 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
338
+ // Return Error if all boxes failed to parse and we have actual errors
339
+ // Return Ok with warnings if at least some elements were parsed successfully
331
340
  let totalElements = finalAst.scenes->Array.reduce(0, (acc, scene) => {
332
341
  acc + Array.length(scene.elements)
333
342
  })
334
343
 
335
- if Array.length(allErrors) > 0 && totalElements === 0 {
336
- // No elements parsed and we have errors - return error
337
- Error(allErrors)
344
+ if Array.length(errors) > 0 && totalElements === 0 {
345
+ // No elements parsed and we have errors - return error (include warnings too)
346
+ Error(allIssues)
338
347
  } else {
339
- // Either no errors, or some elements were parsed - return Ok
340
- Ok(finalAst)
348
+ // Either no errors, or some elements were parsed - return Ok with warnings
349
+ Ok((finalAst, warnings))
341
350
  }
342
351
  }
343
352
  }
@@ -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 goto = id => {
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
- return;
344
- }
345
- if (action.TAG !== "Goto") {
346
- return;
347
- }
348
- let goto = gotoRef.contents;
349
- if (goto !== undefined) {
350
- return goto(action.target);
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 ast = Parser.parse(text);
379
- if (ast.TAG !== "Ok") {
475
+ let errors = Parser.parse(text);
476
+ if (errors.TAG !== "Ok") {
380
477
  return {
381
478
  TAG: "Error",
382
- _0: ast._0
479
+ _0: errors._0
383
480
  };
384
481
  }
385
- let ast$1 = ast._0;
386
- let match = render(ast$1, options);
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$1
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 ast = Parser.parse(text);
399
- if (ast.TAG === "Ok") {
400
- let ast$1 = ast._0;
401
- let match = render(ast$1, options);
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$1
503
+ root: match$1.root,
504
+ sceneManager: match$1.sceneManager,
505
+ ast: ast,
506
+ warnings: match[1]
406
507
  };
407
508
  }
408
- let messages = ast._0.map(err => ErrorMessages.getTitle(err.code)).join("\n");
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
 
@@ -77,6 +77,11 @@ let defaultOptions: renderOptions = {
77
77
  */
78
78
  type sceneManager = {
79
79
  goto: string => unit,
80
+ back: unit => unit,
81
+ forward: unit => unit,
82
+ refresh: unit => unit,
83
+ canGoBack: unit => bool,
84
+ canGoForward: unit => bool,
80
85
  getCurrentScene: unit => option<string>,
81
86
  getSceneIds: unit => array<string>,
82
87
  }
@@ -415,8 +420,11 @@ let renderScene = (scene: scene, ~onAction: option<actionHandler>=?): DomBinding
415
420
 
416
421
  let createSceneManager = (scenes: Map.t<string, DomBindings.element>): sceneManager => {
417
422
  let currentScene = ref(None)
423
+ let historyStack: ref<array<string>> = ref([])
424
+ let forwardStack: ref<array<string>> = ref([])
418
425
 
419
- let goto = (id: string): unit => {
426
+ // Internal function to switch scenes without affecting history
427
+ let switchToScene = (id: string): unit => {
420
428
  switch currentScene.contents {
421
429
  | Some(currentId) => {
422
430
  switch scenes->Map.get(currentId) {
@@ -436,6 +444,88 @@ let createSceneManager = (scenes: Map.t<string, DomBindings.element>): sceneMana
436
444
  }
437
445
  }
438
446
 
447
+ let goto = (id: string): unit => {
448
+ // Add current scene to history before navigating
449
+ switch currentScene.contents {
450
+ | Some(currentId) if currentId != id => {
451
+ historyStack := historyStack.contents->Array.concat([currentId])
452
+ // Clear forward stack when navigating to new scene
453
+ forwardStack := []
454
+ }
455
+ | _ => ()
456
+ }
457
+ switchToScene(id)
458
+ }
459
+
460
+ let back = (): unit => {
461
+ let history = historyStack.contents
462
+ let len = history->Array.length
463
+ if len > 0 {
464
+ switch history->Array.get(len - 1) {
465
+ | Some(prevId) => {
466
+ // Add current scene to forward stack
467
+ switch currentScene.contents {
468
+ | Some(currentId) => {
469
+ forwardStack := forwardStack.contents->Array.concat([currentId])
470
+ }
471
+ | None => ()
472
+ }
473
+ // Remove last item from history
474
+ historyStack := history->Array.slice(~start=0, ~end=len - 1)
475
+ // Navigate to previous scene
476
+ switchToScene(prevId)
477
+ }
478
+ | None => ()
479
+ }
480
+ }
481
+ }
482
+
483
+ let forward = (): unit => {
484
+ let fwdStack = forwardStack.contents
485
+ let len = fwdStack->Array.length
486
+ if len > 0 {
487
+ switch fwdStack->Array.get(len - 1) {
488
+ | Some(nextId) => {
489
+ // Add current scene to history
490
+ switch currentScene.contents {
491
+ | Some(currentId) => {
492
+ historyStack := historyStack.contents->Array.concat([currentId])
493
+ }
494
+ | None => ()
495
+ }
496
+ // Remove last item from forward stack
497
+ forwardStack := fwdStack->Array.slice(~start=0, ~end=len - 1)
498
+ // Navigate to next scene
499
+ switchToScene(nextId)
500
+ }
501
+ | None => ()
502
+ }
503
+ }
504
+ }
505
+
506
+ let refresh = (): unit => {
507
+ switch currentScene.contents {
508
+ | Some(id) => {
509
+ // Remove active class and re-add it to trigger any CSS animations
510
+ switch scenes->Map.get(id) {
511
+ | Some(el) => {
512
+ el->DomBindings.classList->DomBindings.remove("active")
513
+ // Use setTimeout to ensure the class removal is processed
514
+ let _ = Js.Global.setTimeout(() => {
515
+ el->DomBindings.classList->DomBindings.add("active")
516
+ }, 0)
517
+ }
518
+ | None => ()
519
+ }
520
+ }
521
+ | None => ()
522
+ }
523
+ }
524
+
525
+ let canGoBack = (): bool => historyStack.contents->Array.length > 0
526
+
527
+ let canGoForward = (): bool => forwardStack.contents->Array.length > 0
528
+
439
529
  let getCurrentScene = (): option<string> => currentScene.contents
440
530
 
441
531
  let getSceneIds = (): array<string> => {
@@ -444,6 +534,11 @@ let createSceneManager = (scenes: Map.t<string, DomBindings.element>): sceneMana
444
534
 
445
535
  {
446
536
  goto,
537
+ back,
538
+ forward,
539
+ refresh,
540
+ canGoBack,
541
+ canGoForward,
447
542
  getCurrentScene,
448
543
  getSceneIds,
449
544
  }
@@ -494,10 +589,12 @@ let render = (ast: ast, options: option<renderOptions>): renderResult => {
494
589
  | None => ()
495
590
  }
496
591
 
497
- // Create a ref to hold the goto function (set after sceneManager is created)
592
+ // Create refs to hold navigation functions (set after sceneManager is created)
498
593
  let gotoRef: ref<option<string => unit>> = ref(None)
594
+ let backRef: ref<option<unit => unit>> = ref(None)
595
+ let forwardRef: ref<option<unit => unit>> = ref(None)
499
596
 
500
- // Create action handler that uses the gotoRef
597
+ // Create action handler that uses the refs
501
598
  let handleAction = (action: interactionAction): unit => {
502
599
  switch action {
503
600
  | Goto({target, _}) => {
@@ -507,12 +604,16 @@ let render = (ast: ast, options: option<renderOptions>): renderResult => {
507
604
  }
508
605
  }
509
606
  | Back => {
510
- // TODO: Implement history-based back navigation
511
- ()
607
+ switch backRef.contents {
608
+ | Some(back) => back()
609
+ | None => ()
610
+ }
512
611
  }
513
612
  | Forward => {
514
- // TODO: Implement history-based forward navigation
515
- ()
613
+ switch forwardRef.contents {
614
+ | Some(forward) => forward()
615
+ | None => ()
616
+ }
516
617
  }
517
618
  | Validate(_) => {
518
619
  // TODO: Implement field validation
@@ -535,8 +636,10 @@ let render = (ast: ast, options: option<renderOptions>): renderResult => {
535
636
 
536
637
  let manager = createSceneManager(sceneMap)
537
638
 
538
- // Now that sceneManager is created, set the gotoRef
639
+ // Now that sceneManager is created, set the refs
539
640
  gotoRef := Some(manager.goto)
641
+ backRef := Some(manager.back)
642
+ forwardRef := Some(manager.forward)
540
643
 
541
644
  if ast.scenes->Array.length > 0 {
542
645
  switch ast.scenes->Array.get(0) {
@@ -568,6 +671,7 @@ type createUISuccessResult = {
568
671
  root: DomBindings.element,
569
672
  sceneManager: sceneManager,
570
673
  ast: Types.ast,
674
+ warnings: array<ErrorTypes.t>,
571
675
  }
572
676
 
573
677
  type createUIResult = result<createUISuccessResult, array<ErrorTypes.t>>
@@ -595,9 +699,9 @@ type createUIResult = result<createUISuccessResult, array<ErrorTypes.t>>
595
699
  @genType
596
700
  let createUI = (text: string, options: option<renderOptions>): createUIResult => {
597
701
  switch Parser.parse(text) {
598
- | Ok(ast) => {
702
+ | Ok((ast, warnings)) => {
599
703
  let {root, sceneManager} = render(ast, options)
600
- Ok({root, sceneManager, ast})
704
+ Ok({root, sceneManager, ast, warnings})
601
705
  }
602
706
  | Error(errors) => Error(errors)
603
707
  }
@@ -621,9 +725,9 @@ let createUI = (text: string, options: option<renderOptions>): createUIResult =>
621
725
  @genType
622
726
  let createUIOrThrow = (text: string, options: option<renderOptions>): createUISuccessResult => {
623
727
  switch Parser.parse(text) {
624
- | Ok(ast) => {
728
+ | Ok((ast, warnings)) => {
625
729
  let {root, sceneManager} = render(ast, options)
626
- {root, sceneManager, ast}
730
+ {root, sceneManager, ast, warnings}
627
731
  }
628
732
  | Error(errors) => {
629
733
  let messages = errors
@@ -1,9 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
-
4
- let pass;
5
-
6
- export {
7
- pass,
8
- }
9
- /* No side effect */