@jumoo/usync 17.5.2-prerelease.15 → 17.5.2

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 (41) hide show
  1. package/README.md +6 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/index.js +1 -1
  4. package/dist/api/sdk.gen.d.ts +1 -2
  5. package/dist/api/sdk.gen.js +0 -7
  6. package/dist/api/types.gen.d.ts +0 -361
  7. package/dist/api/types.gen.js +0 -13
  8. package/dist/components/index.d.ts +0 -3
  9. package/dist/components/index.js +0 -3
  10. package/dist/components/usync-results-view.js +5 -13
  11. package/dist/dialogs/details-modal-element.d.ts +1 -12
  12. package/dist/dialogs/details-modal-element.js +9 -139
  13. package/dist/dialogs/details-modal-token.d.ts +1 -10
  14. package/dist/lang/files/da-dk.d.ts +0 -37
  15. package/dist/lang/files/da-dk.js +0 -37
  16. package/dist/lang/files/de-de.d.ts +0 -37
  17. package/dist/lang/files/de-de.js +0 -37
  18. package/dist/lang/files/en-us.d.ts +0 -37
  19. package/dist/lang/files/en-us.js +0 -37
  20. package/dist/lang/files/es-es.d.ts +0 -37
  21. package/dist/lang/files/es-es.js +0 -37
  22. package/dist/lang/files/fr-fr.d.ts +0 -37
  23. package/dist/lang/files/fr-fr.js +0 -37
  24. package/dist/lang/files/nl-nl.d.ts +0 -37
  25. package/dist/lang/files/nl-nl.js +0 -37
  26. package/dist/repository/SyncAction.respositoy.d.ts +0 -8
  27. package/dist/repository/SyncAction.respositoy.js +0 -6
  28. package/dist/repository/sources/SyncAction.source.d.ts +0 -5
  29. package/dist/repository/sources/SyncAction.source.js +0 -5
  30. package/dist/signalr/signalr.context.js +73 -26
  31. package/package.json +1 -1
  32. package/dist/components/usync-content-preview.d.ts +0 -27
  33. package/dist/components/usync-content-preview.js +0 -540
  34. package/dist/components/usync-content-type-preview.d.ts +0 -25
  35. package/dist/components/usync-content-type-preview.js +0 -548
  36. package/dist/components/usync-data-type-preview.d.ts +0 -21
  37. package/dist/components/usync-data-type-preview.js +0 -293
  38. package/dist/utils/preview/content-type-diff.d.ts +0 -18
  39. package/dist/utils/preview/content-type-diff.js +0 -72
  40. package/dist/utils/preview/content-type-layout.d.ts +0 -39
  41. package/dist/utils/preview/content-type-layout.js +0 -100
@@ -1,293 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _uSyncDataTypePreviewElement_instances, _uSyncDataTypePreviewElement_renderSide, _uSyncDataTypePreviewElement_renderMissing, _uSyncDataTypePreviewElement_renderInfo, _uSyncDataTypePreviewElement_renderEditor, _uSyncDataTypePreviewElement_renderConfig;
13
- import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
14
- import { classMap, css, customElement, html, nothing, property, } from '@umbraco-cms/backoffice/external/lit';
15
- import '@umbraco-cms/backoffice/components';
16
- import { deepEqual, prettyJson } from '../utils/diff/index.js';
17
- /**
18
- * Shows the current and incoming versions of a data type side by side,
19
- * rendered with the real property editor, so you can see what an import
20
- * will do before running it.
21
- */
22
- let uSyncDataTypePreviewElement = class uSyncDataTypePreviewElement extends UmbLitElement {
23
- constructor() {
24
- super(...arguments);
25
- _uSyncDataTypePreviewElement_instances.add(this);
26
- }
27
- render() {
28
- if (!this.preview)
29
- return nothing;
30
- const current = this.preview.currentDataType ?? undefined;
31
- const incoming = this.preview.incomingDataType ?? undefined;
32
- return html `
33
- <div class="sides">
34
- ${__classPrivateFieldGet(this, _uSyncDataTypePreviewElement_instances, "m", _uSyncDataTypePreviewElement_renderSide).call(this, 'current', current, incoming)}
35
- ${__classPrivateFieldGet(this, _uSyncDataTypePreviewElement_instances, "m", _uSyncDataTypePreviewElement_renderSide).call(this, 'incoming', incoming, current)}
36
- </div>
37
- `;
38
- }
39
- };
40
- _uSyncDataTypePreviewElement_instances = new WeakSet();
41
- _uSyncDataTypePreviewElement_renderSide = function _uSyncDataTypePreviewElement_renderSide(side, dataType, other) {
42
- const headline = side === 'current'
43
- ? this.localize.termOrDefault('uSync_previewCurrent', 'Current (this site)')
44
- : this.localize.termOrDefault('uSync_previewIncoming', 'Incoming (uSync files)');
45
- return html `
46
- <div class="side">
47
- <h4 class="side-headline">${headline}</h4>
48
- ${dataType
49
- ? html `
50
- ${__classPrivateFieldGet(this, _uSyncDataTypePreviewElement_instances, "m", _uSyncDataTypePreviewElement_renderInfo).call(this, dataType, other)} ${__classPrivateFieldGet(this, _uSyncDataTypePreviewElement_instances, "m", _uSyncDataTypePreviewElement_renderEditor).call(this, dataType)}
51
- ${__classPrivateFieldGet(this, _uSyncDataTypePreviewElement_instances, "m", _uSyncDataTypePreviewElement_renderConfig).call(this, dataType, other, side)}
52
- `
53
- : __classPrivateFieldGet(this, _uSyncDataTypePreviewElement_instances, "m", _uSyncDataTypePreviewElement_renderMissing).call(this, side)}
54
- </div>
55
- `;
56
- };
57
- _uSyncDataTypePreviewElement_renderMissing = function _uSyncDataTypePreviewElement_renderMissing(side) {
58
- const message = side === 'current'
59
- ? this.localize.termOrDefault('uSync_previewNotInSite', 'This item does not exist in the site yet')
60
- : this.preview?.isDelete
61
- ? this.localize.termOrDefault('uSync_previewDeleted', 'This item will be deleted or renamed by the import')
62
- : this.localize.termOrDefault('uSync_previewFailed', 'The incoming item could not be built for the preview');
63
- return html `<uui-box class="info"><p class="empty">${message}</p></uui-box>`;
64
- };
65
- _uSyncDataTypePreviewElement_renderInfo = function _uSyncDataTypePreviewElement_renderInfo(dataType, other) {
66
- const changed = (field) => other !== undefined && other[field] !== dataType[field];
67
- return html `
68
- <uui-box class="info">
69
- <div slot="headline" class="headline">
70
- <umb-icon name="icon-autofill"></umb-icon>
71
- <span class=${classMap({ changed: changed('name') })}>${dataType.name}</span>
72
- </div>
73
- <dl class="meta">
74
- <dt>
75
- ${this.localize.termOrDefault('uSync_previewPropertyEditor', 'Property editor')}
76
- </dt>
77
- <dd class=${classMap({ changed: changed('editorAlias') })}>
78
- <code>${dataType.editorAlias}</code>
79
- </dd>
80
- <dt>
81
- ${this.localize.termOrDefault('uSync_previewPropertyEditorUi', 'Property editor UI')}
82
- </dt>
83
- <dd class=${classMap({ changed: changed('editorUiAlias') })}>
84
- <code>${dataType.editorUiAlias}</code>
85
- </dd>
86
- </dl>
87
- </uui-box>
88
- `;
89
- };
90
- _uSyncDataTypePreviewElement_renderEditor = function _uSyncDataTypePreviewElement_renderEditor(dataType) {
91
- return html `
92
- <uui-box
93
- .headline=${this.localize.termOrDefault('uSync_previewEditor', 'Editor preview')}>
94
- <!-- inert rather than readonly: most editors render only their value when
95
- readonly, which is empty here, so you wouldn't see the editor at all. -->
96
- <umb-property-dataset .value=${[]} inert>
97
- <umb-property
98
- alias="usyncPreview"
99
- .label=${dataType.name}
100
- .description=${this.localize.termOrDefault('uSync_previewEditorDesc', 'How this data type looks to editors')}
101
- .propertyEditorUiAlias=${dataType.editorUiAlias}
102
- .config=${dataType.values}></umb-property>
103
- </umb-property-dataset>
104
- </uui-box>
105
- `;
106
- };
107
- _uSyncDataTypePreviewElement_renderConfig = function _uSyncDataTypePreviewElement_renderConfig(dataType, other, side) {
108
- const values = [...dataType.values].sort((a, b) => a.alias.localeCompare(b.alias));
109
- return html `
110
- <uui-box
111
- .headline=${this.localize.termOrDefault('uSync_previewConfiguration', 'Configuration')}>
112
- ${values.length === 0
113
- ? html `<p class="empty">
114
- ${this.localize.termOrDefault('uSync_previewNoConfiguration', 'No configuration')}
115
- </p>`
116
- : html `<table>
117
- ${values.map((value) => {
118
- const status = configStatus(value.alias, value.value, other, side);
119
- return html `<tr class="row-${status}">
120
- <th><code>${value.alias}</code></th>
121
- <td><pre>${formatConfigValue(value.value)}</pre></td>
122
- </tr>`;
123
- })}
124
- </table>`}
125
- </uui-box>
126
- `;
127
- };
128
- uSyncDataTypePreviewElement.styles = css `
129
- :host {
130
- display: block;
131
- container-type: inline-size;
132
- }
133
-
134
- /* the two sides share rows (headline, info, editor, config), so the
135
- boxes line up across the sides. */
136
- .sides {
137
- display: grid;
138
- grid-template-columns: 1fr 1fr;
139
- grid-template-rows: auto auto auto 1fr;
140
- gap: var(--uui-size-space-4) var(--uui-size-space-5);
141
- }
142
-
143
- .side {
144
- display: grid;
145
- grid-row: span 4;
146
- grid-template-rows: subgrid;
147
- min-width: 0;
148
- }
149
-
150
- .side > :last-child {
151
- align-self: start;
152
- }
153
-
154
- @container (max-width: 760px) {
155
- .sides {
156
- grid-template-columns: 1fr;
157
- grid-template-rows: none;
158
- row-gap: var(--uui-size-layout-1);
159
- }
160
-
161
- .side {
162
- display: flex;
163
- flex-direction: column;
164
- gap: var(--uui-size-space-4);
165
- grid-row: auto;
166
- }
167
- }
168
-
169
- .side-headline {
170
- margin: 0;
171
- font-size: var(--uui-type-small-size);
172
- text-transform: uppercase;
173
- letter-spacing: 0.04em;
174
- color: var(--uui-color-text-alt);
175
- }
176
-
177
- .headline {
178
- display: flex;
179
- align-items: center;
180
- gap: var(--uui-size-space-3);
181
- }
182
-
183
- .meta {
184
- display: grid;
185
- grid-template-columns: max-content 1fr;
186
- align-items: baseline;
187
- gap: var(--uui-size-space-3) var(--uui-size-space-5);
188
- margin: 0;
189
- }
190
-
191
- .meta dt {
192
- font-weight: 700;
193
- }
194
-
195
- .meta dd {
196
- margin: 0;
197
- overflow-wrap: anywhere;
198
- }
199
-
200
- /* a changed field gets a soft highlight, spread with a shadow so the text doesn't move. */
201
- .changed {
202
- background: color-mix(in srgb, var(--uui-color-warning) 35%, transparent);
203
- border-radius: var(--uui-border-radius);
204
- box-shadow: 0 0 0 var(--uui-size-space-1)
205
- color-mix(in srgb, var(--uui-color-warning) 35%, transparent);
206
- }
207
-
208
- /* umb-property pads itself for a full workspace, tighten it up here. */
209
- umb-property {
210
- --uui-size-layout-1: var(--uui-size-space-2);
211
- }
212
-
213
- table {
214
- width: 100%;
215
- border-collapse: collapse;
216
- margin: calc(-1 * var(--uui-size-space-2)) 0;
217
- }
218
-
219
- tr + tr {
220
- border-top: 1px solid var(--uui-color-divider);
221
- }
222
-
223
- th,
224
- td {
225
- text-align: left;
226
- vertical-align: baseline;
227
- padding: var(--uui-size-space-3) var(--uui-size-space-3);
228
- }
229
-
230
- /* the alias column is as wide as the longest alias (up to 40%), so values sit beside their names. */
231
- th {
232
- width: 1%;
233
- max-width: 40%;
234
- white-space: nowrap;
235
- font-weight: 700;
236
- }
237
-
238
- th code,
239
- td pre {
240
- font-size: var(--uui-type-small-size);
241
- }
242
-
243
- td pre {
244
- margin: 0;
245
- white-space: pre-wrap;
246
- word-break: break-word;
247
- }
248
-
249
- /* marked with an inset bar rather than a border, so the content doesn't shift. */
250
- tr.row-changed {
251
- box-shadow: inset 3px 0 0 var(--uui-color-warning);
252
- background: color-mix(in srgb, var(--uui-color-warning) 10%, transparent);
253
- }
254
-
255
- tr.row-added {
256
- box-shadow: inset 3px 0 0 var(--uui-color-positive);
257
- background: color-mix(in srgb, var(--uui-color-positive) 6%, transparent);
258
- }
259
-
260
- tr.row-removed {
261
- box-shadow: inset 3px 0 0 var(--uui-color-danger);
262
- background: color-mix(in srgb, var(--uui-color-danger) 6%, transparent);
263
- }
264
-
265
- .empty {
266
- margin: 0;
267
- color: var(--uui-color-text-alt);
268
- }
269
- `;
270
- __decorate([
271
- property({ type: Object })
272
- ], uSyncDataTypePreviewElement.prototype, "preview", void 0);
273
- uSyncDataTypePreviewElement = __decorate([
274
- customElement('usync-data-type-preview')
275
- ], uSyncDataTypePreviewElement);
276
- export { uSyncDataTypePreviewElement };
277
- /**
278
- * how a config value on this side compares to the other side. an alias
279
- * missing from the current side is being added, one missing from the
280
- * incoming side is being removed.
281
- */
282
- function configStatus(alias, value, other, side) {
283
- if (!other)
284
- return 'unchanged';
285
- const match = other.values.find((v) => v.alias === alias);
286
- if (!match)
287
- return side === 'incoming' ? 'added' : 'removed';
288
- return deepEqual(value, match.value) ? 'unchanged' : 'changed';
289
- }
290
- function formatConfigValue(value) {
291
- return typeof value === 'string' ? value : prettyJson(value);
292
- }
293
- export default uSyncDataTypePreviewElement;
@@ -1,18 +0,0 @@
1
- import { type USyncPreviewContentType } from './content-type-layout.js';
2
- export type USyncPreviewStatus = 'added' | 'removed' | 'changed';
3
- export interface USyncPreviewItemChange {
4
- status: USyncPreviewStatus;
5
- /** the fields that differ (for a changed item) */
6
- fields: string[];
7
- }
8
- export interface USyncContentTypeDiff {
9
- /** top level fields (name, icon, compositions...) that differ */
10
- fields: Set<string>;
11
- properties: Map<string, USyncPreviewItemChange>;
12
- containers: Map<string, USyncPreviewItemChange>;
13
- }
14
- /**
15
- * compare two versions of a content type by key. Items only in the current
16
- * version are being removed, items only in the incoming version are being added.
17
- */
18
- export declare function diffContentTypes(current: USyncPreviewContentType | undefined, incoming: USyncPreviewContentType | undefined): USyncContentTypeDiff;
@@ -1,72 +0,0 @@
1
- import { deepEqual } from '../diff/deep-equal.js';
2
- import { compositionIds, } from './content-type-layout.js';
3
- const TYPE_FIELDS = [
4
- 'alias',
5
- 'name',
6
- 'description',
7
- 'icon',
8
- 'allowedAsRoot',
9
- 'variesByCulture',
10
- 'variesBySegment',
11
- 'isElement',
12
- 'collection',
13
- ];
14
- const PROPERTY_FIELDS = [
15
- 'alias',
16
- 'name',
17
- 'description',
18
- 'dataType',
19
- 'container',
20
- 'sortOrder',
21
- 'variesByCulture',
22
- 'variesBySegment',
23
- 'validation',
24
- 'appearance',
25
- ];
26
- const CONTAINER_FIELDS = ['name', 'type', 'parent', 'sortOrder'];
27
- /**
28
- * compare two versions of a content type by key. Items only in the current
29
- * version are being removed, items only in the incoming version are being added.
30
- */
31
- export function diffContentTypes(current, incoming) {
32
- const fields = new Set();
33
- if (current && incoming) {
34
- for (const field of TYPE_FIELDS) {
35
- if (!deepEqual(current[field], incoming[field]))
36
- fields.add(field);
37
- }
38
- if (!deepEqual([...compositionIds(current)].sort(), [...compositionIds(incoming)].sort())) {
39
- fields.add('compositions');
40
- }
41
- }
42
- return {
43
- fields,
44
- properties: diffById(current?.properties, incoming?.properties, PROPERTY_FIELDS),
45
- containers: diffById(current?.containers, incoming?.containers, CONTAINER_FIELDS),
46
- };
47
- }
48
- function diffById(current, incoming, fields) {
49
- const changes = new Map();
50
- // a whole type being added or removed is shown by its headline, don't mark every item.
51
- if (!current || !incoming)
52
- return changes;
53
- const before = new Map(current.map((item) => [item.id, item]));
54
- const after = new Map(incoming.map((item) => [item.id, item]));
55
- for (const [id, item] of before) {
56
- const other = after.get(id);
57
- if (!other) {
58
- changes.set(id, { status: 'removed', fields: [] });
59
- continue;
60
- }
61
- const changed = fields
62
- .filter((field) => !deepEqual(item[field], other[field]))
63
- .map(String);
64
- if (changed.length > 0)
65
- changes.set(id, { status: 'changed', fields: changed });
66
- }
67
- for (const id of after.keys()) {
68
- if (!before.has(id))
69
- changes.set(id, { status: 'added', fields: [] });
70
- }
71
- return changes;
72
- }
@@ -1,39 +0,0 @@
1
- import type { DocumentTypeResponseModel, MediaTypeResponseModel, MemberTypeResponseModel } from '../../api';
2
- /**
3
- * the shape document, media and member types share - enough to lay out
4
- * the editor view and compare two versions.
5
- */
6
- export type USyncPreviewContentType = DocumentTypeResponseModel | MediaTypeResponseModel | MemberTypeResponseModel;
7
- export type USyncPreviewProperty = USyncPreviewContentType['properties'][number];
8
- export type USyncPreviewContainer = USyncPreviewContentType['containers'][number];
9
- export interface USyncLayoutProperty {
10
- property: USyncPreviewProperty;
11
- /** name of the composition this property comes from (undefined when it's the type's own) */
12
- inheritedFrom?: string;
13
- }
14
- export interface USyncLayoutGroup {
15
- name: string;
16
- /** ids of every container merged into this group (own + compositions) */
17
- ids: string[];
18
- inherited: boolean;
19
- sortOrder: number;
20
- properties: USyncLayoutProperty[];
21
- }
22
- export interface USyncLayoutTab {
23
- name: string;
24
- ids: string[];
25
- inherited: boolean;
26
- sortOrder: number;
27
- /** properties placed directly on the tab, outside any group */
28
- properties: USyncLayoutProperty[];
29
- groups: USyncLayoutGroup[];
30
- }
31
- /** name used for the tab that holds groups which aren't inside a tab */
32
- export declare const USYNC_GENERIC_TAB = "";
33
- /** the ids of the types this one is composed of (or inherits from). */
34
- export declare function compositionIds(type: USyncPreviewContentType): string[];
35
- /**
36
- * build the tabs and groups an editor would see, merging in the containers
37
- * and properties from compositions the way the backoffice does (by name).
38
- */
39
- export declare function buildContentTypeLayout(type: USyncPreviewContentType, compositions: Map<string, USyncPreviewContentType>): USyncLayoutTab[];
@@ -1,100 +0,0 @@
1
- /** name used for the tab that holds groups which aren't inside a tab */
2
- export const USYNC_GENERIC_TAB = '';
3
- /** the ids of the types this one is composed of (or inherits from). */
4
- export function compositionIds(type) {
5
- return type.compositions.map((composition) => {
6
- if ('documentType' in composition)
7
- return composition.documentType.id;
8
- if ('mediaType' in composition)
9
- return composition.mediaType.id;
10
- return composition.memberType.id;
11
- });
12
- }
13
- /**
14
- * build the tabs and groups an editor would see, merging in the containers
15
- * and properties from compositions the way the backoffice does (by name).
16
- */
17
- export function buildContentTypeLayout(type, compositions) {
18
- const tabs = new Map();
19
- const tabFor = (name, id, sortOrder, inherited) => {
20
- let tab = tabs.get(name);
21
- if (!tab) {
22
- tab = { name, ids: [], inherited, sortOrder, properties: [], groups: [] };
23
- tabs.set(name, tab);
24
- }
25
- if (id)
26
- tab.ids.push(id);
27
- tab.inherited && (tab.inherited = inherited);
28
- return tab;
29
- };
30
- const groupFor = (tab, name, id, sortOrder, inherited) => {
31
- let group = tab.groups.find((g) => g.name === name);
32
- if (!group) {
33
- group = { name, ids: [], inherited, sortOrder, properties: [] };
34
- tab.groups.push(group);
35
- }
36
- group.ids.push(id);
37
- group.inherited && (group.inherited = inherited);
38
- return group;
39
- };
40
- for (const source of contributors(type, compositions)) {
41
- const inherited = source !== type;
42
- const containers = new Map(source.containers.map((c) => [c.id, c]));
43
- // where each container (and so each property) lands in the merged layout.
44
- const placed = new Map();
45
- for (const container of [...source.containers].sort(bySortOrder)) {
46
- if (container.type === 'Tab') {
47
- placed.set(container.id, tabFor(container.name ?? '', container.id, container.sortOrder, inherited));
48
- }
49
- }
50
- for (const container of [...source.containers].sort(bySortOrder)) {
51
- if (container.type !== 'Group')
52
- continue;
53
- const parent = container.parent ? containers.get(container.parent.id) : undefined;
54
- const tab = parent
55
- ? tabFor(parent.name ?? '', undefined, parent.sortOrder, inherited)
56
- : tabFor(USYNC_GENERIC_TAB, undefined, -1, inherited);
57
- placed.set(container.id, groupFor(tab, container.name ?? '', container.id, container.sortOrder, inherited));
58
- }
59
- for (const property of [...source.properties].sort(bySortOrder)) {
60
- const target = property.container ? placed.get(property.container.id) : undefined;
61
- const entry = { property, inheritedFrom: inherited ? source.name : undefined };
62
- if (target) {
63
- target.properties.push(entry);
64
- }
65
- else {
66
- tabFor(USYNC_GENERIC_TAB, undefined, -1, inherited).properties.push(entry);
67
- }
68
- }
69
- }
70
- const result = [...tabs.values()].sort(bySortOrder);
71
- for (const tab of result) {
72
- tab.groups.sort(bySortOrder);
73
- tab.properties.sort((a, b) => a.property.sortOrder - b.property.sortOrder);
74
- for (const group of tab.groups) {
75
- group.properties.sort((a, b) => a.property.sortOrder - b.property.sortOrder);
76
- }
77
- }
78
- return result.filter((tab) => tab.properties.length > 0 || tab.groups.length > 0);
79
- }
80
- /** the type itself first, then everything it is composed of (depth first, no repeats). */
81
- function contributors(type, compositions) {
82
- const seen = new Set();
83
- const out = [];
84
- const visit = (current) => {
85
- if (seen.has(current.id))
86
- return;
87
- seen.add(current.id);
88
- out.push(current);
89
- for (const id of compositionIds(current)) {
90
- const composition = compositions.get(id);
91
- if (composition)
92
- visit(composition);
93
- }
94
- };
95
- visit(type);
96
- return out;
97
- }
98
- function bySortOrder(a, b) {
99
- return a.sortOrder - b.sortOrder;
100
- }