slice-machine-ui 1.14.1-dev-next-hotfix.3 → 1.14.1-dev-upgrade-legacy.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +287 -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 +47 -0
  9. package/lib/builders/CustomTypeBuilder/SliceZone/List.tsx +12 -4
  10. package/lib/builders/CustomTypeBuilder/SliceZone/UpdateSliceZoneModalList.tsx +1 -1
  11. package/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +8 -0
  12. package/lib/models/ui/Slice/index.tsx +52 -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/M1R8KztYSsGkogo8JNvDL/_buildManifest.js +1 -0
  18. package/out/_next/static/chunks/130-5b1dc0c67078c48b.js +1 -0
  19. package/out/_next/static/chunks/4-11b3d83d878be4e2.js +1 -0
  20. package/out/_next/static/chunks/799-00944cdde5b22566.js +1 -0
  21. package/out/_next/static/chunks/80-dce703180b269337.js +1 -0
  22. package/out/_next/static/chunks/934-7e172f9ea1128015.js +1 -0
  23. package/out/_next/static/chunks/{740-d0642219804fa9f0.js → 969-bcfa23e8fd4773e7.js} +1 -1
  24. package/out/_next/static/chunks/970-d63bac52b8b2439a.js +1 -0
  25. package/out/_next/static/chunks/pages/[lib]/[sliceName]/[variation]-fc545d9beb1bb757.js +1 -0
  26. package/out/_next/static/chunks/pages/{_app-ac435e6d17e508ea.js → _app-fea3c7cfb3b5c1b5.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]-f8e48a51edbb5e1c.js → [customTypeId]-94ffa7cbfab2ef0f.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]-ddc8d057c3e251d1.js → [pageTypeId]-81e1a6a78dd46d0f.js} +1 -1
  32. package/out/_next/static/chunks/pages/slices-3ecbaeec98bc9eab.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/QgCYzlcxRZASXnHc94VmZ/_buildManifest.js +0 -1
  43. package/out/_next/static/chunks/482-a4c1c3721ca38516.js +0 -1
  44. package/out/_next/static/chunks/573-aaae870d20e5f86c.js +0 -1
  45. package/out/_next/static/chunks/799-5ea3393033a9f617.js +0 -1
  46. package/out/_next/static/chunks/906-9224feb63b5e36f3.js +0 -1
  47. package/out/_next/static/chunks/97-f6188ebe22eb21da.js +0 -1
  48. package/out/_next/static/chunks/970-a6a3a5a5b9f896ab.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-24d2714462334469.js +0 -1
  52. /package/out/_next/static/{QgCYzlcxRZASXnHc94VmZ → M1R8KztYSsGkogo8JNvDL}/_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,287 @@
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 { getLibraries } from "@src/modules/slices";
14
+ import { VariationSM } from "@models/common/Slice";
15
+ import { SliceMachineStoreType } from "@src/redux/type";
16
+ import { managerClient } from "@src/managerClient";
17
+ import { getState, telemetry } from "@src/apiClient";
18
+ import useSliceMachineActions from "@src/modules/useSliceMachineActions";
19
+ import { ToasterType } from "@src/modules/toaster";
20
+
21
+ import { FormAsNewSlice } from "./FormAsNewSlice";
22
+ import { FormAsNewVariation } from "./FormAsNewVariation";
23
+ import { FormMergeWithIdentical } from "./FormMergeWithIdentical";
24
+ import {
25
+ ConvertLegacySliceModalProps,
26
+ ConvertLegacySliceAndTrackArgs,
27
+ IdenticalSlice,
28
+ Type,
29
+ } from "./types";
30
+
31
+ const getFieldMappingFingerprint = (
32
+ slice: CompositeSlice | VariationSM
33
+ ): {
34
+ primary: string;
35
+ items: string;
36
+ } => {
37
+ const primary: Record<string, string> = {};
38
+ const items: Record<string, string> = {};
39
+
40
+ if ("type" in slice && slice.type === "Slice") {
41
+ for (const key in slice["non-repeat"]) {
42
+ primary[key] = slice["non-repeat"][key].type;
43
+ }
44
+ for (const key in slice.repeat) {
45
+ items[key] = slice.repeat[key].type;
46
+ }
47
+ } else if ("id" in slice) {
48
+ for (const { key, value } of slice.primary ?? []) {
49
+ primary[key] = value.type;
50
+ }
51
+ for (const { key, value } of slice.items ?? []) {
52
+ items[key] = value.type;
53
+ }
54
+ }
55
+
56
+ return {
57
+ primary: JSON.stringify(
58
+ Object.keys(primary)
59
+ .sort()
60
+ .map((key) => [key, primary[key]])
61
+ ),
62
+ items: JSON.stringify(
63
+ Object.keys(items)
64
+ .sort()
65
+ .map((key) => [key, items[key]])
66
+ ),
67
+ };
68
+ };
69
+
70
+ export const ConvertLegacySliceModal: React.FC<
71
+ ConvertLegacySliceModalProps
72
+ > = ({ slice, slices, path }) => {
73
+ const { refreshState, replaceCustomTypeSharedSlice, openToaster } =
74
+ useSliceMachineActions();
75
+
76
+ const [isLoading, setIsLoading] = useState(false);
77
+ const [isModalOpen, setIsModalOpen] = useState<false | Type>(false);
78
+
79
+ const { libraries: allLibraries } = useSelector(
80
+ (store: SliceMachineStoreType) => ({
81
+ libraries: getLibraries(store),
82
+ })
83
+ );
84
+
85
+ const {} = useSliceMachineActions();
86
+
87
+ const sliceName = useMemo(() => {
88
+ return slice.value.type === "Slice"
89
+ ? slice.value.fieldset ?? slice.key
90
+ : slice.key;
91
+ }, [slice]);
92
+ const libraries = useMemo(() => {
93
+ return allLibraries.filter((library) => library.isLocal);
94
+ }, [allLibraries]);
95
+ const localSharedSlices = useMemo(() => {
96
+ return libraries.map((library) => library.components).flat();
97
+ }, [libraries]);
98
+ const identicalSlices = useMemo<IdenticalSlice[]>(() => {
99
+ const results: IdenticalSlice[] = [];
100
+
101
+ if (slice.value.type !== "Slice") {
102
+ return results;
103
+ }
104
+
105
+ const sliceFields = getFieldMappingFingerprint(slice.value);
106
+
107
+ for (const sharedSlice of localSharedSlices) {
108
+ for (const variation of sharedSlice.model.variations) {
109
+ const variationFields = getFieldMappingFingerprint(variation);
110
+
111
+ if (
112
+ sliceFields.primary === variationFields.primary &&
113
+ sliceFields.items === variationFields.items
114
+ ) {
115
+ results.push({
116
+ libraryID: sharedSlice.from,
117
+ sliceID: sharedSlice.model.id,
118
+ variationID: variation.id,
119
+ path: `${sharedSlice.from}::${sharedSlice.model.id}::${variation.id}`,
120
+ });
121
+ }
122
+ }
123
+ }
124
+
125
+ return results;
126
+ }, [slice, localSharedSlices]);
127
+
128
+ const convertLegacySliceAndTrack = (args: ConvertLegacySliceAndTrackArgs) => {
129
+ if (isModalOpen === false) {
130
+ return;
131
+ }
132
+
133
+ setIsLoading(true);
134
+ void (async () => {
135
+ void telemetry.track({
136
+ event: "legacy-slice:converted",
137
+ id: args.sliceID,
138
+ variation: args.variationID ?? "default",
139
+ library: args.libraryID,
140
+ conversionType: isModalOpen,
141
+ });
142
+
143
+ const { errors } =
144
+ await managerClient.slices.convertLegacySliceToSharedSlice({
145
+ model: slice.value,
146
+ src: {
147
+ ...path,
148
+ sliceID: slice.key,
149
+ },
150
+ dest: {
151
+ libraryID: args.libraryID,
152
+ sliceID: args.sliceID,
153
+ variationName: args.variationName ?? "Default",
154
+ variationID: args.variationID ?? "default",
155
+ },
156
+ });
157
+
158
+ if (errors.length) {
159
+ console.error(
160
+ `Errors happened when converting slice \`${sliceName}\``,
161
+ errors
162
+ );
163
+ }
164
+
165
+ // TODO(DT-1453): Remove the need of the global getState
166
+ const serverState = await getState();
167
+ // Update Redux store
168
+ refreshState(serverState);
169
+
170
+ setIsLoading(false);
171
+ setIsModalOpen(false);
172
+ switch (isModalOpen) {
173
+ case "as_new_slice":
174
+ openToaster(
175
+ `${sliceName} has been upgraded to a new slice ${args.libraryID} > ${args.sliceID}`,
176
+ ToasterType.SUCCESS
177
+ );
178
+ break;
179
+
180
+ case "as_new_variation":
181
+ openToaster(
182
+ `${sliceName} has been converted as a variation of ${args.libraryID} > ${args.sliceID}`,
183
+ ToasterType.SUCCESS
184
+ );
185
+ break;
186
+
187
+ case "merge_with_identical":
188
+ default:
189
+ openToaster(
190
+ `${sliceName} has been merged with ${args.libraryID} > ${args.sliceID}`,
191
+ ToasterType.SUCCESS
192
+ );
193
+ break;
194
+ }
195
+
196
+ replaceCustomTypeSharedSlice(
197
+ path.tabID,
198
+ [
199
+ ...slices
200
+ .map((slice) => {
201
+ if (
202
+ "model" in slice.payload &&
203
+ slice.payload.model.id !== args.sliceID
204
+ ) {
205
+ return slice.payload.model.id;
206
+ }
207
+ return "";
208
+ })
209
+ .filter(Boolean),
210
+ args.sliceID,
211
+ ],
212
+ slices
213
+ .map((_slice) => {
214
+ if ("key" in _slice.payload && _slice.payload.key !== slice.key) {
215
+ return _slice.payload.key;
216
+ }
217
+ return "";
218
+ })
219
+ .filter(Boolean)
220
+ );
221
+ })();
222
+ };
223
+
224
+ const formProps = {
225
+ path,
226
+ slice,
227
+ sliceName,
228
+ libraries,
229
+ localSharedSlices,
230
+ identicalSlices,
231
+ close: () => setIsModalOpen(false),
232
+ onSubmit: convertLegacySliceAndTrack,
233
+ isLoading,
234
+ };
235
+
236
+ return (
237
+ <>
238
+ <DropdownMenu>
239
+ <DropdownMenuTrigger>
240
+ <Button
241
+ data-cy="convert-legacy-slice"
242
+ startIcon="refresh"
243
+ endIcon="arrowDropDown"
244
+ size="medium"
245
+ variant="secondary"
246
+ >
247
+ Migrate legacy slice
248
+ </Button>
249
+ </DropdownMenuTrigger>
250
+ <DropdownMenuContent align="start">
251
+ <DropdownMenuItem
252
+ startIcon={<Icon name="folder" size="large" />}
253
+ description="Use it with new types"
254
+ onSelect={() => setIsModalOpen("as_new_slice")}
255
+ >
256
+ Upgrade slice
257
+ </DropdownMenuItem>
258
+ <DropdownMenuItem
259
+ startIcon={<Icon name="viewDay" size="large" />}
260
+ description="Add it to another slice"
261
+ onSelect={() => setIsModalOpen("as_new_variation")}
262
+ disabled={!localSharedSlices.length}
263
+ >
264
+ Convert to slice variation
265
+ </DropdownMenuItem>
266
+ <DropdownMenuItem
267
+ startIcon={<Icon name="driveFileMove" size="large" />}
268
+ description="Combine identical slices"
269
+ onSelect={() => setIsModalOpen("merge_with_identical")}
270
+ disabled={!identicalSlices.length}
271
+ >
272
+ Merge with another slice
273
+ </DropdownMenuItem>
274
+ </DropdownMenuContent>
275
+ </DropdownMenu>
276
+ <FormAsNewSlice {...formProps} isOpen={isModalOpen === "as_new_slice"} />
277
+ <FormAsNewVariation
278
+ {...formProps}
279
+ isOpen={isModalOpen === "as_new_variation"}
280
+ />
281
+ <FormMergeWithIdentical
282
+ {...formProps}
283
+ isOpen={isModalOpen === "merge_with_identical"}
284
+ />
285
+ </>
286
+ );
287
+ };
@@ -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
+ };