@cratis/scene.blueprint.vendors 0.0.0-stage → 4.78.3

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 (132) hide show
  1. package/README.md +69 -2
  2. package/blueprint.ts +16 -0
  3. package/blueprintCatalog.json +107 -0
  4. package/dist/cjs/blueprint.js +19 -0
  5. package/dist/cjs/blueprint.js.map +1 -0
  6. package/dist/cjs/index.js +68 -0
  7. package/dist/cjs/index.js.map +1 -0
  8. package/dist/cjs/manifest.js +87 -0
  9. package/dist/cjs/manifest.js.map +1 -0
  10. package/dist/cjs/navigation.js +109 -0
  11. package/dist/cjs/navigation.js.map +1 -0
  12. package/dist/cjs/package.json +3 -0
  13. package/dist/cjs/packageName.js +8 -0
  14. package/dist/cjs/packageName.js.map +1 -0
  15. package/dist/cjs/screens/index.js +22 -0
  16. package/dist/cjs/screens/index.js.map +1 -0
  17. package/dist/cjs/templateProvenance.js +24 -0
  18. package/dist/cjs/templateProvenance.js.map +1 -0
  19. package/dist/cjs/templates/boardTemplate.js +78 -0
  20. package/dist/cjs/templates/boardTemplate.js.map +1 -0
  21. package/dist/cjs/templates/dashboardTemplates.js +151 -0
  22. package/dist/cjs/templates/dashboardTemplates.js.map +1 -0
  23. package/dist/cjs/templates/formTemplates.js +50 -0
  24. package/dist/cjs/templates/formTemplates.js.map +1 -0
  25. package/dist/cjs/templates/index.js +25 -0
  26. package/dist/cjs/templates/index.js.map +1 -0
  27. package/dist/cjs/templates/listTemplates.js +145 -0
  28. package/dist/cjs/templates/listTemplates.js.map +1 -0
  29. package/dist/cjs/templates/recordTemplates.js +135 -0
  30. package/dist/cjs/templates/recordTemplates.js.map +1 -0
  31. package/dist/cjs/vendorsApplication.js +49 -0
  32. package/dist/cjs/vendorsApplication.js.map +1 -0
  33. package/dist/cjs/vendorsParts.js +151 -0
  34. package/dist/cjs/vendorsParts.js.map +1 -0
  35. package/dist/cjs/vendorsQueries.js +64 -0
  36. package/dist/cjs/vendorsQueries.js.map +1 -0
  37. package/dist/cjs/vendorsScreenSet.js +40 -0
  38. package/dist/cjs/vendorsScreenSet.js.map +1 -0
  39. package/dist/esm/blueprint.d.ts +3 -0
  40. package/dist/esm/blueprint.d.ts.map +1 -0
  41. package/dist/esm/blueprint.js +17 -0
  42. package/dist/esm/blueprint.js.map +1 -0
  43. package/dist/esm/index.d.ts +12 -0
  44. package/dist/esm/index.d.ts.map +1 -0
  45. package/dist/esm/index.js +18 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/manifest.d.ts +3 -0
  48. package/dist/esm/manifest.d.ts.map +1 -0
  49. package/dist/esm/manifest.js +85 -0
  50. package/dist/esm/manifest.js.map +1 -0
  51. package/dist/esm/navigation.d.ts +5 -0
  52. package/dist/esm/navigation.d.ts.map +1 -0
  53. package/dist/esm/navigation.js +105 -0
  54. package/dist/esm/navigation.js.map +1 -0
  55. package/dist/esm/package.json +3 -0
  56. package/dist/esm/packageName.d.ts +2 -0
  57. package/dist/esm/packageName.d.ts.map +1 -0
  58. package/dist/esm/packageName.js +6 -0
  59. package/dist/esm/packageName.js.map +1 -0
  60. package/dist/esm/screens/index.d.ts +3 -0
  61. package/dist/esm/screens/index.d.ts.map +1 -0
  62. package/dist/esm/screens/index.js +20 -0
  63. package/dist/esm/screens/index.js.map +1 -0
  64. package/dist/esm/templateProvenance.d.ts +3 -0
  65. package/dist/esm/templateProvenance.d.ts.map +1 -0
  66. package/dist/esm/templateProvenance.js +22 -0
  67. package/dist/esm/templateProvenance.js.map +1 -0
  68. package/dist/esm/templates/boardTemplate.d.ts +3 -0
  69. package/dist/esm/templates/boardTemplate.d.ts.map +1 -0
  70. package/dist/esm/templates/boardTemplate.js +76 -0
  71. package/dist/esm/templates/boardTemplate.js.map +1 -0
  72. package/dist/esm/templates/dashboardTemplates.d.ts +3 -0
  73. package/dist/esm/templates/dashboardTemplates.d.ts.map +1 -0
  74. package/dist/esm/templates/dashboardTemplates.js +149 -0
  75. package/dist/esm/templates/dashboardTemplates.js.map +1 -0
  76. package/dist/esm/templates/formTemplates.d.ts +3 -0
  77. package/dist/esm/templates/formTemplates.d.ts.map +1 -0
  78. package/dist/esm/templates/formTemplates.js +48 -0
  79. package/dist/esm/templates/formTemplates.js.map +1 -0
  80. package/dist/esm/templates/index.d.ts +8 -0
  81. package/dist/esm/templates/index.d.ts.map +1 -0
  82. package/dist/esm/templates/index.js +17 -0
  83. package/dist/esm/templates/index.js.map +1 -0
  84. package/dist/esm/templates/listTemplates.d.ts +3 -0
  85. package/dist/esm/templates/listTemplates.d.ts.map +1 -0
  86. package/dist/esm/templates/listTemplates.js +143 -0
  87. package/dist/esm/templates/listTemplates.js.map +1 -0
  88. package/dist/esm/templates/recordTemplates.d.ts +3 -0
  89. package/dist/esm/templates/recordTemplates.d.ts.map +1 -0
  90. package/dist/esm/templates/recordTemplates.js +133 -0
  91. package/dist/esm/templates/recordTemplates.js.map +1 -0
  92. package/dist/esm/tsconfig.tsbuildinfo +1 -0
  93. package/dist/esm/vendorsApplication.d.ts +10 -0
  94. package/dist/esm/vendorsApplication.d.ts.map +1 -0
  95. package/dist/esm/vendorsApplication.js +44 -0
  96. package/dist/esm/vendorsApplication.js.map +1 -0
  97. package/dist/esm/vendorsParts.d.ts +16 -0
  98. package/dist/esm/vendorsParts.d.ts.map +1 -0
  99. package/dist/esm/vendorsParts.js +133 -0
  100. package/dist/esm/vendorsParts.js.map +1 -0
  101. package/dist/esm/vendorsQueries.d.ts +38 -0
  102. package/dist/esm/vendorsQueries.d.ts.map +1 -0
  103. package/dist/esm/vendorsQueries.js +56 -0
  104. package/dist/esm/vendorsQueries.js.map +1 -0
  105. package/dist/esm/vendorsScreenSet.d.ts +7 -0
  106. package/dist/esm/vendorsScreenSet.d.ts.map +1 -0
  107. package/dist/esm/vendorsScreenSet.js +38 -0
  108. package/dist/esm/vendorsScreenSet.js.map +1 -0
  109. package/index.ts +14 -0
  110. package/manifest.ts +41 -0
  111. package/navigation.ts +53 -0
  112. package/package.json +78 -4
  113. package/packageName.ts +5 -0
  114. package/sampleData/vendors-en.json +863 -0
  115. package/screens/index.ts +20 -0
  116. package/templateProvenance.ts +15 -0
  117. package/templates/boardTemplate.ts +54 -0
  118. package/templates/dashboardTemplates.ts +76 -0
  119. package/templates/formTemplates.ts +33 -0
  120. package/templates/index.ts +18 -0
  121. package/templates/listTemplates.ts +96 -0
  122. package/templates/recordTemplates.ts +78 -0
  123. package/thumbnails/VendorPage.svg +17 -0
  124. package/thumbnails/VendorsList.svg +29 -0
  125. package/thumbnails/VendorsOnboardingBoard.svg +23 -0
  126. package/thumbnails/VendorsPerformance.svg +15 -0
  127. package/thumbnails/VendorsQuickCreateVendor.svg +12 -0
  128. package/thumbnails/vendors.svg +14 -0
  129. package/vendorsApplication.ts +28 -0
  130. package/vendorsParts.ts +58 -0
  131. package/vendorsQueries.ts +56 -0
  132. package/vendorsScreenSet.ts +21 -0
@@ -0,0 +1,20 @@
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 { Screen } from '@cratis/scene.model';
5
+ import { LayoutName } from '@cratis/scene.blueprint.default';
6
+ import { vendorsScreenDefinitions } from '../vendorsScreenSet';
7
+
8
+ /**
9
+ * The ready-to-use screens: real `Screen` instances, one for each entry of the manifest's `blueprint.screenSet`, each filling the screen template it names in
10
+ * the foundation's application shell. A new application that adopts the blueprint gets these, wired into its navigation (`vendorsNavigation`).
11
+ */
12
+ export const vendorsScreens: Screen[] = vendorsScreenDefinitions.map(definition => ({
13
+ name: definition.name,
14
+ layout: LayoutName.AppShell,
15
+ screenTemplate: definition.template,
16
+ slotContent: {},
17
+ forms: [],
18
+ contributions: [],
19
+ path: definition.name.split('.'),
20
+ }));
@@ -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 { TemplateMetadata } from '@cratis/scene.model';
5
+
6
+ /** The compatibility, attribution and license metadata every template this blueprint ships carries. */
7
+ export const vendorsTemplateProvenance: Pick<TemplateMetadata, 'compatibility' | 'attribution' | 'license' | 'licenseUrl'> = {
8
+ compatibility: {
9
+ scene: '^4.13.0',
10
+ packages: [{ name: 'Cratis.Blueprint.Default', versionRange: '^1.0.0' }],
11
+ },
12
+ attribution: { author: 'Cratis', url: 'https://github.com/Cratis/Scene/tree/main/Source/JavaScript/blueprint.vendors' },
13
+ license: 'MIT',
14
+ licenseUrl: 'https://github.com/Cratis/Scene/blob/main/LICENSE',
15
+ };
@@ -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 { ScreenTemplate, TemplateScope } from '@cratis/scene.model';
5
+ import { boardScreenTemplate } from '@cratis/scene.blueprint.default';
6
+ import { VendorsEntityKind, VendorsQuery, fromQuery, localeOfData, vendorsQueriesOf } from '../vendorsQueries';
7
+ import { actionButton, component, vendorsMetadata, opensDialog, withBehaviors } from '../vendorsParts';
8
+
9
+ interface BoardOptions {
10
+ name: string;
11
+ displayName: string;
12
+ description: string;
13
+ title: string;
14
+ subtitle: string;
15
+ searchLabel: string;
16
+ label: string;
17
+ entity: VendorsEntityKind;
18
+ columns: string;
19
+ cards: string;
20
+ primary: { label: string; dialog: string };
21
+ }
22
+
23
+ /**
24
+ * One board: the foundation `BoardScreen` with a column for each step and a card for each record. A card moves to another step by dragging or by its own Move-to menu,
25
+ * and a column says in words when its limit is exceeded.
26
+ */
27
+ function boardOf(options: BoardOptions): ScreenTemplate {
28
+ const id = options.name;
29
+ return {
30
+ name: options.name,
31
+ fitsSlot: 'content',
32
+ slots: boardScreenTemplate.slots,
33
+ arrangement: boardScreenTemplate.arrangement,
34
+ content: {
35
+ header: [component(`${id}-header`, 'pageHeader', { title: options.title, subtitle: options.subtitle }, {
36
+ actions: [withBehaviors(actionButton(`${id}-new`, options.primary.label, 'primary'), opensDialog(options.primary.dialog))],
37
+ })],
38
+ toolbar: [
39
+ component(`${id}-search`, 'globalSearch', { placeholder: options.searchLabel, label: options.searchLabel }),
40
+ component(`${id}-views`, 'savedViewsMenu', { views: fromQuery(vendorsQueriesOf(options.entity).views), savePermission: 'enabled', managePermission: 'enabled' }),
41
+ ],
42
+ board: [component(`${id}-columns`, 'kanbanBoard', { columns: fromQuery(options.columns), cards: fromQuery(options.cards), editPermission: 'enabled', label: options.label, ...localeOfData })],
43
+ },
44
+ displayName: options.displayName,
45
+ description: options.description,
46
+ metadata: vendorsMetadata(options.name, 'Board', [TemplateScope.Module, TemplateScope.Feature]),
47
+ };
48
+ }
49
+
50
+ /** The onboarding board: a column for each step a vendor goes through and a card for each vendor with its category and country. */
51
+ export const vendorsOnboardingTemplate = boardOf({
52
+ name: 'VendorsOnboardingBoard', displayName: 'Vendors onboarding board', description: 'Vendors as cards in a column for each onboarding step, moved by dragging or by menu.', title: 'Onboarding', subtitle: 'Drag a vendor to its next step', searchLabel: 'Search the board', label: 'Vendors by onboarding step',
53
+ entity: VendorsEntityKind.Vendor, columns: VendorsQuery.OnboardingColumns, cards: VendorsQuery.OnboardingCards, primary: { label: 'New vendor', dialog: 'VendorsQuickCreateVendor' },
54
+ });
@@ -0,0 +1,76 @@
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 { ScreenTemplate, TemplateScope } from '@cratis/scene.model';
5
+ import { dashboardScreenTemplate } from '@cratis/scene.blueprint.default';
6
+ import { VendorsEntityKind, VendorsQuery, currencyOfData, fromQuery, localeOfData, vendorsQueriesOf } from '../vendorsQueries';
7
+ import { actionButton, component, goesTo, vendorsMetadata, opensDialog, withBehaviors } from '../vendorsParts';
8
+
9
+ interface KeyFigure { id: string; label: string; query: string; index: number; format?: 'currency' | 'percent'; fractionDigits?: number; goodDirection?: 'up' | 'down'; comparison: string }
10
+ interface Chart { id: string; title: string; kind: 'bar' | 'line' | 'donut' | 'funnel'; query: string }
11
+ interface Action { id: string; label: string; emphasis: 'primary' | 'secondary' | 'quiet' | 'danger'; dialog?: string; screen?: string }
12
+
13
+ interface DashboardOptions {
14
+ name: string;
15
+ displayName: string;
16
+ description: string;
17
+ title: string;
18
+ subtitle: string;
19
+ figures: KeyFigure[];
20
+ charts: Chart[];
21
+ table: { label: string; rows: string; columns: string; titleField: string; pageSize: number };
22
+ aside: { kind: 'activity'; label: string; query: string } | { kind: 'checklist'; label: string; query: string };
23
+ actions: Action[];
24
+ }
25
+
26
+ /**
27
+ * One dashboard: the foundation `DashboardScreen` - key figures over two charts, a table beneath and a list beside - with every region its own component with its own
28
+ * loading, empty and error states, so one failed query leaves the others on the page.
29
+ */
30
+ function dashboardOf(options: DashboardOptions): ScreenTemplate {
31
+ const id = options.name;
32
+ return {
33
+ name: options.name,
34
+ fitsSlot: 'content',
35
+ slots: dashboardScreenTemplate.slots,
36
+ arrangement: dashboardScreenTemplate.arrangement,
37
+ content: {
38
+ header: [component(`${id}-header`, 'pageHeader', { title: options.title, subtitle: options.subtitle }, {
39
+ actions: options.actions.map(action => withBehaviors(actionButton(`${id}-${action.id}`, action.label, action.emphasis), action.dialog ? opensDialog(action.dialog) : goesTo(action.screen!))),
40
+ })],
41
+ kpis: options.figures.map(figure => component(`${id}-${figure.id}`, 'statCard', {
42
+ label: figure.label, value: fromQuery(figure.query, `${figure.index}.value`), change: fromQuery(figure.query, `${figure.index}.change`), comparison: figure.comparison,
43
+ ...(figure.format ? { format: figure.format } : {}), ...(figure.fractionDigits === undefined ? {} : { fractionDigits: figure.fractionDigits }), ...(figure.goodDirection ? { goodDirection: figure.goodDirection } : {}), ...currencyOfData,
44
+ })),
45
+ charts: options.charts.map(chart => component(`${id}-${chart.id}`, 'chartWrapper', {
46
+ title: chart.title, kind: chart.kind, categories: fromQuery(chart.query, '0.categories'), series: fromQuery(chart.query, '0.series'), ...localeOfData,
47
+ })),
48
+ tables: [component(`${id}-table`, 'dataList', {
49
+ label: options.table.label, rows: fromQuery(options.table.rows), columns: fromQuery(options.table.columns), titleField: options.table.titleField, pageSize: options.table.pageSize, ...localeOfData,
50
+ })],
51
+ aside: [options.aside.kind === 'activity'
52
+ ? component(`${id}-aside`, 'activityFeed', { label: options.aside.label, entries: fromQuery(options.aside.query), ...localeOfData })
53
+ : component(`${id}-aside`, 'checklistSection', { label: options.aside.label, sections: fromQuery(options.aside.query), ...localeOfData })],
54
+ },
55
+ displayName: options.displayName,
56
+ description: options.description,
57
+ metadata: vendorsMetadata(options.name, 'Dashboard', [TemplateScope.Module, TemplateScope.Feature]),
58
+ };
59
+ }
60
+
61
+ /** The performance dashboard: three key figures over two charts - spend by category and on-time delivery - with the vendors beneath and the latest activity beside them. */
62
+ export const vendorsPerformanceTemplate = dashboardOf({
63
+ name: 'VendorsPerformance', displayName: 'Vendors performance', description: 'Active vendors, spend this year, on-time delivery, spend by category, delivery over the quarters and the vendors to watch.', title: 'Vendor performance', subtitle: 'Spend and delivery this year',
64
+ figures: [
65
+ { id: 'active', label: 'Active vendors', query: VendorsQuery.Kpis, index: 0, comparison: 'last year' },
66
+ { id: 'spend', label: 'Spend this year', query: VendorsQuery.Kpis, index: 1, format: 'currency', fractionDigits: 0, comparison: 'last year' },
67
+ { id: 'ontime', label: 'On-time delivery', query: VendorsQuery.Kpis, index: 2, format: 'percent', comparison: 'last quarter' },
68
+ ],
69
+ charts: [
70
+ { id: 'spend-chart', title: 'Spend by category', kind: 'bar', query: VendorsQuery.SpendByCategory },
71
+ { id: 'ontime-chart', title: 'On-time delivery', kind: 'line', query: VendorsQuery.OnTimeDelivery },
72
+ ],
73
+ table: { label: 'Vendors', titleField: 'name', pageSize: 5, rows: vendorsQueriesOf(VendorsEntityKind.Vendor).rows, columns: vendorsQueriesOf(VendorsEntityKind.Vendor).columns },
74
+ aside: { kind: 'activity', label: 'Latest activity', query: VendorsQuery.Activity },
75
+ actions: [{ id: 'vendors', label: 'Open the vendors', emphasis: 'secondary', screen: 'vendors.list' }],
76
+ });
@@ -0,0 +1,33 @@
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, TemplateScope } from '@cratis/scene.model';
5
+ import { quickCreateDialogTemplate } from '@cratis/scene.blueprint.default';
6
+ import { VendorsQuery, fromQuery, localeOfData } from '../vendorsQueries';
7
+ import { closesDialog, component, vendorsMetadata, withBehaviors } from '../vendorsParts';
8
+
9
+ /**
10
+ * One quick-create dialog: the foundation `QuickCreateDialog` - a short form opened from a page to make one record without leaving it - with the fields
11
+ * that thing needs to get started.
12
+ */
13
+ function quickCreateOf(name: string, title: string, sections: string, noun: string, submit: string): DialogTemplate {
14
+ return {
15
+ name,
16
+ slots: quickCreateDialogTemplate.slots,
17
+ content: {
18
+ header: [component(`${name}-frame`, 'dialogFrame', { title, subtitle: 'Only what is needed to get started' })],
19
+ body: [withBehaviors(component(`${name}-form`, 'formScreenBody', {
20
+ mode: 'create', saveAndNew: false, strings: { formSave: submit }, sections: fromQuery(sections), errors: fromQuery(VendorsQuery.FormErrors, '0'), ...localeOfData,
21
+ }), closesDialog())],
22
+ actions: [],
23
+ },
24
+ displayName: `Vendors quick create ${noun}`,
25
+ description: `A short form that creates one ${noun} without leaving the page.`,
26
+ metadata: vendorsMetadata(name, 'Dialog', [TemplateScope.Slice]),
27
+ };
28
+ }
29
+
30
+ /** The quick dialogs of the blueprint. */
31
+ export const vendorsDialogs: DialogTemplate[] = [
32
+ quickCreateOf('VendorsQuickCreateVendor', 'New vendor', 'VendorQuickSections', 'vendor', 'Create'),
33
+ ];
@@ -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 { vendorsListTemplate } from './listTemplates';
5
+ import { vendorPageTemplate } from './recordTemplates';
6
+ import { vendorsOnboardingTemplate } from './boardTemplate';
7
+ import { vendorsPerformanceTemplate } from './dashboardTemplates';
8
+
9
+ export * from './listTemplates';
10
+ export * from './recordTemplates';
11
+ export * from './boardTemplate';
12
+ export * from './dashboardTemplates';
13
+ export * from './formTemplates';
14
+
15
+ /** Every screen template this blueprint provides, in the order the manifest lists them. */
16
+ export const vendorsScreenTemplates = [vendorsListTemplate, vendorPageTemplate, vendorsOnboardingTemplate, vendorsPerformanceTemplate];
17
+
18
+ export { vendorsDialogs as vendorsDialogTemplates } from './formTemplates';
@@ -0,0 +1,96 @@
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 { InteractionActionKind, InteractionTriggerKind, NotificationLevel, NotifyAction, ScreenTemplate, TemplateScope } from '@cratis/scene.model';
5
+ import { listScreenTemplate } from '@cratis/scene.blueprint.default';
6
+ import { VendorsEntityKind, fromQuery, vendorsQueriesOf, localeOfData } from '../vendorsQueries';
7
+ import { actionButton, component, goesTo, vendorsMetadata, opensDialog, opensDialogOnSelect, opensRecordOnSelect, withBehaviors } from '../vendorsParts';
8
+
9
+ const listScopes = [TemplateScope.Module, TemplateScope.Feature];
10
+
11
+ /** The Export button: it tells the user the export has started, which is what the shell does for a long-running action it does not wait on. */
12
+ const exportAction = (id: string) => withBehaviors(actionButton(id, 'Export', 'secondary'), [{
13
+ bindings: [{
14
+ trigger: { kind: InteractionTriggerKind.Click },
15
+ actions: [{ kind: InteractionActionKind.Notify, level: NotificationLevel.Info, message: { text: 'The export has started.' } } as unknown as NotifyAction],
16
+ }],
17
+ }]);
18
+
19
+ interface ListOptions {
20
+ name: string;
21
+ entity: VendorsEntityKind;
22
+ displayName: string;
23
+ description: string;
24
+ title: string;
25
+ subtitle: string;
26
+ searchLabel: string;
27
+ urlKey: string;
28
+ /** The primary action of the header: a quick-create dialog to open, or a screen to go to. */
29
+ primary: { label: string; dialog?: string; screen?: string };
30
+ rowActions: string[];
31
+ bulkActions: string[];
32
+ titleField: string;
33
+ /** The screen a chosen row opens; the list then selects one row at a time. Absent, rows are selected many at a time for the bulk bar. */
34
+ recordScreen?: string;
35
+ /** The side sheet a chosen row opens instead of a record page; the list then selects one row at a time. */
36
+ recordDialog?: string;
37
+ /** Group the rows (`variant: grouped`) by this field. */
38
+ grouped?: { groupBy: string };
39
+ /** Rows are not selectable at all: a record of what happened, which nobody edits. */
40
+ readOnly?: boolean;
41
+ /** Show the records as cards, each with the picture its `imageField` names. */
42
+ cards?: { imageField?: string };
43
+ /** Tint a row by the tone in this field (always beside a status column that says the same in words). */
44
+ toneField?: string;
45
+ }
46
+
47
+ /**
48
+ * One list: the foundation `ListScreen` with its header, toolbar, filters, list and bulk bar filled for an entity. The slots, arrangement and
49
+ * every behavior - sort, search, paging and selection in the address, the table collapsing to cards on a phone - are the foundation's.
50
+ */
51
+ function listTemplateOf(options: ListOptions): ScreenTemplate {
52
+ const queries = vendorsQueriesOf(options.entity);
53
+ const id = options.name;
54
+ const listId = `${id}-records`;
55
+ const primary = withBehaviors(actionButton(`${id}-primary`, options.primary.label, 'primary'), options.primary.dialog ? opensDialog(options.primary.dialog) : goesTo(options.primary.screen!));
56
+ const list = component(listId, 'dataList', {
57
+ label: options.title, rows: fromQuery(queries.rows), columns: fromQuery(queries.columns), titleField: options.titleField, pageSize: options.cards ? 12 : 10, urlKey: options.urlKey, ...localeOfData,
58
+ rowActions: options.rowActions,
59
+ ...(options.readOnly ? {} : { selectionMode: options.recordScreen || options.recordDialog ? 'single' : 'multiple' }),
60
+ ...(options.cards ? { variant: 'cards', ...(options.cards.imageField ? { imageField: options.cards.imageField } : {}) } : {}),
61
+ ...(options.grouped ? { variant: 'grouped', groupBy: options.grouped.groupBy } : {}),
62
+ ...(options.toneField ? { toneField: options.toneField } : {}),
63
+ });
64
+
65
+ return {
66
+ name: options.name,
67
+ fitsSlot: 'content',
68
+ slots: listScreenTemplate.slots,
69
+ arrangement: listScreenTemplate.arrangement,
70
+ content: {
71
+ header: [component(`${id}-header`, 'pageHeader', { title: options.title, subtitle: options.subtitle }, { actions: [exportAction(`${id}-export`), primary] })],
72
+ toolbar: [
73
+ component(`${id}-search`, 'globalSearch', { placeholder: options.searchLabel, label: options.searchLabel, urlKey: `${options.urlKey}q` }),
74
+ component(`${id}-views`, 'savedViewsMenu', { views: fromQuery(queries.views), savePermission: 'enabled', managePermission: 'enabled' }),
75
+ component(`${id}-columns`, 'columnChooser', { columns: fromQuery(queries.columnChoices) }),
76
+ ],
77
+ filters: [component(`${id}-filter`, 'filterBuilder', { fields: fromQuery(queries.filterFields), ...localeOfData })],
78
+ list: [options.recordScreen ? withBehaviors(list, opensRecordOnSelect(listId, options.recordScreen)) : options.recordDialog ? withBehaviors(list, opensDialogOnSelect(options.recordDialog)) : list],
79
+ bulkBar: options.recordScreen || options.recordDialog || options.readOnly ? [] : [component(`${id}-bulk`, 'bulkBar', { selection: fromQuery(queries.selection), actions: options.bulkActions })],
80
+ footer: [],
81
+ },
82
+ displayName: options.displayName,
83
+ description: options.description,
84
+ metadata: vendorsMetadata(options.name, 'List', listScopes),
85
+ };
86
+ }
87
+
88
+
89
+ /** The vendors: every vendor with its category, country, spend this year, rating and status. Choosing a vendor opens it. */
90
+ export const vendorsListTemplate = listTemplateOf({
91
+ name: 'VendorsList',
92
+ entity: VendorsEntityKind.Vendor, displayName: 'Vendors list', description: 'Every vendor with its category, country, spend, rating and status, filtered by category, status, country and account manager.', title: 'Vendors', subtitle: 'Everyone you buy from', searchLabel: 'Search vendors', urlKey: 'vendors',
93
+ primary: { label: 'New vendor', dialog: 'VendorsQuickCreateVendor' },
94
+ rowActions: ['Open', 'Suspend'], bulkActions: [],
95
+ titleField: 'name', recordScreen: 'vendors.record'
96
+ });
@@ -0,0 +1,78 @@
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 { ScreenTemplate, TemplateScope } from '@cratis/scene.model';
5
+ import { recordPageTemplate } from '@cratis/scene.blueprint.default';
6
+ import { VendorsEntityKind, currencyOfData, fromQuery, localeOfData, vendorsQueriesOf } from '../vendorsQueries';
7
+ import { actionButton, component, goesTo, vendorsMetadata, opensDialog, withBehaviors } from '../vendorsParts';
8
+
9
+ interface Fact { id: string; label: string; field: string; format?: 'currency' | 'percent' }
10
+ interface Extra { key: string; label: string; titleField: string; pageSize?: number }
11
+ interface Action { id: string; label: string; emphasis: 'primary' | 'secondary' | 'quiet' | 'danger'; dialog?: string; screen?: string }
12
+
13
+ interface RecordOptions {
14
+ name: string;
15
+ entity: VendorsEntityKind;
16
+ displayName: string;
17
+ description: string;
18
+ titleField: string;
19
+ backScreen: string;
20
+ backLabel: string;
21
+ facts: Fact[];
22
+ extras: Extra[];
23
+ actions: Action[];
24
+ }
25
+
26
+ /**
27
+ * One record page: the foundation `RecordPage` with the record's own title, its status and key facts at the top, its fields and the lists that belong to it in the
28
+ * main region, and the activity beside it. What it shows is the record the list handed it the id of.
29
+ */
30
+ function recordPageOf(options: RecordOptions): ScreenTemplate {
31
+ const queries = vendorsQueriesOf(options.entity);
32
+ const id = options.name;
33
+ return {
34
+ name: options.name,
35
+ fitsSlot: 'content',
36
+ slots: recordPageTemplate.slots,
37
+ arrangement: recordPageTemplate.arrangement,
38
+ content: {
39
+ header: [component(`${id}-header`, 'pageHeader', { title: fromQuery(queries.record, `0.${options.titleField}`), subtitle: fromQuery(queries.record, '0.summary') }, {
40
+ actions: [
41
+ withBehaviors(actionButton(`${id}-back`, options.backLabel, 'secondary'), goesTo(options.backScreen)),
42
+ ...options.actions.map(action => withBehaviors(actionButton(`${id}-${action.id}`, action.label, action.emphasis), action.dialog ? opensDialog(action.dialog) : goesTo(action.screen!))),
43
+ ],
44
+ })],
45
+ summary: [
46
+ component(`${id}-status`, 'statusChip', { status: fromQuery(queries.summary, '0.status'), tone: fromQuery(queries.summary, '0.tone') }),
47
+ ...options.facts.map(fact => component(`${id}-${fact.id}`, 'statCard', {
48
+ label: fact.label, value: fromQuery(queries.summary, `0.${fact.field}`), ...(fact.format ? { format: fact.format } : {}), ...currencyOfData,
49
+ })),
50
+ ],
51
+ tabs: [component(`${id}-tabs`, 'tabSet', { tabs: fromQuery(queries.tabs), urlKey: 'tab', label: `${options.displayName} sections` })],
52
+ main: [
53
+ component(`${id}-form`, 'formScreenBody', { mode: 'readOnly', sections: fromQuery(queries.sections), values: fromQuery(queries.record, '0'), ...currencyOfData }),
54
+ ...options.extras.map(extra => component(`${id}-${extra.key.toLowerCase()}`, 'dataList', {
55
+ label: extra.label, rows: fromQuery(`${options.entity}${extra.key}`), columns: fromQuery(`${options.entity}${extra.key}Columns`), titleField: extra.titleField, pageSize: extra.pageSize ?? 8, ...currencyOfData,
56
+ })),
57
+ ],
58
+ related: [],
59
+ activity: [component(`${id}-activity`, 'activityFeed', { label: 'Activity', entries: fromQuery(queries.activity), ...localeOfData })],
60
+ aside: [],
61
+ },
62
+ displayName: options.displayName,
63
+ description: options.description,
64
+ metadata: vendorsMetadata(options.name, 'Detail', [TemplateScope.Feature, TemplateScope.Subfeature]),
65
+ };
66
+ }
67
+
68
+ /** The vendor page: the vendor's name as its title, its status, spend, rating and open orders at the top, its details, contacts, purchase orders and certificates in the main region, and the activity beside it. */
69
+ export const vendorPageTemplate = recordPageOf({
70
+ name: 'VendorPage', entity: VendorsEntityKind.Vendor, displayName: 'Vendor page', description: 'One vendor: status, spend, rating and open orders, its details, contacts, purchase orders and certificates, and its activity.', titleField: 'name', backScreen: 'vendors.list', backLabel: 'Back to vendors',
71
+ facts: [
72
+ { id: 'spend', label: 'Spend this year', field: 'spend', format: 'currency' },
73
+ { id: 'rating', label: 'Rating', field: 'rating' },
74
+ { id: 'open', label: 'Open orders', field: 'openOrders' },
75
+ ],
76
+ extras: [{ key: 'Contacts', label: 'Contacts', titleField: 'name' }, { key: 'Orders', label: 'Purchase orders', titleField: 'number' }, { key: 'Certificates', label: 'Certificates', titleField: 'name' }],
77
+ actions: [],
78
+ });
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200" role="img" aria-label="VendorPage">
2
+ <rect x="0" y="0" width="320" height="200" rx="4" fill="#f4f5f7"/>
3
+ <rect x="12" y="12" width="296" height="20" rx="4" fill="#7c3aed"/>
4
+ <rect x="12" y="40" width="64" height="148" rx="4" fill="#d5d9df"/>
5
+ <rect x="84" y="40" width="70" height="30" rx="4" fill="#ffffff" stroke="#d5d9df"/>
6
+ <rect x="160" y="40" width="70" height="30" rx="4" fill="#ffffff" stroke="#d5d9df"/>
7
+ <rect x="236" y="40" width="70" height="30" rx="4" fill="#ffffff" stroke="#d5d9df"/>
8
+ <rect x="84" y="78" width="150" height="110" rx="4" fill="#ffffff" stroke="#d5d9df"/>
9
+ <rect x="242" y="78" width="66" height="110" rx="4" fill="#ffffff" stroke="#d5d9df"/>
10
+ <rect x="94" y="90" width="130" height="6" rx="3" fill="#9aa3b2"/>
11
+ <rect x="94" y="107" width="130" height="6" rx="3" fill="#9aa3b2"/>
12
+ <rect x="94" y="124" width="130" height="6" rx="3" fill="#9aa3b2"/>
13
+ <rect x="94" y="141" width="130" height="6" rx="3" fill="#9aa3b2"/>
14
+ <rect x="250" y="90" width="50" height="6" rx="3" fill="#7c3aed"/>
15
+ <rect x="250" y="107" width="50" height="6" rx="3" fill="#7c3aed"/>
16
+ <rect x="250" y="124" width="50" height="6" rx="3" fill="#7c3aed"/>
17
+ </svg>
@@ -0,0 +1,29 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200" role="img" aria-label="VendorsList">
2
+ <rect x="0" y="0" width="320" height="200" rx="4" fill="#f4f5f7"/>
3
+ <rect x="12" y="12" width="296" height="20" rx="4" fill="#7c3aed"/>
4
+ <rect x="12" y="40" width="64" height="148" rx="4" fill="#d5d9df"/>
5
+ <rect x="84" y="40" width="68" height="70" rx="4" fill="#ffffff" stroke="#d5d9df"/>
6
+ <rect x="88" y="44" width="60" height="30" rx="4" fill="#d5d9df"/>
7
+ <rect x="90" y="82" width="40" height="5" rx="3" fill="#7c3aed"/>
8
+ <rect x="90" y="94" width="52" height="5" rx="3" fill="#9aa3b2"/>
9
+ <rect x="160" y="40" width="68" height="70" rx="4" fill="#ffffff" stroke="#d5d9df"/>
10
+ <rect x="164" y="44" width="60" height="30" rx="4" fill="#d5d9df"/>
11
+ <rect x="166" y="82" width="40" height="5" rx="3" fill="#7c3aed"/>
12
+ <rect x="166" y="94" width="52" height="5" rx="3" fill="#9aa3b2"/>
13
+ <rect x="236" y="40" width="68" height="70" rx="4" fill="#ffffff" stroke="#d5d9df"/>
14
+ <rect x="240" y="44" width="60" height="30" rx="4" fill="#d5d9df"/>
15
+ <rect x="242" y="82" width="40" height="5" rx="3" fill="#7c3aed"/>
16
+ <rect x="242" y="94" width="52" height="5" rx="3" fill="#9aa3b2"/>
17
+ <rect x="84" y="116" width="68" height="70" rx="4" fill="#ffffff" stroke="#d5d9df"/>
18
+ <rect x="88" y="120" width="60" height="30" rx="4" fill="#d5d9df"/>
19
+ <rect x="90" y="158" width="40" height="5" rx="3" fill="#7c3aed"/>
20
+ <rect x="90" y="170" width="52" height="5" rx="3" fill="#9aa3b2"/>
21
+ <rect x="160" y="116" width="68" height="70" rx="4" fill="#ffffff" stroke="#d5d9df"/>
22
+ <rect x="164" y="120" width="60" height="30" rx="4" fill="#d5d9df"/>
23
+ <rect x="166" y="158" width="40" height="5" rx="3" fill="#7c3aed"/>
24
+ <rect x="166" y="170" width="52" height="5" rx="3" fill="#9aa3b2"/>
25
+ <rect x="236" y="116" width="68" height="70" rx="4" fill="#ffffff" stroke="#d5d9df"/>
26
+ <rect x="240" y="120" width="60" height="30" rx="4" fill="#d5d9df"/>
27
+ <rect x="242" y="158" width="40" height="5" rx="3" fill="#7c3aed"/>
28
+ <rect x="242" y="170" width="52" height="5" rx="3" fill="#9aa3b2"/>
29
+ </svg>
@@ -0,0 +1,23 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200" role="img" aria-label="VendorsOnboardingBoard">
2
+ <rect x="0" y="0" width="320" height="200" rx="4" fill="#f4f5f7"/>
3
+ <rect x="12" y="12" width="296" height="20" rx="4" fill="#7c3aed"/>
4
+ <rect x="12" y="40" width="64" height="148" rx="4" fill="#d5d9df"/>
5
+ <rect x="84" y="40" width="52" height="148" rx="4" fill="#ffffff" stroke="#d5d9df"/>
6
+ <rect x="88" y="46" width="30" height="6" rx="3" fill="#7c3aed"/>
7
+ <rect x="88" y="60" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
8
+ <rect x="88" y="100" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
9
+ <rect x="88" y="140" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
10
+ <rect x="141" y="40" width="52" height="148" rx="4" fill="#ffffff" stroke="#d5d9df"/>
11
+ <rect x="145" y="46" width="30" height="6" rx="3" fill="#7c3aed"/>
12
+ <rect x="145" y="60" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
13
+ <rect x="145" y="100" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
14
+ <rect x="198" y="40" width="52" height="148" rx="4" fill="#ffffff" stroke="#d5d9df"/>
15
+ <rect x="202" y="46" width="30" height="6" rx="3" fill="#7c3aed"/>
16
+ <rect x="202" y="60" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
17
+ <rect x="202" y="100" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
18
+ <rect x="202" y="140" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
19
+ <rect x="255" y="40" width="52" height="148" rx="4" fill="#ffffff" stroke="#d5d9df"/>
20
+ <rect x="259" y="46" width="30" height="6" rx="3" fill="#7c3aed"/>
21
+ <rect x="259" y="60" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
22
+ <rect x="259" y="100" width="44" height="32" rx="4" fill="#ffffff" stroke="#d5d9df"/>
23
+ </svg>
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200" role="img" aria-label="VendorsPerformance">
2
+ <rect x="0" y="0" width="320" height="200" rx="4" fill="#f4f5f7"/>
3
+ <rect x="12" y="12" width="296" height="20" rx="4" fill="#7c3aed"/>
4
+ <rect x="12" y="40" width="64" height="148" rx="4" fill="#d5d9df"/>
5
+ <rect x="84" y="40" width="70" height="34" rx="4" fill="#ffffff" stroke="#d5d9df"/>
6
+ <rect x="160" y="40" width="70" height="34" rx="4" fill="#ffffff" stroke="#d5d9df"/>
7
+ <rect x="236" y="40" width="70" height="34" rx="4" fill="#ffffff" stroke="#d5d9df"/>
8
+ <rect x="84" y="82" width="150" height="106" rx="4" fill="#ffffff" stroke="#d5d9df"/>
9
+ <rect x="242" y="82" width="66" height="106" rx="4" fill="#ffffff" stroke="#d5d9df"/>
10
+ <rect x="100" y="191" width="16" height="14" rx="4" fill="#7c3aed"/>
11
+ <rect x="124" y="161" width="16" height="34" rx="4" fill="#7c3aed"/>
12
+ <rect x="148" y="176" width="16" height="24" rx="4" fill="#7c3aed"/>
13
+ <rect x="172" y="131" width="16" height="54" rx="4" fill="#7c3aed"/>
14
+ <rect x="196" y="146" width="16" height="44" rx="4" fill="#7c3aed"/>
15
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200" role="img" aria-label="VendorsQuickCreateVendor">
2
+ <rect x="0" y="0" width="320" height="200" rx="4" fill="#f4f5f7"/>
3
+ <rect x="12" y="12" width="296" height="20" rx="4" fill="#7c3aed"/>
4
+ <rect x="12" y="40" width="64" height="148" rx="4" fill="#d5d9df"/>
5
+ <rect x="84" y="40" width="224" height="148" rx="4" fill="#e4e7ec"/>
6
+ <rect x="120" y="56" width="152" height="120" rx="4" fill="#ffffff" stroke="#d5d9df"/>
7
+ <rect x="130" y="66" width="70" height="7" rx="3" fill="#7c3aed"/>
8
+ <rect x="130" y="86" width="132" height="14" rx="4" fill="#eef0f3"/>
9
+ <rect x="130" y="108" width="132" height="14" rx="4" fill="#eef0f3"/>
10
+ <rect x="130" y="130" width="132" height="14" rx="4" fill="#eef0f3"/>
11
+ <rect x="206" y="152" width="56" height="16" rx="4" fill="#7c3aed"/>
12
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="320" height="200" viewBox="0 0 320 200" role="img" aria-label="Vendors blueprint">
2
+ <rect x="0" y="0" width="320" height="200" rx="4" fill="#f4f5f7"/>
3
+ <rect x="12" y="12" width="296" height="20" rx="4" fill="#7c3aed"/>
4
+ <rect x="12" y="40" width="64" height="148" rx="4" fill="#d5d9df"/>
5
+ <rect x="84" y="40" width="70" height="30" rx="4" fill="#ffffff" stroke="#d5d9df"/>
6
+ <rect x="160" y="40" width="70" height="30" rx="4" fill="#ffffff" stroke="#d5d9df"/>
7
+ <rect x="236" y="40" width="70" height="30" rx="4" fill="#ffffff" stroke="#d5d9df"/>
8
+ <rect x="84" y="78" width="224" height="110" rx="4" fill="#ffffff" stroke="#d5d9df"/>
9
+ <rect x="94" y="90" width="200" height="6" rx="3" fill="#9aa3b2"/>
10
+ <rect x="94" y="107" width="164" height="6" rx="3" fill="#9aa3b2"/>
11
+ <rect x="94" y="124" width="200" height="6" rx="3" fill="#9aa3b2"/>
12
+ <rect x="94" y="141" width="164" height="6" rx="3" fill="#9aa3b2"/>
13
+ <rect x="94" y="158" width="200" height="6" rx="3" fill="#9aa3b2"/>
14
+ </svg>
@@ -0,0 +1,28 @@
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 { UiProfile } from '@cratis/scene.model';
5
+ import { ApplicationStructureIndex } from '@cratis/scene.engine';
6
+ import { ApplicationDocument, CompositionOptions, defaultLayouts, previewProfile } from '@cratis/scene.blueprint.default';
7
+ import { vendorsNavigation, vendorsStructure } from './navigation';
8
+ import { vendorsScreens } from './screens';
9
+ import { vendorsDialogTemplates, vendorsScreenTemplates } from './templates';
10
+
11
+ /** The id of the sample-data pack a new application starts with. */
12
+ export const vendorsDefaultSampleDataId = 'vendors-en';
13
+
14
+ /**
15
+ * The example application of the blueprint: the foundation's layouts, the Vendors templates, the quick-create dialogs and their 4 screens, rendering against the
16
+ * foundation's packages with the default sample-data pack chosen. This is what a gallery previews and what the conformance fixture
17
+ * (`Samples/Vendors/vendors.play`) declares in Screenplay: the document, the structure and the navigation are the three things an application is made of.
18
+ */
19
+ export function vendorsApplication(sampleData: string = vendorsDefaultSampleDataId): ApplicationDocument {
20
+ const profile: UiProfile = { ...previewProfile(undefined), name: 'Vendors', sampleData: { activePack: sampleData, mappings: [], bindings: [] } };
21
+ return { layouts: defaultLayouts, screenTemplates: vendorsScreenTemplates, dialogTemplates: vendorsDialogTemplates, screens: vendorsScreens, uiProfiles: [profile] };
22
+ }
23
+
24
+ /** What a host composes the screens with: the application's logo, which the shell's topbar shows. */
25
+ export const vendorsComposition: CompositionOptions = { logo: vendorsNavigation.application.logo };
26
+
27
+ /** The structure and navigation the shell's regions show and the items' destinations resolve against. */
28
+ export const vendorsNavigationModel = () => ({ structure: new ApplicationStructureIndex(vendorsStructure), navigation: vendorsNavigation });
@@ -0,0 +1,58 @@
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 {
5
+ Behavior, BindingSourceKind, DialogTemplate, ExternalComponent, InteractionActionKind, InteractionTriggerKind, NavigateAction, OpenDialogAction, SceneElement, ScreenTemplate, TemplateMetadata, TemplateScope,
6
+ } from '@cratis/scene.model';
7
+ import { component, panel } from '@cratis/scene.blueprint.default';
8
+ import { vendorsTemplateProvenance } from './templateProvenance';
9
+
10
+ export { component, panel };
11
+
12
+ /** The category of every template of this blueprint (spec Part B 1.2: `Business / <domain>`). */
13
+ export const vendorsCategory = 'Business / Vendors';
14
+
15
+ /** The package-relative thumbnail of a template. */
16
+ export const thumbnailOf = (templateName: string): string => `thumbnails/${templateName}.svg`;
17
+
18
+ /** The metadata of a screen template of this blueprint: provenance, a type from the vocabulary, the category, a thumbnail and the levels it applies at. */
19
+ export const vendorsMetadata = (name: string, type: 'Dashboard' | 'List' | 'Board' | 'Detail' | 'Form' | 'Dialog' | 'Workspace' | 'Calendar' | 'Timeline' | 'Inbox' | 'Profile' | 'Wizard', scopes: TemplateScope[]): TemplateMetadata =>
20
+ ({ ...vendorsTemplateProvenance, type, category: vendorsCategory, thumbnail: thumbnailOf(name), scopes });
21
+
22
+ /** A button of the foundation: a verb, its emphasis, and the permission rule every action declares (spec Part B 1.5). */
23
+ export const actionButton = (id: string, label: string, emphasis: 'primary' | 'secondary' | 'quiet' | 'danger', extra: Record<string, unknown> = {}, behaviors?: Behavior[]): ExternalComponent =>
24
+ ({ ...component(id, 'actionButton', { label, emphasis, permission: 'enabled', ...extra }), ...(behaviors ? { behaviors } : {}) });
25
+
26
+ /** What pressing an element does: open the named dialog template over the page. */
27
+ export const opensDialog = (dialogTemplate: string): Behavior[] =>
28
+ [{ bindings: [{ trigger: { kind: InteractionTriggerKind.Click }, actions: [{ kind: InteractionActionKind.OpenDialog, dialogTemplate } as OpenDialogAction] }] }];
29
+
30
+ /** What pressing an element does: go to the named screen. */
31
+ export const goesTo = (screen: string): Behavior[] =>
32
+ [{ bindings: [{ trigger: { kind: InteractionTriggerKind.Click }, actions: [{ kind: InteractionActionKind.Navigate, screen } as NavigateAction] }] }];
33
+
34
+ /**
35
+ * What choosing a row of a list does: open the record page, handing it the chosen row's id. The list is single-selection, so choosing a row is
36
+ * opening it - the way a table of records works everywhere.
37
+ */
38
+ export const opensRecordOnSelect = (listId: string, screen: string): Behavior[] => [{
39
+ bindings: [{
40
+ trigger: { kind: InteractionTriggerKind.Select },
41
+ actions: [{ kind: InteractionActionKind.Navigate, screen, arguments: [{ name: 'id', value: { kind: BindingSourceKind.ComponentProperty, componentId: listId, path: 'value' } }] } as NavigateAction],
42
+ }],
43
+ }];
44
+
45
+ /**
46
+ * What choosing a row of a list does: open the named dialog over the page - a side sheet with the row's detail, so the list stays where it was.
47
+ */
48
+ export const opensDialogOnSelect = (dialogTemplate: string): Behavior[] =>
49
+ [{ bindings: [{ trigger: { kind: InteractionTriggerKind.Select }, actions: [{ kind: InteractionActionKind.OpenDialog, dialogTemplate } as OpenDialogAction] }] }];
50
+
51
+ /** What a Cancel or Close control does: close the dialog it was opened from. */
52
+ export const closesDialog = (): Behavior[] => [{ bindings: [{ trigger: { kind: InteractionTriggerKind.Click }, actions: [{ kind: InteractionActionKind.CloseDialog }] }] }];
53
+
54
+ /** An element with the given behaviors attached. */
55
+ export const withBehaviors = <T extends SceneElement>(element: T, behaviors: Behavior[]): T => ({ ...element, behaviors });
56
+
57
+ export type VendorsScreenTemplate = ScreenTemplate;
58
+ export type VendorsDialogTemplate = DialogTemplate;