@cratis/scene.engine 4.23.0 → 4.25.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/descriptors/componentBadge.ts +41 -0
- package/descriptors/index.ts +7 -0
- package/descriptors/matchComponentQuery.ts +55 -0
- package/descriptors/rankFrequentlyUsedComponents.ts +29 -0
- package/descriptors/validateComponentDescriptors.ts +122 -0
- package/dist/cjs/descriptors/componentBadge.js +35 -0
- package/dist/cjs/descriptors/componentBadge.js.map +1 -0
- package/dist/cjs/descriptors/matchComponentQuery.js +56 -0
- package/dist/cjs/descriptors/matchComponentQuery.js.map +1 -0
- package/dist/cjs/descriptors/rankFrequentlyUsedComponents.js +18 -0
- package/dist/cjs/descriptors/rankFrequentlyUsedComponents.js.map +1 -0
- package/dist/cjs/descriptors/validateComponentDescriptors.js +116 -0
- package/dist/cjs/descriptors/validateComponentDescriptors.js.map +1 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/templateApplicability.js.map +1 -1
- package/dist/cjs/templates/TemplateReferenceResolution.js +15 -0
- package/dist/cjs/templates/TemplateReferenceResolution.js.map +1 -0
- package/dist/cjs/templates/applicableTemplates.js +199 -0
- package/dist/cjs/templates/applicableTemplates.js.map +1 -0
- package/dist/cjs/templates/describeTemplateCatalog.js +11 -6
- package/dist/cjs/templates/describeTemplateCatalog.js.map +1 -1
- package/dist/cjs/templates/resolveTemplateReference.js +89 -0
- package/dist/cjs/templates/resolveTemplateReference.js.map +1 -0
- package/dist/cjs/templates/validateTemplateMetadata.js +9 -0
- package/dist/cjs/templates/validateTemplateMetadata.js.map +1 -1
- package/dist/esm/descriptors/componentBadge.d.ts +3 -0
- package/dist/esm/descriptors/componentBadge.d.ts.map +1 -0
- package/dist/esm/descriptors/componentBadge.js +33 -0
- package/dist/esm/descriptors/componentBadge.js.map +1 -0
- package/dist/esm/descriptors/index.d.ts +5 -0
- package/dist/esm/descriptors/index.d.ts.map +1 -0
- package/dist/esm/descriptors/index.js +5 -0
- package/dist/esm/descriptors/index.js.map +1 -0
- package/dist/esm/descriptors/matchComponentQuery.d.ts +9 -0
- package/dist/esm/descriptors/matchComponentQuery.d.ts.map +1 -0
- package/dist/esm/descriptors/matchComponentQuery.js +54 -0
- package/dist/esm/descriptors/matchComponentQuery.js.map +1 -0
- package/dist/esm/descriptors/rankFrequentlyUsedComponents.d.ts +7 -0
- package/dist/esm/descriptors/rankFrequentlyUsedComponents.d.ts.map +1 -0
- package/dist/esm/descriptors/rankFrequentlyUsedComponents.js +15 -0
- package/dist/esm/descriptors/rankFrequentlyUsedComponents.js.map +1 -0
- package/dist/esm/descriptors/validateComponentDescriptors.d.ts +3 -0
- package/dist/esm/descriptors/validateComponentDescriptors.d.ts.map +1 -0
- package/dist/esm/descriptors/validateComponentDescriptors.js +114 -0
- package/dist/esm/descriptors/validateComponentDescriptors.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/templateApplicability.d.ts +1 -2
- package/dist/esm/templateApplicability.d.ts.map +1 -1
- package/dist/esm/templateApplicability.js.map +1 -1
- package/dist/esm/templates/ApplicableTemplateSet.d.ts +27 -0
- package/dist/esm/templates/ApplicableTemplateSet.d.ts.map +1 -0
- package/dist/esm/templates/ApplicableTemplateSet.js +2 -0
- package/dist/esm/templates/ApplicableTemplateSet.js.map +1 -0
- package/dist/esm/templates/TemplateCatalogEntry.d.ts +3 -1
- package/dist/esm/templates/TemplateCatalogEntry.d.ts.map +1 -1
- package/dist/esm/templates/TemplateLibrary.d.ts +6 -0
- package/dist/esm/templates/TemplateLibrary.d.ts.map +1 -0
- package/dist/esm/templates/TemplateLibrary.js +2 -0
- package/dist/esm/templates/TemplateLibrary.js.map +1 -0
- package/dist/esm/templates/TemplateReferenceResolution.d.ts +16 -0
- package/dist/esm/templates/TemplateReferenceResolution.d.ts.map +1 -0
- package/dist/esm/templates/TemplateReferenceResolution.js +13 -0
- package/dist/esm/templates/TemplateReferenceResolution.js.map +1 -0
- package/dist/esm/templates/TemplateTarget.d.ts +9 -0
- package/dist/esm/templates/TemplateTarget.d.ts.map +1 -0
- package/dist/esm/templates/TemplateTarget.js +2 -0
- package/dist/esm/templates/TemplateTarget.js.map +1 -0
- package/dist/esm/templates/applicableTemplates.d.ts +9 -0
- package/dist/esm/templates/applicableTemplates.d.ts.map +1 -0
- package/dist/esm/templates/applicableTemplates.js +194 -0
- package/dist/esm/templates/applicableTemplates.js.map +1 -0
- package/dist/esm/templates/describeTemplateCatalog.d.ts.map +1 -1
- package/dist/esm/templates/describeTemplateCatalog.js +12 -7
- package/dist/esm/templates/describeTemplateCatalog.js.map +1 -1
- package/dist/esm/templates/index.d.ts +6 -0
- package/dist/esm/templates/index.d.ts.map +1 -1
- package/dist/esm/templates/index.js +6 -0
- package/dist/esm/templates/index.js.map +1 -1
- package/dist/esm/templates/resolveTemplateReference.d.ts +5 -0
- package/dist/esm/templates/resolveTemplateReference.d.ts.map +1 -0
- package/dist/esm/templates/resolveTemplateReference.js +87 -0
- package/dist/esm/templates/resolveTemplateReference.js.map +1 -0
- package/dist/esm/templates/validateTemplateMetadata.d.ts +2 -0
- package/dist/esm/templates/validateTemplateMetadata.d.ts.map +1 -1
- package/dist/esm/templates/validateTemplateMetadata.js +8 -1
- package/dist/esm/templates/validateTemplateMetadata.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/index.ts +1 -0
- package/package.json +3 -3
- package/templateApplicability.ts +2 -1
- package/templates/ApplicableTemplateSet.ts +54 -0
- package/templates/TemplateCatalogEntry.ts +5 -1
- package/templates/TemplateLibrary.ts +16 -0
- package/templates/TemplateReferenceResolution.ts +35 -0
- package/templates/TemplateTarget.ts +23 -0
- package/templates/applicableTemplates.ts +148 -0
- package/templates/describeTemplateCatalog.ts +11 -7
- package/templates/index.ts +6 -0
- package/templates/resolveTemplateReference.ts +79 -0
- package/templates/validateTemplateMetadata.ts +11 -0
package/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from './panelKind';
|
|
|
19
19
|
export * from './flowNodeKind';
|
|
20
20
|
export * from './ComponentResolution';
|
|
21
21
|
export * from './icons';
|
|
22
|
+
export * from './descriptors';
|
|
22
23
|
export * from './resolveComponentName';
|
|
23
24
|
export * from './computeSizeClass';
|
|
24
25
|
export * from './evaluateFlowArrangement';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/scene.engine",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.25.0",
|
|
4
4
|
"description": "The Scene runtime: walks a Scene element tree and drives whichever renderer is plugged in. Does not know React, or any other rendering platform, exists.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cratis",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"up": "yarn g:up"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@cratis/scene.model": "4.
|
|
51
|
+
"@cratis/scene.model": "4.25.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@cratis/scene.model": "4.
|
|
54
|
+
"@cratis/scene.model": "4.25.0"
|
|
55
55
|
}
|
|
56
56
|
}
|
package/templateApplicability.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { DialogTemplate, Layout, ScreenTemplate, TemplateMetadata, TemplateScope } from '@cratis/scene.model';
|
|
5
5
|
|
|
6
6
|
type TemplateKind = 'Layout' | 'ScreenTemplate' | 'DialogTemplate';
|
|
7
|
-
|
|
7
|
+
/** The containing layout or screen template against which a fitsSlot selection is checked. */
|
|
8
|
+
export type TemplateContainer = Pick<Layout, 'name' | 'slots'>;
|
|
8
9
|
|
|
9
10
|
/** Validates application-shell selection without introducing a competing shell model. */
|
|
10
11
|
export function validateLayoutApplicability(layout: Layout | undefined, scope: TemplateScope): string[] {
|
|
@@ -0,0 +1,54 @@
|
|
|
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 { TemplateMetadata, TemplateReference } from '@cratis/scene.model';
|
|
5
|
+
import { TemplateCatalogKind } from './TemplateCatalogKind';
|
|
6
|
+
|
|
7
|
+
/** One template that can be applied at a target, with the reference that selects it. */
|
|
8
|
+
export interface ApplicableTemplate {
|
|
9
|
+
/** The package that provides the template. */
|
|
10
|
+
package: string;
|
|
11
|
+
|
|
12
|
+
/** The version of that package. */
|
|
13
|
+
packageVersion: string;
|
|
14
|
+
|
|
15
|
+
kind: TemplateCatalogKind;
|
|
16
|
+
name: string;
|
|
17
|
+
displayName?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
metadata?: TemplateMetadata;
|
|
20
|
+
|
|
21
|
+
/** The reference that selects it: an own reference, or a blueprint reference pinned to `^<installed version>`. */
|
|
22
|
+
reference: TemplateReference;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The applicable templates sharing one browse category, in browse order. */
|
|
26
|
+
export interface ApplicableTemplateGroup {
|
|
27
|
+
/** The category, or undefined for templates that declare none. Uncategorized templates are listed last. */
|
|
28
|
+
category?: string;
|
|
29
|
+
|
|
30
|
+
templates: ApplicableTemplate[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A blueprint that has at least one template applicable at the target, with those templates by category. */
|
|
34
|
+
export interface ApplicableBlueprint {
|
|
35
|
+
/** The blueprint package name. */
|
|
36
|
+
blueprint: string;
|
|
37
|
+
|
|
38
|
+
/** The installed version of the blueprint. */
|
|
39
|
+
version: string;
|
|
40
|
+
|
|
41
|
+
/** A human-readable name for a picker, falling back to `blueprint`. */
|
|
42
|
+
displayName?: string;
|
|
43
|
+
|
|
44
|
+
categories: ApplicableTemplateGroup[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** What a template picker offers at a target: own templates by category first, then the blueprints that have anything applicable. */
|
|
48
|
+
export interface ApplicableTemplateSet {
|
|
49
|
+
/** The own templates that apply, grouped by category. */
|
|
50
|
+
own: ApplicableTemplateGroup[];
|
|
51
|
+
|
|
52
|
+
/** The blueprints with at least one applicable template. A blueprint with none is omitted, never listed empty. */
|
|
53
|
+
blueprints: ApplicableBlueprint[];
|
|
54
|
+
}
|
|
@@ -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 { TemplateAttribution, TemplateCompatibility, TemplateScope } from '@cratis/scene.model';
|
|
4
|
+
import { TemplateAttribution, TemplateCompatibility, TemplateScope, SliceKind } from '@cratis/scene.model';
|
|
5
5
|
import { TemplateCatalogKind } from './TemplateCatalogKind';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -22,6 +22,7 @@ export interface TemplateCatalogEntry {
|
|
|
22
22
|
type?: string;
|
|
23
23
|
category?: string;
|
|
24
24
|
scopes?: TemplateScope[];
|
|
25
|
+
sliceKinds?: SliceKind[];
|
|
25
26
|
compatibility?: TemplateCompatibility;
|
|
26
27
|
attribution?: TemplateAttribution;
|
|
27
28
|
license?: string;
|
|
@@ -32,4 +33,7 @@ export interface TemplateCatalogEntry {
|
|
|
32
33
|
|
|
33
34
|
/** Why it is not - missing or invalid metadata, or an unmet Scene or package requirement. */
|
|
34
35
|
problems: string[];
|
|
36
|
+
|
|
37
|
+
/** Recommendations that do not make the template unusable, such as an own template without a category. */
|
|
38
|
+
warnings: string[];
|
|
35
39
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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 { TemplateSource } from './TemplateSource';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every template a host can choose from: the ones the user created in the workspace and the ones blueprint
|
|
8
|
+
* packages ship.
|
|
9
|
+
*/
|
|
10
|
+
export interface TemplateLibrary {
|
|
11
|
+
/** The workspace's own templates. Its manifest is never treated as a blueprint. */
|
|
12
|
+
own?: TemplateSource;
|
|
13
|
+
|
|
14
|
+
/** The blueprint packages and the templates they ship, in browse order. */
|
|
15
|
+
blueprints: TemplateSource[];
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 { TemplateCatalogKind } from './TemplateCatalogKind';
|
|
5
|
+
|
|
6
|
+
/** The outcome of resolving a template reference against a template library. */
|
|
7
|
+
export enum TemplateReferenceStatus {
|
|
8
|
+
/** The reference names a template that exists, at a version the reference accepts. */
|
|
9
|
+
Resolved = 'Resolved',
|
|
10
|
+
/** The reference is not well formed: an own reference carrying a blueprint or version, a blueprint reference without a blueprint, or an invalid version range. */
|
|
11
|
+
Malformed = 'Malformed',
|
|
12
|
+
/** No blueprint package of that name is available. */
|
|
13
|
+
UnknownBlueprint = 'UnknownBlueprint',
|
|
14
|
+
/** The installed version of the blueprint is outside the reference's version range: the blueprint has drifted from what was chosen. */
|
|
15
|
+
VersionNotSatisfied = 'VersionNotSatisfied',
|
|
16
|
+
/** The blueprint or workspace has no template of that name. */
|
|
17
|
+
UnknownTemplate = 'UnknownTemplate',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The result of resolving a template reference. */
|
|
21
|
+
export interface TemplateReferenceResolution {
|
|
22
|
+
status: TemplateReferenceStatus;
|
|
23
|
+
|
|
24
|
+
/** The package providing the template, when it was found or its blueprint was. */
|
|
25
|
+
package?: string;
|
|
26
|
+
|
|
27
|
+
/** The installed version of that package. */
|
|
28
|
+
packageVersion?: string;
|
|
29
|
+
|
|
30
|
+
/** Which kind of template was found, when the status is Resolved. */
|
|
31
|
+
kind?: TemplateCatalogKind;
|
|
32
|
+
|
|
33
|
+
/** A diagnostic when the reference did not resolve. */
|
|
34
|
+
message?: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 { TemplateScope, SliceKind } from '@cratis/scene.model';
|
|
5
|
+
import { TemplateContainer } from '../templateApplicability';
|
|
6
|
+
|
|
7
|
+
/** Where a template is about to be applied: a level of the application hierarchy and what it sits inside. */
|
|
8
|
+
export interface TemplateTarget {
|
|
9
|
+
/** The hierarchy level. */
|
|
10
|
+
scope: TemplateScope;
|
|
11
|
+
|
|
12
|
+
/** The kind of the slice when `scope` is {@link TemplateScope.Slice}. */
|
|
13
|
+
sliceKind?: SliceKind;
|
|
14
|
+
|
|
15
|
+
/** The layout or template whose slots the template must fit, if known. */
|
|
16
|
+
container?: TemplateContainer;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The names of the templates already chosen above the target, outermost first. A template in the chain is not
|
|
20
|
+
* offered again, because nesting it inside itself would loop.
|
|
21
|
+
*/
|
|
22
|
+
chain?: string[];
|
|
23
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
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 { DialogTemplate, Layout, ScreenTemplate, TemplateMetadata, TemplateReference, TemplateReferenceSource, TemplateScope, SliceKind } from '@cratis/scene.model';
|
|
5
|
+
import { resolveScreenTemplates } from '../resolveScreenTemplates';
|
|
6
|
+
import { validateDialogTemplateApplicability, validateLayoutApplicability, validateScreenTemplateApplicability } from '../templateApplicability';
|
|
7
|
+
import { ApplicableBlueprint, ApplicableTemplate, ApplicableTemplateGroup, ApplicableTemplateSet } from './ApplicableTemplateSet';
|
|
8
|
+
import { TemplateCatalogKind } from './TemplateCatalogKind';
|
|
9
|
+
import { TemplateLibrary } from './TemplateLibrary';
|
|
10
|
+
import { TemplateSource } from './TemplateSource';
|
|
11
|
+
import { TemplateTarget } from './TemplateTarget';
|
|
12
|
+
|
|
13
|
+
const screenScopes = [TemplateScope.Module, TemplateScope.Feature, TemplateScope.Subfeature];
|
|
14
|
+
const dialogSliceKinds = [SliceKind.StateChange];
|
|
15
|
+
const screenSliceKinds = [SliceKind.StateChange, SliceKind.StateView];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Decides which templates a picker offers at a target, so no host re-implements the rules: own templates by
|
|
19
|
+
* category, then each blueprint that has at least one applicable template. A blueprint with none is omitted.
|
|
20
|
+
*
|
|
21
|
+
* It composes the applicability validators (kind and scope, explicit scopes override, category never implies a
|
|
22
|
+
* scope, slot fit against the container) and `resolveScreenTemplates` (a template must find exactly one home and
|
|
23
|
+
* nothing may nest inside itself) with the defaults of each hierarchy level, used when a template declares no scopes:
|
|
24
|
+
*
|
|
25
|
+
* - Application - layouts.
|
|
26
|
+
* - Module, Feature, Subfeature - screen templates, fitting a slot of the container.
|
|
27
|
+
* - Slice, state change - dialog templates.
|
|
28
|
+
* - Slice, state view - screen templates that declare the Slice scope or state view slice kinds.
|
|
29
|
+
*
|
|
30
|
+
* Declared `scopes` and `sliceKinds` always override the defaults. The C# engine (`ApplicableTemplates.Compute`)
|
|
31
|
+
* returns identical results; both are asserted against `template-applicability-fixtures.json`.
|
|
32
|
+
*/
|
|
33
|
+
export function applicableTemplates(library: TemplateLibrary, target: TemplateTarget): ApplicableTemplateSet {
|
|
34
|
+
const own = library.own ? groupByCategory(applicableIn(library.own, target, false)) : [];
|
|
35
|
+
const blueprints: ApplicableBlueprint[] = [];
|
|
36
|
+
for (const source of library.blueprints) {
|
|
37
|
+
const categories = groupByCategory(applicableIn(source, target, true));
|
|
38
|
+
if (categories.length > 0) {
|
|
39
|
+
blueprints.push({ blueprint: source.manifest.name, version: source.manifest.version, displayName: source.manifest.displayName, categories });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { own, blueprints };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Why a layout does not apply at a target; empty when it does. */
|
|
46
|
+
export function validateLayoutForTarget(layout: Layout, target: TemplateTarget): string[] {
|
|
47
|
+
return withDefaults(validateLayoutApplicability(layout, target.scope), layout.name, TemplateCatalogKind.Layout, layout.metadata, target);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Why a screen template does not apply at a target, including whether its slot nesting resolves; empty when it does. */
|
|
51
|
+
export function validateScreenTemplateForTarget(template: ScreenTemplate, target: TemplateTarget): string[] {
|
|
52
|
+
const problems = withDefaults(
|
|
53
|
+
validateScreenTemplateApplicability(template, target.scope, target.container),
|
|
54
|
+
template.name,
|
|
55
|
+
TemplateCatalogKind.ScreenTemplate,
|
|
56
|
+
template.metadata,
|
|
57
|
+
target,
|
|
58
|
+
);
|
|
59
|
+
if (problems.length > 0) return problems;
|
|
60
|
+
|
|
61
|
+
if (target.chain?.includes(template.name)) {
|
|
62
|
+
return [`Template '${template.name}' is already chosen above this target; nesting it again would make the chain loop`];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (template.fitsSlot !== undefined && target.container) {
|
|
66
|
+
const containerName = target.container.name;
|
|
67
|
+
const resolution = resolveScreenTemplates({ name: containerName, slots: target.container.slots } as Layout, [template]);
|
|
68
|
+
const valid = resolution.unplaced.length === 0 && resolution.cycles.length === 0;
|
|
69
|
+
if (!valid || !resolution.placements.some(placement => placement.template === template.name && placement.container === containerName)) {
|
|
70
|
+
return [`Template '${template.name}' cannot be placed in '${containerName}': its slot nesting does not resolve`];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Why a dialog template does not apply at a target; empty when it does. */
|
|
77
|
+
export function validateDialogTemplateForTarget(template: DialogTemplate, target: TemplateTarget): string[] {
|
|
78
|
+
return withDefaults(validateDialogTemplateApplicability(template, target.scope), template.name, TemplateCatalogKind.DialogTemplate, template.metadata, target);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applicableIn(source: TemplateSource, target: TemplateTarget, isBlueprint: boolean): ApplicableTemplate[] {
|
|
82
|
+
const { manifest } = source;
|
|
83
|
+
const referenceTo = (name: string): TemplateReference => isBlueprint
|
|
84
|
+
? { source: TemplateReferenceSource.Blueprint, id: name, blueprint: manifest.name, version: `^${manifest.version}` }
|
|
85
|
+
: { source: TemplateReferenceSource.Own, id: name };
|
|
86
|
+
|
|
87
|
+
const applicable: ApplicableTemplate[] = [];
|
|
88
|
+
for (const layout of source.layouts ?? []) {
|
|
89
|
+
if (validateLayoutForTarget(layout, target).length > 0) continue;
|
|
90
|
+
applicable.push({
|
|
91
|
+
package: manifest.name, packageVersion: manifest.version, kind: TemplateCatalogKind.Layout, name: layout.name,
|
|
92
|
+
metadata: layout.metadata, reference: referenceTo(layout.name),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
for (const template of source.screenTemplates ?? []) {
|
|
96
|
+
if (validateScreenTemplateForTarget(template, target).length > 0) continue;
|
|
97
|
+
applicable.push({
|
|
98
|
+
package: manifest.name, packageVersion: manifest.version, kind: TemplateCatalogKind.ScreenTemplate, name: template.name,
|
|
99
|
+
displayName: template.displayName, description: template.description, metadata: template.metadata, reference: referenceTo(template.name),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
for (const template of source.dialogTemplates ?? []) {
|
|
103
|
+
if (validateDialogTemplateForTarget(template, target).length > 0) continue;
|
|
104
|
+
applicable.push({
|
|
105
|
+
package: manifest.name, packageVersion: manifest.version, kind: TemplateCatalogKind.DialogTemplate, name: template.name,
|
|
106
|
+
displayName: template.displayName, description: template.description, metadata: template.metadata, reference: referenceTo(template.name),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return applicable;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function groupByCategory(templates: ApplicableTemplate[]): ApplicableTemplateGroup[] {
|
|
113
|
+
const categoryOf = (template: ApplicableTemplate) => template.metadata?.category?.trim() ? template.metadata.category : undefined;
|
|
114
|
+
const categories = [...new Set(templates.map(categoryOf).filter((category): category is string => category !== undefined))];
|
|
115
|
+
const groups: ApplicableTemplateGroup[] = categories.map(category => ({ category, templates: templates.filter(template => categoryOf(template) === category) }));
|
|
116
|
+
const uncategorized = templates.filter(template => categoryOf(template) === undefined);
|
|
117
|
+
if (uncategorized.length > 0) groups.push({ templates: uncategorized });
|
|
118
|
+
return groups;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function withDefaults(problems: string[], name: string, kind: TemplateCatalogKind, metadata: TemplateMetadata | undefined, target: TemplateTarget): string[] {
|
|
122
|
+
if (problems.length > 0 || !Object.values(TemplateScope).includes(target.scope)) return problems;
|
|
123
|
+
|
|
124
|
+
if (metadata?.scopes === undefined && !defaultScopes(kind, metadata).includes(target.scope)) {
|
|
125
|
+
return [`Template '${name}' is not offered at scope '${target.scope}' unless it declares that scope`];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (target.scope === TemplateScope.Slice && target.sliceKind !== undefined) {
|
|
129
|
+
const sliceKind = target.sliceKind;
|
|
130
|
+
if (!Object.values(SliceKind).includes(sliceKind)) return [`Template '${name}' has unknown target slice kind '${sliceKind}'`];
|
|
131
|
+
if (sliceKind === SliceKind.Automation) return [`Template '${name}' cannot be applied to an Automation slice, which has no presentation`];
|
|
132
|
+
const kinds = metadata?.sliceKinds ?? defaultSliceKinds(kind);
|
|
133
|
+
if (!kinds.includes(sliceKind)) return [`Template '${name}' does not apply to slice kind '${sliceKind}'`];
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function defaultScopes(kind: TemplateCatalogKind, metadata: TemplateMetadata | undefined): TemplateScope[] {
|
|
139
|
+
switch (kind) {
|
|
140
|
+
case TemplateCatalogKind.Layout: return [TemplateScope.Application];
|
|
141
|
+
case TemplateCatalogKind.DialogTemplate: return [TemplateScope.Slice];
|
|
142
|
+
default: return metadata?.sliceKinds === undefined ? screenScopes : [TemplateScope.Slice];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function defaultSliceKinds(kind: TemplateCatalogKind): SliceKind[] {
|
|
147
|
+
return kind === TemplateCatalogKind.DialogTemplate ? dialogSliceKinds : screenSliceKinds;
|
|
148
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
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 { ScenePackage, TemplateMetadata } from '@cratis/scene.model';
|
|
4
|
+
import { PackageKind, ScenePackage, TemplateMetadata } from '@cratis/scene.model';
|
|
5
5
|
import { isVersionSatisfiedBy } from '../packageVersionRange';
|
|
6
6
|
import { TemplateCatalogEntry } from './TemplateCatalogEntry';
|
|
7
7
|
import { TemplateCatalogKind } from './TemplateCatalogKind';
|
|
8
8
|
import { TemplateSource } from './TemplateSource';
|
|
9
|
-
import { validateTemplateMetadata } from './validateTemplateMetadata';
|
|
9
|
+
import { validateTemplateCategory, validateTemplateMetadata } from './validateTemplateMetadata';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Describes every template the given packages provide, in package then layout, screen template, dialog
|
|
@@ -14,7 +14,8 @@ import { validateTemplateMetadata } from './validateTemplateMetadata';
|
|
|
14
14
|
*
|
|
15
15
|
* A template is compatible when its metadata is complete and valid, the Scene version (when given) satisfies
|
|
16
16
|
* its Scene range, and every package it requires is among the given packages at a satisfying version and is
|
|
17
|
-
* either its own package or one that package depends on.
|
|
17
|
+
* either its own package or one that package depends on. A blueprint template without a category is a problem; an
|
|
18
|
+
* own template without one is a warning. Nothing is filtered out: a browser shows incompatible
|
|
18
19
|
* templates with their problems rather than hiding why they are unavailable.
|
|
19
20
|
*/
|
|
20
21
|
export function describeTemplateCatalog(sources: TemplateSource[], sceneVersion?: string): TemplateCatalogEntry[] {
|
|
@@ -31,10 +32,11 @@ export function describeTemplateCatalog(sources: TemplateSource[], sceneVersion?
|
|
|
31
32
|
for (const { kind, template } of templates) {
|
|
32
33
|
const metadata = template.metadata;
|
|
33
34
|
const describable = template as { displayName?: string; description?: string };
|
|
34
|
-
const problems =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const problems = validateTemplateMetadata(template.name, metadata);
|
|
36
|
+
const warnings: string[] = [];
|
|
37
|
+
const missingCategory = validateTemplateCategory(template.name, metadata);
|
|
38
|
+
if (missingCategory) (source.manifest.kind === PackageKind.Blueprint ? problems : warnings).push(missingCategory);
|
|
39
|
+
problems.push(...requirementProblems(template.name, metadata, source.manifest, versions, sceneVersion));
|
|
38
40
|
|
|
39
41
|
entries.push({
|
|
40
42
|
package: source.manifest.name,
|
|
@@ -46,12 +48,14 @@ export function describeTemplateCatalog(sources: TemplateSource[], sceneVersion?
|
|
|
46
48
|
type: metadata?.type,
|
|
47
49
|
category: metadata?.category,
|
|
48
50
|
scopes: metadata?.scopes,
|
|
51
|
+
sliceKinds: metadata?.sliceKinds,
|
|
49
52
|
compatibility: metadata?.compatibility,
|
|
50
53
|
attribution: metadata?.attribution,
|
|
51
54
|
license: metadata?.license,
|
|
52
55
|
licenseUrl: metadata?.licenseUrl,
|
|
53
56
|
compatible: problems.length === 0,
|
|
54
57
|
problems,
|
|
58
|
+
warnings,
|
|
55
59
|
});
|
|
56
60
|
}
|
|
57
61
|
}
|
package/templates/index.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
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
|
+
export * from './ApplicableTemplateSet';
|
|
4
5
|
export * from './TemplateCatalogEntry';
|
|
5
6
|
export * from './TemplateCatalogKind';
|
|
7
|
+
export * from './TemplateLibrary';
|
|
8
|
+
export * from './TemplateReferenceResolution';
|
|
6
9
|
export * from './TemplateSource';
|
|
10
|
+
export * from './TemplateTarget';
|
|
11
|
+
export * from './applicableTemplates';
|
|
7
12
|
export * from './describeTemplateCatalog';
|
|
13
|
+
export * from './resolveTemplateReference';
|
|
8
14
|
export * from './templateMetadataRules';
|
|
9
15
|
export * from './validateTemplateMetadata';
|
|
@@ -0,0 +1,79 @@
|
|
|
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 { TemplateReference, TemplateReferenceSource } from '@cratis/scene.model';
|
|
5
|
+
import { isValidVersionRange, isVersionSatisfiedBy } from '../packageVersionRange';
|
|
6
|
+
import { TemplateCatalogKind } from './TemplateCatalogKind';
|
|
7
|
+
import { TemplateLibrary } from './TemplateLibrary';
|
|
8
|
+
import { TemplateReferenceResolution, TemplateReferenceStatus } from './TemplateReferenceResolution';
|
|
9
|
+
import { TemplateSource } from './TemplateSource';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolves a template reference against the templates that exist, including the blueprint version pin. The C#
|
|
13
|
+
* engine (`TemplateReferenceResolver.Resolve`) reports the same outcomes and messages; both are asserted against
|
|
14
|
+
* `template-applicability-fixtures.json`.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveTemplateReference(reference: TemplateReference, library: TemplateLibrary): TemplateReferenceResolution {
|
|
17
|
+
if (!reference.id?.trim()) {
|
|
18
|
+
return { status: TemplateReferenceStatus.Malformed, message: 'A template reference must name a template' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (reference.source === TemplateReferenceSource.Own) {
|
|
22
|
+
if (reference.blueprint !== undefined || reference.version !== undefined) {
|
|
23
|
+
return { status: TemplateReferenceStatus.Malformed, message: `Own template reference '${reference.id}' must not name a blueprint or a version` };
|
|
24
|
+
}
|
|
25
|
+
return library.own ? find(reference.id, library.own, reference) : unknown(reference, undefined);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!reference.blueprint?.trim()) {
|
|
29
|
+
return { status: TemplateReferenceStatus.Malformed, message: `Blueprint template reference '${reference.id}' must name its blueprint` };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const blueprint = library.blueprints.find(source => source.manifest.name === reference.blueprint);
|
|
33
|
+
if (!blueprint) {
|
|
34
|
+
return { status: TemplateReferenceStatus.UnknownBlueprint, message: `Blueprint '${reference.blueprint}' referenced by template '${reference.id}' is not available` };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const installed = blueprint.manifest.version;
|
|
38
|
+
if (reference.version !== undefined) {
|
|
39
|
+
if (!isValidVersionRange(reference.version)) {
|
|
40
|
+
return {
|
|
41
|
+
status: TemplateReferenceStatus.Malformed, package: blueprint.manifest.name, packageVersion: installed,
|
|
42
|
+
message: `Template reference '${reference.id}' declares an invalid version range '${reference.version}'`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!isVersionSatisfiedBy(installed, reference.version)) {
|
|
46
|
+
return {
|
|
47
|
+
status: TemplateReferenceStatus.VersionNotSatisfied, package: blueprint.manifest.name, packageVersion: installed,
|
|
48
|
+
message: `Template '${reference.id}' was chosen from blueprint '${blueprint.manifest.name}' ${reference.version} but version ${installed} is installed`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return find(reference.id, blueprint, reference);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function find(name: string, source: TemplateSource, reference: TemplateReference): TemplateReferenceResolution {
|
|
57
|
+
const kind = kindOf(name, source);
|
|
58
|
+
return kind === undefined
|
|
59
|
+
? unknown(reference, source)
|
|
60
|
+
: { status: TemplateReferenceStatus.Resolved, package: source.manifest.name, packageVersion: source.manifest.version, kind };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function unknown(reference: TemplateReference, source: TemplateSource | undefined): TemplateReferenceResolution {
|
|
64
|
+
return {
|
|
65
|
+
status: TemplateReferenceStatus.UnknownTemplate,
|
|
66
|
+
package: source?.manifest.name,
|
|
67
|
+
packageVersion: source?.manifest.version,
|
|
68
|
+
message: reference.source === TemplateReferenceSource.Own
|
|
69
|
+
? `Own template '${reference.id}' does not exist`
|
|
70
|
+
: `Blueprint '${reference.blueprint}' has no template '${reference.id}'`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function kindOf(name: string, source: TemplateSource): TemplateCatalogKind | undefined {
|
|
75
|
+
if (source.screenTemplates?.some(template => template.name === name)) return TemplateCatalogKind.ScreenTemplate;
|
|
76
|
+
if (source.dialogTemplates?.some(template => template.name === name)) return TemplateCatalogKind.DialogTemplate;
|
|
77
|
+
if (source.layouts?.some(layout => layout.name === name)) return TemplateCatalogKind.Layout;
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
@@ -53,3 +53,14 @@ export function validateTemplateMetadata(name: string, metadata: TemplateMetadat
|
|
|
53
53
|
|
|
54
54
|
return problems;
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The diagnostic code reported for a template without a category. Category is required for blueprint templates
|
|
59
|
+
* (an error) and recommended for own templates (a warning).
|
|
60
|
+
*/
|
|
61
|
+
export const templateCategoryMissingCode = 'templateCategoryMissing';
|
|
62
|
+
|
|
63
|
+
/** Checks that a template declares a browse category; returns the problem, or undefined when it does. */
|
|
64
|
+
export function validateTemplateCategory(name: string, metadata: TemplateMetadata | undefined): string | undefined {
|
|
65
|
+
return metadata?.category?.trim() ? undefined : `Template '${name}' declares no category`;
|
|
66
|
+
}
|