@cratis/scene.engine 4.26.0 → 4.27.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.
Files changed (121) hide show
  1. package/dist/cjs/editing/resolveEffectiveConfiguration.js +8 -0
  2. package/dist/cjs/editing/resolveEffectiveConfiguration.js.map +1 -1
  3. package/dist/cjs/index.js +23 -0
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/navigation/NavigationDiagnosticCode.js +5 -0
  6. package/dist/cjs/navigation/NavigationDiagnosticCode.js.map +1 -1
  7. package/dist/cjs/navigation/NavigationEditKind.js +16 -0
  8. package/dist/cjs/navigation/NavigationEditKind.js.map +1 -0
  9. package/dist/cjs/navigation/NavigationItemOrigin.js +14 -0
  10. package/dist/cjs/navigation/NavigationItemOrigin.js.map +1 -0
  11. package/dist/cjs/navigation/NavigationSourcesKind.js +16 -0
  12. package/dist/cjs/navigation/NavigationSourcesKind.js.map +1 -0
  13. package/dist/cjs/navigation/diagnoseNavigation.js +8 -1
  14. package/dist/cjs/navigation/diagnoseNavigation.js.map +1 -1
  15. package/dist/cjs/navigation/diagnoseNavigationRegions.js +99 -0
  16. package/dist/cjs/navigation/diagnoseNavigationRegions.js.map +1 -0
  17. package/dist/cjs/navigation/effectiveNavigation.js +133 -0
  18. package/dist/cjs/navigation/effectiveNavigation.js.map +1 -0
  19. package/dist/cjs/navigation/navigationEdits.js +230 -0
  20. package/dist/cjs/navigation/navigationEdits.js.map +1 -0
  21. package/dist/cjs/navigation/navigationRegions.js +42 -0
  22. package/dist/cjs/navigation/navigationRegions.js.map +1 -0
  23. package/dist/cjs/validateNavigationDestinations.js +2 -0
  24. package/dist/cjs/validateNavigationDestinations.js.map +1 -1
  25. package/dist/esm/editing/resolveEffectiveConfiguration.d.ts.map +1 -1
  26. package/dist/esm/editing/resolveEffectiveConfiguration.js +8 -0
  27. package/dist/esm/editing/resolveEffectiveConfiguration.js.map +1 -1
  28. package/dist/esm/index.js +7 -0
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/navigation/EffectiveNavItem.d.ts +14 -0
  31. package/dist/esm/navigation/EffectiveNavItem.d.ts.map +1 -0
  32. package/dist/esm/navigation/EffectiveNavItem.js +2 -0
  33. package/dist/esm/navigation/EffectiveNavItem.js.map +1 -0
  34. package/dist/esm/navigation/EffectiveNavigationRegion.d.ts +11 -0
  35. package/dist/esm/navigation/EffectiveNavigationRegion.d.ts.map +1 -0
  36. package/dist/esm/navigation/EffectiveNavigationRegion.js +2 -0
  37. package/dist/esm/navigation/EffectiveNavigationRegion.js.map +1 -0
  38. package/dist/esm/navigation/NavigationDiagnosticCode.d.ts +6 -1
  39. package/dist/esm/navigation/NavigationDiagnosticCode.d.ts.map +1 -1
  40. package/dist/esm/navigation/NavigationDiagnosticCode.js +5 -0
  41. package/dist/esm/navigation/NavigationDiagnosticCode.js.map +1 -1
  42. package/dist/esm/navigation/NavigationEdit.d.ts +13 -0
  43. package/dist/esm/navigation/NavigationEdit.d.ts.map +1 -0
  44. package/dist/esm/navigation/NavigationEdit.js +2 -0
  45. package/dist/esm/navigation/NavigationEdit.js.map +1 -0
  46. package/dist/esm/navigation/NavigationEditKind.d.ts +7 -0
  47. package/dist/esm/navigation/NavigationEditKind.d.ts.map +1 -0
  48. package/dist/esm/navigation/NavigationEditKind.js +14 -0
  49. package/dist/esm/navigation/NavigationEditKind.js.map +1 -0
  50. package/dist/esm/navigation/NavigationEditOutcome.d.ts +7 -0
  51. package/dist/esm/navigation/NavigationEditOutcome.d.ts.map +1 -0
  52. package/dist/esm/navigation/NavigationEditOutcome.js +2 -0
  53. package/dist/esm/navigation/NavigationEditOutcome.js.map +1 -0
  54. package/dist/esm/navigation/NavigationEditPlan.d.ts +6 -0
  55. package/dist/esm/navigation/NavigationEditPlan.d.ts.map +1 -0
  56. package/dist/esm/navigation/NavigationEditPlan.js +2 -0
  57. package/dist/esm/navigation/NavigationEditPlan.js.map +1 -0
  58. package/dist/esm/navigation/NavigationGraph.d.ts +4 -1
  59. package/dist/esm/navigation/NavigationGraph.d.ts.map +1 -1
  60. package/dist/esm/navigation/NavigationItemOrigin.d.ts +5 -0
  61. package/dist/esm/navigation/NavigationItemOrigin.d.ts.map +1 -0
  62. package/dist/esm/navigation/NavigationItemOrigin.js +12 -0
  63. package/dist/esm/navigation/NavigationItemOrigin.js.map +1 -0
  64. package/dist/esm/navigation/NavigationSources.d.ts +6 -0
  65. package/dist/esm/navigation/NavigationSources.d.ts.map +1 -0
  66. package/dist/esm/navigation/NavigationSources.js +2 -0
  67. package/dist/esm/navigation/NavigationSources.js.map +1 -0
  68. package/dist/esm/navigation/NavigationSourcesKind.d.ts +7 -0
  69. package/dist/esm/navigation/NavigationSourcesKind.d.ts.map +1 -0
  70. package/dist/esm/navigation/NavigationSourcesKind.js +14 -0
  71. package/dist/esm/navigation/NavigationSourcesKind.js.map +1 -0
  72. package/dist/esm/navigation/ResolvedNavigationTarget.d.ts +6 -0
  73. package/dist/esm/navigation/ResolvedNavigationTarget.d.ts.map +1 -0
  74. package/dist/esm/navigation/ResolvedNavigationTarget.js +2 -0
  75. package/dist/esm/navigation/ResolvedNavigationTarget.js.map +1 -0
  76. package/dist/esm/navigation/diagnoseNavigation.d.ts.map +1 -1
  77. package/dist/esm/navigation/diagnoseNavigation.js +8 -1
  78. package/dist/esm/navigation/diagnoseNavigation.js.map +1 -1
  79. package/dist/esm/navigation/diagnoseNavigationRegions.d.ts +8 -0
  80. package/dist/esm/navigation/diagnoseNavigationRegions.d.ts.map +1 -0
  81. package/dist/esm/navigation/diagnoseNavigationRegions.js +97 -0
  82. package/dist/esm/navigation/diagnoseNavigationRegions.js.map +1 -0
  83. package/dist/esm/navigation/effectiveNavigation.d.ts +5 -0
  84. package/dist/esm/navigation/effectiveNavigation.d.ts.map +1 -0
  85. package/dist/esm/navigation/effectiveNavigation.js +131 -0
  86. package/dist/esm/navigation/effectiveNavigation.js.map +1 -0
  87. package/dist/esm/navigation/index.d.ts +14 -0
  88. package/dist/esm/navigation/index.d.ts.map +1 -1
  89. package/dist/esm/navigation/index.js +14 -0
  90. package/dist/esm/navigation/index.js.map +1 -1
  91. package/dist/esm/navigation/navigationEdits.d.ts +11 -0
  92. package/dist/esm/navigation/navigationEdits.d.ts.map +1 -0
  93. package/dist/esm/navigation/navigationEdits.js +225 -0
  94. package/dist/esm/navigation/navigationEdits.js.map +1 -0
  95. package/dist/esm/navigation/navigationRegions.d.ts +9 -0
  96. package/dist/esm/navigation/navigationRegions.d.ts.map +1 -0
  97. package/dist/esm/navigation/navigationRegions.js +34 -0
  98. package/dist/esm/navigation/navigationRegions.js.map +1 -0
  99. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  100. package/dist/esm/validateNavigationDestinations.js +2 -0
  101. package/dist/esm/validateNavigationDestinations.js.map +1 -1
  102. package/editing/resolveEffectiveConfiguration.ts +13 -0
  103. package/navigation/EffectiveNavItem.ts +37 -0
  104. package/navigation/EffectiveNavigationRegion.ts +26 -0
  105. package/navigation/NavigationDiagnosticCode.ts +15 -0
  106. package/navigation/NavigationEdit.ts +36 -0
  107. package/navigation/NavigationEditKind.ts +19 -0
  108. package/navigation/NavigationEditOutcome.ts +18 -0
  109. package/navigation/NavigationEditPlan.ts +15 -0
  110. package/navigation/NavigationGraph.ts +10 -1
  111. package/navigation/NavigationItemOrigin.ts +13 -0
  112. package/navigation/NavigationSources.ts +15 -0
  113. package/navigation/NavigationSourcesKind.ts +19 -0
  114. package/navigation/ResolvedNavigationTarget.ts +19 -0
  115. package/navigation/diagnoseNavigation.ts +7 -1
  116. package/navigation/diagnoseNavigationRegions.ts +122 -0
  117. package/navigation/effectiveNavigation.ts +105 -0
  118. package/navigation/index.ts +14 -0
  119. package/navigation/navigationEdits.ts +176 -0
  120. package/navigation/navigationRegions.ts +39 -0
  121. package/package.json +3 -3
@@ -1,4 +1,6 @@
1
1
  import { diagnoseNavigation } from './navigation/diagnoseNavigation.js';
2
+ import './navigation/navigationRegions.js';
3
+ import '@cratis/scene.model';
2
4
 
3
5
  // Copyright (c) Cratis. All rights reserved.
4
6
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
@@ -1 +1 @@
1
- {"version":3,"file":"validateNavigationDestinations.js","sources":["../../validateNavigationDestinations.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { DestinationReference, DialogTemplate, Layout, Screen, ScreenTemplate } from '@cratis/scene.model';\nimport { NavigationDiagnostic, diagnoseNavigation } from './navigation';\n\n/**\n * A navigation problem as {@link validateNavigationDestinations} reports it.\n */\nexport type NavigationDestinationDiagnostic = NavigationDiagnostic;\n\n/**\n * Validates destinations against the outlet/route declarations owned by layouts and templates, and - when\n * the screens and dialog templates are given - against the targets that actually exist.\n *\n * A shorthand for {@link diagnoseNavigation} over destinations without entry identities.\n */\nexport function validateNavigationDestinations(\n destinations: DestinationReference[],\n surfaces: { layouts?: Layout[]; screenTemplates?: ScreenTemplate[]; screens?: Screen[]; dialogTemplates?: DialogTemplate[] } = {},\n): NavigationDestinationDiagnostic[] {\n return diagnoseNavigation({ entries: destinations.map(destination => ({ destination })), ...surfaces });\n}\n"],"names":["validateNavigationDestinations","destinations","surfaces","diagnoseNavigation","entries","map","destination"],"mappings":";;AAAA;AACA;AAUA;;;;;AAKC,IACM,SAASA,8BAAAA,CACZC,YAAoC,EACpCC,QAAAA,GAA+H,EAAE,EAAA;AAEjI,IAAA,OAAOC,kBAAAA,CAAmB;AAAEC,QAAAA,OAAAA,EAASH,YAAAA,CAAaI,GAAG,CAACC,CAAAA,eAAgB;AAAEA,gBAAAA;aAAY,CAAA,CAAA;AAAK,QAAA,GAAGJ;AAAS,KAAA,CAAA;AACzG;;"}
1
+ {"version":3,"file":"validateNavigationDestinations.js","sources":["../../validateNavigationDestinations.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { DestinationReference, DialogTemplate, Layout, Screen, ScreenTemplate } from '@cratis/scene.model';\nimport { NavigationDiagnostic, diagnoseNavigation } from './navigation';\n\n/**\n * A navigation problem as {@link validateNavigationDestinations} reports it.\n */\nexport type NavigationDestinationDiagnostic = NavigationDiagnostic;\n\n/**\n * Validates destinations against the outlet/route declarations owned by layouts and templates, and - when\n * the screens and dialog templates are given - against the targets that actually exist.\n *\n * A shorthand for {@link diagnoseNavigation} over destinations without entry identities.\n */\nexport function validateNavigationDestinations(\n destinations: DestinationReference[],\n surfaces: { layouts?: Layout[]; screenTemplates?: ScreenTemplate[]; screens?: Screen[]; dialogTemplates?: DialogTemplate[] } = {},\n): NavigationDestinationDiagnostic[] {\n return diagnoseNavigation({ entries: destinations.map(destination => ({ destination })), ...surfaces });\n}\n"],"names":["validateNavigationDestinations","destinations","surfaces","diagnoseNavigation","entries","map","destination"],"mappings":";;;;AAAA;AACA;AAUA;;;;;AAKC,IACM,SAASA,8BAAAA,CACZC,YAAoC,EACpCC,QAAAA,GAA+H,EAAE,EAAA;AAEjI,IAAA,OAAOC,kBAAAA,CAAmB;AAAEC,QAAAA,OAAAA,EAASH,YAAAA,CAAaI,GAAG,CAACC,CAAAA,eAAgB;AAAEA,gBAAAA;aAAY,CAAA,CAAA;AAAK,QAAA,GAAGJ;AAAS,KAAA,CAAA;AACzG;;"}
@@ -51,6 +51,19 @@ function collectionValue(grant: ExposureGrant, matching: InstanceContribution[],
51
51
  continue;
52
52
  }
53
53
 
54
+ if (contribution.replaceOwnerItems === true) {
55
+ if (!grant.operations.includes(CollectionOperation.Replace)) {
56
+ diagnostics.push(errorDiagnostic(
57
+ DiagnosticCode.ContributionOperationNotPermitted,
58
+ `'${instance}' replaces the items it inherits in '${grant.path}', but replacing items is not exposed. It is ignored and kept.`,
59
+ context));
60
+ continue;
61
+ }
62
+
63
+ items.splice(0, items.length);
64
+ contributed = true;
65
+ }
66
+
54
67
  if (contribution.items.length > 0 && !grant.operations.includes(CollectionOperation.Add)) {
55
68
  diagnostics.push(errorDiagnostic(
56
69
  DiagnosticCode.ContributionOperationNotPermitted,
@@ -0,0 +1,37 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { NavigationItemOrigin } from './NavigationItemOrigin';
5
+ import { ResolvedNavigationTarget } from './ResolvedNavigationTarget';
6
+
7
+ /**
8
+ * One item a region effectively shows, with its provenance.
9
+ */
10
+ export interface EffectiveNavItem {
11
+ /** The item identity. */
12
+ id: string;
13
+
14
+ /** The text shown for the item. */
15
+ title: string;
16
+
17
+ /** Qualified icon reference, when the item has one. */
18
+ icon?: string;
19
+
20
+ /** The slot the target renders into; `content` when the item names none. */
21
+ slot: string;
22
+
23
+ /** What the item navigates to, with a module resolved to its first feature; absent when the item has no target. */
24
+ target?: ResolvedNavigationTarget;
25
+
26
+ /** Which level defined the item. */
27
+ origin: NavigationItemOrigin;
28
+
29
+ /** Whether the item may not be edited here: it is inherited from the application into a module. */
30
+ readOnly: boolean;
31
+
32
+ /** A sentence saying why the item is shown and whether it can be edited here. */
33
+ reason: string;
34
+
35
+ /** The sub-items; only sidebar items have them. */
36
+ children?: EffectiveNavItem[];
37
+ }
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { NavigationRegionKind, NavigationRegionMode } from '@cratis/scene.model';
5
+ import { EffectiveNavItem } from './EffectiveNavItem';
6
+ import { NavigationSources } from './NavigationSources';
7
+
8
+ /**
9
+ * The items a region effectively shows for the application or for one module, with where they came from.
10
+ */
11
+ export interface EffectiveNavigationRegion {
12
+ /** Which region this is. */
13
+ region: NavigationRegionKind;
14
+
15
+ /** The module the region is resolved for; absent for the application itself. */
16
+ module?: string;
17
+
18
+ /** The effective mode: absent at the application level, which is the base. */
19
+ mode?: NavigationRegionMode;
20
+
21
+ /** Where the items come from, as the region tag shows it. */
22
+ sources: NavigationSources;
23
+
24
+ /** The items in the order they are shown. */
25
+ items: EffectiveNavItem[];
26
+ }
@@ -23,4 +23,19 @@ export enum NavigationDiagnosticCode {
23
23
 
24
24
  /** A destination opens a screen or dialog that does not exist, or names nothing to open. */
25
25
  UnavailableTarget = 'unavailableTarget',
26
+
27
+ /** A navigation item has no target, or targets a module or feature the application does not have. */
28
+ NavTargetMissing = 'navTargetMissing',
29
+
30
+ /** A navigation item renders into a slot the layout does not declare. */
31
+ NavSlotUnknown = 'navSlotUnknown',
32
+
33
+ /** Two navigation items of one region share an id. */
34
+ NavDuplicateId = 'navDuplicateId',
35
+
36
+ /** A navigation item targets a module that has no feature to go to. */
37
+ NavModuleWithoutFeatures = 'navModuleWithoutFeatures',
38
+
39
+ /** A module's own navigation item targets a different module. */
40
+ NavTargetOutOfScope = 'navTargetOutOfScope',
26
41
  }
@@ -0,0 +1,36 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { ApplicationLogo, NavigationRegionKind, NavigationRegionMode, NavItem } from '@cratis/scene.model';
5
+ import { NavigationEditKind } from './NavigationEditKind';
6
+
7
+ /**
8
+ * One change to a {@link NavigationModel}. An edit helper returns the edits that carry out a user's gesture - for
9
+ * example adding an item to an inheriting region is a switch to `extend` and then the add - so every host that offers
10
+ * the gesture makes the same change.
11
+ */
12
+ export interface NavigationEdit {
13
+ /** What the edit does. */
14
+ kind: NavigationEditKind;
15
+
16
+ /** The module the edit is for; absent for the application level. */
17
+ module?: string;
18
+
19
+ /** The region the edit changes. */
20
+ region?: NavigationRegionKind;
21
+
22
+ /** For `setRegionMode`: the new mode. */
23
+ mode?: NavigationRegionMode;
24
+
25
+ /** For `setRegionItems`: the items the level's region holds afterwards. */
26
+ items?: NavItem[];
27
+
28
+ /** For `addItem`: the id of the item the new item becomes a sub-item of; absent for a top-level item. */
29
+ parent?: string;
30
+
31
+ /** For `addItem`: the item to add, after the items already there. */
32
+ item?: NavItem;
33
+
34
+ /** For `setLogo`: the application logo. */
35
+ logo?: ApplicationLogo;
36
+ }
@@ -0,0 +1,19 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * What a navigation edit does. The values are shared with the C# engine.
6
+ */
7
+ export enum NavigationEditKind {
8
+ /** Sets how a module's region relates to the application's. */
9
+ SetRegionMode = 'setRegionMode',
10
+
11
+ /** Replaces the items a level holds in a region. */
12
+ SetRegionItems = 'setRegionItems',
13
+
14
+ /** Adds an item, or a sub-item, after the ones already there. */
15
+ AddItem = 'addItem',
16
+
17
+ /** Sets the application logo. */
18
+ SetLogo = 'setLogo',
19
+ }
@@ -0,0 +1,18 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { NavigationModel } from '@cratis/scene.model';
5
+
6
+ /**
7
+ * The result of applying navigation edits. Either every edit applies, or none does and `model` is the model given.
8
+ */
9
+ export interface NavigationEditOutcome {
10
+ /** Whether every edit was applied. */
11
+ applied: boolean;
12
+
13
+ /** The model after the edits; the model given when they were not applied. */
14
+ model: NavigationModel;
15
+
16
+ /** Why the edits were refused; absent when they were applied. */
17
+ rejected?: string;
18
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { NavigationEdit } from './NavigationEdit';
5
+
6
+ /**
7
+ * The edits an edit helper decided on, or why it refused.
8
+ */
9
+ export interface NavigationEditPlan {
10
+ /** The edits to apply, in order; empty when the helper refused or nothing needs to change. */
11
+ edits: NavigationEdit[];
12
+
13
+ /** Why the gesture is refused; absent when it is allowed. */
14
+ rejected?: string;
15
+ }
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { DialogTemplate, Layout, Screen, ScreenTemplate } from '@cratis/scene.model';
4
+ import { ApplicationStructure, DialogTemplate, Layout, NavigationModel, Screen, ScreenTemplate } from '@cratis/scene.model';
5
5
  import { NavigationEntry } from './NavigationEntry';
6
6
 
7
7
  /**
@@ -23,4 +23,13 @@ export interface NavigationGraph {
23
23
 
24
24
  /** The dialog templates dialog destinations may open. */
25
25
  dialogTemplates?: DialogTemplate[];
26
+
27
+ /** The modules, features and slices of the application; with `navigation`, the navigation regions are checked against them. */
28
+ structure?: ApplicationStructure;
29
+
30
+ /** The application and module navigation regions to check against `structure`. */
31
+ navigation?: NavigationModel;
32
+
33
+ /** The rendering slots navigation items may render into; `content` only when absent. */
34
+ slots?: string[];
26
35
  }
@@ -0,0 +1,13 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * Which level defined a navigation item.
6
+ */
7
+ export enum NavigationItemOrigin {
8
+ /** The application defined the item. */
9
+ Application = 'application',
10
+
11
+ /** A module defined the item. */
12
+ Module = 'module',
13
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { NavigationSourcesKind } from './NavigationSourcesKind';
5
+
6
+ /**
7
+ * Where a region's items come from, as the region tag in design mode shows it.
8
+ */
9
+ export interface NavigationSources {
10
+ /** How the effective items relate to the application's. */
11
+ kind: NavigationSourcesKind;
12
+
13
+ /** The compact tag text: `defined here`, `inherited`, `Application + Sales` or `Sales only`. */
14
+ label: string;
15
+ }
@@ -0,0 +1,19 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * How a region's effective items relate to the application's. The values are shared with the C# engine.
6
+ */
7
+ export enum NavigationSourcesKind {
8
+ /** The application level: every region is defined here. */
9
+ DefinedHere = 'definedHere',
10
+
11
+ /** A module region that shows the application's items. */
12
+ Inherited = 'inherited',
13
+
14
+ /** A module region that shows the application's items and its own. */
15
+ Extended = 'extended',
16
+
17
+ /** A module region that shows its own items only. */
18
+ Overridden = 'overridden',
19
+ }
@@ -0,0 +1,19 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * Where a navigation item goes, resolved against the application structure.
6
+ */
7
+ export interface ResolvedNavigationTarget {
8
+ /** The module the item targets. */
9
+ module: string;
10
+
11
+ /** The feature the item names, a dotted path inside the module; absent when it targets the module. */
12
+ feature?: string;
13
+
14
+ /**
15
+ * The qualified id of the feature the item opens: the named feature, or for a module target the module's first
16
+ * feature. Absent when the module or feature does not exist, or the module has no features.
17
+ */
18
+ resolvedFeature?: string;
19
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { DestinationKind, DestinationReference, Screen } from '@cratis/scene.model';
5
5
  import { collectOutlets } from './collectOutlets';
6
+ import { diagnoseNavigationRegions } from './diagnoseNavigationRegions';
6
7
  import { findHostingCycles } from './findHostingCycles';
7
8
  import { NavigationDiagnostic } from './NavigationDiagnostic';
8
9
  import { NavigationDiagnosticCode } from './NavigationDiagnosticCode';
@@ -16,7 +17,8 @@ import { OutletOwner } from './OutletOwner';
16
17
  *
17
18
  * Routes are compared by `routeKey` - by default the authored route override, since the engine constructs no
18
19
  * URLs. Nothing is rewritten. The C# engine (`NavigationDiagnostics.Diagnose`) reports the same codes and
19
- * messages in the same order for the same model; both are asserted against `navigation-diagnostic-fixtures.json`.
20
+ * messages in the same order for the same model; both are asserted against `navigation-diagnostic-fixtures.json`. When the graph carries
21
+ an application `structure` and `navigation` regions, the regions are checked too ({@link diagnoseNavigationRegions}).
20
22
  */
21
23
  export function diagnoseNavigation(graph: NavigationGraph, routeKey: NavigationRouteKey = authoredRouteKey): NavigationDiagnostic[] {
22
24
  const diagnostics: NavigationDiagnostic[] = [];
@@ -62,6 +64,10 @@ export function diagnoseNavigation(graph: NavigationGraph, routeKey: NavigationR
62
64
  });
63
65
  }
64
66
 
67
+ if (graph.structure && graph.navigation) {
68
+ diagnostics.push(...diagnoseNavigationRegions(graph.structure, graph.navigation, { slots: graph.slots }));
69
+ }
70
+
65
71
  return diagnostics;
66
72
  }
67
73
 
@@ -0,0 +1,122 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { ApplicationStructure, NavigationModel, NavigationRegion, NavigationRegionKind, NavigationRegionMode, NavItem } from '@cratis/scene.model';
5
+ import { ApplicationStructureIndex } from '../structure';
6
+ import { NavigationDiagnostic } from './NavigationDiagnostic';
7
+ import { NavigationDiagnosticCode } from './NavigationDiagnosticCode';
8
+ import { applicationItems, collectItemIds, findNavigationRegion, navigationRegionOrder, primarySlot } from './navigationRegions';
9
+
10
+ /** What the region diagnostics are checked against. */
11
+ export interface NavigationRegionDiagnosticOptions {
12
+ /** The rendering slots items may render into; `content` only when absent. */
13
+ slots?: string[];
14
+ }
15
+
16
+ /**
17
+ * Diagnoses the navigation regions of an application against its structure: duplicate item ids, targets that are
18
+ * missing, out of scope or lead nowhere, and slots the layout does not declare. The application is checked first,
19
+ * then each module in the order they are declared; within a level the topbar comes before the sidebar and items are
20
+ * visited depth first.
21
+ *
22
+ * The C# engine (`NavigationRegionDiagnostics`) reports the same codes and messages in the same order; both are
23
+ * asserted against `navigation-inheritance-fixtures.json`, `navigation-targets-fixtures.json` and
24
+ * `navigation-diagnostic-fixtures.json`.
25
+ *
26
+ * @param structure The modules, features and slices of the application.
27
+ * @param navigation The application and module regions.
28
+ * @param options The slots items may render into.
29
+ */
30
+ export function diagnoseNavigationRegions(
31
+ structure: ApplicationStructure | ApplicationStructureIndex,
32
+ navigation: NavigationModel,
33
+ options: NavigationRegionDiagnosticOptions = {},
34
+ ): NavigationDiagnostic[] {
35
+ const index = structure instanceof ApplicationStructureIndex ? structure : new ApplicationStructureIndex(structure);
36
+ const slots = options.slots ?? [primarySlot];
37
+ const diagnostics: NavigationDiagnostic[] = [];
38
+
39
+ for (const region of navigationRegionOrder) {
40
+ const defined = findNavigationRegion(navigation.application.regions, region);
41
+ if (defined) checkRegion(index, slots, diagnostics, undefined, defined, []);
42
+ }
43
+
44
+ for (const moduleNavigation of navigation.modules) {
45
+ for (const region of navigationRegionOrder) {
46
+ const defined = findNavigationRegion(moduleNavigation.regions, region);
47
+ if (!defined) continue;
48
+
49
+ const inherited = defined.mode === NavigationRegionMode.Extend ? collectItemIds(applicationItems(navigation.application, region), region) : [];
50
+ checkRegion(index, slots, diagnostics, moduleNavigation.module, defined, inherited);
51
+ }
52
+ }
53
+
54
+ return diagnostics;
55
+ }
56
+
57
+ function checkRegion(
58
+ index: ApplicationStructureIndex,
59
+ slots: string[],
60
+ diagnostics: NavigationDiagnostic[],
61
+ owner: string | undefined,
62
+ defined: NavigationRegion,
63
+ applicationIds: string[],
64
+ ): void {
65
+ const scope = owner === undefined ? 'the application' : `module '${owner}'`;
66
+ const seen = new Set<string>();
67
+
68
+ const visit = (items: NavItem[]): void => {
69
+ for (const item of items) {
70
+ const entry = `${owner ?? 'application'}:${defined.region}:${item.id}`;
71
+ const report = (code: NavigationDiagnosticCode, message: string): void => { diagnostics.push({ code, message, entry }); };
72
+
73
+ if (seen.has(item.id)) {
74
+ report(NavigationDiagnosticCode.NavDuplicateId, `Navigation item '${item.id}' is used more than once in ${scope} ${defined.region}.`);
75
+ } else if (applicationIds.includes(item.id)) {
76
+ report(NavigationDiagnosticCode.NavDuplicateId, `Navigation item '${item.id}' in ${scope} ${defined.region} is also used by an application item, and the region extends the application.`);
77
+ }
78
+ seen.add(item.id);
79
+
80
+ checkTarget(index, owner, item, report);
81
+
82
+ const slot = item.slot ?? primarySlot;
83
+ if (!slots.includes(slot)) {
84
+ report(NavigationDiagnosticCode.NavSlotUnknown, `Navigation item '${item.id}' renders into slot '${slot}', which the layout does not declare.`);
85
+ }
86
+
87
+ if (defined.region === NavigationRegionKind.Sidebar && item.children) visit(item.children);
88
+ }
89
+ };
90
+
91
+ visit(defined.items);
92
+ }
93
+
94
+ function checkTarget(index: ApplicationStructureIndex, owner: string | undefined, item: NavItem, report: (code: NavigationDiagnosticCode, message: string) => void): void {
95
+ const module = item.target?.module;
96
+ const hasChildren = (item.children?.length ?? 0) > 0;
97
+
98
+ if (!module) {
99
+ // A sidebar item that only groups sub-items needs no target of its own.
100
+ if (!hasChildren) report(NavigationDiagnosticCode.NavTargetMissing, `Navigation item '${item.id}' has no target.`);
101
+ return;
102
+ }
103
+
104
+ if (!index.findModule(module)) {
105
+ report(NavigationDiagnosticCode.NavTargetMissing, `Navigation item '${item.id}' targets module '${module}', which does not exist.`);
106
+ return;
107
+ }
108
+
109
+ if (owner !== undefined && module !== owner) {
110
+ report(NavigationDiagnosticCode.NavTargetOutOfScope, `Navigation item '${item.id}' of module '${owner}' targets module '${module}'; a module's own items may only target that module.`);
111
+ return;
112
+ }
113
+
114
+ const feature = item.target?.feature;
115
+ if (feature) {
116
+ if (!index.findFeature(`${module}.${feature}`)) {
117
+ report(NavigationDiagnosticCode.NavTargetMissing, `Navigation item '${item.id}' targets feature '${module}.${feature}', which does not exist.`);
118
+ }
119
+ } else if (!index.firstFeature(module)) {
120
+ report(NavigationDiagnosticCode.NavModuleWithoutFeatures, `Navigation item '${item.id}' targets module '${module}', which has no features to go to.`);
121
+ }
122
+ }
@@ -0,0 +1,105 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { ApplicationStructure, NavigationModel, NavigationRegionKind, NavigationRegionMode, NavItem } from '@cratis/scene.model';
5
+ import { ApplicationStructureIndex } from '../structure';
6
+ import { EffectiveNavigationRegion } from './EffectiveNavigationRegion';
7
+ import { EffectiveNavItem } from './EffectiveNavItem';
8
+ import { NavigationItemOrigin } from './NavigationItemOrigin';
9
+ import { NavigationSources } from './NavigationSources';
10
+ import { NavigationSourcesKind } from './NavigationSourcesKind';
11
+ import { applicationItems, findModuleNavigation, findNavigationRegion, modeOfModuleRegion, primarySlot } from './navigationRegions';
12
+ import { ResolvedNavigationTarget } from './ResolvedNavigationTarget';
13
+
14
+ /**
15
+ * Resolves the items a navigation region effectively shows, for the application or for one module.
16
+ *
17
+ * - Application (no `module`): the application items, editable, `defined here`.
18
+ * - `inherit` (the default at a module): the application items, read-only; the module's own items are cleared.
19
+ * - `extend`: the application items, read-only, followed by the module's items.
20
+ * - `override`: the module's items only.
21
+ *
22
+ * Each item carries its `origin`, whether it is `readOnly`, a `reason`, and its target resolved against the
23
+ * structure: a module target goes to the module's first feature. Sub-items belong to the sidebar; a topbar is flat
24
+ * and its sub-items are ignored. This is the one function React renderers, Stage, Studio design mode and Studio
25
+ * preview call; nothing re-implements it.
26
+ *
27
+ * The C# engine (`EffectiveNavigation.Resolve`) returns the same result; both are asserted against
28
+ * `navigation-inheritance-fixtures.json` and `navigation-targets-fixtures.json`.
29
+ *
30
+ * @param structure The modules, features and slices of the application.
31
+ * @param navigation The application and module regions.
32
+ * @param module The module to resolve for; undefined for the application.
33
+ * @param region Which region to resolve.
34
+ */
35
+ export function effectiveNavigation(
36
+ structure: ApplicationStructure | ApplicationStructureIndex,
37
+ navigation: NavigationModel,
38
+ module: string | undefined,
39
+ region: NavigationRegionKind,
40
+ ): EffectiveNavigationRegion {
41
+ const index = structure instanceof ApplicationStructureIndex ? structure : new ApplicationStructureIndex(structure);
42
+ const applicationOwned = applicationItems(navigation.application, region);
43
+
44
+ if (module === undefined) {
45
+ return {
46
+ region,
47
+ sources: { kind: NavigationSourcesKind.DefinedHere, label: 'defined here' },
48
+ items: applicationOwned.map(item => resolveItem(index, region, item, NavigationItemOrigin.Application, false, 'Defined here.')),
49
+ };
50
+ }
51
+
52
+ const mode = modeOfModuleRegion(navigation, module, region);
53
+ const moduleOwned = findNavigationRegion(findModuleNavigation(navigation, module)?.regions, region)?.items ?? [];
54
+ const name = index.findModule(module)?.name ?? module;
55
+ const inherited = (): EffectiveNavItem[] => applicationOwned.map(item => resolveItem(index, region, item, NavigationItemOrigin.Application, true, 'Inherited from the application; edit it there.'));
56
+ const own = (): EffectiveNavItem[] => moduleOwned.map(item => resolveItem(index, region, item, NavigationItemOrigin.Module, false, `Defined in ${name}.`));
57
+
58
+ switch (mode) {
59
+ case NavigationRegionMode.Extend:
60
+ return { region, module, mode, sources: sourcesOf(NavigationSourcesKind.Extended, name), items: [...inherited(), ...own()] };
61
+ case NavigationRegionMode.Override:
62
+ return { region, module, mode, sources: sourcesOf(NavigationSourcesKind.Overridden, name), items: own() };
63
+ default:
64
+ return { region, module, mode: NavigationRegionMode.Inherit, sources: sourcesOf(NavigationSourcesKind.Inherited, name), items: inherited() };
65
+ }
66
+ }
67
+
68
+ function sourcesOf(kind: NavigationSourcesKind, moduleName: string): NavigationSources {
69
+ switch (kind) {
70
+ case NavigationSourcesKind.Extended: return { kind, label: `Application + ${moduleName}` };
71
+ case NavigationSourcesKind.Overridden: return { kind, label: `${moduleName} only` };
72
+ default: return { kind: NavigationSourcesKind.Inherited, label: 'inherited' };
73
+ }
74
+ }
75
+
76
+ function resolveItem(index: ApplicationStructureIndex, region: NavigationRegionKind, item: NavItem, origin: NavigationItemOrigin, readOnly: boolean, reason: string): EffectiveNavItem {
77
+ const resolved: EffectiveNavItem = { id: item.id, title: item.title, slot: item.slot ?? primarySlot, origin, readOnly, reason };
78
+ if (item.icon !== undefined) resolved.icon = item.icon;
79
+
80
+ const target = resolveTarget(index, item);
81
+ if (target) resolved.target = target;
82
+
83
+ if (region === NavigationRegionKind.Sidebar && item.children && item.children.length > 0) {
84
+ resolved.children = item.children.map(child => resolveItem(index, region, child, origin, readOnly, reason));
85
+ }
86
+
87
+ return resolved;
88
+ }
89
+
90
+ function resolveTarget(index: ApplicationStructureIndex, item: NavItem): ResolvedNavigationTarget | undefined {
91
+ const module = item.target?.module;
92
+ if (!module) return undefined;
93
+
94
+ const target: ResolvedNavigationTarget = { module };
95
+ const feature = item.target?.feature;
96
+ if (feature) {
97
+ target.feature = feature;
98
+ if (index.findFeature(`${module}.${feature}`)) target.resolvedFeature = `${module}.${feature}`;
99
+ } else {
100
+ const first = index.firstFeatureId(module);
101
+ if (first !== undefined) target.resolvedFeature = first;
102
+ }
103
+
104
+ return target;
105
+ }
@@ -8,3 +8,17 @@ export * from './NavigationGraph';
8
8
  export * from './diagnoseNavigation';
9
9
  export * from './navigationEntriesFrom';
10
10
  export * from './navigationRoute';
11
+ export * from './navigationRegions';
12
+ export * from './NavigationItemOrigin';
13
+ export * from './NavigationSourcesKind';
14
+ export * from './NavigationSources';
15
+ export * from './ResolvedNavigationTarget';
16
+ export * from './EffectiveNavItem';
17
+ export * from './EffectiveNavigationRegion';
18
+ export * from './effectiveNavigation';
19
+ export * from './diagnoseNavigationRegions';
20
+ export * from './NavigationEditKind';
21
+ export * from './NavigationEdit';
22
+ export * from './NavigationEditPlan';
23
+ export * from './NavigationEditOutcome';
24
+ export * from './navigationEdits';