slice-machine-ui 1.14.2-dev-end-release.0 → 1.15.1-dev-upgrade-legacy.1

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 (52) hide show
  1. package/components/ChangesItems/ChangesItems.tsx +1 -1
  2. package/components/Forms/ConvertLegacySliceModal/ConvertLegacySliceModal.css.ts +35 -0
  3. package/components/Forms/ConvertLegacySliceModal/ConvertLegacySliceModal.tsx +284 -0
  4. package/components/Forms/ConvertLegacySliceModal/FormAsNewSlice.tsx +153 -0
  5. package/components/Forms/ConvertLegacySliceModal/FormAsNewVariation.tsx +234 -0
  6. package/components/Forms/ConvertLegacySliceModal/FormMergeWithIdentical.tsx +104 -0
  7. package/components/Forms/ConvertLegacySliceModal/index.tsx +1 -0
  8. package/components/Forms/ConvertLegacySliceModal/types.ts +45 -0
  9. package/lib/builders/CustomTypeBuilder/SliceZone/List.tsx +11 -4
  10. package/lib/builders/CustomTypeBuilder/SliceZone/UpdateSliceZoneModalList.tsx +1 -1
  11. package/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +9 -0
  12. package/lib/models/ui/Slice/index.tsx +46 -26
  13. package/out/404.html +1 -1
  14. package/out/[lib]/[sliceName]/[variation]/screenshot.html +1 -1
  15. package/out/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  16. package/out/[lib]/[sliceName]/[variation].html +1 -1
  17. package/out/_next/static/85_HcNXu06Bs3Oh4iRFpf/_buildManifest.js +1 -0
  18. package/out/_next/static/chunks/130-5b1dc0c67078c48b.js +1 -0
  19. package/out/_next/static/chunks/323-02b5868f1cabe630.js +1 -0
  20. package/out/_next/static/chunks/4-25986da31c1ce2be.js +1 -0
  21. package/out/_next/static/chunks/799-00944cdde5b22566.js +1 -0
  22. package/out/_next/static/chunks/80-dce703180b269337.js +1 -0
  23. package/out/_next/static/chunks/934-7e172f9ea1128015.js +1 -0
  24. package/out/_next/static/chunks/{740-d0642219804fa9f0.js → 969-bcfa23e8fd4773e7.js} +1 -1
  25. package/out/_next/static/chunks/pages/[lib]/[sliceName]/[variation]-fc545d9beb1bb757.js +1 -0
  26. package/out/_next/static/chunks/pages/{_app-83f816a50181ce54.js → _app-5ae4b74626b97680.js} +7 -7
  27. package/out/_next/static/chunks/pages/changes-eb708af5609cc7d2.js +1 -0
  28. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-4fe86acfed194773.js → [customTypeId]-0ac437801bc7a8d8.js} +1 -1
  29. package/out/_next/static/chunks/pages/{custom-types-abb8c8c92880120e.js → custom-types-8d55eca5790c0406.js} +1 -1
  30. package/out/_next/static/chunks/pages/{index-f382c8090bd21628.js → index-203d9ef99d22841a.js} +1 -1
  31. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-87f5f9704f2c3f2d.js → [pageTypeId]-2aa8767c8a0f1e97.js} +1 -1
  32. package/out/_next/static/chunks/pages/slices-d30dd7a4b77612eb.js +1 -0
  33. package/out/changelog.html +1 -1
  34. package/out/changes.html +1 -1
  35. package/out/custom-types/[customTypeId].html +1 -1
  36. package/out/custom-types.html +1 -1
  37. package/out/index.html +1 -1
  38. package/out/page-types/[pageTypeId].html +1 -1
  39. package/out/slices.html +1 -1
  40. package/package.json +5 -5
  41. package/pages/slices.tsx +1 -1
  42. package/out/_next/static/9z4nwFKpmyMKrjQjtDHXy/_buildManifest.js +0 -1
  43. package/out/_next/static/chunks/323-7b84275e59479db8.js +0 -1
  44. package/out/_next/static/chunks/482-a4c1c3721ca38516.js +0 -1
  45. package/out/_next/static/chunks/573-aaae870d20e5f86c.js +0 -1
  46. package/out/_next/static/chunks/799-5ea3393033a9f617.js +0 -1
  47. package/out/_next/static/chunks/906-9224feb63b5e36f3.js +0 -1
  48. package/out/_next/static/chunks/97-f6188ebe22eb21da.js +0 -1
  49. package/out/_next/static/chunks/pages/[lib]/[sliceName]/[variation]-9fff494255e34c39.js +0 -1
  50. package/out/_next/static/chunks/pages/changes-6a039e823313b41c.js +0 -1
  51. package/out/_next/static/chunks/pages/slices-950d2b9b372ba5ab.js +0 -1
  52. /package/out/_next/static/{9z4nwFKpmyMKrjQjtDHXy → 85_HcNXu06Bs3Oh4iRFpf}/_ssgManifest.js +0 -0
@@ -118,7 +118,7 @@ export const ChangesItems: React.FC<ChangesItemsProps> = ({
118
118
  gridTemplateMinPx="290px"
119
119
  defineElementKey={(slice: ComponentUI) => slice.model.name}
120
120
  renderElem={(slice: ComponentUI) => {
121
- return SharedSlice.render({
121
+ return SharedSlice.Render({
122
122
  showActions: true,
123
123
  slice: slice,
124
124
  wrapperType:
@@ -0,0 +1,35 @@
1
+ import { sprinkles } from "@prismicio/editor-ui";
2
+ import { style } from "@vanilla-extract/css";
3
+
4
+ export const form = style([
5
+ sprinkles({
6
+ display: "flex",
7
+ flexDirection: "column",
8
+ }),
9
+ ]);
10
+
11
+ export const scrollArea = style([
12
+ sprinkles({
13
+ height: "100%",
14
+ display: "flex",
15
+ gap: 8,
16
+ paddingInline: 16,
17
+ paddingBlock: 16,
18
+ }),
19
+ ]);
20
+
21
+ export const field = style([
22
+ sprinkles({
23
+ display: "flex",
24
+ flexDirection: "column",
25
+ gap: 4,
26
+ }),
27
+ ]);
28
+
29
+ export const label = style([
30
+ sprinkles({
31
+ display: "inline-flex",
32
+ alignItems: "center",
33
+ gap: 4,
34
+ }),
35
+ ]);
@@ -0,0 +1,284 @@
1
+ import { useMemo, useState } from "react";
2
+ import { useSelector } from "react-redux";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ Icon,
9
+ Button,
10
+ } from "@prismicio/editor-ui";
11
+ import { CompositeSlice } from "@prismicio/types-internal/lib/customtypes";
12
+
13
+ import { VariationSM } from "@models/common/Slice";
14
+ import { CustomTypes } from "@models/common/CustomType";
15
+ import { getLibraries } from "@src/modules/slices";
16
+ import { SliceMachineStoreType } from "@src/redux/type";
17
+ import { managerClient } from "@src/managerClient";
18
+ import { getState, telemetry } from "@src/apiClient";
19
+ import useSliceMachineActions from "@src/modules/useSliceMachineActions";
20
+ import { ToasterType } from "@src/modules/toaster";
21
+
22
+ import { FormAsNewSlice } from "./FormAsNewSlice";
23
+ import { FormAsNewVariation } from "./FormAsNewVariation";
24
+ import { FormMergeWithIdentical } from "./FormMergeWithIdentical";
25
+ import {
26
+ ConvertLegacySliceModalProps,
27
+ ConvertLegacySliceAndTrackArgs,
28
+ IdenticalSlice,
29
+ Type,
30
+ } from "./types";
31
+
32
+ const getFieldMappingFingerprint = (
33
+ slice: CompositeSlice | VariationSM
34
+ ): {
35
+ primary: string;
36
+ items: string;
37
+ } => {
38
+ const primary: Record<string, string> = {};
39
+ const items: Record<string, string> = {};
40
+
41
+ if ("type" in slice && slice.type === "Slice") {
42
+ for (const key in slice["non-repeat"]) {
43
+ primary[key] = slice["non-repeat"][key].type;
44
+ }
45
+ for (const key in slice.repeat) {
46
+ items[key] = slice.repeat[key].type;
47
+ }
48
+ } else if ("id" in slice) {
49
+ for (const { key, value } of slice.primary ?? []) {
50
+ primary[key] = value.type;
51
+ }
52
+ for (const { key, value } of slice.items ?? []) {
53
+ items[key] = value.type;
54
+ }
55
+ }
56
+
57
+ return {
58
+ primary: JSON.stringify(
59
+ Object.keys(primary)
60
+ .sort()
61
+ .map((key) => [key, primary[key]])
62
+ ),
63
+ items: JSON.stringify(
64
+ Object.keys(items)
65
+ .sort()
66
+ .map((key) => [key, items[key]])
67
+ ),
68
+ };
69
+ };
70
+
71
+ export const ConvertLegacySliceModal: React.FC<
72
+ ConvertLegacySliceModalProps
73
+ > = ({ slice, path }) => {
74
+ const {
75
+ refreshState,
76
+ openToaster,
77
+ initCustomTypeStore,
78
+ saveCustomTypeSuccess,
79
+ } = useSliceMachineActions();
80
+
81
+ const [isLoading, setIsLoading] = useState(false);
82
+ const [isModalOpen, setIsModalOpen] = useState<false | Type>(false);
83
+
84
+ const { libraries: allLibraries } = useSelector(
85
+ (store: SliceMachineStoreType) => ({
86
+ libraries: getLibraries(store),
87
+ })
88
+ );
89
+
90
+ const {} = useSliceMachineActions();
91
+
92
+ const sliceName = useMemo(() => {
93
+ return slice.value.type === "Slice"
94
+ ? slice.value.fieldset ?? slice.key
95
+ : slice.key;
96
+ }, [slice]);
97
+ const libraries = useMemo(() => {
98
+ return allLibraries.filter((library) => library.isLocal);
99
+ }, [allLibraries]);
100
+ const localSharedSlices = useMemo(() => {
101
+ return libraries.map((library) => library.components).flat();
102
+ }, [libraries]);
103
+ const identicalSlices = useMemo<IdenticalSlice[]>(() => {
104
+ const results: IdenticalSlice[] = [];
105
+
106
+ if (slice.value.type !== "Slice") {
107
+ return results;
108
+ }
109
+
110
+ const sliceFields = getFieldMappingFingerprint(slice.value);
111
+
112
+ for (const sharedSlice of localSharedSlices) {
113
+ for (const variation of sharedSlice.model.variations) {
114
+ const variationFields = getFieldMappingFingerprint(variation);
115
+
116
+ if (
117
+ sliceFields.primary === variationFields.primary &&
118
+ sliceFields.items === variationFields.items
119
+ ) {
120
+ results.push({
121
+ libraryID: sharedSlice.from,
122
+ sliceID: sharedSlice.model.id,
123
+ variationID: variation.id,
124
+ path: `${sharedSlice.from}::${sharedSlice.model.id}::${variation.id}`,
125
+ });
126
+ }
127
+ }
128
+ }
129
+
130
+ return results;
131
+ }, [slice, localSharedSlices]);
132
+
133
+ const convertLegacySliceAndTrack = (args: ConvertLegacySliceAndTrackArgs) => {
134
+ if (isModalOpen === false) {
135
+ return;
136
+ }
137
+
138
+ setIsLoading(true);
139
+ void (async () => {
140
+ void telemetry.track({
141
+ event: "legacy-slice:converted",
142
+ id: args.sliceID,
143
+ variation: args.variationID ?? "default",
144
+ library: args.libraryID,
145
+ conversionType: isModalOpen,
146
+ });
147
+
148
+ const { errors } =
149
+ await managerClient.slices.convertLegacySliceToSharedSlice({
150
+ model: slice.value,
151
+ src: {
152
+ ...path,
153
+ sliceID: slice.key,
154
+ },
155
+ dest: {
156
+ libraryID: args.libraryID,
157
+ sliceID: args.sliceID,
158
+ variationName: args.variationName ?? "Default",
159
+ variationID: args.variationID ?? "default",
160
+ },
161
+ });
162
+
163
+ if (errors.length) {
164
+ console.error(
165
+ `Errors happened when converting slice \`${sliceName}\``,
166
+ errors
167
+ );
168
+ }
169
+
170
+ const { model: customType, errors: customTypeReadErrors } =
171
+ await managerClient.customTypes.readCustomType({
172
+ id: path.customTypeID,
173
+ });
174
+
175
+ if (customTypeReadErrors.length || !customType) {
176
+ console.error(
177
+ `Errors happened when converting slice \`${sliceName}\``,
178
+ customTypeReadErrors
179
+ );
180
+
181
+ throw customTypeReadErrors;
182
+ }
183
+
184
+ // TODO(DT-1453): Remove the need of the global getState
185
+ const serverState = await getState();
186
+ // Update Redux store
187
+ refreshState(serverState);
188
+
189
+ setIsLoading(false);
190
+ setIsModalOpen(false);
191
+ switch (isModalOpen) {
192
+ case "as_new_slice":
193
+ openToaster(
194
+ `${sliceName} has been upgraded to a new slice ${args.libraryID} > ${args.sliceID}`,
195
+ ToasterType.SUCCESS
196
+ );
197
+ break;
198
+
199
+ case "as_new_variation":
200
+ openToaster(
201
+ `${sliceName} has been converted as a variation of ${args.libraryID} > ${args.sliceID}`,
202
+ ToasterType.SUCCESS
203
+ );
204
+ break;
205
+
206
+ case "merge_with_identical":
207
+ default:
208
+ openToaster(
209
+ `${sliceName} has been merged with ${args.libraryID} > ${args.sliceID}`,
210
+ ToasterType.SUCCESS
211
+ );
212
+ break;
213
+ }
214
+
215
+ const customTypeSM = CustomTypes.toSM(customType);
216
+ initCustomTypeStore(customTypeSM, customTypeSM);
217
+ saveCustomTypeSuccess(customType);
218
+ })();
219
+ };
220
+
221
+ const formProps = {
222
+ path,
223
+ slice,
224
+ sliceName,
225
+ libraries,
226
+ localSharedSlices,
227
+ identicalSlices,
228
+ close: () => setIsModalOpen(false),
229
+ onSubmit: convertLegacySliceAndTrack,
230
+ isLoading,
231
+ };
232
+
233
+ return (
234
+ <>
235
+ <DropdownMenu>
236
+ <DropdownMenuTrigger>
237
+ <Button
238
+ data-cy="convert-legacy-slice"
239
+ startIcon="refresh"
240
+ endIcon="arrowDropDown"
241
+ size="medium"
242
+ variant="secondary"
243
+ >
244
+ Migrate legacy slice
245
+ </Button>
246
+ </DropdownMenuTrigger>
247
+ <DropdownMenuContent align="start">
248
+ <DropdownMenuItem
249
+ startIcon={<Icon name="folder" size="large" />}
250
+ description="Use it with new types"
251
+ onSelect={() => setIsModalOpen("as_new_slice")}
252
+ >
253
+ Upgrade slice
254
+ </DropdownMenuItem>
255
+ <DropdownMenuItem
256
+ startIcon={<Icon name="viewDay" size="large" />}
257
+ description="Add it to another slice"
258
+ onSelect={() => setIsModalOpen("as_new_variation")}
259
+ disabled={!localSharedSlices.length}
260
+ >
261
+ Convert to slice variation
262
+ </DropdownMenuItem>
263
+ <DropdownMenuItem
264
+ startIcon={<Icon name="driveFileMove" size="large" />}
265
+ description="Combine identical slices"
266
+ onSelect={() => setIsModalOpen("merge_with_identical")}
267
+ disabled={!identicalSlices.length}
268
+ >
269
+ Merge with another slice
270
+ </DropdownMenuItem>
271
+ </DropdownMenuContent>
272
+ </DropdownMenu>
273
+ <FormAsNewSlice {...formProps} isOpen={isModalOpen === "as_new_slice"} />
274
+ <FormAsNewVariation
275
+ {...formProps}
276
+ isOpen={isModalOpen === "as_new_variation"}
277
+ />
278
+ <FormMergeWithIdentical
279
+ {...formProps}
280
+ isOpen={isModalOpen === "merge_with_identical"}
281
+ />
282
+ </>
283
+ );
284
+ };
@@ -0,0 +1,153 @@
1
+ import { useSelector } from "react-redux";
2
+ import { Formik } from "formik";
3
+ import {
4
+ Dialog,
5
+ DialogActions,
6
+ DialogContent,
7
+ DialogHeader,
8
+ ScrollArea,
9
+ FormInput,
10
+ Text,
11
+ Select,
12
+ SelectItem,
13
+ } from "@prismicio/editor-ui";
14
+
15
+ import { SliceMachineStoreType } from "@src/redux/type";
16
+ import { getRemoteSlices } from "@src/modules/slices";
17
+ import { pascalize } from "@lib/utils/str";
18
+
19
+ import { validateSliceModalValues as validateAsNewSliceValues } from "../formsValidator";
20
+ import * as styles from "./ConvertLegacySliceModal.css";
21
+ import { FormProps } from "./types";
22
+
23
+ export const FormAsNewSlice: React.FC<FormProps> = ({
24
+ isOpen,
25
+ close,
26
+ onSubmit,
27
+ isLoading,
28
+ slice,
29
+ libraries,
30
+ }) => {
31
+ const { remoteSlices } = useSelector((store: SliceMachineStoreType) => ({
32
+ remoteSlices: getRemoteSlices(store),
33
+ }));
34
+
35
+ return (
36
+ <Dialog
37
+ open={isOpen}
38
+ onOpenChange={(open) => !open && close()}
39
+ size={{ width: 448, height: "auto" }}
40
+ >
41
+ <DialogHeader title="Upgrade slice" />
42
+ <DialogContent>
43
+ <Formik
44
+ validateOnChange
45
+ initialValues={{
46
+ _init: false,
47
+ from: libraries[0]?.name,
48
+ sliceName: pascalize(slice.key),
49
+ }}
50
+ validate={(values) => {
51
+ return validateAsNewSliceValues(values, libraries, remoteSlices);
52
+ }}
53
+ onSubmit={(values) => {
54
+ onSubmit({ libraryID: values.from, sliceID: values.sliceName });
55
+ }}
56
+ >
57
+ {(formik) => {
58
+ // Just a hack to trigger validation on init for better UX
59
+ if (!formik.values._init) {
60
+ void formik.setFieldValue("_init", true);
61
+ }
62
+
63
+ return (
64
+ <form
65
+ id="convert-legacy-slice-modal-as-new-slice"
66
+ data-cy="convert-legacy-slice-modal-as-new-slice"
67
+ className={styles.form}
68
+ >
69
+ <ScrollArea className={styles.scrollArea}>
70
+ <Text variant="normal" color="grey11">
71
+ This will create a new slice with the same fields. The new
72
+ slice will replace the legacy slice in all of your types,
73
+ and the existing slice content will be re-mapped to the new
74
+ slice.
75
+ </Text>
76
+ <Text variant="normal" color="grey11">
77
+ This will not migrate your component. You will need to do
78
+ that manually.
79
+ </Text>
80
+ <div className={styles.field}>
81
+ <label className={styles.label}>
82
+ <Text variant="bold">Display name*</Text>
83
+ {typeof formik.errors.sliceName === "string" ? (
84
+ <Text variant="small" color="tomato10">
85
+ {formik.errors.sliceName}
86
+ </Text>
87
+ ) : null}
88
+ </label>
89
+ <FormInput
90
+ placeholder={`Pascalised Slice API ID (e.g. ${pascalize(
91
+ slice.key
92
+ )})`}
93
+ error={typeof formik.errors.sliceName === "string"}
94
+ value={formik.values.sliceName}
95
+ onValueChange={(value) =>
96
+ void formik.setFieldValue(
97
+ "sliceName",
98
+ value.slice(0, 30)
99
+ )
100
+ }
101
+ data-cy="slice-name-input"
102
+ />
103
+ <Text variant="normal" color="grey11">
104
+ A display name for the slice
105
+ </Text>
106
+ </div>
107
+ <div className={styles.field}>
108
+ <label className={styles.label}>
109
+ <Text variant="bold">Slice library*</Text>
110
+ {typeof formik.errors.from === "string" ? (
111
+ <Text variant="small" color="tomato10">
112
+ {formik.errors.from}
113
+ </Text>
114
+ ) : null}
115
+ </label>
116
+ <Select
117
+ size="medium"
118
+ variant="secondary"
119
+ startIcon="folder"
120
+ flexContent={true}
121
+ value={formik.values.from}
122
+ onValueChange={(value) =>
123
+ value ? void formik.setFieldValue("from", value) : null
124
+ }
125
+ >
126
+ {libraries.map((library) => (
127
+ <SelectItem key={library.name} value={library.name}>
128
+ {library.name}
129
+ </SelectItem>
130
+ ))}
131
+ </Select>
132
+ <Text variant="normal" color="grey11">
133
+ The library where we'll store your slice
134
+ </Text>
135
+ </div>
136
+ </ScrollArea>
137
+ <DialogActions
138
+ ok={{
139
+ text: "Upgrade",
140
+ onClick: () => void formik.submitForm(),
141
+ loading: isLoading,
142
+ disabled: !formik.isValid,
143
+ }}
144
+ cancel={{ text: "Cancel" }}
145
+ />
146
+ </form>
147
+ );
148
+ }}
149
+ </Formik>
150
+ </DialogContent>
151
+ </Dialog>
152
+ );
153
+ };