@webiny/app-headless-cms 6.5.0-beta.5 → 6.5.0-beta.7

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 (23) hide show
  1. package/features/formModel/CmsFormModelBuilder.d.ts +1 -1
  2. package/features/formModel/CmsFormModelBuilder.js +1 -13
  3. package/features/formModel/CmsFormModelBuilder.js.map +1 -1
  4. package/features/formModel/CmsFormModelBuilder.test.js +141 -0
  5. package/features/formModel/CmsFormModelBuilder.test.js.map +1 -1
  6. package/features/formModel/CmsLayoutMapper.d.ts +8 -1
  7. package/features/formModel/CmsLayoutMapper.js +23 -1
  8. package/features/formModel/CmsLayoutMapper.js.map +1 -1
  9. package/features/formModel/mappers/DynamicZoneFieldMapper.js +3 -0
  10. package/features/formModel/mappers/DynamicZoneFieldMapper.js.map +1 -1
  11. package/features/formModel/mappers/ObjectFieldMapper.js +11 -6
  12. package/features/formModel/mappers/ObjectFieldMapper.js.map +1 -1
  13. package/package.json +20 -20
  14. package/presentation/contentEntries/form/ContentEntryFormPresenter.js +10 -6
  15. package/presentation/contentEntries/form/ContentEntryFormPresenter.js.map +1 -1
  16. package/presentation/contentEntries/form/ContentEntryFormPresenter.test.d.ts +1 -0
  17. package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js +110 -0
  18. package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js.map +1 -0
  19. package/presentation/contentEntries/singleEntry/SingleEntryPresenter.js +7 -4
  20. package/presentation/contentEntries/singleEntry/SingleEntryPresenter.js.map +1 -1
  21. package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.d.ts +1 -0
  22. package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js +122 -0
  23. package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js.map +1 -0
@@ -1,5 +1,5 @@
1
1
  import type { IFormModelConfig } from "@webiny/app-admin/features/formModel/abstractions.js";
2
- import { type CmsModel } from "../../types.js";
2
+ import type { CmsModel } from "../../types.js";
3
3
  import type { ICmsFieldTypeMapper, ICmsFormModelBuilder } from "./abstractions.js";
4
4
  import type { ICmsFieldRenderer } from "../../presentation/fieldRenderers/abstractions.js";
5
5
  declare class CmsFormModelBuilderImpl implements ICmsFormModelBuilder {
@@ -1,7 +1,6 @@
1
- import { isLayoutField } from "../../types.js";
2
1
  import { CmsFieldTypeMapper, CmsFormModelBuilder } from "./abstractions.js";
3
2
  import { CmsFieldRenderer } from "../../presentation/fieldRenderers/abstractions.js";
4
- import { mapCmsLayout } from "./CmsLayoutMapper.js";
3
+ import { collectFieldIds, mapCmsLayout } from "./CmsLayoutMapper.js";
5
4
  import { createBuiltInMappers } from "./mappers/index.js";
6
5
  import { applyFieldProps } from "./mappers/applyFieldProps.js";
7
6
  class CmsFormModelBuilderImpl {
@@ -82,17 +81,6 @@ const CmsFormModelBuilder_CmsFormModelBuilder = CmsFormModelBuilder.createImplem
82
81
  ]
83
82
  ]
84
83
  });
85
- function collectFieldIds(layout, idToFieldId) {
86
- const fieldIds = new Set();
87
- for (const row of layout)for (const cell of row)if ("string" == typeof cell) {
88
- const fieldId = idToFieldId.get(cell);
89
- if (fieldId) fieldIds.add(fieldId);
90
- } else if (isLayoutField(cell) && "tabs" === cell.type) {
91
- const tabsField = cell;
92
- for (const tab of tabsField.tabs)for (const id of collectFieldIds(tab.layout, idToFieldId))fieldIds.add(id);
93
- }
94
- return fieldIds;
95
- }
96
84
  export { CmsFormModelBuilder_CmsFormModelBuilder as CmsFormModelBuilder };
97
85
 
98
86
  //# sourceMappingURL=CmsFormModelBuilder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"features/formModel/CmsFormModelBuilder.js","sources":["../../../src/features/formModel/CmsFormModelBuilder.ts"],"sourcesContent":["import type {\n IFieldBuilder,\n IFieldBuilderRegistry,\n IFormModelConfig\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport {\n isLayoutField,\n type CmsEditorFieldsLayout,\n type CmsModel,\n type CmsModelField\n} from \"~/types.js\";\nimport type {\n ICmsFieldTypeMapper,\n ICmsFieldMapperContext,\n ICmsFormModelBuilder\n} from \"./abstractions.js\";\nimport { CmsFormModelBuilder as BuilderAbstraction } from \"./abstractions.js\";\nimport { CmsFieldTypeMapper } from \"./abstractions.js\";\nimport { CmsFieldRenderer } from \"~/presentation/fieldRenderers/abstractions.js\";\nimport type { ICmsFieldRenderer } from \"~/presentation/fieldRenderers/abstractions.js\";\nimport { mapCmsLayout } from \"./CmsLayoutMapper.js\";\nimport { createBuiltInMappers } from \"./mappers/index.js\";\nimport { applyFieldProps } from \"./mappers/applyFieldProps.js\";\n\nclass CmsFormModelBuilderImpl implements ICmsFormModelBuilder {\n private mappers: Map<string, ICmsFieldTypeMapper>;\n private rendererMap: Map<string, string>;\n private renderers: Map<string, ICmsFieldRenderer>;\n\n constructor(\n customMappers: ICmsFieldTypeMapper[] | undefined,\n fieldRenderers: ICmsFieldRenderer[] | undefined\n ) {\n this.mappers = new Map();\n for (const mapper of createBuiltInMappers()) {\n this.mappers.set(mapper.type, mapper);\n }\n for (const mapper of customMappers || []) {\n this.mappers.set(mapper.type, mapper);\n }\n\n this.rendererMap = new Map();\n this.renderers = new Map();\n for (const renderer of fieldRenderers || []) {\n this.rendererMap.set(renderer.rendererName, renderer.formRenderer);\n this.renderers.set(renderer.rendererName, renderer);\n }\n }\n\n build(model: CmsModel): IFormModelConfig {\n const context: ICmsFieldMapperContext = {\n model,\n rendererMap: this.rendererMap,\n mapField: (field, registry) => this.mapField(field, registry, context)\n };\n\n const idToFieldId = new Map<string, string>();\n for (const field of model.fields) {\n idToFieldId.set(field.id, field.fieldId);\n }\n\n const fieldIdsInLayout = model.layout ? collectFieldIds(model.layout, idToFieldId) : null;\n\n return {\n fields: registry => {\n const result: Record<string, IFieldBuilder> = {};\n for (const field of model.fields) {\n const builder = this.mapField(field, registry, context);\n if (fieldIdsInLayout && !fieldIdsInLayout.has(field.fieldId)) {\n builder.hidden();\n }\n result[field.fieldId] = builder;\n }\n return result;\n },\n layout: model.layout\n ? layout => mapCmsLayout(model.layout!, layout, idToFieldId)\n : undefined\n };\n }\n\n private mapField(\n modelField: CmsModelField,\n registry: IFieldBuilderRegistry,\n context: ICmsFieldMapperContext\n ): IFieldBuilder {\n const field = this.withCompatibleRenderer(modelField, context.model);\n const mapper = this.mappers.get(field.type);\n if (mapper) {\n return mapper.map(field, registry, context);\n }\n\n return applyFieldProps(registry.text(), field, context.rendererMap);\n }\n\n /**\n * A list renderer can't render a single value: `dateTimeInputs` calls `.map()` on a\n * stored date string and crashes the form. When a single-value field is configured with\n * a renderer that only accepts list fields, drop it so the field type's default\n * single-value renderer is used instead.\n */\n private withCompatibleRenderer(field: CmsModelField, model: CmsModel): CmsModelField {\n if (field.list || !field.renderer || typeof field.renderer !== \"object\") {\n return field;\n }\n const renderer = this.renderers.get(field.renderer.name);\n if (!renderer || renderer.canUse({ field, model })) {\n return field;\n }\n if (!renderer.canUse({ field: { ...field, list: true }, model })) {\n return field;\n }\n if (process.env.NODE_ENV === \"development\") {\n console.warn(\n `[CmsFormModelBuilder] Renderer \"${field.renderer.name}\" is for list fields; ` +\n `field \"${field.fieldId}\" is not a list and uses its default renderer instead.`\n );\n }\n // Mappers treat a missing renderer as \"use the field type's default\".\n const { renderer: _renderer, ...rest } = field;\n return rest as CmsModelField;\n }\n}\n\nexport const CmsFormModelBuilder = BuilderAbstraction.createImplementation({\n implementation: CmsFormModelBuilderImpl,\n dependencies: [\n [CmsFieldTypeMapper, { multiple: true, optional: true }],\n [CmsFieldRenderer, { multiple: true, optional: true }]\n ]\n});\n\nfunction collectFieldIds(\n layout: CmsEditorFieldsLayout,\n idToFieldId: Map<string, string>\n): Set<string> {\n const fieldIds = new Set<string>();\n\n for (const row of layout) {\n for (const cell of row) {\n if (typeof cell === \"string\") {\n const fieldId = idToFieldId.get(cell);\n if (fieldId) {\n fieldIds.add(fieldId);\n }\n } else if (isLayoutField(cell) && cell.type === \"tabs\") {\n const tabsField = cell as { tabs: Array<{ layout: CmsEditorFieldsLayout }> };\n for (const tab of tabsField.tabs) {\n for (const id of collectFieldIds(tab.layout, idToFieldId)) {\n fieldIds.add(id);\n }\n }\n }\n }\n }\n\n return fieldIds;\n}\n"],"names":["CmsFormModelBuilderImpl","customMappers","fieldRenderers","Map","mapper","createBuiltInMappers","renderer","model","context","field","registry","idToFieldId","fieldIdsInLayout","collectFieldIds","result","builder","layout","mapCmsLayout","undefined","modelField","applyFieldProps","process","console","_renderer","rest","CmsFormModelBuilder","BuilderAbstraction","CmsFieldTypeMapper","CmsFieldRenderer","fieldIds","Set","row","cell","fieldId","isLayoutField","tabsField","tab","id"],"mappings":"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"}
1
+ {"version":3,"file":"features/formModel/CmsFormModelBuilder.js","sources":["../../../src/features/formModel/CmsFormModelBuilder.ts"],"sourcesContent":["import type {\n IFieldBuilder,\n IFieldBuilderRegistry,\n IFormModelConfig\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsModel, CmsModelField } from \"~/types.js\";\nimport type {\n ICmsFieldTypeMapper,\n ICmsFieldMapperContext,\n ICmsFormModelBuilder\n} from \"./abstractions.js\";\nimport { CmsFormModelBuilder as BuilderAbstraction } from \"./abstractions.js\";\nimport { CmsFieldTypeMapper } from \"./abstractions.js\";\nimport { CmsFieldRenderer } from \"~/presentation/fieldRenderers/abstractions.js\";\nimport type { ICmsFieldRenderer } from \"~/presentation/fieldRenderers/abstractions.js\";\nimport { collectFieldIds, mapCmsLayout } from \"./CmsLayoutMapper.js\";\nimport { createBuiltInMappers } from \"./mappers/index.js\";\nimport { applyFieldProps } from \"./mappers/applyFieldProps.js\";\n\nclass CmsFormModelBuilderImpl implements ICmsFormModelBuilder {\n private mappers: Map<string, ICmsFieldTypeMapper>;\n private rendererMap: Map<string, string>;\n private renderers: Map<string, ICmsFieldRenderer>;\n\n constructor(\n customMappers: ICmsFieldTypeMapper[] | undefined,\n fieldRenderers: ICmsFieldRenderer[] | undefined\n ) {\n this.mappers = new Map();\n for (const mapper of createBuiltInMappers()) {\n this.mappers.set(mapper.type, mapper);\n }\n for (const mapper of customMappers || []) {\n this.mappers.set(mapper.type, mapper);\n }\n\n this.rendererMap = new Map();\n this.renderers = new Map();\n for (const renderer of fieldRenderers || []) {\n this.rendererMap.set(renderer.rendererName, renderer.formRenderer);\n this.renderers.set(renderer.rendererName, renderer);\n }\n }\n\n build(model: CmsModel): IFormModelConfig {\n const context: ICmsFieldMapperContext = {\n model,\n rendererMap: this.rendererMap,\n mapField: (field, registry) => this.mapField(field, registry, context)\n };\n\n const idToFieldId = new Map<string, string>();\n for (const field of model.fields) {\n idToFieldId.set(field.id, field.fieldId);\n }\n\n const fieldIdsInLayout = model.layout ? collectFieldIds(model.layout, idToFieldId) : null;\n\n return {\n fields: registry => {\n const result: Record<string, IFieldBuilder> = {};\n for (const field of model.fields) {\n const builder = this.mapField(field, registry, context);\n if (fieldIdsInLayout && !fieldIdsInLayout.has(field.fieldId)) {\n builder.hidden();\n }\n result[field.fieldId] = builder;\n }\n return result;\n },\n layout: model.layout\n ? layout => mapCmsLayout(model.layout!, layout, idToFieldId)\n : undefined\n };\n }\n\n private mapField(\n modelField: CmsModelField,\n registry: IFieldBuilderRegistry,\n context: ICmsFieldMapperContext\n ): IFieldBuilder {\n const field = this.withCompatibleRenderer(modelField, context.model);\n const mapper = this.mappers.get(field.type);\n if (mapper) {\n return mapper.map(field, registry, context);\n }\n\n return applyFieldProps(registry.text(), field, context.rendererMap);\n }\n\n /**\n * A list renderer can't render a single value: `dateTimeInputs` calls `.map()` on a\n * stored date string and crashes the form. When a single-value field is configured with\n * a renderer that only accepts list fields, drop it so the field type's default\n * single-value renderer is used instead.\n */\n private withCompatibleRenderer(field: CmsModelField, model: CmsModel): CmsModelField {\n if (field.list || !field.renderer || typeof field.renderer !== \"object\") {\n return field;\n }\n const renderer = this.renderers.get(field.renderer.name);\n if (!renderer || renderer.canUse({ field, model })) {\n return field;\n }\n if (!renderer.canUse({ field: { ...field, list: true }, model })) {\n return field;\n }\n if (process.env.NODE_ENV === \"development\") {\n console.warn(\n `[CmsFormModelBuilder] Renderer \"${field.renderer.name}\" is for list fields; ` +\n `field \"${field.fieldId}\" is not a list and uses its default renderer instead.`\n );\n }\n // Mappers treat a missing renderer as \"use the field type's default\".\n const { renderer: _renderer, ...rest } = field;\n return rest as CmsModelField;\n }\n}\n\nexport const CmsFormModelBuilder = BuilderAbstraction.createImplementation({\n implementation: CmsFormModelBuilderImpl,\n dependencies: [\n [CmsFieldTypeMapper, { multiple: true, optional: true }],\n [CmsFieldRenderer, { multiple: true, optional: true }]\n ]\n});\n"],"names":["CmsFormModelBuilderImpl","customMappers","fieldRenderers","Map","mapper","createBuiltInMappers","renderer","model","context","field","registry","idToFieldId","fieldIdsInLayout","collectFieldIds","result","builder","layout","mapCmsLayout","undefined","modelField","applyFieldProps","process","console","_renderer","rest","CmsFormModelBuilder","BuilderAbstraction","CmsFieldTypeMapper","CmsFieldRenderer"],"mappings":"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"}
@@ -107,5 +107,146 @@ describe("CmsFormModelBuilder renderer cardinality", ()=>{
107
107
  expect(item.children.get("openTime").vm.renderer).toBe("dateTimeInput");
108
108
  });
109
109
  });
110
+ const rowNames = (layout)=>layout.map((row)=>row.fields.map((f)=>f.name));
111
+ describe("CmsFormModelBuilder nested layouts", ()=>{
112
+ it("lays out a single object with the layout stored on the field", ()=>{
113
+ const form = createCmsForm([
114
+ field("address", "object", false, "object-accordion", {
115
+ settings: {
116
+ fields: [
117
+ field("street", "text", false, "text-input"),
118
+ field("city", "text", false, "text-input", {
119
+ id: "c1ty"
120
+ }),
121
+ field("zipCode", "text", false, "text-input")
122
+ ],
123
+ layout: [
124
+ [
125
+ "street"
126
+ ],
127
+ [
128
+ "c1ty",
129
+ "zipCode"
130
+ ]
131
+ ]
132
+ }
133
+ })
134
+ ]);
135
+ expect(rowNames(form.field("address").vm.layout)).toEqual([
136
+ [
137
+ "street"
138
+ ],
139
+ [
140
+ "city",
141
+ "zipCode"
142
+ ]
143
+ ]);
144
+ });
145
+ it("lays out every item of an object list", ()=>{
146
+ const form = createCmsForm([
147
+ field("facilityStats", "object", true, "objects-accordion", {
148
+ settings: {
149
+ fields: [
150
+ field("statValue", "text", false, "text-input"),
151
+ field("statLabel", "text", false, "text-input")
152
+ ],
153
+ layout: [
154
+ [
155
+ "statValue",
156
+ "statLabel"
157
+ ]
158
+ ]
159
+ }
160
+ })
161
+ ]);
162
+ form.setData({
163
+ facilityStats: [
164
+ {
165
+ statValue: "1",
166
+ statLabel: "a"
167
+ }
168
+ ]
169
+ });
170
+ const vm = form.field("facilityStats").vm;
171
+ expect(rowNames(vm.items[0].layout)).toEqual([
172
+ [
173
+ "statValue",
174
+ "statLabel"
175
+ ]
176
+ ]);
177
+ });
178
+ it("gives children missing from the stored layout a row of their own", ()=>{
179
+ const form = createCmsForm([
180
+ field("address", "object", false, "object-accordion", {
181
+ settings: {
182
+ fields: [
183
+ field("street", "text", false, "text-input"),
184
+ field("city", "text", false, "text-input"),
185
+ field("state", "text", false, "text-input")
186
+ ],
187
+ layout: [
188
+ [
189
+ "street",
190
+ "city"
191
+ ]
192
+ ]
193
+ }
194
+ })
195
+ ]);
196
+ expect(rowNames(form.field("address").vm.layout)).toEqual([
197
+ [
198
+ "street",
199
+ "city"
200
+ ],
201
+ [
202
+ "state"
203
+ ]
204
+ ]);
205
+ });
206
+ it("lays out a dynamic zone template with the template's layout", ()=>{
207
+ const form = createCmsForm([
208
+ field("blocks", "dynamicZone", true, "dynamicZone", {
209
+ settings: {
210
+ templates: [
211
+ {
212
+ id: "hero",
213
+ name: "Hero",
214
+ gqlTypeName: "Hero",
215
+ description: "",
216
+ icon: null,
217
+ fields: [
218
+ field("title", "text", false, "text-input"),
219
+ field("subtitle", "text", false, "text-input")
220
+ ],
221
+ layout: [
222
+ [
223
+ "title",
224
+ "subtitle"
225
+ ]
226
+ ],
227
+ validation: []
228
+ }
229
+ ]
230
+ }
231
+ })
232
+ ]);
233
+ form.setData({
234
+ blocks: [
235
+ {
236
+ _templateId: "hero",
237
+ title: "T",
238
+ subtitle: "S"
239
+ }
240
+ ]
241
+ });
242
+ const vm = form.field("blocks").vm;
243
+ expect(rowNames(vm.items[0].layout)).toEqual([
244
+ [
245
+ "title",
246
+ "subtitle"
247
+ ]
248
+ ]);
249
+ });
250
+ });
110
251
 
111
252
  //# sourceMappingURL=CmsFormModelBuilder.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"features/formModel/CmsFormModelBuilder.test.js","sources":["../../../src/features/formModel/CmsFormModelBuilder.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFeature } from \"@webiny/app-admin/features/formModel/feature.js\";\nimport { IdentityContextFeature } from \"@webiny/app-admin/features/security/IdentityContext/feature.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsFormModelFeature } from \"~/features/formModel/feature.js\";\nimport { DateTimeInputRenderer } from \"~/presentation/fieldTypes/renderers/dateTime/DateTimeInputRenderer.js\";\nimport { DateTimeInputsRenderer } from \"~/presentation/fieldTypes/renderers/dateTime/DateTimeInputsRenderer.js\";\nimport { TextInputRenderer } from \"~/presentation/fieldTypes/renderers/text/TextInputRenderer.js\";\nimport { TextInputsRenderer } from \"~/presentation/fieldTypes/renderers/text/TextInputsRenderer.js\";\n\nconst field = (fieldId: string, type: string, list: boolean, renderer: string, extra = {}) => ({\n id: fieldId,\n fieldId,\n type,\n label: fieldId,\n tags: [],\n list,\n renderer: { name: renderer, settings: {} },\n validation: [],\n listValidation: [],\n settings: {},\n predefinedValues: { enabled: false, values: [] },\n ...extra\n});\n\nfunction createCmsForm(fields: any[]) {\n const container = new Container();\n IdentityContextFeature.register(container);\n FormModelFeature.register(container);\n CmsFormModelFeature.register(container);\n container.register(DateTimeInputRenderer);\n container.register(DateTimeInputsRenderer);\n container.register(TextInputRenderer);\n container.register(TextInputsRenderer);\n\n const model = {\n modelId: \"test\",\n fields,\n layout: fields.map(f => [f.fieldId])\n } as any;\n const config = container.resolve(CmsFormModelBuilder).build(model);\n return container.resolve(FormModelFactory).create(config);\n}\n\ndescribe(\"CmsFormModelBuilder renderer cardinality\", () => {\n it(\"keeps renderers that match the field's cardinality\", () => {\n const form = createCmsForm([\n field(\"single\", \"datetime\", false, \"date-time-input\"),\n field(\"many\", \"datetime\", true, \"date-time-inputs\")\n ]);\n\n expect(form.field(\"single\").vm.renderer).toBe(\"dateTimeInput\");\n expect(form.field(\"many\").vm.renderer).toBe(\"dateTimeInputs\");\n });\n\n it(\"falls back to the single-value renderer when a list renderer is set on a non-list field\", () => {\n const form = createCmsForm([\n field(\"openTime\", \"datetime\", false, \"date-time-inputs\"),\n field(\"name\", \"text\", false, \"text-inputs\")\n ]);\n\n expect(form.field(\"openTime\").vm.renderer).toBe(\"dateTimeInput\");\n expect(form.field(\"name\").vm.renderer).toBe(\"textInput\");\n });\n\n it(\"keeps the list renderer for list datetime fields with a subtype\", () => {\n const form = createCmsForm(\n [\"date\", \"time\", \"dateTimeWithTimezone\", \"dateTimeWithoutTimezone\"].map(type =>\n field(type, \"datetime\", true, \"date-time-inputs\", { settings: { type } })\n )\n );\n\n expect(form.field(\"date\").vm.renderer).toBe(\"dateTimeInputs\");\n expect(form.field(\"time\").vm.renderer).toBe(\"dateTimeInputs\");\n expect(form.field(\"dateTimeWithTimezone\").vm.renderer).toBe(\"dateTimeInputs\");\n expect(form.field(\"dateTimeWithoutTimezone\").vm.renderer).toBe(\"dateTimeInputs\");\n });\n\n it(\"applies the fallback to fields nested in object lists\", () => {\n const form = createCmsForm([\n field(\"holidayHours\", \"object\", true, \"objects-accordion\", {\n settings: {\n fields: [field(\"openTime\", \"datetime\", false, \"date-time-inputs\")],\n layout: [[\"openTime\"]]\n }\n })\n ]);\n\n form.setData({ holidayHours: [{ openTime: \"2026-12-24T09:00:00.000Z\" }] });\n const item = (form.field(\"holidayHours\") as any).items[0];\n\n expect(item.children.get(\"openTime\").vm.renderer).toBe(\"dateTimeInput\");\n });\n});\n"],"names":["field","fieldId","type","list","renderer","extra","createCmsForm","fields","container","Container","IdentityContextFeature","FormModelFeature","CmsFormModelFeature","DateTimeInputRenderer","DateTimeInputsRenderer","TextInputRenderer","TextInputsRenderer","model","f","config","CmsFormModelBuilder","FormModelFactory","describe","it","form","expect","item"],"mappings":"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"}
1
+ {"version":3,"file":"features/formModel/CmsFormModelBuilder.test.js","sources":["../../../src/features/formModel/CmsFormModelBuilder.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFeature } from \"@webiny/app-admin/features/formModel/feature.js\";\nimport { IdentityContextFeature } from \"@webiny/app-admin/features/security/IdentityContext/feature.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsFormModelFeature } from \"~/features/formModel/feature.js\";\nimport { DateTimeInputRenderer } from \"~/presentation/fieldTypes/renderers/dateTime/DateTimeInputRenderer.js\";\nimport { DateTimeInputsRenderer } from \"~/presentation/fieldTypes/renderers/dateTime/DateTimeInputsRenderer.js\";\nimport { TextInputRenderer } from \"~/presentation/fieldTypes/renderers/text/TextInputRenderer.js\";\nimport { TextInputsRenderer } from \"~/presentation/fieldTypes/renderers/text/TextInputsRenderer.js\";\n\nconst field = (fieldId: string, type: string, list: boolean, renderer: string, extra = {}) => ({\n id: fieldId,\n fieldId,\n type,\n label: fieldId,\n tags: [],\n list,\n renderer: { name: renderer, settings: {} },\n validation: [],\n listValidation: [],\n settings: {},\n predefinedValues: { enabled: false, values: [] },\n ...extra\n});\n\nfunction createCmsForm(fields: any[]) {\n const container = new Container();\n IdentityContextFeature.register(container);\n FormModelFeature.register(container);\n CmsFormModelFeature.register(container);\n container.register(DateTimeInputRenderer);\n container.register(DateTimeInputsRenderer);\n container.register(TextInputRenderer);\n container.register(TextInputsRenderer);\n\n const model = {\n modelId: \"test\",\n fields,\n layout: fields.map(f => [f.fieldId])\n } as any;\n const config = container.resolve(CmsFormModelBuilder).build(model);\n return container.resolve(FormModelFactory).create(config);\n}\n\ndescribe(\"CmsFormModelBuilder renderer cardinality\", () => {\n it(\"keeps renderers that match the field's cardinality\", () => {\n const form = createCmsForm([\n field(\"single\", \"datetime\", false, \"date-time-input\"),\n field(\"many\", \"datetime\", true, \"date-time-inputs\")\n ]);\n\n expect(form.field(\"single\").vm.renderer).toBe(\"dateTimeInput\");\n expect(form.field(\"many\").vm.renderer).toBe(\"dateTimeInputs\");\n });\n\n it(\"falls back to the single-value renderer when a list renderer is set on a non-list field\", () => {\n const form = createCmsForm([\n field(\"openTime\", \"datetime\", false, \"date-time-inputs\"),\n field(\"name\", \"text\", false, \"text-inputs\")\n ]);\n\n expect(form.field(\"openTime\").vm.renderer).toBe(\"dateTimeInput\");\n expect(form.field(\"name\").vm.renderer).toBe(\"textInput\");\n });\n\n it(\"keeps the list renderer for list datetime fields with a subtype\", () => {\n const form = createCmsForm(\n [\"date\", \"time\", \"dateTimeWithTimezone\", \"dateTimeWithoutTimezone\"].map(type =>\n field(type, \"datetime\", true, \"date-time-inputs\", { settings: { type } })\n )\n );\n\n expect(form.field(\"date\").vm.renderer).toBe(\"dateTimeInputs\");\n expect(form.field(\"time\").vm.renderer).toBe(\"dateTimeInputs\");\n expect(form.field(\"dateTimeWithTimezone\").vm.renderer).toBe(\"dateTimeInputs\");\n expect(form.field(\"dateTimeWithoutTimezone\").vm.renderer).toBe(\"dateTimeInputs\");\n });\n\n it(\"applies the fallback to fields nested in object lists\", () => {\n const form = createCmsForm([\n field(\"holidayHours\", \"object\", true, \"objects-accordion\", {\n settings: {\n fields: [field(\"openTime\", \"datetime\", false, \"date-time-inputs\")],\n layout: [[\"openTime\"]]\n }\n })\n ]);\n\n form.setData({ holidayHours: [{ openTime: \"2026-12-24T09:00:00.000Z\" }] });\n const item = (form.field(\"holidayHours\") as any).items[0];\n\n expect(item.children.get(\"openTime\").vm.renderer).toBe(\"dateTimeInput\");\n });\n});\n\nconst rowNames = (layout: any[]) => layout.map(row => row.fields.map((f: any) => f.name));\n\ndescribe(\"CmsFormModelBuilder nested layouts\", () => {\n it(\"lays out a single object with the layout stored on the field\", () => {\n const form = createCmsForm([\n field(\"address\", \"object\", false, \"object-accordion\", {\n settings: {\n fields: [\n field(\"street\", \"text\", false, \"text-input\"),\n // Fields created in the model editor have an `id` that differs from `fieldId`.\n field(\"city\", \"text\", false, \"text-input\", { id: \"c1ty\" }),\n field(\"zipCode\", \"text\", false, \"text-input\")\n ],\n layout: [[\"street\"], [\"c1ty\", \"zipCode\"]]\n }\n })\n ]);\n\n expect(rowNames((form.field(\"address\").vm as any).layout)).toEqual([\n [\"street\"],\n [\"city\", \"zipCode\"]\n ]);\n });\n\n it(\"lays out every item of an object list\", () => {\n const form = createCmsForm([\n field(\"facilityStats\", \"object\", true, \"objects-accordion\", {\n settings: {\n fields: [\n field(\"statValue\", \"text\", false, \"text-input\"),\n field(\"statLabel\", \"text\", false, \"text-input\")\n ],\n layout: [[\"statValue\", \"statLabel\"]]\n }\n })\n ]);\n\n form.setData({ facilityStats: [{ statValue: \"1\", statLabel: \"a\" }] });\n const vm = form.field(\"facilityStats\").vm as any;\n\n expect(rowNames(vm.items[0].layout)).toEqual([[\"statValue\", \"statLabel\"]]);\n });\n\n it(\"gives children missing from the stored layout a row of their own\", () => {\n const form = createCmsForm([\n field(\"address\", \"object\", false, \"object-accordion\", {\n settings: {\n fields: [\n field(\"street\", \"text\", false, \"text-input\"),\n field(\"city\", \"text\", false, \"text-input\"),\n field(\"state\", \"text\", false, \"text-input\")\n ],\n layout: [[\"street\", \"city\"]]\n }\n })\n ]);\n\n expect(rowNames((form.field(\"address\").vm as any).layout)).toEqual([\n [\"street\", \"city\"],\n [\"state\"]\n ]);\n });\n\n it(\"lays out a dynamic zone template with the template's layout\", () => {\n const form = createCmsForm([\n field(\"blocks\", \"dynamicZone\", true, \"dynamicZone\", {\n settings: {\n templates: [\n {\n id: \"hero\",\n name: \"Hero\",\n gqlTypeName: \"Hero\",\n description: \"\",\n icon: null,\n fields: [\n field(\"title\", \"text\", false, \"text-input\"),\n field(\"subtitle\", \"text\", false, \"text-input\")\n ],\n layout: [[\"title\", \"subtitle\"]],\n validation: []\n }\n ]\n }\n })\n ]);\n\n form.setData({ blocks: [{ _templateId: \"hero\", title: \"T\", subtitle: \"S\" }] });\n const vm = form.field(\"blocks\").vm as any;\n\n expect(rowNames(vm.items[0].layout)).toEqual([[\"title\", \"subtitle\"]]);\n });\n});\n"],"names":["field","fieldId","type","list","renderer","extra","createCmsForm","fields","container","Container","IdentityContextFeature","FormModelFeature","CmsFormModelFeature","DateTimeInputRenderer","DateTimeInputsRenderer","TextInputRenderer","TextInputsRenderer","model","f","config","CmsFormModelBuilder","FormModelFactory","describe","it","form","expect","item","rowNames","layout","row","vm"],"mappings":"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"}
@@ -1,3 +1,10 @@
1
- import { type CmsEditorFieldsLayout } from "../../types.js";
1
+ import { type CmsEditorFieldsLayout, type CmsModelField } from "../../types.js";
2
2
  import type { ILayoutBuilder, ILayoutNodeBuilder } from "@webiny/app-admin/features/formModel/abstractions.js";
3
3
  export declare function mapCmsLayout(cmsLayout: CmsEditorFieldsLayout, layoutBuilder: ILayoutBuilder, idToFieldId: Map<string, string>): ILayoutNodeBuilder[];
4
+ export declare function collectFieldIds(layout: CmsEditorFieldsLayout, idToFieldId: Map<string, string>): Set<string>;
5
+ /**
6
+ * Maps the layout stored on an object field or a dynamic zone template. Returns `undefined`
7
+ * when there is no layout, so the form falls back to one row per child. Children missing
8
+ * from the layout get a row of their own at the end, so a stale layout can't hide them.
9
+ */
10
+ export declare function mapCmsNestedLayout(cmsLayout: CmsEditorFieldsLayout | undefined, fields: CmsModelField[]): ((layoutBuilder: ILayoutBuilder) => ILayoutNodeBuilder[]) | undefined;
@@ -19,6 +19,28 @@ function mapCmsLayout(cmsLayout, layoutBuilder, idToFieldId) {
19
19
  }
20
20
  return nodes;
21
21
  }
22
+ function collectFieldIds(layout, idToFieldId) {
23
+ const fieldIds = new Set();
24
+ for (const row of layout)for (const cell of row)if ("string" == typeof cell) {
25
+ const fieldId = idToFieldId.get(cell);
26
+ if (fieldId) fieldIds.add(fieldId);
27
+ } else if (isLayoutField(cell) && "tabs" === cell.type) {
28
+ const tabsField = cell;
29
+ for (const tab of tabsField.tabs)for (const id of collectFieldIds(tab.layout, idToFieldId))fieldIds.add(id);
30
+ }
31
+ return fieldIds;
32
+ }
33
+ function mapCmsNestedLayout(cmsLayout, fields) {
34
+ if (!cmsLayout || 0 === cmsLayout.length) return;
35
+ const idToFieldId = new Map();
36
+ for (const field of fields)idToFieldId.set(field.id, field.fieldId);
37
+ const fieldIdsInLayout = collectFieldIds(cmsLayout, idToFieldId);
38
+ return (layoutBuilder)=>{
39
+ const nodes = mapCmsLayout(cmsLayout, layoutBuilder, idToFieldId);
40
+ for (const field of fields)if (!fieldIdsInLayout.has(field.fieldId)) nodes.push(layoutBuilder.row(field.fieldId));
41
+ return nodes;
42
+ };
43
+ }
22
44
  function mapLayoutField(field, layoutBuilder, idToFieldId) {
23
45
  switch(field.type){
24
46
  case "separator":
@@ -83,6 +105,6 @@ function mapAlert(field, layoutBuilder) {
83
105
  })));
84
106
  return builder;
85
107
  }
86
- export { mapCmsLayout };
108
+ export { collectFieldIds, mapCmsLayout, mapCmsNestedLayout };
87
109
 
88
110
  //# sourceMappingURL=CmsLayoutMapper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"features/formModel/CmsLayoutMapper.js","sources":["../../../src/features/formModel/CmsLayoutMapper.ts"],"sourcesContent":["import {\n isLayoutField,\n type CmsEditorFieldsLayout,\n type CmsLayoutField,\n type CmsSeparatorLayoutField,\n type CmsTabLayoutField,\n type CmsAlertLayoutField\n} from \"~/types.js\";\nimport type {\n ILayoutBuilder,\n ILayoutNodeBuilder\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\n\nexport function mapCmsLayout(\n cmsLayout: CmsEditorFieldsLayout,\n layoutBuilder: ILayoutBuilder,\n idToFieldId: Map<string, string>\n): ILayoutNodeBuilder[] {\n const nodes: ILayoutNodeBuilder[] = [];\n\n for (const row of cmsLayout) {\n if (row.length === 0) {\n continue;\n }\n\n const firstCell = row[0];\n\n if (row.length === 1 && isLayoutField(firstCell)) {\n const layoutField = firstCell as CmsLayoutField;\n const node = mapLayoutField(layoutField, layoutBuilder, idToFieldId);\n if (node) {\n nodes.push(node);\n }\n continue;\n }\n\n const fieldIds: string[] = [];\n for (const cell of row) {\n if (typeof cell === \"string\") {\n const fieldId = idToFieldId.get(cell);\n if (fieldId) {\n fieldIds.push(fieldId);\n }\n }\n }\n\n if (fieldIds.length > 0) {\n nodes.push(layoutBuilder.row(...fieldIds));\n }\n }\n\n return nodes;\n}\n\nfunction mapLayoutField(\n field: CmsLayoutField,\n layoutBuilder: ILayoutBuilder,\n idToFieldId: Map<string, string>\n): ILayoutNodeBuilder | null {\n switch (field.type) {\n case \"separator\":\n return mapSeparator(field as CmsSeparatorLayoutField, layoutBuilder);\n\n case \"tabs\":\n return mapTabs(field as CmsTabLayoutField, layoutBuilder, idToFieldId);\n\n case \"alert\":\n return mapAlert(field as CmsAlertLayoutField, layoutBuilder);\n\n default:\n return null;\n }\n}\n\nfunction mapSeparator(\n field: CmsSeparatorLayoutField,\n layoutBuilder: ILayoutBuilder\n): ILayoutNodeBuilder {\n const builder = layoutBuilder.separator();\n if (field.label) {\n builder.title(field.label);\n }\n if (field.description) {\n builder.description(field.description);\n }\n if (field.rules) {\n builder.rules(\n field.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n return builder;\n}\n\nfunction mapTabs(\n field: CmsTabLayoutField,\n layoutBuilder: ILayoutBuilder,\n idToFieldId: Map<string, string>\n): ILayoutNodeBuilder {\n const tabsBuilder = layoutBuilder.tabs(field.id);\n\n for (const tab of field.tabs) {\n tabsBuilder.tab(tab.id, t => {\n t.label(tab.label);\n if (tab.icon) {\n t.icon({ type: \"icon\", name: tab.icon });\n }\n t.layout(l => mapCmsLayout(tab.layout, l, idToFieldId));\n if (tab.rules) {\n t.rules(\n tab.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n });\n }\n\n if (field.rules) {\n tabsBuilder.rules(\n field.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n\n return tabsBuilder;\n}\n\nfunction mapAlert(field: CmsAlertLayoutField, layoutBuilder: ILayoutBuilder): ILayoutNodeBuilder {\n const builder = layoutBuilder.alert();\n if (field.label) {\n builder.message(field.label);\n }\n if (field.alertType) {\n builder.alertType(field.alertType);\n }\n if (field.rules) {\n builder.rules(\n field.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n return builder;\n}\n"],"names":["mapCmsLayout","cmsLayout","layoutBuilder","idToFieldId","nodes","row","firstCell","isLayoutField","layoutField","node","mapLayoutField","fieldIds","cell","fieldId","field","mapSeparator","mapTabs","mapAlert","builder","r","tabsBuilder","tab","t","l"],"mappings":"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"}
1
+ {"version":3,"file":"features/formModel/CmsLayoutMapper.js","sources":["../../../src/features/formModel/CmsLayoutMapper.ts"],"sourcesContent":["import {\n isLayoutField,\n type CmsEditorFieldsLayout,\n type CmsLayoutField,\n type CmsModelField,\n type CmsSeparatorLayoutField,\n type CmsTabLayoutField,\n type CmsAlertLayoutField\n} from \"~/types.js\";\nimport type {\n ILayoutBuilder,\n ILayoutNodeBuilder\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\n\nexport function mapCmsLayout(\n cmsLayout: CmsEditorFieldsLayout,\n layoutBuilder: ILayoutBuilder,\n idToFieldId: Map<string, string>\n): ILayoutNodeBuilder[] {\n const nodes: ILayoutNodeBuilder[] = [];\n\n for (const row of cmsLayout) {\n if (row.length === 0) {\n continue;\n }\n\n const firstCell = row[0];\n\n if (row.length === 1 && isLayoutField(firstCell)) {\n const layoutField = firstCell as CmsLayoutField;\n const node = mapLayoutField(layoutField, layoutBuilder, idToFieldId);\n if (node) {\n nodes.push(node);\n }\n continue;\n }\n\n const fieldIds: string[] = [];\n for (const cell of row) {\n if (typeof cell === \"string\") {\n const fieldId = idToFieldId.get(cell);\n if (fieldId) {\n fieldIds.push(fieldId);\n }\n }\n }\n\n if (fieldIds.length > 0) {\n nodes.push(layoutBuilder.row(...fieldIds));\n }\n }\n\n return nodes;\n}\n\nexport function collectFieldIds(\n layout: CmsEditorFieldsLayout,\n idToFieldId: Map<string, string>\n): Set<string> {\n const fieldIds = new Set<string>();\n\n for (const row of layout) {\n for (const cell of row) {\n if (typeof cell === \"string\") {\n const fieldId = idToFieldId.get(cell);\n if (fieldId) {\n fieldIds.add(fieldId);\n }\n } else if (isLayoutField(cell) && cell.type === \"tabs\") {\n const tabsField = cell as { tabs: Array<{ layout: CmsEditorFieldsLayout }> };\n for (const tab of tabsField.tabs) {\n for (const id of collectFieldIds(tab.layout, idToFieldId)) {\n fieldIds.add(id);\n }\n }\n }\n }\n }\n\n return fieldIds;\n}\n\n/**\n * Maps the layout stored on an object field or a dynamic zone template. Returns `undefined`\n * when there is no layout, so the form falls back to one row per child. Children missing\n * from the layout get a row of their own at the end, so a stale layout can't hide them.\n */\nexport function mapCmsNestedLayout(\n cmsLayout: CmsEditorFieldsLayout | undefined,\n fields: CmsModelField[]\n): ((layoutBuilder: ILayoutBuilder) => ILayoutNodeBuilder[]) | undefined {\n if (!cmsLayout || cmsLayout.length === 0) {\n return undefined;\n }\n\n const idToFieldId = new Map<string, string>();\n for (const field of fields) {\n idToFieldId.set(field.id, field.fieldId);\n }\n\n const fieldIdsInLayout = collectFieldIds(cmsLayout, idToFieldId);\n\n return layoutBuilder => {\n const nodes = mapCmsLayout(cmsLayout, layoutBuilder, idToFieldId);\n for (const field of fields) {\n if (!fieldIdsInLayout.has(field.fieldId)) {\n nodes.push(layoutBuilder.row(field.fieldId));\n }\n }\n return nodes;\n };\n}\n\nfunction mapLayoutField(\n field: CmsLayoutField,\n layoutBuilder: ILayoutBuilder,\n idToFieldId: Map<string, string>\n): ILayoutNodeBuilder | null {\n switch (field.type) {\n case \"separator\":\n return mapSeparator(field as CmsSeparatorLayoutField, layoutBuilder);\n\n case \"tabs\":\n return mapTabs(field as CmsTabLayoutField, layoutBuilder, idToFieldId);\n\n case \"alert\":\n return mapAlert(field as CmsAlertLayoutField, layoutBuilder);\n\n default:\n return null;\n }\n}\n\nfunction mapSeparator(\n field: CmsSeparatorLayoutField,\n layoutBuilder: ILayoutBuilder\n): ILayoutNodeBuilder {\n const builder = layoutBuilder.separator();\n if (field.label) {\n builder.title(field.label);\n }\n if (field.description) {\n builder.description(field.description);\n }\n if (field.rules) {\n builder.rules(\n field.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n return builder;\n}\n\nfunction mapTabs(\n field: CmsTabLayoutField,\n layoutBuilder: ILayoutBuilder,\n idToFieldId: Map<string, string>\n): ILayoutNodeBuilder {\n const tabsBuilder = layoutBuilder.tabs(field.id);\n\n for (const tab of field.tabs) {\n tabsBuilder.tab(tab.id, t => {\n t.label(tab.label);\n if (tab.icon) {\n t.icon({ type: \"icon\", name: tab.icon });\n }\n t.layout(l => mapCmsLayout(tab.layout, l, idToFieldId));\n if (tab.rules) {\n t.rules(\n tab.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n });\n }\n\n if (field.rules) {\n tabsBuilder.rules(\n field.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n\n return tabsBuilder;\n}\n\nfunction mapAlert(field: CmsAlertLayoutField, layoutBuilder: ILayoutBuilder): ILayoutNodeBuilder {\n const builder = layoutBuilder.alert();\n if (field.label) {\n builder.message(field.label);\n }\n if (field.alertType) {\n builder.alertType(field.alertType);\n }\n if (field.rules) {\n builder.rules(\n field.rules.map(r => ({\n type: r.type,\n target: r.target,\n operator: r.operator,\n value: r.value ?? null,\n action: r.action as \"hide\" | \"disable\"\n }))\n );\n }\n return builder;\n}\n"],"names":["mapCmsLayout","cmsLayout","layoutBuilder","idToFieldId","nodes","row","firstCell","isLayoutField","layoutField","node","mapLayoutField","fieldIds","cell","fieldId","collectFieldIds","layout","Set","tabsField","tab","id","mapCmsNestedLayout","fields","Map","field","fieldIdsInLayout","mapSeparator","mapTabs","mapAlert","builder","r","tabsBuilder","t","l"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { applyFieldProps } from "./applyFieldProps.js";
2
+ import { mapCmsNestedLayout } from "../CmsLayoutMapper.js";
2
3
  import { generateAlphaNumericLowerCaseId } from "@webiny/utils/generateId";
3
4
  class DynamicZoneFieldMapper {
4
5
  map(field, registry, context) {
@@ -21,6 +22,8 @@ class DynamicZoneFieldMapper {
21
22
  if (template.fields && template.fields.length > 0) for (const child of template.fields)result[child.fieldId] = context.mapField(child, childRegistry);
22
23
  return result;
23
24
  });
25
+ const layout = mapCmsNestedLayout(template.layout, template.fields ?? []);
26
+ if (layout) t.layout(layout);
24
27
  });
25
28
  return applyFieldProps(builder, field, context.rendererMap);
26
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"features/formModel/mappers/DynamicZoneFieldMapper.js","sources":["../../../../src/features/formModel/mappers/DynamicZoneFieldMapper.ts"],"sourcesContent":["import type { ICmsFieldTypeMapper, ICmsFieldMapperContext } from \"../abstractions.js\";\nimport type {\n IFieldBuilder,\n IFieldBuilderRegistry,\n IObjectFieldBuilder\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsModelField, CmsDynamicZoneTemplate } from \"~/types.js\";\nimport { applyFieldProps } from \"./applyFieldProps.js\";\nimport type { ITemplateIcon } from \"@webiny/app-admin/features/formModel/index.js\";\nimport { generateAlphaNumericLowerCaseId } from \"@webiny/utils/generateId\";\n\nexport class DynamicZoneFieldMapper implements ICmsFieldTypeMapper {\n readonly type = \"dynamicZone\";\n\n map(\n field: CmsModelField,\n registry: IFieldBuilderRegistry,\n context: ICmsFieldMapperContext\n ): IFieldBuilder {\n const builder = registry.object() as IObjectFieldBuilder;\n\n const templates = field.settings?.templates as CmsDynamicZoneTemplate[] | undefined;\n if (templates) {\n for (const template of templates) {\n builder.template(template.id, t => {\n t.label(template.name);\n if (template.description) {\n t.description(template.description);\n }\n if (template.icon) {\n const icon = template.icon;\n if (typeof icon === \"object\" && \"name\" in icon) {\n t.icon(icon as ITemplateIcon);\n } else if (typeof icon === \"string\") {\n t.icon({ type: \"icon\", name: icon });\n }\n }\n t.fields(childRegistry => {\n const result: Record<string, IFieldBuilder> = {};\n result[\"_id\"] = childRegistry\n .text()\n .hidden()\n .defaultValue(() => generateAlphaNumericLowerCaseId(12))\n .cloneValue(() => generateAlphaNumericLowerCaseId(12));\n if (template.fields && template.fields.length > 0) {\n for (const child of template.fields) {\n result[child.fieldId] = context.mapField(child, childRegistry);\n }\n }\n return result;\n });\n });\n }\n }\n\n return applyFieldProps(builder, field, context.rendererMap);\n }\n}\n"],"names":["DynamicZoneFieldMapper","field","registry","context","builder","templates","template","t","icon","childRegistry","result","generateAlphaNumericLowerCaseId","child","applyFieldProps"],"mappings":";;AAWO,MAAMA;IAGT,IACIC,KAAoB,EACpBC,QAA+B,EAC/BC,OAA+B,EAClB;QACb,MAAMC,UAAUF,SAAS,MAAM;QAE/B,MAAMG,YAAYJ,MAAM,QAAQ,EAAE;QAClC,IAAII,WACA,KAAK,MAAMC,YAAYD,UACnBD,QAAQ,QAAQ,CAACE,SAAS,EAAE,EAAEC,CAAAA;YAC1BA,EAAE,KAAK,CAACD,SAAS,IAAI;YACrB,IAAIA,SAAS,WAAW,EACpBC,EAAE,WAAW,CAACD,SAAS,WAAW;YAEtC,IAAIA,SAAS,IAAI,EAAE;gBACf,MAAME,OAAOF,SAAS,IAAI;gBAC1B,IAAI,AAAgB,YAAhB,OAAOE,QAAqB,UAAUA,MACtCD,EAAE,IAAI,CAACC;qBACJ,IAAI,AAAgB,YAAhB,OAAOA,MACdD,EAAE,IAAI,CAAC;oBAAE,MAAM;oBAAQ,MAAMC;gBAAK;YAE1C;YACAD,EAAE,MAAM,CAACE,CAAAA;gBACL,MAAMC,SAAwC,CAAC;gBAC/CA,MAAM,CAAC,MAAM,GAAGD,cACX,IAAI,GACJ,MAAM,GACN,YAAY,CAAC,IAAME,gCAAgC,KACnD,UAAU,CAAC,IAAMA,gCAAgC;gBACtD,IAAIL,SAAS,MAAM,IAAIA,SAAS,MAAM,CAAC,MAAM,GAAG,GAC5C,KAAK,MAAMM,SAASN,SAAS,MAAM,CAC/BI,MAAM,CAACE,MAAM,OAAO,CAAC,GAAGT,QAAQ,QAAQ,CAACS,OAAOH;gBAGxD,OAAOC;YACX;QACJ;QAIR,OAAOG,gBAAgBT,SAASH,OAAOE,QAAQ,WAAW;IAC9D;;aA5CS,IAAI,GAAG;;AA6CpB"}
1
+ {"version":3,"file":"features/formModel/mappers/DynamicZoneFieldMapper.js","sources":["../../../../src/features/formModel/mappers/DynamicZoneFieldMapper.ts"],"sourcesContent":["import type { ICmsFieldTypeMapper, ICmsFieldMapperContext } from \"../abstractions.js\";\nimport type {\n IFieldBuilder,\n IFieldBuilderRegistry,\n IObjectFieldBuilder\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsModelField, CmsDynamicZoneTemplate } from \"~/types.js\";\nimport { applyFieldProps } from \"./applyFieldProps.js\";\nimport { mapCmsNestedLayout } from \"../CmsLayoutMapper.js\";\nimport type { ITemplateIcon } from \"@webiny/app-admin/features/formModel/index.js\";\nimport { generateAlphaNumericLowerCaseId } from \"@webiny/utils/generateId\";\n\nexport class DynamicZoneFieldMapper implements ICmsFieldTypeMapper {\n readonly type = \"dynamicZone\";\n\n map(\n field: CmsModelField,\n registry: IFieldBuilderRegistry,\n context: ICmsFieldMapperContext\n ): IFieldBuilder {\n const builder = registry.object() as IObjectFieldBuilder;\n\n const templates = field.settings?.templates as CmsDynamicZoneTemplate[] | undefined;\n if (templates) {\n for (const template of templates) {\n builder.template(template.id, t => {\n t.label(template.name);\n if (template.description) {\n t.description(template.description);\n }\n if (template.icon) {\n const icon = template.icon;\n if (typeof icon === \"object\" && \"name\" in icon) {\n t.icon(icon as ITemplateIcon);\n } else if (typeof icon === \"string\") {\n t.icon({ type: \"icon\", name: icon });\n }\n }\n t.fields(childRegistry => {\n const result: Record<string, IFieldBuilder> = {};\n result[\"_id\"] = childRegistry\n .text()\n .hidden()\n .defaultValue(() => generateAlphaNumericLowerCaseId(12))\n .cloneValue(() => generateAlphaNumericLowerCaseId(12));\n if (template.fields && template.fields.length > 0) {\n for (const child of template.fields) {\n result[child.fieldId] = context.mapField(child, childRegistry);\n }\n }\n return result;\n });\n const layout = mapCmsNestedLayout(template.layout, template.fields ?? []);\n if (layout) {\n t.layout(layout);\n }\n });\n }\n }\n\n return applyFieldProps(builder, field, context.rendererMap);\n }\n}\n"],"names":["DynamicZoneFieldMapper","field","registry","context","builder","templates","template","t","icon","childRegistry","result","generateAlphaNumericLowerCaseId","child","layout","mapCmsNestedLayout","applyFieldProps"],"mappings":";;;AAYO,MAAMA;IAGT,IACIC,KAAoB,EACpBC,QAA+B,EAC/BC,OAA+B,EAClB;QACb,MAAMC,UAAUF,SAAS,MAAM;QAE/B,MAAMG,YAAYJ,MAAM,QAAQ,EAAE;QAClC,IAAII,WACA,KAAK,MAAMC,YAAYD,UACnBD,QAAQ,QAAQ,CAACE,SAAS,EAAE,EAAEC,CAAAA;YAC1BA,EAAE,KAAK,CAACD,SAAS,IAAI;YACrB,IAAIA,SAAS,WAAW,EACpBC,EAAE,WAAW,CAACD,SAAS,WAAW;YAEtC,IAAIA,SAAS,IAAI,EAAE;gBACf,MAAME,OAAOF,SAAS,IAAI;gBAC1B,IAAI,AAAgB,YAAhB,OAAOE,QAAqB,UAAUA,MACtCD,EAAE,IAAI,CAACC;qBACJ,IAAI,AAAgB,YAAhB,OAAOA,MACdD,EAAE,IAAI,CAAC;oBAAE,MAAM;oBAAQ,MAAMC;gBAAK;YAE1C;YACAD,EAAE,MAAM,CAACE,CAAAA;gBACL,MAAMC,SAAwC,CAAC;gBAC/CA,MAAM,CAAC,MAAM,GAAGD,cACX,IAAI,GACJ,MAAM,GACN,YAAY,CAAC,IAAME,gCAAgC,KACnD,UAAU,CAAC,IAAMA,gCAAgC;gBACtD,IAAIL,SAAS,MAAM,IAAIA,SAAS,MAAM,CAAC,MAAM,GAAG,GAC5C,KAAK,MAAMM,SAASN,SAAS,MAAM,CAC/BI,MAAM,CAACE,MAAM,OAAO,CAAC,GAAGT,QAAQ,QAAQ,CAACS,OAAOH;gBAGxD,OAAOC;YACX;YACA,MAAMG,SAASC,mBAAmBR,SAAS,MAAM,EAAEA,SAAS,MAAM,IAAI,EAAE;YACxE,IAAIO,QACAN,EAAE,MAAM,CAACM;QAEjB;QAIR,OAAOE,gBAAgBX,SAASH,OAAOE,QAAQ,WAAW;IAC9D;;aAhDS,IAAI,GAAG;;AAiDpB"}
@@ -1,15 +1,20 @@
1
1
  import { applyFieldProps } from "./applyFieldProps.js";
2
+ import { mapCmsNestedLayout } from "../CmsLayoutMapper.js";
2
3
  import { generateAlphaNumericLowerCaseId } from "@webiny/utils/generateId";
3
4
  class ObjectFieldMapper {
4
5
  map(field, registry, context) {
5
6
  const builder = registry.object();
6
7
  const childFields = field.settings?.fields;
7
- if (childFields && childFields.length > 0) builder.fields((childRegistry)=>{
8
- const result = {};
9
- result["_id"] = childRegistry.text().hidden().defaultValue(()=>generateAlphaNumericLowerCaseId(12)).cloneValue(()=>generateAlphaNumericLowerCaseId(12));
10
- for (const child of childFields)result[child.fieldId] = context.mapField(child, childRegistry);
11
- return result;
12
- });
8
+ if (childFields && childFields.length > 0) {
9
+ builder.fields((childRegistry)=>{
10
+ const result = {};
11
+ result["_id"] = childRegistry.text().hidden().defaultValue(()=>generateAlphaNumericLowerCaseId(12)).cloneValue(()=>generateAlphaNumericLowerCaseId(12));
12
+ for (const child of childFields)result[child.fieldId] = context.mapField(child, childRegistry);
13
+ return result;
14
+ });
15
+ const layout = mapCmsNestedLayout(field.settings?.layout, childFields);
16
+ if (layout) builder.layout(layout);
17
+ }
13
18
  return applyFieldProps(builder, field, context.rendererMap);
14
19
  }
15
20
  constructor(){
@@ -1 +1 @@
1
- {"version":3,"file":"features/formModel/mappers/ObjectFieldMapper.js","sources":["../../../../src/features/formModel/mappers/ObjectFieldMapper.ts"],"sourcesContent":["import type { ICmsFieldTypeMapper, ICmsFieldMapperContext } from \"../abstractions.js\";\nimport type {\n IFieldBuilder,\n IFieldBuilderRegistry,\n IObjectFieldBuilder\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsModelField } from \"~/types.js\";\nimport { applyFieldProps } from \"./applyFieldProps.js\";\nimport { generateAlphaNumericLowerCaseId } from \"@webiny/utils/generateId\";\n\nexport class ObjectFieldMapper implements ICmsFieldTypeMapper {\n readonly type = \"object\";\n\n map(\n field: CmsModelField,\n registry: IFieldBuilderRegistry,\n context: ICmsFieldMapperContext\n ): IFieldBuilder {\n const builder = registry.object() as IObjectFieldBuilder;\n\n const childFields = field.settings?.fields;\n if (childFields && childFields.length > 0) {\n builder.fields(childRegistry => {\n const result: Record<string, IFieldBuilder> = {};\n result[\"_id\"] = childRegistry\n .text()\n .hidden()\n .defaultValue(() => generateAlphaNumericLowerCaseId(12))\n .cloneValue(() => generateAlphaNumericLowerCaseId(12));\n for (const child of childFields) {\n result[child.fieldId] = context.mapField(child, childRegistry);\n }\n return result;\n });\n }\n\n return applyFieldProps(builder, field, context.rendererMap);\n }\n}\n"],"names":["ObjectFieldMapper","field","registry","context","builder","childFields","childRegistry","result","generateAlphaNumericLowerCaseId","child","applyFieldProps"],"mappings":";;AAUO,MAAMA;IAGT,IACIC,KAAoB,EACpBC,QAA+B,EAC/BC,OAA+B,EAClB;QACb,MAAMC,UAAUF,SAAS,MAAM;QAE/B,MAAMG,cAAcJ,MAAM,QAAQ,EAAE;QACpC,IAAII,eAAeA,YAAY,MAAM,GAAG,GACpCD,QAAQ,MAAM,CAACE,CAAAA;YACX,MAAMC,SAAwC,CAAC;YAC/CA,MAAM,CAAC,MAAM,GAAGD,cACX,IAAI,GACJ,MAAM,GACN,YAAY,CAAC,IAAME,gCAAgC,KACnD,UAAU,CAAC,IAAMA,gCAAgC;YACtD,KAAK,MAAMC,SAASJ,YAChBE,MAAM,CAACE,MAAM,OAAO,CAAC,GAAGN,QAAQ,QAAQ,CAACM,OAAOH;YAEpD,OAAOC;QACX;QAGJ,OAAOG,gBAAgBN,SAASH,OAAOE,QAAQ,WAAW;IAC9D;;aA1BS,IAAI,GAAG;;AA2BpB"}
1
+ {"version":3,"file":"features/formModel/mappers/ObjectFieldMapper.js","sources":["../../../../src/features/formModel/mappers/ObjectFieldMapper.ts"],"sourcesContent":["import type { ICmsFieldTypeMapper, ICmsFieldMapperContext } from \"../abstractions.js\";\nimport type {\n IFieldBuilder,\n IFieldBuilderRegistry,\n IObjectFieldBuilder\n} from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsModelField } from \"~/types.js\";\nimport { applyFieldProps } from \"./applyFieldProps.js\";\nimport { mapCmsNestedLayout } from \"../CmsLayoutMapper.js\";\nimport { generateAlphaNumericLowerCaseId } from \"@webiny/utils/generateId\";\n\nexport class ObjectFieldMapper implements ICmsFieldTypeMapper {\n readonly type = \"object\";\n\n map(\n field: CmsModelField,\n registry: IFieldBuilderRegistry,\n context: ICmsFieldMapperContext\n ): IFieldBuilder {\n const builder = registry.object() as IObjectFieldBuilder;\n\n const childFields = field.settings?.fields;\n if (childFields && childFields.length > 0) {\n builder.fields(childRegistry => {\n const result: Record<string, IFieldBuilder> = {};\n result[\"_id\"] = childRegistry\n .text()\n .hidden()\n .defaultValue(() => generateAlphaNumericLowerCaseId(12))\n .cloneValue(() => generateAlphaNumericLowerCaseId(12));\n for (const child of childFields) {\n result[child.fieldId] = context.mapField(child, childRegistry);\n }\n return result;\n });\n\n const layout = mapCmsNestedLayout(field.settings?.layout, childFields);\n if (layout) {\n builder.layout(layout);\n }\n }\n\n return applyFieldProps(builder, field, context.rendererMap);\n }\n}\n"],"names":["ObjectFieldMapper","field","registry","context","builder","childFields","childRegistry","result","generateAlphaNumericLowerCaseId","child","layout","mapCmsNestedLayout","applyFieldProps"],"mappings":";;;AAWO,MAAMA;IAGT,IACIC,KAAoB,EACpBC,QAA+B,EAC/BC,OAA+B,EAClB;QACb,MAAMC,UAAUF,SAAS,MAAM;QAE/B,MAAMG,cAAcJ,MAAM,QAAQ,EAAE;QACpC,IAAII,eAAeA,YAAY,MAAM,GAAG,GAAG;YACvCD,QAAQ,MAAM,CAACE,CAAAA;gBACX,MAAMC,SAAwC,CAAC;gBAC/CA,MAAM,CAAC,MAAM,GAAGD,cACX,IAAI,GACJ,MAAM,GACN,YAAY,CAAC,IAAME,gCAAgC,KACnD,UAAU,CAAC,IAAMA,gCAAgC;gBACtD,KAAK,MAAMC,SAASJ,YAChBE,MAAM,CAACE,MAAM,OAAO,CAAC,GAAGN,QAAQ,QAAQ,CAACM,OAAOH;gBAEpD,OAAOC;YACX;YAEA,MAAMG,SAASC,mBAAmBV,MAAM,QAAQ,EAAE,QAAQI;YAC1D,IAAIK,QACAN,QAAQ,MAAM,CAACM;QAEvB;QAEA,OAAOE,gBAAgBR,SAASH,OAAOE,QAAQ,WAAW;IAC9D;;aA/BS,IAAI,GAAG;;AAgCpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/app-headless-cms",
3
- "version": "6.5.0-beta.5",
3
+ "version": "6.5.0-beta.7",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -28,25 +28,25 @@
28
28
  "@fortawesome/react-fontawesome": "3.5.0",
29
29
  "@lexical/utils": "0.51.0",
30
30
  "@types/react": "18.3.31",
31
- "@webiny/admin-ui": "6.5.0-beta.5",
32
- "@webiny/app": "6.5.0-beta.5",
33
- "@webiny/app-aco": "6.5.0-beta.5",
34
- "@webiny/app-admin": "6.5.0-beta.5",
35
- "@webiny/app-graphql-playground": "6.5.0-beta.5",
36
- "@webiny/app-headless-cms-common": "6.5.0-beta.5",
37
- "@webiny/cms-sdk": "6.5.0-beta.5",
31
+ "@webiny/admin-ui": "6.5.0-beta.7",
32
+ "@webiny/app": "6.5.0-beta.7",
33
+ "@webiny/app-aco": "6.5.0-beta.7",
34
+ "@webiny/app-admin": "6.5.0-beta.7",
35
+ "@webiny/app-graphql-playground": "6.5.0-beta.7",
36
+ "@webiny/app-headless-cms-common": "6.5.0-beta.7",
37
+ "@webiny/cms-sdk": "6.5.0-beta.7",
38
38
  "@webiny/di": "1.1.0",
39
- "@webiny/feature": "6.5.0-beta.5",
40
- "@webiny/form": "6.5.0-beta.5",
41
- "@webiny/frontend-settings": "6.5.0-beta.5",
42
- "@webiny/icons": "6.5.0-beta.5",
43
- "@webiny/lexical-editor": "6.5.0-beta.5",
44
- "@webiny/lexical-nodes": "6.5.0-beta.5",
45
- "@webiny/plugins": "6.5.0-beta.5",
46
- "@webiny/react-composition": "6.5.0-beta.5",
47
- "@webiny/react-properties": "6.5.0-beta.5",
48
- "@webiny/utils": "6.5.0-beta.5",
49
- "@webiny/validation": "6.5.0-beta.5",
39
+ "@webiny/feature": "6.5.0-beta.7",
40
+ "@webiny/form": "6.5.0-beta.7",
41
+ "@webiny/frontend-settings": "6.5.0-beta.7",
42
+ "@webiny/icons": "6.5.0-beta.7",
43
+ "@webiny/lexical-editor": "6.5.0-beta.7",
44
+ "@webiny/lexical-nodes": "6.5.0-beta.7",
45
+ "@webiny/plugins": "6.5.0-beta.7",
46
+ "@webiny/react-composition": "6.5.0-beta.7",
47
+ "@webiny/react-properties": "6.5.0-beta.7",
48
+ "@webiny/utils": "6.5.0-beta.7",
49
+ "@webiny/validation": "6.5.0-beta.7",
50
50
  "dot-prop-immutable": "2.1.1",
51
51
  "graphql": "16.14.2",
52
52
  "graphql-tag": "2.12.7",
@@ -63,7 +63,7 @@
63
63
  "zod": "4.6.5"
64
64
  },
65
65
  "devDependencies": {
66
- "@webiny/build-tools": "6.5.0-beta.5",
66
+ "@webiny/build-tools": "6.5.0-beta.7",
67
67
  "rimraf": "6.1.3",
68
68
  "typescript": "7.0.2",
69
69
  "vitest": "5.0.2"
@@ -79,8 +79,10 @@ class ContentEntryFormPresenterImpl {
79
79
  }
80
80
  async saveRevision(options) {
81
81
  if (!this.form) return false;
82
+ const form = this.form;
83
+ const model = this.model;
82
84
  const skipValidation = options?.skipValidation ?? true;
83
- const data = await this.form.submit({
85
+ const data = await form.submit({
84
86
  skipValidation
85
87
  });
86
88
  if (!data) return false;
@@ -95,9 +97,10 @@ class ContentEntryFormPresenterImpl {
95
97
  skipValidation
96
98
  });
97
99
  runInAction(()=>{
100
+ if (this.form !== form) return;
98
101
  this.entry = entry;
99
- this.form.setData(entry.values);
100
- this.form.reset();
102
+ form.setData(entry.values);
103
+ form.reset();
101
104
  });
102
105
  } else {
103
106
  const createData = {
@@ -114,14 +117,15 @@ class ContentEntryFormPresenterImpl {
114
117
  }
115
118
  });
116
119
  runInAction(()=>{
120
+ if (this.form !== form) return;
117
121
  this.entry = entry;
118
- this.form.setData(entry.values);
119
- this.form.reset();
122
+ form.setData(entry.values);
123
+ form.reset();
120
124
  });
121
125
  }
122
126
  return true;
123
127
  } catch (error) {
124
- this.form.setErrors(toFormErrors(error, this.model));
128
+ if (this.form === form) form.setErrors(toFormErrors(error, model));
125
129
  return false;
126
130
  } finally{
127
131
  runInAction(()=>{
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/contentEntries/form/ContentEntryFormPresenter.js","sources":["../../../../src/presentation/contentEntries/form/ContentEntryFormPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction, toJS } from \"mobx\";\nimport { FormModel, FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { Confirmation } from \"@webiny/app-admin/features/confirmation/abstractions.js\";\nimport type { CmsContentEntry } from \"~/types.js\";\nimport { GetEntryUseCase } from \"~/features/contentEntry/getEntry/abstractions.js\";\nimport { CreateEntryUseCase } from \"~/features/contentEntry/createEntry/abstractions.js\";\nimport { UpdateEntryUseCase } from \"~/features/contentEntry/updateEntry/abstractions.js\";\nimport { PublishEntryUseCase } from \"~/features/contentEntry/publishEntry/abstractions.js\";\nimport { UnpublishEntryUseCase } from \"~/features/contentEntry/unpublishEntry/abstractions.js\";\nimport { DeleteEntryUseCase } from \"~/features/contentEntry/deleteEntry/abstractions.js\";\nimport { UpdateRevisionDescriptionUseCase } from \"~/features/contentEntry/updateRevisionDescription/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n ContentEntryFormModelModifier,\n ContentEntryFormPresenter as Abstraction\n} from \"./abstractions.js\";\nimport { TRASH_ENTRY_DIALOG } from \"~/presentation/contentEntries/list/ContentEntriesPresenter.js\";\nimport { CreateRevisionFromUseCase } from \"~/features/contentEntry/createRevisionFrom/abstractions.js\";\nimport { toFormErrors } from \"./toFormErrors.js\";\n\ninterface PublishEntryDialogData {\n revisionDescription: string;\n}\n\ninterface SaveEntryParams {\n entry: CmsContentEntry;\n data: Record<string, unknown>;\n skipValidation?: boolean;\n}\n\nexport const PUBLISH_ENTRY_DIALOG = \"publish-entry\";\n\nclass ContentEntryFormPresenterImpl implements Abstraction.Interface {\n private entry: CmsContentEntry | null = null;\n private form: FormModel.Interface | null = null;\n private loading: string | null = null;\n private folderId: string | null = null;\n\n constructor(\n private readonly formModelFactory: FormModelFactory.Interface,\n private readonly cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private readonly confirmation: Confirmation.Interface,\n private readonly modelAccessor: CmsModelContext.Interface,\n private readonly getEntryUseCase: GetEntryUseCase.Interface,\n private readonly createEntryUseCase: CreateEntryUseCase.Interface,\n private readonly createRevisionFromUseCase: CreateRevisionFromUseCase.Interface,\n private readonly updateEntryUseCase: UpdateEntryUseCase.Interface,\n private readonly publishEntryUseCase: PublishEntryUseCase.Interface,\n private readonly unpublishEntryUseCase: UnpublishEntryUseCase.Interface,\n private readonly deleteEntryUseCase: DeleteEntryUseCase.Interface,\n private readonly updateRevisionDescriptionUseCase: UpdateRevisionDescriptionUseCase.Interface,\n private readonly formModelModifiers: ContentEntryFormModelModifier.Interface[]\n ) {\n makeAutoObservable<\n ContentEntryFormPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"confirmation\"\n | \"modelAccessor\"\n | \"getEntryUseCase\"\n | \"createEntryUseCase\"\n | \"updateEntryUseCase\"\n | \"publishEntryUseCase\"\n | \"unpublishEntryUseCase\"\n | \"deleteEntryUseCase\"\n | \"updateRevisionDescriptionUseCase\"\n | \"formModelModifiers\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n confirmation: false,\n modelAccessor: false,\n getEntryUseCase: false,\n createEntryUseCase: false,\n updateEntryUseCase: false,\n publishEntryUseCase: false,\n unpublishEntryUseCase: false,\n deleteEntryUseCase: false,\n updateRevisionDescriptionUseCase: false,\n formModelModifiers: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): Abstraction.ViewModel {\n const entry = this.entry;\n const meta = entry?.meta;\n const isLocked = meta?.locked ?? false;\n const status = meta?.status;\n /**\n * Can save if the entry is locked and published/unpublished - it will trigger create new revision.\n * Or if the entry is not locked and the form is not null (meaning we are creating a new entry or editing an existing one).\n */\n const canSave =\n (isLocked && (status === \"published\" || status === \"unpublished\")) ||\n (!isLocked && this.form !== null);\n\n return {\n loading: this.loading,\n model: this.model,\n entry: toJS(this.entry),\n form: this.form?.vm ?? null,\n canSave,\n status,\n canCreateNewRevision:\n !!status && canSave && [\"published\", \"unpublished\"].includes(status),\n canPublish: this.entry !== null && status !== \"published\",\n canUnpublish: this.entry !== null && status === \"published\",\n canDelete: this.entry !== null,\n isNewEntry: this.entry === null && this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async saveRevision(options?: { skipValidation?: boolean }): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n const skipValidation = options?.skipValidation ?? true;\n const data = await this.form.submit({ skipValidation });\n\n if (!data) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Saving...\";\n });\n\n try {\n if (this.entry) {\n /**\n * In case the entry already exists, and it is NOT locked, we will update the existing revision.\n * If it is locked, we will create a new revision from the existing one, and then update that new revision.\n */\n const entry = await this.saveEntry({\n entry: this.entry,\n data,\n skipValidation\n });\n\n runInAction(() => {\n this.entry = entry;\n this.form!.setData(entry.values);\n this.form!.reset();\n });\n } else {\n const createData: Record<string, unknown> = { values: data };\n if (this.folderId) {\n createData.wbyAco_location = { folderId: this.folderId };\n }\n\n const entry = await this.createEntryUseCase.execute({\n model: this.model,\n data: createData,\n options: { skipValidation }\n });\n\n runInAction(() => {\n this.entry = entry;\n this.form!.setData(entry.values);\n this.form!.reset();\n });\n }\n\n return true;\n } catch (error) {\n this.form.setErrors(toFormErrors(error, this.model));\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async publishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const result = await this.confirmation.confirm<PublishEntryDialogData>(\n PUBLISH_ENTRY_DIALOG,\n { entry: this.entry }\n );\n\n if (result === false) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Publishing...\";\n });\n\n try {\n if (result.revisionDescription) {\n await this.updateRevisionDescriptionUseCase.execute({\n model: this.model,\n id: this.entry.id,\n revisionDescription: result.revisionDescription\n });\n }\n\n const entry = await this.publishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async unpublishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n this.loading = \"Unpublishing...\";\n\n try {\n const entry = await this.unpublishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async deleteEntry(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const model = this.model;\n const entry = this.entry;\n\n return this.confirmation.confirm(\n TRASH_ENTRY_DIALOG,\n { entryId: entry.entryId },\n async () => {\n try {\n await this.deleteEntryUseCase.execute({\n model,\n id: entry.entryId\n });\n\n runInAction(() => {\n this.entry = null;\n this.form = null;\n });\n\n return true;\n } catch {\n return false;\n }\n }\n );\n }\n\n async loadRevision(id: string): Promise<void> {\n this.loading = \"Loading entry...\";\n\n try {\n const entry = await this.getEntryUseCase.execute({\n model: this.model,\n id\n });\n\n runInAction(() => {\n this.entry = entry;\n this.buildAndPopulateForm(entry);\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n setFolderId(folderId: string | null): void {\n this.folderId = folderId;\n }\n\n newEntry(initialValues?: Record<string, unknown>): void {\n this.entry = null;\n this.form = this.buildForm();\n if (initialValues) {\n this.form.setData(initialValues, { dirty: true });\n }\n }\n\n patchEntryMeta(meta: Partial<CmsContentEntry[\"meta\"]>): void {\n if (!this.entry) {\n return;\n }\n this.entry = {\n ...this.entry,\n meta: { ...this.entry.meta, ...meta }\n };\n }\n\n reset(): void {\n this.form = null;\n this.entry = null;\n }\n\n private buildForm(): FormModel.Interface {\n const formConfig = this.cmsFormModelBuilder.build(this.model);\n const form = this.formModelFactory.create(formConfig);\n for (const modifier of this.formModelModifiers) {\n modifier.modifyForm(form, this.model);\n }\n return form;\n }\n\n private buildAndPopulateForm(entry: CmsContentEntry): void {\n this.form = this.buildForm();\n this.form.setData(entry.values);\n this.form.reset();\n }\n /**\n * Depending on whether the entry is locked or not, this method will either create a new revision from the existing one and update it, or simply update the existing revision.\n */\n private async saveEntry(params: SaveEntryParams): Promise<any> {\n const { entry, data, skipValidation } = params;\n\n if (entry.meta.locked) {\n return await this.createRevisionFromUseCase.execute({\n model: this.model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n return await this.updateEntryUseCase.execute({\n model: this.model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n}\n\nexport const ContentEntryFormPresenter = Abstraction.createImplementation({\n implementation: ContentEntryFormPresenterImpl,\n dependencies: [\n FormModelFactory,\n CmsFormModelBuilder,\n Confirmation,\n CmsModelContext,\n GetEntryUseCase,\n CreateEntryUseCase,\n CreateRevisionFromUseCase,\n UpdateEntryUseCase,\n PublishEntryUseCase,\n UnpublishEntryUseCase,\n DeleteEntryUseCase,\n UpdateRevisionDescriptionUseCase,\n [ContentEntryFormModelModifier, { multiple: true }]\n ]\n});\n"],"names":["PUBLISH_ENTRY_DIALOG","ContentEntryFormPresenterImpl","formModelFactory","cmsFormModelBuilder","confirmation","modelAccessor","getEntryUseCase","createEntryUseCase","createRevisionFromUseCase","updateEntryUseCase","publishEntryUseCase","unpublishEntryUseCase","deleteEntryUseCase","updateRevisionDescriptionUseCase","formModelModifiers","makeAutoObservable","computed","entry","meta","isLocked","status","canSave","toJS","options","skipValidation","data","runInAction","createData","error","toFormErrors","result","model","TRASH_ENTRY_DIALOG","id","folderId","initialValues","formConfig","form","modifier","params","ContentEntryFormPresenter","Abstraction","FormModelFactory","CmsFormModelBuilder","Confirmation","CmsModelContext","GetEntryUseCase","CreateEntryUseCase","CreateRevisionFromUseCase","UpdateEntryUseCase","PublishEntryUseCase","UnpublishEntryUseCase","DeleteEntryUseCase","UpdateRevisionDescriptionUseCase","ContentEntryFormModelModifier"],"mappings":"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"}
1
+ {"version":3,"file":"presentation/contentEntries/form/ContentEntryFormPresenter.js","sources":["../../../../src/presentation/contentEntries/form/ContentEntryFormPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction, toJS } from \"mobx\";\nimport { FormModel, FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { Confirmation } from \"@webiny/app-admin/features/confirmation/abstractions.js\";\nimport type { CmsContentEntry } from \"~/types.js\";\nimport { GetEntryUseCase } from \"~/features/contentEntry/getEntry/abstractions.js\";\nimport { CreateEntryUseCase } from \"~/features/contentEntry/createEntry/abstractions.js\";\nimport { UpdateEntryUseCase } from \"~/features/contentEntry/updateEntry/abstractions.js\";\nimport { PublishEntryUseCase } from \"~/features/contentEntry/publishEntry/abstractions.js\";\nimport { UnpublishEntryUseCase } from \"~/features/contentEntry/unpublishEntry/abstractions.js\";\nimport { DeleteEntryUseCase } from \"~/features/contentEntry/deleteEntry/abstractions.js\";\nimport { UpdateRevisionDescriptionUseCase } from \"~/features/contentEntry/updateRevisionDescription/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n ContentEntryFormModelModifier,\n ContentEntryFormPresenter as Abstraction\n} from \"./abstractions.js\";\nimport { TRASH_ENTRY_DIALOG } from \"~/presentation/contentEntries/list/ContentEntriesPresenter.js\";\nimport { CreateRevisionFromUseCase } from \"~/features/contentEntry/createRevisionFrom/abstractions.js\";\nimport { toFormErrors } from \"./toFormErrors.js\";\n\ninterface PublishEntryDialogData {\n revisionDescription: string;\n}\n\ninterface SaveEntryParams {\n entry: CmsContentEntry;\n data: Record<string, unknown>;\n skipValidation?: boolean;\n}\n\nexport const PUBLISH_ENTRY_DIALOG = \"publish-entry\";\n\nclass ContentEntryFormPresenterImpl implements Abstraction.Interface {\n private entry: CmsContentEntry | null = null;\n private form: FormModel.Interface | null = null;\n private loading: string | null = null;\n private folderId: string | null = null;\n\n constructor(\n private readonly formModelFactory: FormModelFactory.Interface,\n private readonly cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private readonly confirmation: Confirmation.Interface,\n private readonly modelAccessor: CmsModelContext.Interface,\n private readonly getEntryUseCase: GetEntryUseCase.Interface,\n private readonly createEntryUseCase: CreateEntryUseCase.Interface,\n private readonly createRevisionFromUseCase: CreateRevisionFromUseCase.Interface,\n private readonly updateEntryUseCase: UpdateEntryUseCase.Interface,\n private readonly publishEntryUseCase: PublishEntryUseCase.Interface,\n private readonly unpublishEntryUseCase: UnpublishEntryUseCase.Interface,\n private readonly deleteEntryUseCase: DeleteEntryUseCase.Interface,\n private readonly updateRevisionDescriptionUseCase: UpdateRevisionDescriptionUseCase.Interface,\n private readonly formModelModifiers: ContentEntryFormModelModifier.Interface[]\n ) {\n makeAutoObservable<\n ContentEntryFormPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"confirmation\"\n | \"modelAccessor\"\n | \"getEntryUseCase\"\n | \"createEntryUseCase\"\n | \"updateEntryUseCase\"\n | \"publishEntryUseCase\"\n | \"unpublishEntryUseCase\"\n | \"deleteEntryUseCase\"\n | \"updateRevisionDescriptionUseCase\"\n | \"formModelModifiers\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n confirmation: false,\n modelAccessor: false,\n getEntryUseCase: false,\n createEntryUseCase: false,\n updateEntryUseCase: false,\n publishEntryUseCase: false,\n unpublishEntryUseCase: false,\n deleteEntryUseCase: false,\n updateRevisionDescriptionUseCase: false,\n formModelModifiers: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): Abstraction.ViewModel {\n const entry = this.entry;\n const meta = entry?.meta;\n const isLocked = meta?.locked ?? false;\n const status = meta?.status;\n /**\n * Can save if the entry is locked and published/unpublished - it will trigger create new revision.\n * Or if the entry is not locked and the form is not null (meaning we are creating a new entry or editing an existing one).\n */\n const canSave =\n (isLocked && (status === \"published\" || status === \"unpublished\")) ||\n (!isLocked && this.form !== null);\n\n return {\n loading: this.loading,\n model: this.model,\n entry: toJS(this.entry),\n form: this.form?.vm ?? null,\n canSave,\n status,\n canCreateNewRevision:\n !!status && canSave && [\"published\", \"unpublished\"].includes(status),\n canPublish: this.entry !== null && status !== \"published\",\n canUnpublish: this.entry !== null && status === \"published\",\n canDelete: this.entry !== null,\n isNewEntry: this.entry === null && this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async saveRevision(options?: { skipValidation?: boolean }): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n /*\n * Saving is async, and the form can be reset or replaced by another entry before it\n * finishes. Keep hold of the form and model being saved, and only write the result back\n * while that form is still the one on screen.\n */\n const form = this.form;\n const model = this.model;\n\n const skipValidation = options?.skipValidation ?? true;\n const data = await form.submit({ skipValidation });\n\n if (!data) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Saving...\";\n });\n\n try {\n if (this.entry) {\n /**\n * In case the entry already exists, and it is NOT locked, we will update the existing revision.\n * If it is locked, we will create a new revision from the existing one, and then update that new revision.\n */\n const entry = await this.saveEntry({\n entry: this.entry,\n data,\n skipValidation\n });\n\n runInAction(() => {\n if (this.form !== form) {\n return;\n }\n this.entry = entry;\n form.setData(entry.values);\n form.reset();\n });\n } else {\n const createData: Record<string, unknown> = { values: data };\n if (this.folderId) {\n createData.wbyAco_location = { folderId: this.folderId };\n }\n\n const entry = await this.createEntryUseCase.execute({\n model: this.model,\n data: createData,\n options: { skipValidation }\n });\n\n runInAction(() => {\n if (this.form !== form) {\n return;\n }\n this.entry = entry;\n form.setData(entry.values);\n form.reset();\n });\n }\n\n return true;\n } catch (error) {\n if (this.form === form) {\n form.setErrors(toFormErrors(error, model));\n }\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async publishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const result = await this.confirmation.confirm<PublishEntryDialogData>(\n PUBLISH_ENTRY_DIALOG,\n { entry: this.entry }\n );\n\n if (result === false) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Publishing...\";\n });\n\n try {\n if (result.revisionDescription) {\n await this.updateRevisionDescriptionUseCase.execute({\n model: this.model,\n id: this.entry.id,\n revisionDescription: result.revisionDescription\n });\n }\n\n const entry = await this.publishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async unpublishRevision(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n this.loading = \"Unpublishing...\";\n\n try {\n const entry = await this.unpublishEntryUseCase.execute({\n model: this.model,\n revisionId: this.entry.id\n });\n\n runInAction(() => {\n this.entry = entry;\n });\n\n return true;\n } catch {\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async deleteEntry(): Promise<boolean> {\n if (!this.entry) {\n return false;\n }\n\n const model = this.model;\n const entry = this.entry;\n\n return this.confirmation.confirm(\n TRASH_ENTRY_DIALOG,\n { entryId: entry.entryId },\n async () => {\n try {\n await this.deleteEntryUseCase.execute({\n model,\n id: entry.entryId\n });\n\n runInAction(() => {\n this.entry = null;\n this.form = null;\n });\n\n return true;\n } catch {\n return false;\n }\n }\n );\n }\n\n async loadRevision(id: string): Promise<void> {\n this.loading = \"Loading entry...\";\n\n try {\n const entry = await this.getEntryUseCase.execute({\n model: this.model,\n id\n });\n\n runInAction(() => {\n this.entry = entry;\n this.buildAndPopulateForm(entry);\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n setFolderId(folderId: string | null): void {\n this.folderId = folderId;\n }\n\n newEntry(initialValues?: Record<string, unknown>): void {\n this.entry = null;\n this.form = this.buildForm();\n if (initialValues) {\n this.form.setData(initialValues, { dirty: true });\n }\n }\n\n patchEntryMeta(meta: Partial<CmsContentEntry[\"meta\"]>): void {\n if (!this.entry) {\n return;\n }\n this.entry = {\n ...this.entry,\n meta: { ...this.entry.meta, ...meta }\n };\n }\n\n reset(): void {\n this.form = null;\n this.entry = null;\n }\n\n private buildForm(): FormModel.Interface {\n const formConfig = this.cmsFormModelBuilder.build(this.model);\n const form = this.formModelFactory.create(formConfig);\n for (const modifier of this.formModelModifiers) {\n modifier.modifyForm(form, this.model);\n }\n return form;\n }\n\n private buildAndPopulateForm(entry: CmsContentEntry): void {\n this.form = this.buildForm();\n this.form.setData(entry.values);\n this.form.reset();\n }\n /**\n * Depending on whether the entry is locked or not, this method will either create a new revision from the existing one and update it, or simply update the existing revision.\n */\n private async saveEntry(params: SaveEntryParams): Promise<any> {\n const { entry, data, skipValidation } = params;\n\n if (entry.meta.locked) {\n return await this.createRevisionFromUseCase.execute({\n model: this.model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n return await this.updateEntryUseCase.execute({\n model: this.model,\n revisionId: entry.id,\n data: {\n values: data\n },\n options: { skipValidation }\n });\n }\n}\n\nexport const ContentEntryFormPresenter = Abstraction.createImplementation({\n implementation: ContentEntryFormPresenterImpl,\n dependencies: [\n FormModelFactory,\n CmsFormModelBuilder,\n Confirmation,\n CmsModelContext,\n GetEntryUseCase,\n CreateEntryUseCase,\n CreateRevisionFromUseCase,\n UpdateEntryUseCase,\n PublishEntryUseCase,\n UnpublishEntryUseCase,\n DeleteEntryUseCase,\n UpdateRevisionDescriptionUseCase,\n [ContentEntryFormModelModifier, { multiple: true }]\n ]\n});\n"],"names":["PUBLISH_ENTRY_DIALOG","ContentEntryFormPresenterImpl","formModelFactory","cmsFormModelBuilder","confirmation","modelAccessor","getEntryUseCase","createEntryUseCase","createRevisionFromUseCase","updateEntryUseCase","publishEntryUseCase","unpublishEntryUseCase","deleteEntryUseCase","updateRevisionDescriptionUseCase","formModelModifiers","makeAutoObservable","computed","entry","meta","isLocked","status","canSave","toJS","options","form","model","skipValidation","data","runInAction","createData","error","toFormErrors","result","TRASH_ENTRY_DIALOG","id","folderId","initialValues","formConfig","modifier","params","ContentEntryFormPresenter","Abstraction","FormModelFactory","CmsFormModelBuilder","Confirmation","CmsModelContext","GetEntryUseCase","CreateEntryUseCase","CreateRevisionFromUseCase","UpdateEntryUseCase","PublishEntryUseCase","UnpublishEntryUseCase","DeleteEntryUseCase","UpdateRevisionDescriptionUseCase","ContentEntryFormModelModifier"],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Container } from "@webiny/di";
3
+ import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
4
+ import { Confirmation } from "@webiny/app-admin/features/confirmation/abstractions.js";
5
+ import { GetEntryUseCase } from "../../../features/contentEntry/getEntry/abstractions.js";
6
+ import { CreateEntryUseCase } from "../../../features/contentEntry/createEntry/abstractions.js";
7
+ import { UpdateEntryUseCase } from "../../../features/contentEntry/updateEntry/abstractions.js";
8
+ import { PublishEntryUseCase } from "../../../features/contentEntry/publishEntry/abstractions.js";
9
+ import { UnpublishEntryUseCase } from "../../../features/contentEntry/unpublishEntry/abstractions.js";
10
+ import { DeleteEntryUseCase } from "../../../features/contentEntry/deleteEntry/abstractions.js";
11
+ import { UpdateRevisionDescriptionUseCase } from "../../../features/contentEntry/updateRevisionDescription/abstractions.js";
12
+ import { CreateRevisionFromUseCase } from "../../../features/contentEntry/createRevisionFrom/abstractions.js";
13
+ import { CmsFormModelBuilder } from "../../../features/formModel/abstractions.js";
14
+ import { CmsModelContext } from "../../../features/contentEntry/abstractions.js";
15
+ import { ContentEntryFormPresenter } from "./abstractions.js";
16
+ import { ContentEntryFormPresenter as external_ContentEntryFormPresenter_js_ContentEntryFormPresenter } from "./ContentEntryFormPresenter.js";
17
+ const createFakeForm = ()=>({
18
+ vm: {},
19
+ isDirty: false,
20
+ submit: vi.fn(async ()=>({
21
+ title: "Draft"
22
+ })),
23
+ setData: vi.fn(),
24
+ reset: vi.fn(),
25
+ setErrors: vi.fn()
26
+ });
27
+ const deferred = ()=>{
28
+ let resolve;
29
+ let reject;
30
+ const promise = new Promise((res, rej)=>{
31
+ resolve = res;
32
+ reject = rej;
33
+ });
34
+ return {
35
+ promise,
36
+ resolve,
37
+ reject
38
+ };
39
+ };
40
+ const setup = ()=>{
41
+ const forms = [];
42
+ const create = deferred();
43
+ const container = new Container();
44
+ container.registerInstance(FormModelFactory, {
45
+ create: ()=>{
46
+ const form = createFakeForm();
47
+ forms.push(form);
48
+ return form;
49
+ }
50
+ });
51
+ container.registerInstance(CmsFormModelBuilder, {
52
+ build: ()=>({})
53
+ });
54
+ container.registerInstance(CmsModelContext, {
55
+ getModel: ()=>({
56
+ modelId: "article",
57
+ fields: []
58
+ })
59
+ });
60
+ container.registerInstance(CreateEntryUseCase, {
61
+ execute: ()=>create.promise
62
+ });
63
+ for (const abstraction of [
64
+ Confirmation,
65
+ GetEntryUseCase,
66
+ CreateRevisionFromUseCase,
67
+ UpdateEntryUseCase,
68
+ PublishEntryUseCase,
69
+ UnpublishEntryUseCase,
70
+ DeleteEntryUseCase,
71
+ UpdateRevisionDescriptionUseCase
72
+ ])container.registerInstance(abstraction, {});
73
+ container.register(external_ContentEntryFormPresenter_js_ContentEntryFormPresenter);
74
+ return {
75
+ presenter: container.resolve(ContentEntryFormPresenter),
76
+ forms,
77
+ create
78
+ };
79
+ };
80
+ describe("ContentEntryFormPresenter.saveRevision", ()=>{
81
+ it("puts a rejected save's errors on the form", async ()=>{
82
+ const { presenter, forms, create } = setup();
83
+ presenter.newEntry();
84
+ const saving = presenter.saveRevision();
85
+ create.reject(new Error("Value must be unique."));
86
+ await expect(saving).resolves.toBe(false);
87
+ expect(forms[0].setErrors).toHaveBeenCalledTimes(1);
88
+ });
89
+ it("returns false instead of throwing when the form was reset mid-save", async ()=>{
90
+ const { presenter, create } = setup();
91
+ presenter.newEntry();
92
+ const saving = presenter.saveRevision();
93
+ presenter.reset();
94
+ create.reject(new Error("Value must be unique."));
95
+ await expect(saving).resolves.toBe(false);
96
+ });
97
+ it("leaves a form built after the save started alone", async ()=>{
98
+ const { presenter, forms, create } = setup();
99
+ presenter.newEntry();
100
+ const saving = presenter.saveRevision();
101
+ presenter.newEntry();
102
+ create.reject(new Error("Value must be unique."));
103
+ await expect(saving).resolves.toBe(false);
104
+ expect(forms).toHaveLength(2);
105
+ expect(forms[0].setErrors).not.toHaveBeenCalled();
106
+ expect(forms[1].setErrors).not.toHaveBeenCalled();
107
+ });
108
+ });
109
+
110
+ //# sourceMappingURL=ContentEntryFormPresenter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/contentEntries/form/ContentEntryFormPresenter.test.js","sources":["../../../../src/presentation/contentEntries/form/ContentEntryFormPresenter.test.ts"],"sourcesContent":["// @vitest-environment jsdom\nimport { describe, it, expect, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { Confirmation } from \"@webiny/app-admin/features/confirmation/abstractions.js\";\nimport { GetEntryUseCase } from \"~/features/contentEntry/getEntry/abstractions.js\";\nimport { CreateEntryUseCase } from \"~/features/contentEntry/createEntry/abstractions.js\";\nimport { UpdateEntryUseCase } from \"~/features/contentEntry/updateEntry/abstractions.js\";\nimport { PublishEntryUseCase } from \"~/features/contentEntry/publishEntry/abstractions.js\";\nimport { UnpublishEntryUseCase } from \"~/features/contentEntry/unpublishEntry/abstractions.js\";\nimport { DeleteEntryUseCase } from \"~/features/contentEntry/deleteEntry/abstractions.js\";\nimport { UpdateRevisionDescriptionUseCase } from \"~/features/contentEntry/updateRevisionDescription/abstractions.js\";\nimport { CreateRevisionFromUseCase } from \"~/features/contentEntry/createRevisionFrom/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport { ContentEntryFormPresenter as Abstraction } from \"./abstractions.js\";\nimport { ContentEntryFormPresenter } from \"./ContentEntryFormPresenter.js\";\n\nconst createFakeForm = () => ({\n vm: {},\n isDirty: false,\n submit: vi.fn(async () => ({ title: \"Draft\" })),\n setData: vi.fn(),\n reset: vi.fn(),\n setErrors: vi.fn()\n});\n\n/** A promise the test settles by hand, so it can act while a save is still in flight. */\nconst deferred = <T>() => {\n let resolve!: (value: T) => void;\n let reject!: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n};\n\nconst setup = () => {\n const forms: ReturnType<typeof createFakeForm>[] = [];\n const create = deferred<any>();\n\n const container = new Container();\n container.registerInstance(FormModelFactory, {\n create: () => {\n const form = createFakeForm();\n forms.push(form);\n return form;\n }\n } as unknown as FormModelFactory.Interface);\n container.registerInstance(CmsFormModelBuilder, {\n build: () => ({})\n } as unknown as CmsFormModelBuilder.Interface);\n container.registerInstance(CmsModelContext, {\n getModel: () => ({ modelId: \"article\", fields: [] })\n } as unknown as CmsModelContext.Interface);\n container.registerInstance(CreateEntryUseCase, {\n execute: () => create.promise\n } as unknown as CreateEntryUseCase.Interface);\n\n // Not reached by the create path these tests exercise.\n for (const abstraction of [\n Confirmation,\n GetEntryUseCase,\n CreateRevisionFromUseCase,\n UpdateEntryUseCase,\n PublishEntryUseCase,\n UnpublishEntryUseCase,\n DeleteEntryUseCase,\n UpdateRevisionDescriptionUseCase\n ]) {\n container.registerInstance(abstraction as any, {});\n }\n container.register(ContentEntryFormPresenter);\n\n return { presenter: container.resolve(Abstraction), forms, create };\n};\n\ndescribe(\"ContentEntryFormPresenter.saveRevision\", () => {\n it(\"puts a rejected save's errors on the form\", async () => {\n const { presenter, forms, create } = setup();\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n create.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms[0].setErrors).toHaveBeenCalledTimes(1);\n });\n\n it(\"returns false instead of throwing when the form was reset mid-save\", async () => {\n const { presenter, create } = setup();\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n presenter.reset();\n create.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n });\n\n it(\"leaves a form built after the save started alone\", async () => {\n const { presenter, forms, create } = setup();\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n presenter.newEntry();\n create.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms).toHaveLength(2);\n expect(forms[0].setErrors).not.toHaveBeenCalled();\n expect(forms[1].setErrors).not.toHaveBeenCalled();\n });\n});\n"],"names":["createFakeForm","vi","deferred","resolve","reject","promise","Promise","res","rej","setup","forms","create","container","Container","FormModelFactory","form","CmsFormModelBuilder","CmsModelContext","CreateEntryUseCase","abstraction","Confirmation","GetEntryUseCase","CreateRevisionFromUseCase","UpdateEntryUseCase","PublishEntryUseCase","UnpublishEntryUseCase","DeleteEntryUseCase","UpdateRevisionDescriptionUseCase","ContentEntryFormPresenter","Abstraction","describe","it","presenter","saving","Error","expect"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAMA,iBAAiB,IAAO;QAC1B,IAAI,CAAC;QACL,SAAS;QACT,QAAQC,GAAG,EAAE,CAAC,UAAa;gBAAE,OAAO;YAAQ;QAC5C,SAASA,GAAG,EAAE;QACd,OAAOA,GAAG,EAAE;QACZ,WAAWA,GAAG,EAAE;IACpB;AAGA,MAAMC,WAAW;IACb,IAAIC;IACJ,IAAIC;IACJ,MAAMC,UAAU,IAAIC,QAAW,CAACC,KAAKC;QACjCL,UAAUI;QACVH,SAASI;IACb;IACA,OAAO;QAAEH;QAASF;QAASC;IAAO;AACtC;AAEA,MAAMK,QAAQ;IACV,MAAMC,QAA6C,EAAE;IACrD,MAAMC,SAAST;IAEf,MAAMU,YAAY,IAAIC;IACtBD,UAAU,gBAAgB,CAACE,kBAAkB;QACzC,QAAQ;YACJ,MAAMC,OAAOf;YACbU,MAAM,IAAI,CAACK;YACX,OAAOA;QACX;IACJ;IACAH,UAAU,gBAAgB,CAACI,qBAAqB;QAC5C,OAAO,IAAO,EAAC;IACnB;IACAJ,UAAU,gBAAgB,CAACK,iBAAiB;QACxC,UAAU,IAAO;gBAAE,SAAS;gBAAW,QAAQ,EAAE;YAAC;IACtD;IACAL,UAAU,gBAAgB,CAACM,oBAAoB;QAC3C,SAAS,IAAMP,OAAO,OAAO;IACjC;IAGA,KAAK,MAAMQ,eAAe;QACtBC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;KACH,CACGf,UAAU,gBAAgB,CAACO,aAAoB,CAAC;IAEpDP,UAAU,QAAQ,CAACgB;IAEnB,OAAO;QAAE,WAAWhB,UAAU,OAAO,CAACiB;QAAcnB;QAAOC;IAAO;AACtE;AAEAmB,SAAS,0CAA0C;IAC/CC,GAAG,6CAA6C;QAC5C,MAAM,EAAEC,SAAS,EAAEtB,KAAK,EAAEC,MAAM,EAAE,GAAGF;QACrCuB,UAAU,QAAQ;QAElB,MAAMC,SAASD,UAAU,YAAY;QACrCrB,OAAO,MAAM,CAAC,IAAIuB,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOzB,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,CAAC;IACrD;IAEAqB,GAAG,sEAAsE;QACrE,MAAM,EAAEC,SAAS,EAAErB,MAAM,EAAE,GAAGF;QAC9BuB,UAAU,QAAQ;QAElB,MAAMC,SAASD,UAAU,YAAY;QACrCA,UAAU,KAAK;QACfrB,OAAO,MAAM,CAAC,IAAIuB,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;IACvC;IAEAF,GAAG,oDAAoD;QACnD,MAAM,EAAEC,SAAS,EAAEtB,KAAK,EAAEC,MAAM,EAAE,GAAGF;QACrCuB,UAAU,QAAQ;QAElB,MAAMC,SAASD,UAAU,YAAY;QACrCA,UAAU,QAAQ;QAClBrB,OAAO,MAAM,CAAC,IAAIuB,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOzB,OAAO,YAAY,CAAC;QAC3ByB,OAAOzB,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB;QAC/CyB,OAAOzB,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB;IACnD;AACJ"}
@@ -38,7 +38,9 @@ class SingleEntryPresenterImpl {
38
38
  }
39
39
  async save() {
40
40
  if (!this.form) return false;
41
- const data = await this.form.submit();
41
+ const form = this.form;
42
+ const model = this.model;
43
+ const data = await form.submit();
42
44
  if (!data) return false;
43
45
  this.loading = "Saving...";
44
46
  try {
@@ -49,13 +51,14 @@ class SingleEntryPresenterImpl {
49
51
  }
50
52
  });
51
53
  runInAction(()=>{
54
+ if (this.form !== form) return;
52
55
  this.entry = entry;
53
- this.form.setData(entry.values);
54
- this.form.reset();
56
+ form.setData(entry.values);
57
+ form.reset();
55
58
  });
56
59
  return true;
57
60
  } catch (error) {
58
- this.form.setErrors(toFormErrors(error, this.model));
61
+ if (this.form === form) form.setErrors(toFormErrors(error, model));
59
62
  return false;
60
63
  } finally{
61
64
  runInAction(()=>{
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/contentEntries/singleEntry/SingleEntryPresenter.js","sources":["../../../../src/presentation/contentEntries/singleEntry/SingleEntryPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction } from \"mobx\";\nimport type { IFormModel } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsContentEntry } from \"~/types.js\";\nimport { GetSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { UpdateSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n SingleEntryPresenter as Abstraction,\n type ISingleEntryPresenter,\n type ISingleEntryViewModel\n} from \"./abstractions.js\";\nimport { toFormErrors } from \"~/presentation/contentEntries/form/toFormErrors.js\";\n\nclass SingleEntryPresenterImpl implements ISingleEntryPresenter {\n private entry: CmsContentEntry | null = null;\n private form: IFormModel | null = null;\n private loading: string | null = null;\n\n constructor(\n private formModelFactory: FormModelFactory.Interface,\n private cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private modelAccessor: CmsModelContext.Interface,\n private getSingletonEntryUseCase: GetSingletonEntryUseCase.Interface,\n private updateSingletonEntryUseCase: UpdateSingletonEntryUseCase.Interface\n ) {\n makeAutoObservable<\n SingleEntryPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"modelAccessor\"\n | \"getSingletonEntryUseCase\"\n | \"updateSingletonEntryUseCase\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n modelAccessor: false,\n getSingletonEntryUseCase: false,\n updateSingletonEntryUseCase: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): ISingleEntryViewModel {\n return {\n loading: this.loading,\n entry: this.entry,\n form: this.form?.vm ?? null,\n canSave: this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async save(): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n const data = await this.form.submit();\n if (!data) {\n return false;\n }\n\n this.loading = \"Saving...\";\n\n try {\n const entry = await this.updateSingletonEntryUseCase.execute({\n model: this.model,\n data: {\n values: data\n }\n });\n\n runInAction(() => {\n this.entry = entry;\n this.form!.setData(entry.values);\n this.form!.reset();\n });\n\n return true;\n } catch (error) {\n this.form.setErrors(toFormErrors(error, this.model));\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async init(): Promise<void> {\n this.loading = \"Loading...\";\n\n try {\n const entry = await this.getSingletonEntryUseCase.execute({\n model: this.model\n });\n\n runInAction(() => {\n this.entry = entry;\n const formConfig = this.cmsFormModelBuilder.build(this.model);\n this.form = this.formModelFactory.create(formConfig);\n this.form.setData(entry.values);\n this.form.reset();\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n dispose(): void {\n this.form = null;\n this.entry = null;\n }\n}\n\nexport const SingleEntryPresenter = Abstraction.createImplementation({\n implementation: SingleEntryPresenterImpl,\n dependencies: [\n FormModelFactory,\n CmsFormModelBuilder,\n CmsModelContext,\n GetSingletonEntryUseCase,\n UpdateSingletonEntryUseCase\n ]\n});\n"],"names":["SingleEntryPresenterImpl","formModelFactory","cmsFormModelBuilder","modelAccessor","getSingletonEntryUseCase","updateSingletonEntryUseCase","makeAutoObservable","computed","data","entry","runInAction","error","toFormErrors","formConfig","SingleEntryPresenter","Abstraction","FormModelFactory","CmsFormModelBuilder","CmsModelContext","GetSingletonEntryUseCase","UpdateSingletonEntryUseCase"],"mappings":";;;;;;;AAeA,MAAMA;IAKF,YACYC,gBAA4C,EAC5CC,mBAAkD,EAClDC,aAAwC,EACxCC,wBAA4D,EAC5DC,2BAAkE,CAC5E;aALUJ,gBAAgB,GAAhBA;aACAC,mBAAmB,GAAnBA;aACAC,aAAa,GAAbA;aACAC,wBAAwB,GAAxBA;aACAC,2BAA2B,GAA3BA;aATJ,KAAK,GAA2B;aAChC,IAAI,GAAsB;aAC1B,OAAO,GAAkB;QAS7BC,mBAOE,IAAI,EAAE;YACJ,kBAAkB;YAClB,qBAAqB;YACrB,eAAe;YACf,0BAA0B;YAC1B,6BAA6B;YAC7B,IAAIC;QACR;IACJ;IAEA,IAAY,QAAQ;QAChB,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ;IACtC;IAEA,IAAI,KAA4B;QAC5B,OAAO;YACH,SAAS,IAAI,CAAC,OAAO;YACrB,OAAO,IAAI,CAAC,KAAK;YACjB,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM;YACvB,SAAS,AAAc,SAAd,IAAI,CAAC,IAAI;YAClB,SAAS,IAAI,CAAC,IAAI,EAAE,WAAW;QACnC;IACJ;IAEA,MAAM,OAAyB;QAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,EACV,OAAO;QAGX,MAAMC,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM;QACnC,IAAI,CAACA,MACD,OAAO;QAGX,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI;YACA,MAAMC,QAAQ,MAAM,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC;gBACzD,OAAO,IAAI,CAAC,KAAK;gBACjB,MAAM;oBACF,QAAQD;gBACZ;YACJ;YAEAE,YAAY;gBACR,IAAI,CAAC,KAAK,GAAGD;gBACb,IAAI,CAAC,IAAI,CAAE,OAAO,CAACA,MAAM,MAAM;gBAC/B,IAAI,CAAC,IAAI,CAAE,KAAK;YACpB;YAEA,OAAO;QACX,EAAE,OAAOE,OAAO;YACZ,IAAI,CAAC,IAAI,CAAC,SAAS,CAACC,aAAaD,OAAO,IAAI,CAAC,KAAK;YAClD,OAAO;QACX,SAAU;YACND,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEA,MAAM,OAAsB;QACxB,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI;YACA,MAAMD,QAAQ,MAAM,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC;gBACtD,OAAO,IAAI,CAAC,KAAK;YACrB;YAEAC,YAAY;gBACR,IAAI,CAAC,KAAK,GAAGD;gBACb,MAAMI,aAAa,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK;gBAC5D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAACA;gBACzC,IAAI,CAAC,IAAI,CAAC,OAAO,CAACJ,MAAM,MAAM;gBAC9B,IAAI,CAAC,IAAI,CAAC,KAAK;YACnB;QACJ,SAAU;YACNC,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEA,UAAgB;QACZ,IAAI,CAAC,IAAI,GAAG;QACZ,IAAI,CAAC,KAAK,GAAG;IACjB;AACJ;AAEO,MAAMI,4CAAuBC,qBAAAA,oBAAgC,CAAC;IACjE,gBAAgBf;IAChB,cAAc;QACVgB;QACAC;QACAC;QACAC;QACAC;KACH;AACL"}
1
+ {"version":3,"file":"presentation/contentEntries/singleEntry/SingleEntryPresenter.js","sources":["../../../../src/presentation/contentEntries/singleEntry/SingleEntryPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction } from \"mobx\";\nimport type { IFormModel } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsContentEntry } from \"~/types.js\";\nimport { GetSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { UpdateSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n SingleEntryPresenter as Abstraction,\n type ISingleEntryPresenter,\n type ISingleEntryViewModel\n} from \"./abstractions.js\";\nimport { toFormErrors } from \"~/presentation/contentEntries/form/toFormErrors.js\";\n\nclass SingleEntryPresenterImpl implements ISingleEntryPresenter {\n private entry: CmsContentEntry | null = null;\n private form: IFormModel | null = null;\n private loading: string | null = null;\n\n constructor(\n private formModelFactory: FormModelFactory.Interface,\n private cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private modelAccessor: CmsModelContext.Interface,\n private getSingletonEntryUseCase: GetSingletonEntryUseCase.Interface,\n private updateSingletonEntryUseCase: UpdateSingletonEntryUseCase.Interface\n ) {\n makeAutoObservable<\n SingleEntryPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"modelAccessor\"\n | \"getSingletonEntryUseCase\"\n | \"updateSingletonEntryUseCase\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n modelAccessor: false,\n getSingletonEntryUseCase: false,\n updateSingletonEntryUseCase: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): ISingleEntryViewModel {\n return {\n loading: this.loading,\n entry: this.entry,\n form: this.form?.vm ?? null,\n canSave: this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async save(): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n // The form can be disposed or rebuilt while saving; only the form being saved gets the result.\n const form = this.form;\n const model = this.model;\n\n const data = await form.submit();\n if (!data) {\n return false;\n }\n\n this.loading = \"Saving...\";\n\n try {\n const entry = await this.updateSingletonEntryUseCase.execute({\n model: this.model,\n data: {\n values: data\n }\n });\n\n runInAction(() => {\n if (this.form !== form) {\n return;\n }\n this.entry = entry;\n form.setData(entry.values);\n form.reset();\n });\n\n return true;\n } catch (error) {\n if (this.form === form) {\n form.setErrors(toFormErrors(error, model));\n }\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async init(): Promise<void> {\n this.loading = \"Loading...\";\n\n try {\n const entry = await this.getSingletonEntryUseCase.execute({\n model: this.model\n });\n\n runInAction(() => {\n this.entry = entry;\n const formConfig = this.cmsFormModelBuilder.build(this.model);\n this.form = this.formModelFactory.create(formConfig);\n this.form.setData(entry.values);\n this.form.reset();\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n dispose(): void {\n this.form = null;\n this.entry = null;\n }\n}\n\nexport const SingleEntryPresenter = Abstraction.createImplementation({\n implementation: SingleEntryPresenterImpl,\n dependencies: [\n FormModelFactory,\n CmsFormModelBuilder,\n CmsModelContext,\n GetSingletonEntryUseCase,\n UpdateSingletonEntryUseCase\n ]\n});\n"],"names":["SingleEntryPresenterImpl","formModelFactory","cmsFormModelBuilder","modelAccessor","getSingletonEntryUseCase","updateSingletonEntryUseCase","makeAutoObservable","computed","form","model","data","entry","runInAction","error","toFormErrors","formConfig","SingleEntryPresenter","Abstraction","FormModelFactory","CmsFormModelBuilder","CmsModelContext","GetSingletonEntryUseCase","UpdateSingletonEntryUseCase"],"mappings":";;;;;;;AAeA,MAAMA;IAKF,YACYC,gBAA4C,EAC5CC,mBAAkD,EAClDC,aAAwC,EACxCC,wBAA4D,EAC5DC,2BAAkE,CAC5E;aALUJ,gBAAgB,GAAhBA;aACAC,mBAAmB,GAAnBA;aACAC,aAAa,GAAbA;aACAC,wBAAwB,GAAxBA;aACAC,2BAA2B,GAA3BA;aATJ,KAAK,GAA2B;aAChC,IAAI,GAAsB;aAC1B,OAAO,GAAkB;QAS7BC,mBAOE,IAAI,EAAE;YACJ,kBAAkB;YAClB,qBAAqB;YACrB,eAAe;YACf,0BAA0B;YAC1B,6BAA6B;YAC7B,IAAIC;QACR;IACJ;IAEA,IAAY,QAAQ;QAChB,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ;IACtC;IAEA,IAAI,KAA4B;QAC5B,OAAO;YACH,SAAS,IAAI,CAAC,OAAO;YACrB,OAAO,IAAI,CAAC,KAAK;YACjB,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM;YACvB,SAAS,AAAc,SAAd,IAAI,CAAC,IAAI;YAClB,SAAS,IAAI,CAAC,IAAI,EAAE,WAAW;QACnC;IACJ;IAEA,MAAM,OAAyB;QAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,EACV,OAAO;QAIX,MAAMC,OAAO,IAAI,CAAC,IAAI;QACtB,MAAMC,QAAQ,IAAI,CAAC,KAAK;QAExB,MAAMC,OAAO,MAAMF,KAAK,MAAM;QAC9B,IAAI,CAACE,MACD,OAAO;QAGX,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI;YACA,MAAMC,QAAQ,MAAM,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC;gBACzD,OAAO,IAAI,CAAC,KAAK;gBACjB,MAAM;oBACF,QAAQD;gBACZ;YACJ;YAEAE,YAAY;gBACR,IAAI,IAAI,CAAC,IAAI,KAAKJ,MACd;gBAEJ,IAAI,CAAC,KAAK,GAAGG;gBACbH,KAAK,OAAO,CAACG,MAAM,MAAM;gBACzBH,KAAK,KAAK;YACd;YAEA,OAAO;QACX,EAAE,OAAOK,OAAO;YACZ,IAAI,IAAI,CAAC,IAAI,KAAKL,MACdA,KAAK,SAAS,CAACM,aAAaD,OAAOJ;YAEvC,OAAO;QACX,SAAU;YACNG,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEA,MAAM,OAAsB;QACxB,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI;YACA,MAAMD,QAAQ,MAAM,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC;gBACtD,OAAO,IAAI,CAAC,KAAK;YACrB;YAEAC,YAAY;gBACR,IAAI,CAAC,KAAK,GAAGD;gBACb,MAAMI,aAAa,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK;gBAC5D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAACA;gBACzC,IAAI,CAAC,IAAI,CAAC,OAAO,CAACJ,MAAM,MAAM;gBAC9B,IAAI,CAAC,IAAI,CAAC,KAAK;YACnB;QACJ,SAAU;YACNC,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEA,UAAgB;QACZ,IAAI,CAAC,IAAI,GAAG;QACZ,IAAI,CAAC,KAAK,GAAG;IACjB;AACJ;AAEO,MAAMI,4CAAuBC,qBAAAA,oBAAgC,CAAC;IACjE,gBAAgBjB;IAChB,cAAc;QACVkB;QACAC;QACAC;QACAC;QACAC;KACH;AACL"}
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Container } from "@webiny/di";
3
+ import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
4
+ import { CmsFormModelBuilder } from "../../../features/formModel/abstractions.js";
5
+ import { CmsModelContext } from "../../../features/contentEntry/abstractions.js";
6
+ import { GetSingletonEntryUseCase, UpdateSingletonEntryUseCase } from "../../../features/contentEntry/singletonEntry/abstractions.js";
7
+ import { SingleEntryPresenter } from "./abstractions.js";
8
+ import { SingleEntryPresenter as external_SingleEntryPresenter_js_SingleEntryPresenter } from "./SingleEntryPresenter.js";
9
+ const createFakeForm = ()=>({
10
+ vm: {},
11
+ isDirty: false,
12
+ submit: vi.fn(async ()=>({
13
+ title: "Draft"
14
+ })),
15
+ setData: vi.fn(),
16
+ reset: vi.fn(),
17
+ setErrors: vi.fn()
18
+ });
19
+ const deferred = ()=>{
20
+ let resolve;
21
+ let reject;
22
+ const promise = new Promise((res, rej)=>{
23
+ resolve = res;
24
+ reject = rej;
25
+ });
26
+ return {
27
+ promise,
28
+ resolve,
29
+ reject
30
+ };
31
+ };
32
+ const setup = ()=>{
33
+ const forms = [];
34
+ let pendingUpdate = deferred();
35
+ const container = new Container();
36
+ container.registerInstance(FormModelFactory, {
37
+ create: ()=>{
38
+ const form = createFakeForm();
39
+ forms.push(form);
40
+ return form;
41
+ }
42
+ });
43
+ container.registerInstance(CmsFormModelBuilder, {
44
+ build: ()=>({})
45
+ });
46
+ container.registerInstance(CmsModelContext, {
47
+ getModel: ()=>({
48
+ modelId: "settings",
49
+ fields: []
50
+ })
51
+ });
52
+ container.registerInstance(GetSingletonEntryUseCase, {
53
+ execute: async ()=>({
54
+ id: "settings#0001",
55
+ values: {
56
+ title: "Saved"
57
+ }
58
+ })
59
+ });
60
+ container.registerInstance(UpdateSingletonEntryUseCase, {
61
+ execute: ()=>pendingUpdate.promise
62
+ });
63
+ container.register(external_SingleEntryPresenter_js_SingleEntryPresenter);
64
+ return {
65
+ presenter: container.resolve(SingleEntryPresenter),
66
+ forms,
67
+ nextUpdate: ()=>{
68
+ pendingUpdate = deferred();
69
+ return pendingUpdate;
70
+ }
71
+ };
72
+ };
73
+ describe("SingleEntryPresenter.save", ()=>{
74
+ it("puts a rejected save's errors on the form", async ()=>{
75
+ const { presenter, forms, nextUpdate } = setup();
76
+ await presenter.init();
77
+ const update = nextUpdate();
78
+ const saving = presenter.save();
79
+ update.reject(new Error("Value must be unique."));
80
+ await expect(saving).resolves.toBe(false);
81
+ expect(forms[0].setErrors).toHaveBeenCalledTimes(1);
82
+ });
83
+ it("returns false instead of throwing when the form was disposed mid-save", async ()=>{
84
+ const { presenter, nextUpdate } = setup();
85
+ await presenter.init();
86
+ const update = nextUpdate();
87
+ const saving = presenter.save();
88
+ presenter.dispose();
89
+ update.reject(new Error("Value must be unique."));
90
+ await expect(saving).resolves.toBe(false);
91
+ });
92
+ it("doesn't put a rejected save's errors on a form built after it started", async ()=>{
93
+ const { presenter, forms, nextUpdate } = setup();
94
+ await presenter.init();
95
+ const update = nextUpdate();
96
+ const saving = presenter.save();
97
+ await presenter.init();
98
+ update.reject(new Error("Value must be unique."));
99
+ await expect(saving).resolves.toBe(false);
100
+ expect(forms).toHaveLength(2);
101
+ expect(forms[0].setErrors).not.toHaveBeenCalled();
102
+ expect(forms[1].setErrors).not.toHaveBeenCalled();
103
+ });
104
+ it("doesn't overwrite a form built after a successful save started", async ()=>{
105
+ const { presenter, forms, nextUpdate } = setup();
106
+ await presenter.init();
107
+ const update = nextUpdate();
108
+ const saving = presenter.save();
109
+ await presenter.init();
110
+ const setDataCallsBefore = forms[1].setData.mock.calls.length;
111
+ update.resolve({
112
+ id: "settings#0002",
113
+ values: {
114
+ title: "From the old save"
115
+ }
116
+ });
117
+ await expect(saving).resolves.toBe(true);
118
+ expect(forms[1].setData).toHaveBeenCalledTimes(setDataCallsBefore);
119
+ });
120
+ });
121
+
122
+ //# sourceMappingURL=SingleEntryPresenter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js","sources":["../../../../src/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.ts"],"sourcesContent":["import { describe, it, expect, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n GetSingletonEntryUseCase,\n UpdateSingletonEntryUseCase\n} from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { SingleEntryPresenter as Abstraction } from \"./abstractions.js\";\nimport { SingleEntryPresenter } from \"./SingleEntryPresenter.js\";\n\nconst createFakeForm = () => ({\n vm: {},\n isDirty: false,\n submit: vi.fn(async () => ({ title: \"Draft\" })),\n setData: vi.fn(),\n reset: vi.fn(),\n setErrors: vi.fn()\n});\n\n/** A promise the test settles by hand, so it can act while a save is still in flight. */\nconst deferred = <T>() => {\n let resolve!: (value: T) => void;\n let reject!: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n};\n\nconst setup = () => {\n const forms: ReturnType<typeof createFakeForm>[] = [];\n let pendingUpdate = deferred<any>();\n\n const container = new Container();\n container.registerInstance(FormModelFactory, {\n create: () => {\n const form = createFakeForm();\n forms.push(form);\n return form;\n }\n } as unknown as FormModelFactory.Interface);\n container.registerInstance(CmsFormModelBuilder, {\n build: () => ({})\n } as unknown as CmsFormModelBuilder.Interface);\n container.registerInstance(CmsModelContext, {\n getModel: () => ({ modelId: \"settings\", fields: [] })\n } as unknown as CmsModelContext.Interface);\n container.registerInstance(GetSingletonEntryUseCase, {\n execute: async () => ({ id: \"settings#0001\", values: { title: \"Saved\" } })\n } as unknown as GetSingletonEntryUseCase.Interface);\n container.registerInstance(UpdateSingletonEntryUseCase, {\n execute: () => pendingUpdate.promise\n } as unknown as UpdateSingletonEntryUseCase.Interface);\n container.register(SingleEntryPresenter);\n\n return {\n presenter: container.resolve(Abstraction),\n forms,\n nextUpdate: () => {\n pendingUpdate = deferred<any>();\n return pendingUpdate;\n }\n };\n};\n\ndescribe(\"SingleEntryPresenter.save\", () => {\n it(\"puts a rejected save's errors on the form\", async () => {\n const { presenter, forms, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n update.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms[0].setErrors).toHaveBeenCalledTimes(1);\n });\n\n it(\"returns false instead of throwing when the form was disposed mid-save\", async () => {\n const { presenter, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n presenter.dispose();\n update.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n });\n\n it(\"doesn't put a rejected save's errors on a form built after it started\", async () => {\n const { presenter, forms, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n await presenter.init();\n update.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms).toHaveLength(2);\n expect(forms[0].setErrors).not.toHaveBeenCalled();\n expect(forms[1].setErrors).not.toHaveBeenCalled();\n });\n\n it(\"doesn't overwrite a form built after a successful save started\", async () => {\n const { presenter, forms, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n await presenter.init();\n const setDataCallsBefore = forms[1].setData.mock.calls.length;\n update.resolve({ id: \"settings#0002\", values: { title: \"From the old save\" } });\n\n await expect(saving).resolves.toBe(true);\n expect(forms[1].setData).toHaveBeenCalledTimes(setDataCallsBefore);\n });\n});\n"],"names":["createFakeForm","vi","deferred","resolve","reject","promise","Promise","res","rej","setup","forms","pendingUpdate","container","Container","FormModelFactory","form","CmsFormModelBuilder","CmsModelContext","GetSingletonEntryUseCase","UpdateSingletonEntryUseCase","SingleEntryPresenter","Abstraction","describe","it","presenter","nextUpdate","update","saving","Error","expect","setDataCallsBefore"],"mappings":"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"}