slice-machine-ui 1.23.2-beta.1 → 1.23.2-beta.10
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.
- package/.depcheckrc +3 -0
- package/components/DeleteSliceModal/index.tsx +34 -24
- package/components/DeleteVariationModal/index.tsx +1 -0
- package/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx +61 -38
- package/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx +2 -2
- package/components/Forms/RenameSliceModal/RenameSliceModal.tsx +33 -21
- package/components/Forms/RenameVariationModal/RenameVariationModal.tsx +0 -1
- package/components/ModalFormCard/index.tsx +0 -1
- package/components/Navigation/ChangesListItem.tsx +14 -32
- package/components/Navigation/index.tsx +29 -12
- package/components/ReviewModal/ReviewForm.tsx +0 -1
- package/lib/builders/common/EditModal/Form.jsx +0 -1
- package/lib/builders/common/Zone/Card/components/NewField.tsx +0 -1
- package/lib/models/common/widgets/StructuredText/index.ts +1 -1
- package/out/404.html +1 -1
- package/out/_next/static/Dz7wwTNVdXvoAjySXz7mE/_buildManifest.js +1 -0
- package/out/_next/static/chunks/18539003-5f78714a35fde4a1.js +97 -0
- package/out/_next/static/chunks/25-01f422cea8066349.js +1 -0
- package/out/_next/static/chunks/318-99b5914b60bf1d17.js +1 -0
- package/out/_next/static/chunks/727-f8fefdd3e50cab2a.js +1 -0
- package/out/_next/static/chunks/777-8c5507e3b119b5dc.js +8 -0
- package/out/_next/static/chunks/799-b616401fe0dafdeb.js +1 -0
- package/out/_next/static/chunks/904-4653b7a849f4db94.js +1 -0
- package/out/_next/static/chunks/pages/_app-5ec41fc53c1f6405.js +434 -0
- package/out/_next/static/chunks/pages/{changelog-afd18276fe018b24.js → changelog-02cd80dad6f40878.js} +1 -1
- package/out/_next/static/chunks/pages/changes-8e9da8fbb809aad3.js +1 -0
- package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-3f862ad6111a1fc0.js → [customTypeId]-97ab3b90b85d645d.js} +1 -1
- package/out/_next/static/chunks/pages/{custom-types-94b8f8dbf7727412.js → custom-types-3c8e721ec6802127.js} +1 -1
- package/out/_next/static/chunks/pages/index-cf93d16e9c63b6db.js +1 -0
- package/out/_next/static/chunks/pages/{labs-ee206005948f8e87.js → labs-39b50541a4c370bf.js} +1 -1
- package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-d1ffca11c42f2603.js → [pageTypeId]-84a22c5e480d3fa9.js} +1 -1
- package/out/_next/static/chunks/pages/settings-879686e3a8759dc3.js +1 -0
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-897ca93c114a1435.js → simulator-c498e9c80cae8cce.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-9b893c62b2f2fe4a.js +1 -0
- package/out/_next/static/chunks/pages/slices-4522d604117d8c66.js +1 -0
- package/out/_next/static/css/3c26dbbbd8f4be12.css +1 -0
- package/out/_next/static/css/{37153180c3b0dd66.css → 838fd8bb5a7c32b0.css} +1 -1
- package/out/_next/static/css/ca32882a5d2d8d88.css +1 -0
- package/out/changelog.html +1 -1
- package/out/changes.html +1 -1
- package/out/custom-types/[customTypeId].html +1 -1
- package/out/custom-types.html +1 -1
- package/out/index.html +1 -1
- package/out/labs.html +1 -1
- package/out/page-types/[pageTypeId].html +1 -1
- package/out/settings.html +1 -0
- package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
- package/out/slices.html +1 -1
- package/package.json +9 -6
- package/pages/settings.tsx +1 -0
- package/pages/slices.tsx +20 -8
- package/postcss.config.js +10 -0
- package/src/apiClient.ts +1 -22
- package/src/components/FieldSet/FieldSet.stories.tsx +20 -11
- package/src/components/RelativeTime/RelativeTime.stories.tsx +25 -0
- package/src/components/RelativeTime/RelativeTime.tsx +40 -0
- package/src/components/RelativeTime/index.ts +1 -0
- package/src/domain/__tests__/customType.test.ts +205 -19
- package/src/domain/customType.ts +132 -7
- package/src/domain/slice.ts +38 -0
- package/src/features/customTypes/PageTypeOnboarding.tsx +21 -26
- package/src/features/customTypes/actions/convertCustomToPageType.ts +14 -10
- package/src/features/customTypes/actions/createCustomType.ts +69 -0
- package/src/features/customTypes/actions/deleteCustomType.ts +13 -3
- package/src/features/customTypes/actions/renameCustomType.ts +10 -4
- package/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx +3 -1
- package/src/features/customTypes/customTypesTable/CustomTypesTable.tsx +8 -8
- package/src/features/customTypes/customTypesTable/CustomTypesTablePage.tsx +18 -17
- package/src/features/environments/actions/setEnvironment.ts +2 -2
- package/src/features/settings/SettingsPage.tsx +47 -0
- package/src/features/settings/git/ConnectGitRepository.tsx +126 -0
- package/src/features/settings/git/ConnectGitRepositoryBlankSlate.tsx +33 -0
- package/src/features/settings/git/GitOwnerSelect.tsx +71 -0
- package/src/features/settings/git/GitProvider.ts +40 -0
- package/src/features/settings/git/GitProviderConnectButtons.tsx +63 -0
- package/src/features/settings/git/GitRepositoriesList.tsx +76 -0
- package/src/features/settings/git/GitRepositoriesSearch.tsx +69 -0
- package/src/features/settings/git/GitRepositoryConnectDialog.tsx +85 -0
- package/src/features/settings/git/GitRepositoryDisconnectDialog.tsx +60 -0
- package/src/features/settings/git/useGitIntegrationExperiment.ts +8 -0
- package/src/features/settings/git/useGitOwners.ts +12 -0
- package/src/features/settings/git/useGitRepos.ts +24 -0
- package/src/features/settings/git/useLinkedGitRepos.ts +41 -0
- package/src/features/settings/git/useWriteAPIToken.ts +23 -0
- package/src/features/slices/actions/createSlice.ts +5 -29
- package/src/features/slices/actions/deleteSlice.ts +33 -0
- package/src/features/slices/actions/renameSlice.ts +37 -0
- package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx +0 -1
- package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx +0 -1
- package/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx +0 -1
- package/src/hooks/useUser.ts +14 -0
- package/src/icons/SettingsIcon.tsx +19 -0
- package/src/modules/availableCustomTypes/index.ts +29 -135
- package/src/modules/loading/types.ts +0 -5
- package/src/modules/modal/types.ts +0 -3
- package/src/modules/slices/index.ts +27 -193
- package/src/modules/slices/selector.ts +0 -14
- package/src/modules/useSliceMachineActions.ts +70 -89
- package/src/modules/userContext/index.ts +3 -3
- package/src/redux/saga.ts +0 -4
- package/test/src/modules/__fixtures__/serverState.ts +1 -2
- package/test/src/modules/availableCustomTypes/index.test.ts +2 -243
- package/test/src/modules/slices.test.ts +1 -267
- package/out/_next/static/chunks/18539003-5cc559566b738915.js +0 -97
- package/out/_next/static/chunks/25-37d6fa7f064be04b.js +0 -1
- package/out/_next/static/chunks/318-ce7e74f18f2d5b22.js +0 -1
- package/out/_next/static/chunks/376-7e784d6069e01654.js +0 -8
- package/out/_next/static/chunks/727-3a3bc32518778317.js +0 -1
- package/out/_next/static/chunks/731-501990ebe6a71b32.js +0 -1
- package/out/_next/static/chunks/904-9a4c1264727ae89a.js +0 -1
- package/out/_next/static/chunks/pages/_app-cd9d4c76a4abc11a.js +0 -432
- package/out/_next/static/chunks/pages/changes-2a976f9efeb719aa.js +0 -1
- package/out/_next/static/chunks/pages/index-64cfb8b0948ba8ef.js +0 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-b2a9d773ce5d7739.js +0 -1
- package/out/_next/static/chunks/pages/slices-e9c70c082ca3e3f9.js +0 -1
- package/out/_next/static/css/200244e3e0463c39.css +0 -1
- package/out/_next/static/m6psnC5u1xT7Y6osWHKjp/_buildManifest.js +0 -1
- package/src/features/customTypes/__tests__/createCustomType.test.ts +0 -159
- package/src/features/customTypes/customTypesTable/createCustomType.ts +0 -107
- package/test/components/CreateCustomTypeModal.test.tsx +0 -73
- /package/out/_next/static/{m6psnC5u1xT7Y6osWHKjp → Dz7wwTNVdXvoAjySXz7mE}/_ssgManifest.js +0 -0
|
@@ -393,32 +393,39 @@ describe("CustomTypeModel test suite", () => {
|
|
|
393
393
|
|
|
394
394
|
it("addField should return the given custom type with the field added to the section", () => {
|
|
395
395
|
expect(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
config: {
|
|
402
|
-
label: "NewField",
|
|
403
|
-
},
|
|
404
|
-
type: "UID",
|
|
405
|
-
},
|
|
406
|
-
}),
|
|
407
|
-
).toEqual({
|
|
408
|
-
...mockCustomType,
|
|
409
|
-
json: {
|
|
410
|
-
...mockCustomType.json,
|
|
411
|
-
mainSection: {
|
|
412
|
-
...mainSection,
|
|
396
|
+
JSON.stringify(
|
|
397
|
+
CustomTypeModel.addField({
|
|
398
|
+
customType: mockCustomType,
|
|
399
|
+
sectionId: "mainSection",
|
|
400
|
+
newFieldId: "newField",
|
|
413
401
|
newField: {
|
|
414
402
|
config: {
|
|
415
403
|
label: "NewField",
|
|
416
404
|
},
|
|
417
405
|
type: "UID",
|
|
418
406
|
},
|
|
407
|
+
}),
|
|
408
|
+
),
|
|
409
|
+
).toEqual(
|
|
410
|
+
JSON.stringify({
|
|
411
|
+
...mockCustomType,
|
|
412
|
+
json: {
|
|
413
|
+
...mockCustomType.json,
|
|
414
|
+
mainSection: {
|
|
415
|
+
uid: mainSection.uid,
|
|
416
|
+
booleanField: mainSection.booleanField,
|
|
417
|
+
groupField: mainSection.groupField,
|
|
418
|
+
newField: {
|
|
419
|
+
config: {
|
|
420
|
+
label: "NewField",
|
|
421
|
+
},
|
|
422
|
+
type: "UID",
|
|
423
|
+
},
|
|
424
|
+
slices: mainSection.slices,
|
|
425
|
+
},
|
|
419
426
|
},
|
|
420
|
-
},
|
|
421
|
-
|
|
427
|
+
}),
|
|
428
|
+
);
|
|
422
429
|
});
|
|
423
430
|
|
|
424
431
|
it("deleteField should return the given custom type without the field", () => {
|
|
@@ -782,4 +789,183 @@ describe("CustomTypeModel test suite", () => {
|
|
|
782
789
|
},
|
|
783
790
|
});
|
|
784
791
|
});
|
|
792
|
+
|
|
793
|
+
it("should create a custom type with repeatable true", () => {
|
|
794
|
+
expect(
|
|
795
|
+
CustomTypeModel.create({
|
|
796
|
+
id: "id",
|
|
797
|
+
label: "lama",
|
|
798
|
+
repeatable: true,
|
|
799
|
+
format: "custom",
|
|
800
|
+
}),
|
|
801
|
+
).toMatchInlineSnapshot(`
|
|
802
|
+
{
|
|
803
|
+
"format": "custom",
|
|
804
|
+
"id": "id",
|
|
805
|
+
"json": {
|
|
806
|
+
"Main": {
|
|
807
|
+
"uid": {
|
|
808
|
+
"config": {
|
|
809
|
+
"label": "UID",
|
|
810
|
+
},
|
|
811
|
+
"type": "UID",
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
"label": "lama",
|
|
816
|
+
"repeatable": true,
|
|
817
|
+
"status": true,
|
|
818
|
+
}
|
|
819
|
+
`);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it("should create a custom type with repeatable false", () => {
|
|
823
|
+
const result = CustomTypeModel.create({
|
|
824
|
+
id: "id",
|
|
825
|
+
label: "lama",
|
|
826
|
+
repeatable: false,
|
|
827
|
+
format: "custom",
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
expect(result.id).toBe("id");
|
|
831
|
+
expect(result.label).toBe("lama");
|
|
832
|
+
expect(result.format).toBe("custom");
|
|
833
|
+
expect(result.json).toHaveProperty("Main");
|
|
834
|
+
expect(result).toMatchInlineSnapshot(`
|
|
835
|
+
{
|
|
836
|
+
"format": "custom",
|
|
837
|
+
"id": "id",
|
|
838
|
+
"json": {
|
|
839
|
+
"Main": {},
|
|
840
|
+
},
|
|
841
|
+
"label": "lama",
|
|
842
|
+
"repeatable": false,
|
|
843
|
+
"status": true,
|
|
844
|
+
}
|
|
845
|
+
`);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it("should create a page type with a slice zone", () => {
|
|
849
|
+
const result = CustomTypeModel.create({
|
|
850
|
+
id: "🥪",
|
|
851
|
+
label: "label",
|
|
852
|
+
repeatable: true,
|
|
853
|
+
format: "page",
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
expect(result.format).toBe("page");
|
|
857
|
+
expect(result.json).toHaveProperty("Main");
|
|
858
|
+
expect(result.json.Main).toHaveProperty("slices");
|
|
859
|
+
expect(result.json).toHaveProperty("SEO & Metadata");
|
|
860
|
+
expect(result).toMatchInlineSnapshot(`
|
|
861
|
+
{
|
|
862
|
+
"format": "page",
|
|
863
|
+
"id": "🥪",
|
|
864
|
+
"json": {
|
|
865
|
+
"Main": {
|
|
866
|
+
"slices": {
|
|
867
|
+
"config": {
|
|
868
|
+
"choices": {},
|
|
869
|
+
},
|
|
870
|
+
"fieldset": "Slice Zone",
|
|
871
|
+
"type": "Slices",
|
|
872
|
+
},
|
|
873
|
+
"uid": {
|
|
874
|
+
"config": {
|
|
875
|
+
"label": "UID",
|
|
876
|
+
},
|
|
877
|
+
"type": "UID",
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
"SEO & Metadata": {
|
|
881
|
+
"meta_description": {
|
|
882
|
+
"config": {
|
|
883
|
+
"label": "Meta Description",
|
|
884
|
+
"placeholder": "A brief summary of the page",
|
|
885
|
+
},
|
|
886
|
+
"type": "Text",
|
|
887
|
+
},
|
|
888
|
+
"meta_image": {
|
|
889
|
+
"config": {
|
|
890
|
+
"constraint": {
|
|
891
|
+
"height": 1260,
|
|
892
|
+
"width": 2400,
|
|
893
|
+
},
|
|
894
|
+
"label": "Meta Image",
|
|
895
|
+
"thumbnails": [],
|
|
896
|
+
},
|
|
897
|
+
"type": "Image",
|
|
898
|
+
},
|
|
899
|
+
"meta_title": {
|
|
900
|
+
"config": {
|
|
901
|
+
"label": "Meta Title",
|
|
902
|
+
"placeholder": "A title of the page used for social media and search engines",
|
|
903
|
+
},
|
|
904
|
+
"type": "Text",
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
"label": "label",
|
|
909
|
+
"repeatable": true,
|
|
910
|
+
"status": true,
|
|
911
|
+
}
|
|
912
|
+
`);
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it("when non repeatable page type is should contain Main with a slice-zone, no uid, and a SEO tab", () => {
|
|
917
|
+
const result = CustomTypeModel.create({
|
|
918
|
+
id: "foo",
|
|
919
|
+
label: "bar",
|
|
920
|
+
repeatable: false,
|
|
921
|
+
format: "page",
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
expect(result).toMatchInlineSnapshot(`
|
|
925
|
+
{
|
|
926
|
+
"format": "page",
|
|
927
|
+
"id": "foo",
|
|
928
|
+
"json": {
|
|
929
|
+
"Main": {
|
|
930
|
+
"slices": {
|
|
931
|
+
"config": {
|
|
932
|
+
"choices": {},
|
|
933
|
+
},
|
|
934
|
+
"fieldset": "Slice Zone",
|
|
935
|
+
"type": "Slices",
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
"SEO & Metadata": {
|
|
939
|
+
"meta_description": {
|
|
940
|
+
"config": {
|
|
941
|
+
"label": "Meta Description",
|
|
942
|
+
"placeholder": "A brief summary of the page",
|
|
943
|
+
},
|
|
944
|
+
"type": "Text",
|
|
945
|
+
},
|
|
946
|
+
"meta_image": {
|
|
947
|
+
"config": {
|
|
948
|
+
"constraint": {
|
|
949
|
+
"height": 1260,
|
|
950
|
+
"width": 2400,
|
|
951
|
+
},
|
|
952
|
+
"label": "Meta Image",
|
|
953
|
+
"thumbnails": [],
|
|
954
|
+
},
|
|
955
|
+
"type": "Image",
|
|
956
|
+
},
|
|
957
|
+
"meta_title": {
|
|
958
|
+
"config": {
|
|
959
|
+
"label": "Meta Title",
|
|
960
|
+
"placeholder": "A title of the page used for social media and search engines",
|
|
961
|
+
},
|
|
962
|
+
"type": "Text",
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
"label": "bar",
|
|
967
|
+
"repeatable": false,
|
|
968
|
+
"status": true,
|
|
969
|
+
}
|
|
970
|
+
`);
|
|
785
971
|
});
|
package/src/domain/customType.ts
CHANGED
|
@@ -104,8 +104,15 @@ type GetGroupFieldArgs = {
|
|
|
104
104
|
groupFieldId: string;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
type CreateArgs = {
|
|
108
|
+
format: CustomTypeFormat;
|
|
109
|
+
id: string;
|
|
110
|
+
label: string;
|
|
111
|
+
repeatable: boolean;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export function getFormat(customType: CustomType): CustomTypeFormat {
|
|
115
|
+
return customType.format ?? "custom";
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
export function getSectionEntries(
|
|
@@ -363,15 +370,32 @@ export function renameSection(
|
|
|
363
370
|
|
|
364
371
|
export function addField(args: AddFieldArgs): CustomType {
|
|
365
372
|
const { customType, sectionId, newField, newFieldId } = args;
|
|
373
|
+
const sectionJson = customType.json[sectionId];
|
|
374
|
+
const maybeSliceZoneEntry = getSectionSliceZoneEntry(customType, sectionId);
|
|
375
|
+
|
|
376
|
+
// Separate the fields into slices and non-slices
|
|
377
|
+
const sectionFields = Object.fromEntries(
|
|
378
|
+
Object.entries(sectionJson).filter(
|
|
379
|
+
([_, value]) => value.type !== SlicesFieldType,
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const updatedSection = {
|
|
384
|
+
...sectionFields,
|
|
385
|
+
// Add the new field to the section
|
|
386
|
+
[newFieldId]: newField,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Merge the SlicesFieldType field back into the section, if it exists
|
|
390
|
+
if (maybeSliceZoneEntry !== undefined) {
|
|
391
|
+
const [sliceZoneKey, sliceZoneField] = maybeSliceZoneEntry;
|
|
392
|
+
updatedSection[sliceZoneKey] = sliceZoneField;
|
|
393
|
+
}
|
|
366
394
|
|
|
367
395
|
const newCustomType = updateSection({
|
|
368
396
|
customType,
|
|
369
397
|
sectionId,
|
|
370
|
-
updatedSection
|
|
371
|
-
...customType.json[sectionId],
|
|
372
|
-
// Add the new field to the section
|
|
373
|
-
[newFieldId]: newField,
|
|
374
|
-
},
|
|
398
|
+
updatedSection,
|
|
375
399
|
});
|
|
376
400
|
|
|
377
401
|
return newCustomType;
|
|
@@ -633,3 +657,104 @@ export function updateGroupFields(
|
|
|
633
657
|
},
|
|
634
658
|
};
|
|
635
659
|
}
|
|
660
|
+
|
|
661
|
+
export function create(args: CreateArgs) {
|
|
662
|
+
const { id, label, repeatable, format } = args;
|
|
663
|
+
const mainTab = makeMainTab(repeatable, format);
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
format,
|
|
667
|
+
id,
|
|
668
|
+
json: mainTab,
|
|
669
|
+
label,
|
|
670
|
+
repeatable,
|
|
671
|
+
status: true,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function makeMainTab(
|
|
676
|
+
repeatable: boolean,
|
|
677
|
+
format: CustomTypeFormat,
|
|
678
|
+
): CustomType["json"] {
|
|
679
|
+
if (repeatable === false && format === "page") {
|
|
680
|
+
return { ...DEFAULT_MAIN_WITH_SLICE_ZONE, ...DEFAULT_SEO_TAB };
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (repeatable === false) {
|
|
684
|
+
return DEFAULT_MAIN;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (format === "page") {
|
|
688
|
+
return {
|
|
689
|
+
...DEFAULT_MAIN_WITH_UID_AND_SLICE_ZONE,
|
|
690
|
+
...DEFAULT_SEO_TAB,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return DEFAULT_MAIN_WITH_UID;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const DEFAULT_MAIN: CustomType["json"] = {
|
|
698
|
+
Main: {},
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
const DEFAULT_MAIN_WITH_SLICE_ZONE: CustomType["json"] = {
|
|
702
|
+
Main: {
|
|
703
|
+
slices: {
|
|
704
|
+
config: {
|
|
705
|
+
choices: {},
|
|
706
|
+
},
|
|
707
|
+
fieldset: "Slice Zone",
|
|
708
|
+
type: "Slices",
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
const DEFAULT_MAIN_WITH_UID: CustomType["json"] = {
|
|
714
|
+
Main: {
|
|
715
|
+
uid: {
|
|
716
|
+
config: {
|
|
717
|
+
label: "UID",
|
|
718
|
+
},
|
|
719
|
+
type: "UID",
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const DEFAULT_MAIN_WITH_UID_AND_SLICE_ZONE: CustomType["json"] = {
|
|
725
|
+
Main: {
|
|
726
|
+
...DEFAULT_MAIN_WITH_UID.Main,
|
|
727
|
+
...DEFAULT_MAIN_WITH_SLICE_ZONE.Main,
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
const DEFAULT_SEO_TAB: CustomType["json"] = {
|
|
732
|
+
"SEO & Metadata": {
|
|
733
|
+
meta_description: {
|
|
734
|
+
config: {
|
|
735
|
+
label: "Meta Description",
|
|
736
|
+
placeholder: "A brief summary of the page",
|
|
737
|
+
},
|
|
738
|
+
type: "Text",
|
|
739
|
+
},
|
|
740
|
+
meta_image: {
|
|
741
|
+
config: {
|
|
742
|
+
constraint: {
|
|
743
|
+
height: 1260,
|
|
744
|
+
width: 2400,
|
|
745
|
+
},
|
|
746
|
+
label: "Meta Image",
|
|
747
|
+
thumbnails: [],
|
|
748
|
+
},
|
|
749
|
+
type: "Image",
|
|
750
|
+
},
|
|
751
|
+
meta_title: {
|
|
752
|
+
config: {
|
|
753
|
+
label: "Meta Title",
|
|
754
|
+
placeholder:
|
|
755
|
+
"A title of the page used for social media and search engines",
|
|
756
|
+
},
|
|
757
|
+
type: "Text",
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
};
|
package/src/domain/slice.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CompositeSlice,
|
|
3
3
|
LegacySlice,
|
|
4
|
+
SharedSlice,
|
|
4
5
|
} from "@prismicio/types-internal/lib/customtypes/widgets/slices";
|
|
5
6
|
import { NestableWidget } from "@prismicio/types-internal/lib/customtypes";
|
|
6
7
|
|
|
7
8
|
import type { ComponentUI } from "@lib/models/common/ComponentUI";
|
|
8
9
|
import type { VariationSM, WidgetsArea } from "@lib/models/common/Slice";
|
|
10
|
+
import { pascalize, snakelize } from "@lib/utils/str";
|
|
9
11
|
|
|
10
12
|
type CopySliceVariationArgs = {
|
|
11
13
|
copiedVariation: VariationSM;
|
|
@@ -46,6 +48,8 @@ type ReorderFieldArgs = {
|
|
|
46
48
|
destinationIndex: number;
|
|
47
49
|
};
|
|
48
50
|
|
|
51
|
+
const DEFAULT_VARIATION_ID = "default";
|
|
52
|
+
|
|
49
53
|
export function countMissingScreenshots(slice: ComponentUI): number {
|
|
50
54
|
return slice.model.variations.length - Object.keys(slice.screenshots).length;
|
|
51
55
|
}
|
|
@@ -243,3 +247,37 @@ export function reorderField(args: ReorderFieldArgs): ComponentUI {
|
|
|
243
247
|
},
|
|
244
248
|
};
|
|
245
249
|
}
|
|
250
|
+
|
|
251
|
+
export function buildEmptySliceModel(sliceName: string): SharedSlice {
|
|
252
|
+
return {
|
|
253
|
+
id: snakelize(sliceName),
|
|
254
|
+
type: "SharedSlice",
|
|
255
|
+
name: sliceName,
|
|
256
|
+
description: sliceName,
|
|
257
|
+
variations: [
|
|
258
|
+
{
|
|
259
|
+
id: DEFAULT_VARIATION_ID,
|
|
260
|
+
name: pascalize(DEFAULT_VARIATION_ID),
|
|
261
|
+
// Property not used yet. Fallback to "...".
|
|
262
|
+
docURL: "...",
|
|
263
|
+
// "initial" is fine here as default value.
|
|
264
|
+
version: "initial",
|
|
265
|
+
description: pascalize(DEFAULT_VARIATION_ID),
|
|
266
|
+
// Empty string is fine, we don't want to save imageUrl.
|
|
267
|
+
// We don't want to compare local and remote image with imageUrl.
|
|
268
|
+
// It will be striped anyway when doing the comparison.
|
|
269
|
+
imageUrl: "",
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function rename(slice: ComponentUI, newSliceName: string): ComponentUI {
|
|
276
|
+
return {
|
|
277
|
+
...slice,
|
|
278
|
+
model: {
|
|
279
|
+
...slice.model,
|
|
280
|
+
name: newSliceName,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
@@ -1,35 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useSelector } from "react-redux";
|
|
1
|
+
import { useState } from "react";
|
|
3
2
|
import { Box, Button, Text } from "@prismicio/editor-ui";
|
|
4
3
|
import { cache } from "@prismicio/editor-support/Suspense";
|
|
5
4
|
|
|
6
5
|
import { CreateCustomTypeModal } from "@components/Forms/CreateCustomTypeModal";
|
|
7
|
-
import { SliceMachineStoreType } from "@src/redux/type";
|
|
8
|
-
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
|
|
9
|
-
import { LoadingKeysEnum } from "@src/modules/loading/types";
|
|
10
|
-
import { isLoading } from "@src/modules/loading";
|
|
11
6
|
import { BlankPageIcon } from "@src/icons/BlankPageIcon";
|
|
12
7
|
import { getIsEmptyProject } from "@src/hooks/useIsEmptyProject";
|
|
13
8
|
|
|
14
9
|
export function PageTypeOnboarding() {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
(
|
|
18
|
-
isCreatingCustomType: isLoading(
|
|
19
|
-
store,
|
|
20
|
-
LoadingKeysEnum.CREATE_CUSTOM_TYPE,
|
|
21
|
-
),
|
|
22
|
-
}),
|
|
23
|
-
);
|
|
24
|
-
// State to ensure button loader is still visible while redirecting
|
|
25
|
-
const [isButtonLoading, setIsButtonLoading] = useState(false);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (isCreatingCustomType) {
|
|
29
|
-
setIsButtonLoading(true);
|
|
30
|
-
cache.clear(getIsEmptyProject, []);
|
|
31
|
-
}
|
|
32
|
-
}, [isCreatingCustomType]);
|
|
10
|
+
const [isCreatingCustomType, setIsCreatingCustomType] = useState(false);
|
|
11
|
+
const [isCreateCustomTypeModalOpen, setIsCreateCustomTypeModalOpen] =
|
|
12
|
+
useState(false);
|
|
33
13
|
|
|
34
14
|
return (
|
|
35
15
|
<Box
|
|
@@ -53,10 +33,25 @@ export function PageTypeOnboarding() {
|
|
|
53
33
|
Welcome to Slice Machine. Your journey starts with your first page
|
|
54
34
|
type — the model that you will use to create pages for your website.
|
|
55
35
|
</Text>
|
|
56
|
-
<Button
|
|
36
|
+
<Button
|
|
37
|
+
loading={isCreatingCustomType}
|
|
38
|
+
onClick={() => {
|
|
39
|
+
setIsCreateCustomTypeModalOpen(true);
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
57
42
|
Create a page type
|
|
58
43
|
</Button>
|
|
59
|
-
<CreateCustomTypeModal
|
|
44
|
+
<CreateCustomTypeModal
|
|
45
|
+
format="page"
|
|
46
|
+
isCreating={isCreatingCustomType}
|
|
47
|
+
isOpen={isCreateCustomTypeModalOpen}
|
|
48
|
+
origin="onboarding"
|
|
49
|
+
onCreateChange={(isCreating) => {
|
|
50
|
+
setIsCreatingCustomType(isCreating);
|
|
51
|
+
cache.clear(getIsEmptyProject, []);
|
|
52
|
+
}}
|
|
53
|
+
onOpenChange={setIsCreateCustomTypeModalOpen}
|
|
54
|
+
/>
|
|
60
55
|
</Box>
|
|
61
56
|
</Box>
|
|
62
57
|
);
|
|
@@ -9,17 +9,20 @@ import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
|
|
|
9
9
|
|
|
10
10
|
export async function convertCustomToPageType(
|
|
11
11
|
customType: CustomType,
|
|
12
|
-
|
|
12
|
+
onSuccess: (newCustomType: CustomType) => void,
|
|
13
13
|
) {
|
|
14
14
|
const customTypesMessages =
|
|
15
15
|
CUSTOM_TYPES_MESSAGES[customType.format as CustomTypeFormat];
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
18
|
const newCustomType = convertToPageType(customType);
|
|
19
|
-
await updateCustomType(newCustomType);
|
|
19
|
+
const { errors } = await updateCustomType(newCustomType);
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
if (errors.length > 0) {
|
|
22
|
+
throw errors;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onSuccess(newCustomType);
|
|
23
26
|
|
|
24
27
|
toast.success(
|
|
25
28
|
`${customTypesMessages.name({
|
|
@@ -28,11 +31,12 @@ export async function convertCustomToPageType(
|
|
|
28
31
|
})} converted to page type`,
|
|
29
32
|
);
|
|
30
33
|
} catch (e) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
34
|
+
const errorMessage = `Internal Error: ${customTypesMessages.name({
|
|
35
|
+
start: true,
|
|
36
|
+
plural: false,
|
|
37
|
+
})} not converted to page type`;
|
|
38
|
+
|
|
39
|
+
console.error(errorMessage, e);
|
|
40
|
+
toast.error(errorMessage);
|
|
37
41
|
}
|
|
38
42
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { toast } from "react-toastify";
|
|
2
|
+
import { CustomType } from "@prismicio/types-internal/lib/customtypes";
|
|
3
|
+
|
|
4
|
+
import { ToastMessageWithPath } from "@components/ToasterContainer";
|
|
5
|
+
import { CustomTypeFormat } from "@slicemachine/manager";
|
|
6
|
+
import { telemetry, updateCustomType } from "@src/apiClient";
|
|
7
|
+
import { create } from "@src/domain/customType";
|
|
8
|
+
|
|
9
|
+
import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
|
|
10
|
+
|
|
11
|
+
type DeleteCustomTypeArgs = {
|
|
12
|
+
format: CustomTypeFormat;
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
origin: CustomTypeOrigin;
|
|
16
|
+
repeatable: boolean;
|
|
17
|
+
onSuccess: (newCustomType: CustomType) => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type CustomTypeOrigin = "onboarding" | "table";
|
|
21
|
+
|
|
22
|
+
export async function createCustomType(args: DeleteCustomTypeArgs) {
|
|
23
|
+
const { id, label, repeatable, format, onSuccess } = args;
|
|
24
|
+
const customTypesMessages = CUSTOM_TYPES_MESSAGES[format];
|
|
25
|
+
|
|
26
|
+
const newCustomType: CustomType = create({
|
|
27
|
+
id,
|
|
28
|
+
label,
|
|
29
|
+
repeatable,
|
|
30
|
+
format,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { errors } = await updateCustomType(newCustomType);
|
|
35
|
+
|
|
36
|
+
if (errors.length > 0) {
|
|
37
|
+
throw errors;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void telemetry.track({
|
|
41
|
+
event: "custom-type:created",
|
|
42
|
+
id: newCustomType.id,
|
|
43
|
+
name: label,
|
|
44
|
+
format: format,
|
|
45
|
+
type: repeatable ? "repeatable" : "single",
|
|
46
|
+
origin: "table",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await onSuccess(newCustomType);
|
|
50
|
+
|
|
51
|
+
toast.success(
|
|
52
|
+
ToastMessageWithPath({
|
|
53
|
+
message: `${customTypesMessages.name({
|
|
54
|
+
start: true,
|
|
55
|
+
plural: false,
|
|
56
|
+
})} saved successfully at `,
|
|
57
|
+
path: `./customtypes/${newCustomType.id}/index.json`,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
const errorMessage = `Internal Error: ${customTypesMessages.name({
|
|
62
|
+
start: true,
|
|
63
|
+
plural: false,
|
|
64
|
+
})} not saved`;
|
|
65
|
+
|
|
66
|
+
console.error(errorMessage, e);
|
|
67
|
+
toast.error(errorMessage);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { toast } from "react-toastify";
|
|
1
2
|
import { CustomType } from "@prismicio/types-internal/lib/customtypes";
|
|
3
|
+
|
|
2
4
|
import { managerClient } from "@src/managerClient";
|
|
5
|
+
import { CustomTypeFormat } from "@slicemachine/manager";
|
|
6
|
+
|
|
3
7
|
import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
|
|
4
|
-
import { CustomTypeFormat } from "@slicemachine/manager/*";
|
|
5
|
-
import { toast } from "react-toastify";
|
|
6
8
|
|
|
7
9
|
type DeleteCustomTypeArgs = {
|
|
8
10
|
customType: CustomType;
|
|
@@ -17,7 +19,13 @@ export async function deleteCustomType({
|
|
|
17
19
|
CUSTOM_TYPES_MESSAGES[customType.format as CustomTypeFormat];
|
|
18
20
|
|
|
19
21
|
try {
|
|
20
|
-
await managerClient.customTypes.deleteCustomType({
|
|
22
|
+
const { errors } = await managerClient.customTypes.deleteCustomType({
|
|
23
|
+
id: customType.id,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (errors.length > 0) {
|
|
27
|
+
throw errors;
|
|
28
|
+
}
|
|
21
29
|
|
|
22
30
|
onSuccess();
|
|
23
31
|
|
|
@@ -32,6 +40,8 @@ export async function deleteCustomType({
|
|
|
32
40
|
start: true,
|
|
33
41
|
plural: false,
|
|
34
42
|
})} could not be deleted`;
|
|
43
|
+
|
|
35
44
|
console.error(errorMessage, e);
|
|
45
|
+
toast.error(errorMessage);
|
|
36
46
|
}
|
|
37
47
|
}
|