slice-machine-ui 1.13.1-dev-next-release.4 → 1.14.1-dev-upgrade-legacy.0

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 (57) hide show
  1. package/components/ChangesItems/ChangesItems.tsx +1 -1
  2. package/components/Forms/ConvertLegacySliceModal/ConvertLegacySliceModal.css.ts +97 -0
  3. package/components/Forms/ConvertLegacySliceModal/ConvertLegacySliceModal.tsx +260 -0
  4. package/components/Forms/ConvertLegacySliceModal/FormAsNewSlice.tsx +165 -0
  5. package/components/Forms/ConvertLegacySliceModal/FormAsNewVariation.tsx +263 -0
  6. package/components/Forms/ConvertLegacySliceModal/FormMergeWithIdentical.tsx +120 -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/HJothAZ-ufia3zK-uZgiR/_buildManifest.js +1 -0
  18. package/out/_next/static/chunks/4-910f159726a3f684.js +1 -0
  19. package/out/_next/static/chunks/400-c4f177099af55f63.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-1d2b243f3faee287.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-7451b7d798438945.js → _app-cdcc9574e096e708.js} +8 -8
  27. package/out/_next/static/chunks/pages/changes-07beedca8a24be6f.js +1 -0
  28. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-f8e48a51edbb5e1c.js → [customTypeId]-67a1bd3229bc7b7c.js} +1 -1
  29. package/out/_next/static/chunks/pages/{custom-types-abb8c8c92880120e.js → custom-types-9284cf805b076b09.js} +1 -1
  30. package/out/_next/static/chunks/pages/{index-f382c8090bd21628.js → index-162379cd9d1baab9.js} +1 -1
  31. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-ddc8d057c3e251d1.js → [pageTypeId]-84d90c359c16cc7d.js} +1 -1
  32. package/out/_next/static/chunks/pages/slices-1f8e5627b061633b.js +1 -0
  33. package/out/_next/static/css/{2d1eda18406a9ff4.css → 4d6fac6cebf3eff3.css} +1 -1
  34. package/out/_next/static/css/dfac2b323a77a5d0.css +1 -0
  35. package/out/_next/static/css/e2c7f4cf38a226ef.css +1 -0
  36. package/out/changelog.html +1 -1
  37. package/out/changes.html +1 -1
  38. package/out/custom-types/[customTypeId].html +1 -1
  39. package/out/custom-types.html +1 -1
  40. package/out/index.html +1 -1
  41. package/out/page-types/[pageTypeId].html +1 -1
  42. package/out/slices.html +1 -1
  43. package/package.json +5 -5
  44. package/pages/slices.tsx +1 -1
  45. package/out/_next/static/IHEYzclg2o-TbPqn5VhQy/_buildManifest.js +0 -1
  46. package/out/_next/static/chunks/482-a4c1c3721ca38516.js +0 -1
  47. package/out/_next/static/chunks/573-aaae870d20e5f86c.js +0 -1
  48. package/out/_next/static/chunks/799-5ea3393033a9f617.js +0 -1
  49. package/out/_next/static/chunks/906-9224feb63b5e36f3.js +0 -1
  50. package/out/_next/static/chunks/97-f6188ebe22eb21da.js +0 -1
  51. package/out/_next/static/chunks/970-a6a3a5a5b9f896ab.js +0 -1
  52. package/out/_next/static/chunks/pages/[lib]/[sliceName]/[variation]-9fff494255e34c39.js +0 -1
  53. package/out/_next/static/chunks/pages/changes-6a039e823313b41c.js +0 -1
  54. package/out/_next/static/chunks/pages/slices-24d2714462334469.js +0 -1
  55. package/out/_next/static/css/6b6c11ed1fffd3e0.css +0 -1
  56. package/out/_next/static/css/e855bef88bb5345b.css +0 -1
  57. /package/out/_next/static/{IHEYzclg2o-TbPqn5VhQy → HJothAZ-ufia3zK-uZgiR}/_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,97 @@
1
+ import { colors, sprinkles, vars } from "@prismicio/editor-ui";
2
+ import { style } from "@vanilla-extract/css";
3
+
4
+ export const layout = {
5
+ small: style([
6
+ sprinkles({
7
+ display: "flex",
8
+ flexDirection: "column",
9
+ gap: 12,
10
+ }),
11
+ ]),
12
+ normal: style([
13
+ sprinkles({
14
+ display: "flex",
15
+ flexDirection: "column",
16
+ gap: 16,
17
+ }),
18
+ ]),
19
+ large: style([
20
+ sprinkles({
21
+ display: "flex",
22
+ flexDirection: "column",
23
+ gap: 24,
24
+ }),
25
+ ]),
26
+ };
27
+
28
+ export const card = style([
29
+ sprinkles({
30
+ display: "flex",
31
+ justifyContent: "space-between",
32
+ alignItems: "center",
33
+ padding: 8,
34
+ paddingRight: 16,
35
+ backgroundColor: colors.grey2,
36
+ borderColor: colors.grey6,
37
+ borderRadius: 6,
38
+ borderStyle: "solid",
39
+ borderWidth: 1,
40
+ overflowX: "hidden",
41
+ cursor: "pointer",
42
+ }),
43
+ {
44
+ selectors: {
45
+ "&:hover, &:focus": {
46
+ backgroundColor: vars.color.greyLight3,
47
+ },
48
+ },
49
+ },
50
+ ]);
51
+
52
+ export const cardDisable = style([
53
+ sprinkles({
54
+ opacity: "60%",
55
+ cursor: "notAllowed",
56
+ }),
57
+ ]);
58
+
59
+ export const cardSelected = style([
60
+ sprinkles({
61
+ backgroundColor: colors.purple10,
62
+ }),
63
+ {
64
+ selectors: {
65
+ "&:hover, &:focus": {
66
+ backgroundColor: vars.color.purple11,
67
+ },
68
+ },
69
+ },
70
+ ]);
71
+
72
+ export const padInline = style([
73
+ sprinkles({
74
+ paddingInline: 8,
75
+ }),
76
+ ]);
77
+
78
+ export const backButton = style([
79
+ sprinkles({
80
+ display: "inline-flex",
81
+ gap: 4,
82
+ alignItems: "center",
83
+ color: colors.grey12,
84
+ paddingInline: 8,
85
+ paddingBlock: 4,
86
+ }),
87
+ {
88
+ textDecoration: "none",
89
+ alignSelf: "start",
90
+ selectors: {
91
+ "&:hover, &:focus": {
92
+ borderRadius: vars.borderRadius["6"],
93
+ backgroundColor: vars.color.greyLight3,
94
+ },
95
+ },
96
+ },
97
+ ]);
@@ -0,0 +1,260 @@
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
+
20
+ import { FormAsNewSlice } from "./FormAsNewSlice";
21
+ import { FormAsNewVariation } from "./FormAsNewVariation";
22
+ import { FormMergeWithIdentical } from "./FormMergeWithIdentical";
23
+ import {
24
+ ConvertLegacySliceModalProps,
25
+ ConvertLegacySliceAndTrackArgs,
26
+ IdenticalSlice,
27
+ Type,
28
+ } from "./types";
29
+
30
+ const getFieldMappingFingerprint = (
31
+ slice: CompositeSlice | VariationSM
32
+ ): {
33
+ primary: string;
34
+ items: string;
35
+ } => {
36
+ const primary: Record<string, string> = {};
37
+ const items: Record<string, string> = {};
38
+
39
+ if ("type" in slice && slice.type === "Slice") {
40
+ for (const key in slice["non-repeat"]) {
41
+ primary[key] = slice["non-repeat"][key].type;
42
+ }
43
+ for (const key in slice.repeat) {
44
+ items[key] = slice.repeat[key].type;
45
+ }
46
+ } else if ("id" in slice) {
47
+ for (const { key, value } of slice.primary ?? []) {
48
+ primary[key] = value.type;
49
+ }
50
+ for (const { key, value } of slice.items ?? []) {
51
+ items[key] = value.type;
52
+ }
53
+ }
54
+
55
+ return {
56
+ primary: JSON.stringify(
57
+ Object.keys(primary)
58
+ .sort()
59
+ .map((key) => [key, primary[key]])
60
+ ),
61
+ items: JSON.stringify(
62
+ Object.keys(items)
63
+ .sort()
64
+ .map((key) => [key, items[key]])
65
+ ),
66
+ };
67
+ };
68
+
69
+ export const ConvertLegacySliceModal: React.FC<
70
+ ConvertLegacySliceModalProps
71
+ > = ({ slice, slices, path }) => {
72
+ const { refreshState, replaceCustomTypeSharedSlice } =
73
+ useSliceMachineActions();
74
+
75
+ const [isLoading, setIsLoading] = useState(false);
76
+ const [isModalOpen, setIsModalOpen] = useState<false | Type>(false);
77
+
78
+ const { libraries: allLibraries } = useSelector(
79
+ (store: SliceMachineStoreType) => ({
80
+ libraries: getLibraries(store),
81
+ })
82
+ );
83
+
84
+ const sliceName = useMemo(() => {
85
+ return slice.value.type === "Slice"
86
+ ? slice.value.fieldset ?? slice.key
87
+ : slice.key;
88
+ }, [slice]);
89
+ const libraries = useMemo(() => {
90
+ return allLibraries.filter((library) => library.isLocal);
91
+ }, [allLibraries]);
92
+ const localSharedSlices = useMemo(() => {
93
+ return libraries.map((library) => library.components).flat();
94
+ }, [libraries]);
95
+ const identicalSlices = useMemo<IdenticalSlice[]>(() => {
96
+ const results: IdenticalSlice[] = [];
97
+
98
+ if (slice.value.type !== "Slice") {
99
+ return results;
100
+ }
101
+
102
+ const sliceFields = getFieldMappingFingerprint(slice.value);
103
+
104
+ for (const sharedSlice of localSharedSlices) {
105
+ for (const variation of sharedSlice.model.variations) {
106
+ const variationFields = getFieldMappingFingerprint(variation);
107
+
108
+ if (
109
+ sliceFields.primary === variationFields.primary &&
110
+ sliceFields.items === variationFields.items
111
+ ) {
112
+ results.push({
113
+ libraryID: sharedSlice.from,
114
+ sliceID: sharedSlice.model.id,
115
+ variationID: variation.id,
116
+ path: `${sharedSlice.from}::${sharedSlice.model.id}::${variation.id}`,
117
+ });
118
+ }
119
+ }
120
+ }
121
+
122
+ return results;
123
+ }, [slice, localSharedSlices]);
124
+
125
+ const convertLegacySliceAndTrack = (args: ConvertLegacySliceAndTrackArgs) => {
126
+ if (isModalOpen === false) {
127
+ return;
128
+ }
129
+
130
+ setIsLoading(true);
131
+ void (async () => {
132
+ void telemetry.track({
133
+ event: "legacy-slice:converted",
134
+ id: args.sliceID,
135
+ variation: args.variationID ?? "default",
136
+ library: args.libraryID,
137
+ conversionType: isModalOpen,
138
+ });
139
+
140
+ const { errors } =
141
+ await managerClient.slices.convertLegacySliceToSharedSlice({
142
+ model: slice.value,
143
+ src: {
144
+ ...path,
145
+ sliceID: slice.key,
146
+ },
147
+ dest: {
148
+ libraryID: args.libraryID,
149
+ sliceID: args.sliceID,
150
+ variationName: args.variationName ?? "Default",
151
+ variationID: args.variationID ?? "default",
152
+ },
153
+ });
154
+
155
+ if (errors.length) {
156
+ console.error(
157
+ `Errors happened when converting slice \`${sliceName}\``,
158
+ errors
159
+ );
160
+ }
161
+
162
+ // TODO(DT-1453): Remove the need of the global getState
163
+ const serverState = await getState();
164
+ // Update Redux store
165
+ refreshState(serverState);
166
+
167
+ setIsLoading(false);
168
+ close();
169
+ replaceCustomTypeSharedSlice(
170
+ path.tabID,
171
+ [
172
+ ...slices
173
+ .map((slice) => {
174
+ if (
175
+ "model" in slice.payload &&
176
+ slice.payload.model.id !== args.sliceID
177
+ ) {
178
+ return slice.payload.model.id;
179
+ }
180
+ return "";
181
+ })
182
+ .filter(Boolean),
183
+ args.sliceID,
184
+ ],
185
+ slices
186
+ .map((_slice) => {
187
+ if ("key" in _slice.payload && _slice.payload.key !== slice.key) {
188
+ return _slice.payload.key;
189
+ }
190
+ return "";
191
+ })
192
+ .filter(Boolean)
193
+ );
194
+ })();
195
+ };
196
+
197
+ const formProps = {
198
+ path,
199
+ slice,
200
+ sliceName,
201
+ libraries,
202
+ localSharedSlices,
203
+ identicalSlices,
204
+ close: () => setIsModalOpen(false),
205
+ onSubmit: convertLegacySliceAndTrack,
206
+ isLoading,
207
+ };
208
+
209
+ return (
210
+ <>
211
+ <DropdownMenu>
212
+ <DropdownMenuTrigger>
213
+ <Button
214
+ data-cy="convert-legacy-slice"
215
+ startIcon="refresh"
216
+ endIcon="arrowDropDown"
217
+ size="medium"
218
+ variant="secondary"
219
+ >
220
+ Turn into shared Slice
221
+ </Button>
222
+ </DropdownMenuTrigger>
223
+ <DropdownMenuContent align="start">
224
+ <DropdownMenuItem
225
+ startIcon={<Icon name="folder" size="large" />}
226
+ description="A brand new shared slice"
227
+ onSelect={() => setIsModalOpen("as_new_slice")}
228
+ >
229
+ Convert to new slice
230
+ </DropdownMenuItem>
231
+ <DropdownMenuItem
232
+ startIcon={<Icon name="viewDay" size="large" />}
233
+ description="An existing slice's new variation"
234
+ onSelect={() => setIsModalOpen("as_new_variation")}
235
+ disabled={!localSharedSlices.length}
236
+ >
237
+ Create a new variation
238
+ </DropdownMenuItem>
239
+ <DropdownMenuItem
240
+ startIcon={<Icon name="driveFileMove" size="large" />}
241
+ description="Identical slices can be merged"
242
+ onSelect={() => setIsModalOpen("merge_with_identical")}
243
+ disabled={!identicalSlices.length}
244
+ >
245
+ Merge with an existing slice
246
+ </DropdownMenuItem>
247
+ </DropdownMenuContent>
248
+ </DropdownMenu>
249
+ <FormAsNewSlice {...formProps} isOpen={isModalOpen === "as_new_slice"} />
250
+ <FormAsNewVariation
251
+ {...formProps}
252
+ isOpen={isModalOpen === "as_new_variation"}
253
+ />
254
+ <FormMergeWithIdentical
255
+ {...formProps}
256
+ isOpen={isModalOpen === "merge_with_identical"}
257
+ />
258
+ </>
259
+ );
260
+ };
@@ -0,0 +1,165 @@
1
+ import { useSelector } from "react-redux";
2
+ import { Field, Input, Label } from "theme-ui";
3
+ import Select from "react-select";
4
+ import { Formik } from "formik";
5
+ import {
6
+ Dialog,
7
+ DialogActions,
8
+ DialogContent,
9
+ DialogHeader,
10
+ ScrollArea,
11
+ Text,
12
+ tokens,
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="small"
40
+ >
41
+ <DialogHeader title="Convert to new slice" />
42
+ <DialogContent>
43
+ <Formik
44
+ validateOnChange
45
+ initialValues={{
46
+ sliceName: pascalize(slice.key),
47
+ from: libraries[0]?.name,
48
+ }}
49
+ validate={(values) => {
50
+ return validateAsNewSliceValues(values, libraries, remoteSlices);
51
+ }}
52
+ onSubmit={(values) => {
53
+ onSubmit({ libraryID: values.from, sliceID: values.sliceName });
54
+ }}
55
+ >
56
+ {(formik) => {
57
+ return (
58
+ <form
59
+ id="convert-legacy-slice-modal-as-new-slice"
60
+ data-cy="convert-legacy-slice-modal-as-new-slice"
61
+ className={styles.layout.small}
62
+ >
63
+ <ScrollArea
64
+ style={{
65
+ height: "100%",
66
+ display: "flex",
67
+ gap: tokens.space[12],
68
+ paddingInline: tokens.space[24],
69
+ paddingBlock: tokens.space[16],
70
+ }}
71
+ >
72
+ <div>
73
+ <Label
74
+ htmlFor="sliceName"
75
+ sx={{
76
+ mb: 2,
77
+ display: "inline-flex",
78
+ alignItems: "center",
79
+ gap: 2,
80
+ }}
81
+ >
82
+ Slice Name
83
+ {typeof formik.errors.sliceName === "string" ? (
84
+ <Text variant="small" color="tomato10">
85
+ {formik.errors.sliceName}
86
+ </Text>
87
+ ) : null}
88
+ </Label>
89
+ <Field
90
+ autoComplete="off"
91
+ id="sliceName"
92
+ name="sliceName"
93
+ type="text"
94
+ placeholder={`Pascalised Slice API ID (e.g. ${pascalize(
95
+ slice.key
96
+ )})`}
97
+ as={Input}
98
+ maxLength={30}
99
+ value={formik.values.sliceName}
100
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
101
+ void formik.setFieldValue(
102
+ "sliceName",
103
+ e.currentTarget.value
104
+ )
105
+ }
106
+ data-cy="slice-name-input"
107
+ />
108
+ </div>
109
+ <div>
110
+ <Label htmlFor="slice" sx={{ mb: 2 }}>
111
+ Target Library
112
+ </Label>
113
+ <Select
114
+ name="slice"
115
+ options={libraries.map((v) => ({
116
+ value: v.name,
117
+ label: v.name,
118
+ }))}
119
+ onChange={(option) =>
120
+ option
121
+ ? void formik.setFieldValue("from", option.value)
122
+ : null
123
+ }
124
+ defaultValue={{
125
+ value: formik.values.from,
126
+ label: formik.values.from,
127
+ }}
128
+ styles={{
129
+ option: (provided) => ({
130
+ ...provided,
131
+ // Color of item text (Dark/Shade-01)
132
+ color: "#161618",
133
+ }),
134
+ }}
135
+ theme={(theme) => {
136
+ return {
137
+ ...theme,
138
+ colors: {
139
+ ...theme.colors,
140
+ // Background of selected item (Gray/Shade-05)
141
+ primary: "#E9E8EA",
142
+ },
143
+ };
144
+ }}
145
+ menuPortalTarget={document.body}
146
+ />
147
+ </div>
148
+ </ScrollArea>
149
+ <DialogActions
150
+ ok={{
151
+ text: "Convert",
152
+ onClick: () => void formik.submitForm(),
153
+ loading: isLoading,
154
+ disabled: !formik.isValid,
155
+ }}
156
+ cancel={{ text: "Cancel" }}
157
+ />
158
+ </form>
159
+ );
160
+ }}
161
+ </Formik>
162
+ </DialogContent>
163
+ </Dialog>
164
+ );
165
+ };