slice-machine-ui 1.22.3-beta.1 → 1.22.3-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/components/DeleteVariationModal/index.tsx +12 -11
  2. package/components/Forms/CreateSliceModal/CreateSliceModal.tsx +2 -5
  3. package/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx +9 -5
  4. package/components/Forms/RenameVariationModal/RenameVariationModal.tsx +12 -11
  5. package/components/ItemHeader/index.tsx +1 -0
  6. package/components/ListItem/index.tsx +5 -2
  7. package/components/ScreenshotChangesModal/VariationDropZone.tsx +31 -9
  8. package/components/ScreenshotChangesModal/index.tsx +3 -0
  9. package/components/ScreenshotChangesModal/useCustomScreenshot.tsx +5 -5
  10. package/components/Simulator/index.tsx +9 -3
  11. package/lib/builders/CustomTypeBuilder/SliceZone/SlicesTemplatesModal.tsx +2 -2
  12. package/lib/builders/CustomTypeBuilder/SliceZone/UpdateSliceZoneModal.tsx +2 -2
  13. package/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +12 -20
  14. package/lib/builders/CustomTypeBuilder/TabModal/update.tsx +3 -1
  15. package/lib/builders/CustomTypeBuilder/TabZone/index.tsx +119 -52
  16. package/lib/builders/CustomTypeBuilder/index.tsx +39 -42
  17. package/lib/builders/SliceBuilder/FieldZones/index.tsx +81 -56
  18. package/lib/builders/SliceBuilder/Sidebar/AddVariationModal.tsx +24 -16
  19. package/lib/builders/SliceBuilder/Sidebar/index.tsx +44 -46
  20. package/lib/builders/SliceBuilder/SimulatorButton/index.tsx +4 -28
  21. package/lib/builders/SliceBuilder/index.tsx +10 -96
  22. package/lib/builders/common/Zone/Card/components/Hints/CodeBlock.tsx +5 -1
  23. package/lib/builders/common/Zone/Card/components/NewField.tsx +16 -15
  24. package/lib/builders/common/Zone/Card/index.jsx +2 -0
  25. package/lib/builders/common/Zone/index.jsx +3 -1
  26. package/lib/models/common/ComponentUI.ts +0 -14
  27. package/lib/models/common/CustomType/sliceZone.ts +0 -29
  28. package/lib/models/common/CustomType/tab.ts +10 -142
  29. package/lib/models/common/widgets/Group/ListItem/index.jsx +61 -23
  30. package/out/404.html +1 -1
  31. package/out/_next/static/chunks/065a3ddb-9a38ca0d60f0bf2f.js +1 -0
  32. package/out/_next/static/chunks/183-4feb6bb3eddbc768.js +1 -0
  33. package/out/_next/static/chunks/18539003-7b2711e6c51d6fac.js +97 -0
  34. package/out/_next/static/chunks/1cc2734a-2c5b6c0367fcde86.js +1 -0
  35. package/out/_next/static/chunks/25-8fedd33ad9b6de5b.js +1 -0
  36. package/out/_next/static/chunks/318-541db69860d31138.js +1 -0
  37. package/out/_next/static/chunks/{505-3f1cf09a086dcba8.js → 376-0ed5822f8afcaa7c.js} +3 -3
  38. package/out/_next/static/chunks/4c744e84-e3161d754618373f.js +1 -0
  39. package/out/_next/static/chunks/50-cfc2514205b86001.js +1 -0
  40. package/out/_next/static/chunks/543-f8766685f7f451e2.js +1 -0
  41. package/out/_next/static/chunks/59b4e022-7a3b5554b806349f.js +1 -0
  42. package/out/_next/static/chunks/696-b12edad30a3617e5.js +31 -0
  43. package/out/_next/static/chunks/727-15cf3f2fea9a37e5.js +1 -0
  44. package/out/_next/static/chunks/731-501990ebe6a71b32.js +1 -0
  45. package/out/_next/static/chunks/787-727e3aef5d2a10a8.js +1 -0
  46. package/out/_next/static/chunks/904-ccd8ca3368ce29fc.js +1 -0
  47. package/out/_next/static/chunks/{918-38d08f1e885b222d.js → 918-5bb55fb464e34768.js} +1 -1
  48. package/out/_next/static/chunks/f36c6662-03bb5e9d9aa0b0aa.js +1 -0
  49. package/out/_next/static/chunks/pages/_app-20876abeebe0fda7.js +430 -0
  50. package/out/_next/static/chunks/pages/changelog-68d115a14f1423d7.js +1 -0
  51. package/out/_next/static/chunks/pages/changes-6f62ae7b6243a1c7.js +1 -0
  52. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-3a37eb3373bb8823.js → [customTypeId]-3f862ad6111a1fc0.js} +1 -1
  53. package/out/_next/static/chunks/pages/{custom-types-2f5664d9492b13fb.js → custom-types-94b8f8dbf7727412.js} +1 -1
  54. package/out/_next/static/chunks/pages/{index-d8e3a2c0d712904f.js → index-64cfb8b0948ba8ef.js} +1 -1
  55. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-733f3890c0466a05.js → [pageTypeId]-d1ffca11c42f2603.js} +1 -1
  56. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-897ca93c114a1435.js +1 -0
  57. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-e89e2e6f772a3454.js +1 -0
  58. package/out/_next/static/chunks/pages/slices-9628f8aa54968d6a.js +1 -0
  59. package/out/_next/static/css/{1bbf650dfd37ce69.css → 1aa04669758c24f4.css} +1 -1
  60. package/out/_next/static/j_voPTrYzsuLCERqtl3Hn/_buildManifest.js +1 -0
  61. package/out/changelog.html +1 -1
  62. package/out/changes.html +1 -1
  63. package/out/custom-types/[customTypeId].html +1 -1
  64. package/out/custom-types.html +1 -1
  65. package/out/index.html +1 -1
  66. package/out/labs.html +1 -1
  67. package/out/page-types/[pageTypeId].html +1 -1
  68. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  69. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  70. package/out/slices.html +1 -1
  71. package/package.json +6 -6
  72. package/pages/slices/[lib]/[sliceName]/[variation]/index.tsx +25 -11
  73. package/pages/slices/[lib]/[sliceName]/[variation]/simulator.tsx +12 -9
  74. package/src/apiClient.ts +7 -6
  75. package/src/components/CodeBlock/CodeBlock.tsx +1 -0
  76. package/src/components/Window/Window.tsx +7 -1
  77. package/src/domain/__tests__/customType.test.ts +785 -0
  78. package/src/domain/customType.ts +519 -6
  79. package/src/domain/slice.ts +166 -1
  80. package/src/features/autoSave/AutoSaveStatusIndicator.tsx +43 -0
  81. package/src/features/autoSave/useAutoSave.tsx +177 -0
  82. package/src/features/customTypes/EditDropdown.tsx +3 -0
  83. package/src/features/customTypes/actions/convertCustomToPageType.ts +2 -4
  84. package/src/features/customTypes/customTypesBuilder/CustomTypeProvider.tsx +90 -0
  85. package/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx +46 -79
  86. package/src/features/customTypes/customTypesMessages.ts +4 -0
  87. package/src/features/slices/actions/addSlicesToSliceZone.ts +2 -7
  88. package/src/features/slices/actions/uploadSliceScreenshot.ts +45 -0
  89. package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx +5 -5
  90. package/src/features/slices/convertLegacySlice/ConvertLegacySliceButton.tsx +4 -10
  91. package/src/features/slices/sliceBuilder/SliceBuilderProvider.tsx +110 -0
  92. package/src/features/slices/sliceBuilder/actions/deleteVariation.ts +8 -24
  93. package/src/features/slices/sliceBuilder/actions/renameVariation.ts +8 -24
  94. package/src/features/slices/sliceCards/SharedSliceCard.tsx +1 -0
  95. package/src/hooks/useCurrentSlice.tsx +4 -16
  96. package/src/hooks/useScreenshotChangesModal.ts +1 -0
  97. package/src/hooks/useServerState.ts +1 -58
  98. package/src/modules/availableCustomTypes/index.ts +8 -3
  99. package/src/modules/simulator/index.ts +0 -2
  100. package/src/modules/slices/index.ts +32 -25
  101. package/src/modules/slices/selector.ts +30 -0
  102. package/src/modules/useSliceMachineActions.ts +20 -270
  103. package/src/redux/reducer.ts +0 -4
  104. package/src/redux/saga.ts +0 -6
  105. package/src/redux/type.ts +0 -4
  106. package/test/src/modules/availableCustomTypes/index.test.ts +8 -10
  107. package/test/src/modules/simulator.test.ts +0 -2
  108. package/lib/models/common/CustomType/group.ts +0 -80
  109. package/lib/models/common/Variation.ts +0 -100
  110. package/out/_next/static/ZrWICkLApT4ZATA_akn-2/_buildManifest.js +0 -1
  111. package/out/_next/static/chunks/18539003-4460ccbfe3109578.js +0 -97
  112. package/out/_next/static/chunks/482-34cee2a2071c235f.js +0 -1
  113. package/out/_next/static/chunks/550-c80fd46fb64d1eef.js +0 -1
  114. package/out/_next/static/chunks/80-7b8a97d0122ff74a.js +0 -1
  115. package/out/_next/static/chunks/840-163f8a1040e6820d.js +0 -1
  116. package/out/_next/static/chunks/904-519dbf0f7e8c6243.js +0 -1
  117. package/out/_next/static/chunks/958-8aca174b80cb0c35.js +0 -1
  118. package/out/_next/static/chunks/pages/_app-132564608e9a9502.js +0 -458
  119. package/out/_next/static/chunks/pages/changelog-4d0f2f1222e403fe.js +0 -1
  120. package/out/_next/static/chunks/pages/changes-52c9d917808d1461.js +0 -1
  121. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-7b17cb59d0db03b5.js +0 -1
  122. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-ef11594ce026b650.js +0 -1
  123. package/out/_next/static/chunks/pages/slices-884a936a9a062a65.js +0 -1
  124. package/src/domain/__tests__/CustomTypeModel.test.ts +0 -247
  125. package/src/layouts/WithSlice.tsx +0 -26
  126. package/src/modules/screenshots/actions.ts +0 -21
  127. package/src/modules/screenshots/sagas.ts +0 -70
  128. package/src/modules/selectedCustomType/actions.ts +0 -147
  129. package/src/modules/selectedCustomType/index.ts +0 -11
  130. package/src/modules/selectedCustomType/reducer.ts +0 -287
  131. package/src/modules/selectedCustomType/sagas.ts +0 -72
  132. package/src/modules/selectedCustomType/selectors.ts +0 -33
  133. package/src/modules/selectedCustomType/stateHelpers.ts +0 -42
  134. package/src/modules/selectedCustomType/types.ts +0 -9
  135. package/src/modules/selectedSlice/actions.ts +0 -106
  136. package/src/modules/selectedSlice/reducer.ts +0 -175
  137. package/src/modules/selectedSlice/sagas.ts +0 -72
  138. package/src/modules/selectedSlice/selectors.ts +0 -55
  139. package/src/modules/selectedSlice/types.ts +0 -3
  140. package/src/modules/toaster/utils.tsx +0 -23
  141. package/test/lib/models/common/CustomType/sliceZone.test.ts +0 -49
  142. package/test/pages/custom-types.test.tsx +0 -625
  143. package/test/src/modules/selectedCustomType/__fixtures__/model.json +0 -119
  144. package/test/src/modules/selectedCustomType/reducer.test.ts +0 -322
  145. package/test/src/modules/selectedCustomType/sagas.test.ts +0 -89
  146. package/test/src/modules/selectedCustomType/selectors.test.ts +0 -54
  147. package/test/src/modules/selectedSlice/__fixtures__/mocks.json +0 -171
  148. package/test/src/modules/selectedSlice/__fixtures__/model.json +0 -35
  149. package/test/src/modules/selectedSlice/__testutils__/getRefreshStateCreatorPayloadData.ts +0 -72
  150. package/test/src/modules/selectedSlice/__testutils__/getSelectedSliceDummyData.ts +0 -30
  151. package/test/src/modules/selectedSlice/reducer.test.ts +0 -170
  152. package/test/src/modules/selectedSlice/sagas.test.ts +0 -82
  153. package/test/src/modules/toaster/utils.test.ts +0 -50
  154. /package/out/_next/static/{ZrWICkLApT4ZATA_akn-2 → j_voPTrYzsuLCERqtl3Hn}/_ssgManifest.js +0 -0
@@ -1,67 +1,74 @@
1
1
  import { Box, ErrorBoundary, ProgressCircle } from "@prismicio/editor-ui";
2
2
  import { FC, Suspense } from "react";
3
+ import { flushSync } from "react-dom";
3
4
  import type { DropResult } from "react-beautiful-dnd";
4
- import { useSelector } from "react-redux";
5
5
  import type { AnyObjectSchema } from "yup";
6
6
  import { useRouter } from "next/router";
7
7
 
8
8
  import ctBuilderArray from "@lib/models/common/widgets/ctBuilderArray";
9
- import type {
10
- CustomTypeSM,
11
- TabField,
12
- TabFields,
9
+ import {
10
+ TabSM,
11
+ type TabField,
12
+ type TabFields,
13
+ CustomTypes,
14
+ TabFieldsModel,
13
15
  } from "@lib/models/common/CustomType";
14
- import type { SlicesSM } from "@lib/models/common/Slices";
16
+ import type { AnyWidget } from "@lib/models/common/widgets/Widget";
15
17
  import { ensureDnDDestination, ensureWidgetTypeExistence } from "@lib/utils";
16
18
  import { List } from "@src/components/List";
17
- import useSliceMachineActions from "@src/modules/useSliceMachineActions";
18
- import type { SliceMachineStoreType } from "@src/redux/type";
19
19
  import { telemetry } from "@src/apiClient";
20
20
  import { transformKeyAccessor } from "@utils/str";
21
+ import { useCustomTypeState } from "@src/features/customTypes/customTypesBuilder/CustomTypeProvider";
22
+ import {
23
+ createSectionSliceZone,
24
+ deleteSectionSliceZone,
25
+ deleteSliceZoneSlice,
26
+ addField,
27
+ deleteField,
28
+ reorderField,
29
+ updateField,
30
+ } from "@src/domain/customType";
21
31
 
22
32
  import * as Widgets from "../../../../lib/models/common/widgets/withGroup";
23
- import { selectCurrentPoolOfFields } from "../../../../src/modules/selectedCustomType";
24
- import type { Widget } from "../../../models/common/widgets/Widget";
25
33
  import EditModal from "../../common/EditModal";
26
34
  import Zone from "../../common/Zone";
27
35
  import SliceZone from "../SliceZone";
36
+ import {
37
+ Group,
38
+ NestableWidget,
39
+ UID,
40
+ } from "@prismicio/types-internal/lib/customtypes";
28
41
 
29
42
  interface TabZoneProps {
30
- customType: CustomTypeSM;
31
43
  tabId: string;
32
- sliceZone?: SlicesSM | null | undefined;
33
- fields: TabFields;
34
44
  }
35
45
 
36
- const TabZone: FC<TabZoneProps> = ({
37
- customType,
38
- tabId,
39
- fields,
40
- sliceZone,
41
- }) => {
42
- const {
43
- deleteCustomTypeField,
44
- addCustomTypeField,
45
- reorderCustomTypeField,
46
- replaceCustomTypeField,
47
- createSliceZone,
48
- deleteSliceZone,
49
- deleteCustomTypeSharedSlice,
50
- } = useSliceMachineActions();
46
+ type PoolOfFields = ReadonlyArray<{ key: string; value: TabField }>;
51
47
 
52
- const { query } = useRouter();
53
-
54
- const { poolOfFields } = useSelector((store: SliceMachineStoreType) => ({
55
- poolOfFields: selectCurrentPoolOfFields(store),
56
- }));
48
+ const TabZone: FC<TabZoneProps> = ({ tabId }) => {
49
+ const { customType, setCustomType } = useCustomTypeState();
50
+ const customTypeSM = CustomTypes.toSM(customType);
51
+ const sliceZone = customTypeSM.tabs.find((tab) => tab.key === tabId)
52
+ ?.sliceZone;
53
+ const fields: TabFields =
54
+ customTypeSM.tabs.find((tab) => tab.key === tabId)?.value ?? [];
57
55
 
58
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
59
- if (!poolOfFields) {
60
- return null;
61
- }
56
+ const { query } = useRouter();
57
+ const poolOfFields = customTypeSM.tabs.reduce<PoolOfFields>(
58
+ (acc: PoolOfFields, curr: TabSM) => {
59
+ return [...acc, ...curr.value];
60
+ },
61
+ [],
62
+ );
62
63
 
63
64
  const onDeleteItem = (fieldId: string) => {
64
- deleteCustomTypeField(tabId, fieldId);
65
+ const newCustomType = deleteField({
66
+ customType,
67
+ fieldId,
68
+ sectionId: tabId,
69
+ });
70
+
71
+ setCustomType(newCustomType);
65
72
  };
66
73
 
67
74
  const onSaveNewField = ({
@@ -80,27 +87,67 @@ const TabZone: FC<TabZoneProps> = ({
80
87
  // @ts-expect-error We have to create a widget map or a service instead of using export name
81
88
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
82
89
  const widget: Widget<TabField, AnyObjectSchema> = Widgets[widgetTypeName];
90
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
91
+ const field: TabField = widget.create(label);
92
+
93
+ if (
94
+ field.type === "Range" ||
95
+ field.type === "IntegrationFields" ||
96
+ field.type === "Separator"
97
+ ) {
98
+ throw new Error(`Unsupported Field Type: ${field.type}`);
99
+ }
100
+
101
+ const CurrentWidget: AnyWidget = Widgets[field.type];
102
+
103
+ try {
104
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
105
+ CurrentWidget.schema.validateSync(field, { stripUnknown: false });
106
+ } catch (error) {
107
+ throw new Error(`Add field: Model is invalid for field "${field.type}".`);
108
+ }
109
+
110
+ const newField: NestableWidget | UID | Group = TabFieldsModel.fromSM(field);
111
+ const newCustomType = addField({
112
+ customType,
113
+ newField,
114
+ newFieldId: id,
115
+ sectionId: tabId,
116
+ });
117
+
118
+ setCustomType(newCustomType);
119
+
83
120
  void telemetry.track({
84
121
  event: "custom-type:field-added",
85
122
  id,
86
123
  name: customType.id,
124
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
87
125
  type: widget.TYPE_NAME,
88
126
  zone: "static",
89
127
  });
90
- addCustomTypeField(tabId, id, widget.create(label));
91
128
  };
92
129
 
93
130
  const onDragEnd = (result: DropResult) => {
94
131
  if (ensureDnDDestination(result)) {
95
132
  return;
96
133
  }
97
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
98
- reorderCustomTypeField(
99
- tabId,
100
- result.source.index,
101
- // @ts-expect-error We have to change the typeGuard above to cast properly the "result" property
102
- result.destination.index,
103
- );
134
+
135
+ const { source, destination } = result;
136
+ if (!destination) {
137
+ return;
138
+ }
139
+
140
+ const newCustomType = reorderField({
141
+ customType,
142
+ sourceIndex: source.index,
143
+ destinationIndex: destination.index,
144
+ sectionId: tabId,
145
+ });
146
+
147
+ // When removing redux and replacing it by a simple useState, react-beautiful-dnd (that is deprecated library) was making the fields flickering on reorder.
148
+ // The problem seems to come from the react non-synchronous way to handle our state update that didn't work well with the library.
149
+ // It's a hack and since it's used on an old pure JavaScript code with a deprecated library it will be removed when updating the UI of the fields.
150
+ flushSync(() => setCustomType(newCustomType));
104
151
  };
105
152
 
106
153
  const onSave = ({
@@ -117,19 +164,39 @@ const TabZone: FC<TabZoneProps> = ({
117
164
  if (ensureWidgetTypeExistence(Widgets, value.type)) {
118
165
  return;
119
166
  }
120
- replaceCustomTypeField(tabId, previousKey, newKey, value);
167
+
168
+ const newField: NestableWidget | UID | Group = TabFieldsModel.fromSM(value);
169
+ const newCustomType = updateField({
170
+ customType,
171
+ previousFieldId: previousKey,
172
+ newFieldId: newKey,
173
+ newField,
174
+ sectionId: tabId,
175
+ });
176
+
177
+ setCustomType(newCustomType);
121
178
  };
122
179
 
123
180
  const onCreateSliceZone = () => {
124
- createSliceZone(tabId);
181
+ const newCustomType = createSectionSliceZone(customType, tabId);
182
+
183
+ setCustomType(newCustomType);
125
184
  };
126
185
 
127
186
  const onDeleteSliceZone = () => {
128
- deleteSliceZone(tabId);
187
+ const newCustomType = deleteSectionSliceZone(customType, tabId);
188
+
189
+ setCustomType(newCustomType);
129
190
  };
130
191
 
131
192
  const onRemoveSharedSlice = (sliceId: string) => {
132
- deleteCustomTypeSharedSlice(tabId, sliceId);
193
+ const newCustomType = deleteSliceZoneSlice({
194
+ customType,
195
+ sectionId: tabId,
196
+ sliceId,
197
+ });
198
+
199
+ setCustomType(newCustomType);
133
200
  };
134
201
 
135
202
  return (
@@ -169,13 +236,13 @@ const TabZone: FC<TabZoneProps> = ({
169
236
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument
170
237
  `data${transformKeyAccessor(key)}`
171
238
  }
172
- dataCy="ct-static-zone"
239
+ dataCy="static-zone-content"
173
240
  isRepeatableCustomType={customType.repeatable}
174
241
  />
175
242
  ) : undefined}
176
243
 
177
244
  <SliceZone
178
- customType={customType}
245
+ customType={customTypeSM}
179
246
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
180
247
  tabId={tabId}
181
248
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -1,8 +1,7 @@
1
1
  import { DropdownMenuItem, Icon } from "@prismicio/editor-ui";
2
- import { type FC, useState } from "react";
2
+ import { useState } from "react";
3
3
  import { useRouter } from "next/router";
4
4
 
5
- import type { CustomTypeSM } from "@lib/models/common/CustomType";
6
5
  import {
7
6
  Window,
8
7
  WindowFrame,
@@ -11,33 +10,35 @@ import {
11
10
  WindowTabsList,
12
11
  WindowTabsTrigger,
13
12
  } from "@src/components/Window";
14
- import useSliceMachineActions from "@src/modules/useSliceMachineActions";
13
+ import { useCustomTypeState } from "@src/features/customTypes/customTypesBuilder/CustomTypeProvider";
14
+ import { CustomTypes } from "@lib/models/common/CustomType";
15
+ import {
16
+ createSection,
17
+ deleteSection,
18
+ renameSection,
19
+ } from "@src/domain/customType";
15
20
 
16
21
  import CreateModal from "./TabModal/create";
17
22
  import DeleteModal from "./TabModal/delete";
18
23
  import UpdateModal from "./TabModal/update";
19
24
  import TabZone from "./TabZone";
20
25
 
21
- type CustomTypeBuilderProps = { customType: CustomTypeSM };
22
-
23
26
  type DialogState =
24
- | { type: "CREATE_CUSTOM_TYPE" }
25
- | { type: "UPDATE_CUSTOM_TYPE"; tabKey: string }
26
- | { type: "DELETE_CUSTOM_TYPE"; tabKey: string }
27
+ | { type: "CREATE_CUSTOM_TYPE_TAB" }
28
+ | { type: "UPDATE_CUSTOM_TYPE_TAB"; tabKey: string }
29
+ | { type: "DELETE_CUSTOM_TYPE_TAB"; tabKey: string }
27
30
  | undefined;
28
31
 
29
- export const CustomTypeBuilder: FC<CustomTypeBuilderProps> = (props) => {
30
- const { customType } = props;
31
-
32
- const [tabValue, setTabValue] = useState(customType.tabs[0]?.key);
32
+ export const CustomTypeBuilder = () => {
33
+ const { customType, setCustomType } = useCustomTypeState();
34
+ const customTypeSM = CustomTypes.toSM(customType);
35
+ const [tabValue, setTabValue] = useState(customTypeSM.tabs[0]?.key);
33
36
 
34
37
  const [dialog, setDialog] = useState<DialogState>();
35
- const { createCustomTypeTab, updateCustomTypeTab, deleteCustomTypeTab } =
36
- useSliceMachineActions();
37
38
 
38
39
  const { query } = useRouter();
39
40
  const sliceZoneEmpty =
40
- customType.tabs.find((tab) => tab.key === tabValue)?.sliceZone?.value
41
+ customTypeSM.tabs.find((tab) => tab.key === tabValue)?.sliceZone?.value
41
42
  .length === 0;
42
43
 
43
44
  return (
@@ -45,20 +46,15 @@ export const CustomTypeBuilder: FC<CustomTypeBuilderProps> = (props) => {
45
46
  <Window style={sliceZoneEmpty ? { flexGrow: 1 } : undefined}>
46
47
  {customType.format === "page" ? <WindowFrame /> : undefined}
47
48
  {query.newPageType === "true" ? (
48
- <TabZone
49
- customType={customType}
50
- fields={customType.tabs[0].value}
51
- sliceZone={customType.tabs[0].sliceZone}
52
- tabId={customType.tabs[0].key}
53
- />
49
+ <TabZone tabId={customTypeSM.tabs[0].key} />
54
50
  ) : (
55
51
  <WindowTabs onValueChange={setTabValue} value={tabValue}>
56
52
  <WindowTabsList
57
53
  onAddNewTab={() => {
58
- setDialog({ type: "CREATE_CUSTOM_TYPE" });
54
+ setDialog({ type: "CREATE_CUSTOM_TYPE_TAB" });
59
55
  }}
60
56
  >
61
- {customType.tabs.map((tab) => (
57
+ {customTypeSM.tabs.map((tab) => (
62
58
  <WindowTabsTrigger
63
59
  key={tab.key}
64
60
  menu={
@@ -66,7 +62,7 @@ export const CustomTypeBuilder: FC<CustomTypeBuilderProps> = (props) => {
66
62
  <DropdownMenuItem
67
63
  onSelect={() => {
68
64
  setDialog({
69
- type: "UPDATE_CUSTOM_TYPE",
65
+ type: "UPDATE_CUSTOM_TYPE_TAB",
70
66
  tabKey: tab.key,
71
67
  });
72
68
  }}
@@ -76,10 +72,10 @@ export const CustomTypeBuilder: FC<CustomTypeBuilderProps> = (props) => {
76
72
  </DropdownMenuItem>
77
73
  <DropdownMenuItem
78
74
  color="tomato"
79
- disabled={customType.tabs.length <= 1}
75
+ disabled={customTypeSM.tabs.length <= 1}
80
76
  onSelect={() => {
81
77
  setDialog({
82
- type: "DELETE_CUSTOM_TYPE",
78
+ type: "DELETE_CUSTOM_TYPE_TAB",
83
79
  tabKey: tab.key,
84
80
  });
85
81
  }}
@@ -95,57 +91,58 @@ export const CustomTypeBuilder: FC<CustomTypeBuilderProps> = (props) => {
95
91
  </WindowTabsTrigger>
96
92
  ))}
97
93
  </WindowTabsList>
98
- {customType.tabs.map((tab) => (
94
+ {customTypeSM.tabs.map((tab) => (
99
95
  <WindowTabsContent key={tab.key} value={tab.key}>
100
- <TabZone
101
- customType={customType}
102
- fields={tab.value}
103
- sliceZone={tab.sliceZone}
104
- tabId={tab.key}
105
- />
96
+ <TabZone tabId={tab.key} />
106
97
  </WindowTabsContent>
107
98
  ))}
108
99
  </WindowTabs>
109
100
  )}
110
101
  </Window>
111
- {dialog?.type === "CREATE_CUSTOM_TYPE" ? (
102
+ {dialog?.type === "CREATE_CUSTOM_TYPE_TAB" ? (
112
103
  <CreateModal
113
104
  close={() => {
114
105
  setDialog(undefined);
115
106
  }}
116
107
  isOpen
117
108
  onSubmit={({ id }) => {
118
- createCustomTypeTab(id);
109
+ const newCustomType = createSection(customType, id);
110
+ setCustomType(newCustomType);
119
111
  setTabValue(id);
120
112
  }}
121
- tabIds={customType.tabs.map((tab) => tab.key.toLowerCase())}
113
+ tabIds={customTypeSM.tabs.map((tab) => tab.key.toLowerCase())}
122
114
  />
123
115
  ) : undefined}
124
- {dialog?.type === "UPDATE_CUSTOM_TYPE" ? (
116
+ {dialog?.type === "UPDATE_CUSTOM_TYPE_TAB" ? (
125
117
  <UpdateModal
126
118
  close={() => {
127
119
  setDialog(undefined);
128
120
  }}
121
+ initialTabKey={dialog.tabKey}
129
122
  isOpen
130
123
  onSubmit={({ id }) => {
131
- updateCustomTypeTab(dialog.tabKey, id);
124
+ const newCustomType = renameSection(customType, dialog.tabKey, id);
125
+ setCustomType(newCustomType);
126
+
132
127
  if (tabValue === dialog.tabKey) setTabValue(id);
133
128
  }}
134
- tabIds={customType.tabs
129
+ tabIds={customTypeSM.tabs
135
130
  .filter((tab) => tab.key !== dialog.tabKey)
136
131
  .map((tab) => tab.key.toLowerCase())}
137
132
  />
138
133
  ) : undefined}
139
- {dialog?.type === "DELETE_CUSTOM_TYPE" ? (
134
+ {dialog?.type === "DELETE_CUSTOM_TYPE_TAB" ? (
140
135
  <DeleteModal
141
136
  close={() => {
142
137
  setDialog(undefined);
143
138
  }}
144
139
  isOpen
145
140
  onSubmit={() => {
146
- deleteCustomTypeTab(dialog.tabKey);
141
+ const newCustomType = deleteSection(customType, dialog.tabKey);
142
+ setCustomType(newCustomType);
143
+
147
144
  if (tabValue === dialog.tabKey) {
148
- const otherTabValue = customType.tabs.find(
145
+ const otherTabValue = customTypeSM.tabs.find(
149
146
  (tab) => tab.key !== dialog.tabKey,
150
147
  )?.key;
151
148
  if (otherTabValue !== undefined) setTabValue(otherTabValue);
@@ -1,18 +1,24 @@
1
+ import { FC } from "react";
2
+ import { flushSync } from "react-dom";
3
+ import { DropResult } from "react-beautiful-dnd";
4
+ import { NestableWidget } from "@prismicio/types-internal/lib/customtypes";
5
+
1
6
  import { ensureDnDDestination } from "@lib/utils";
2
7
  import { transformKeyAccessor } from "@utils/str";
3
-
4
- import Zone from "../../common/Zone";
5
- import EditModal from "../../common/EditModal";
6
-
7
8
  import * as Widgets from "@lib/models/common/widgets";
8
9
  import sliceBuilderWidgetsArray from "@lib/models/common/widgets/sliceBuilderArray";
9
-
10
- import { DropResult } from "react-beautiful-dnd";
11
10
  import { List } from "@src/components/List";
12
- import useSliceMachineActions from "@src/modules/useSliceMachineActions";
13
- import { VariationSM, WidgetsArea } from "@lib/models/common/Slice";
14
-
15
- import { NestableWidget } from "@prismicio/types-internal/lib/customtypes";
11
+ import { WidgetsArea } from "@lib/models/common/Slice";
12
+ import { useSliceState } from "@src/features/slices/sliceBuilder/SliceBuilderProvider";
13
+ import {
14
+ addField,
15
+ deleteField,
16
+ reorderField,
17
+ updateField,
18
+ } from "@src/domain/slice";
19
+ import Zone from "@lib/builders/common/Zone";
20
+ import EditModal from "@lib/builders/common/EditModal";
21
+ import { AnyWidget } from "@lib/models/common/widgets/Widget";
16
22
 
17
23
  const dataTipText = ` The non-repeatable zone
18
24
  is for fields<br/> that should appear once, like a<br/>
@@ -22,24 +28,18 @@ const dataTipText2 = `The repeatable zone is for a group<br/>
22
28
  of fields that you want to be able to repeat an<br/>
23
29
  indeterminate number of times, like FAQs`;
24
30
 
25
- type FieldZonesProps = {
26
- variation: VariationSM;
27
- };
31
+ const FieldZones: FC = () => {
32
+ const { slice, setSlice, variation } = useSliceState();
28
33
 
29
- const FieldZones: React.FunctionComponent<FieldZonesProps> = ({
30
- variation,
31
- }) => {
32
- const {
33
- addSliceWidget,
34
- replaceSliceWidget,
35
- reorderSliceWidget,
36
- removeSliceWidget,
37
- updateSliceWidgetMock,
38
- deleteSliceWidgetMock,
39
- } = useSliceMachineActions();
40
34
  const _onDeleteItem = (widgetArea: WidgetsArea) => (key: string) => {
41
- deleteSliceWidgetMock(variation.id, widgetArea, key);
42
- removeSliceWidget(variation.id, widgetArea, key);
35
+ const newSlice = deleteField({
36
+ slice,
37
+ variationId: variation.id,
38
+ widgetArea,
39
+ fieldId: key,
40
+ });
41
+
42
+ setSlice(newSlice);
43
43
  };
44
44
 
45
45
  const _onSave =
@@ -48,30 +48,23 @@ const FieldZones: React.FunctionComponent<FieldZonesProps> = ({
48
48
  apiId: previousKey,
49
49
  newKey,
50
50
  value,
51
- mockValue,
52
51
  }: {
53
52
  apiId: string;
54
53
  newKey: string;
55
54
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
55
  value: any;
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- mockValue: any;
59
56
  }) => {
60
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
61
- if (mockValue) {
62
- updateSliceWidgetMock(
63
- variation.id,
64
- widgetArea,
65
- previousKey,
66
- newKey,
67
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
68
- mockValue,
69
- );
70
- } else {
71
- deleteSliceWidgetMock(variation.id, widgetArea, newKey);
72
- }
73
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
74
- replaceSliceWidget(variation.id, widgetArea, previousKey, newKey, value);
57
+ const newSlice = updateField({
58
+ slice,
59
+ variationId: variation.id,
60
+ widgetArea,
61
+ previousFieldId: previousKey,
62
+ newFieldId: newKey,
63
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
64
+ newField: value,
65
+ });
66
+
67
+ setSlice(newSlice);
75
68
  };
76
69
 
77
70
  const _onSaveNewField =
@@ -96,24 +89,56 @@ const FieldZones: React.FunctionComponent<FieldZonesProps> = ({
96
89
  );
97
90
  }
98
91
 
99
- addSliceWidget(
100
- variation.id,
101
- widgetArea,
102
- id,
92
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
93
+ const newField = widget.create(label) as NestableWidget;
94
+
95
+ if (
96
+ newField.type === "Range" ||
97
+ newField.type === "IntegrationFields" ||
98
+ newField.type === "Separator"
99
+ ) {
100
+ throw new Error(`Unsupported Field Type: ${newField.type}`);
101
+ }
102
+
103
+ try {
104
+ const CurrentWidget: AnyWidget = Widgets[newField.type];
103
105
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
104
- widget.create(label) as NestableWidget,
105
- );
106
+ CurrentWidget.schema.validateSync(newField, { stripUnknown: false });
107
+ } catch (error) {
108
+ throw new Error(`Model is invalid for widget "${newField.type}".`);
109
+ }
110
+
111
+ const newSlice = addField({
112
+ slice,
113
+ variationId: variation.id,
114
+ widgetArea,
115
+ newFieldId: id,
116
+ newField,
117
+ });
118
+
119
+ setSlice(newSlice);
106
120
  };
107
121
 
108
122
  const _onDragEnd = (widgetArea: WidgetsArea) => (result: DropResult) => {
109
123
  if (ensureDnDDestination(result)) return;
110
124
 
111
- reorderSliceWidget(
112
- variation.id,
125
+ const { source, destination } = result;
126
+ if (!destination) {
127
+ return;
128
+ }
129
+
130
+ const newSlice = reorderField({
131
+ slice,
132
+ variationId: variation.id,
113
133
  widgetArea,
114
- result.source.index,
115
- result.destination?.index ?? undefined,
116
- );
134
+ sourceIndex: source.index,
135
+ destinationIndex: destination.index,
136
+ });
137
+
138
+ // When removing redux and replacing it by a simple useState, react-beautiful-dnd (that is deprecated library) was making the fields flickering on reorder.
139
+ // The problem seems to come from the react non-synchronous way to handle our state update that didn't work well with the library.
140
+ // It's a hack and since it's used on an old pure JavaScript code with a deprecated library it will be removed when updating the UI of the fields.
141
+ flushSync(() => setSlice(newSlice));
117
142
  };
118
143
 
119
144
  return (
@@ -141,7 +166,7 @@ const FieldZones: React.FunctionComponent<FieldZonesProps> = ({
141
166
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
142
167
  `slice.primary${transformKeyAccessor(key)}`
143
168
  }
144
- dataCy="slice-non-repeatable-zone"
169
+ dataCy="static-zone-content"
145
170
  isRepeatableCustomType={undefined}
146
171
  />
147
172
  <Zone