import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Icon, } from "@prismicio/editor-ui"; import { type FC, useMemo, useState } from "react"; import { useSelector } from "react-redux"; import { toast } from "react-toastify"; import { getState, telemetry } from "@/apiClient"; import { getFieldMappingFingerprint } from "@/domain/slice"; import { useCustomTypeState } from "@/features/customTypes/customTypesBuilder/CustomTypeProvider"; import { ComponentUI } from "@/legacy/lib/models/common/ComponentUI"; import { NonSharedSliceInSliceZone } from "@/legacy/lib/models/common/CustomType/sliceZone"; import { managerClient } from "@/managerClient"; import { getLibraries } from "@/modules/slices"; import useSliceMachineActions from "@/modules/useSliceMachineActions"; import { SliceMachineStoreType } from "@/redux/type"; import { NonSharedSliceCardProps } from "../sliceCards/NonSharedSliceCard"; import { ConvertLegacySliceAsNewSliceDialog } from "./ConvertLegacySliceAsNewSliceDialog"; import { ConvertLegacySliceAsNewVariationDialog } from "./ConvertLegacySliceAsNewVariationDialog"; import { ConvertLegacySliceMergeWithIdenticalDialog } from "./ConvertLegacySliceMergeWithIdenticalDialog"; import { ConvertLegacySliceAndTrackArgs, IdenticalSlice, LegacySliceConversionType, } from "./types"; type ConvertLegacySliceButtonProps = NonSharedSliceCardProps; export const ConvertLegacySliceButton: FC = ({ slice, path, }) => { const { refreshState } = useSliceMachineActions(); const [isLoading, setIsLoading] = useState(false); const [dialog, setDialog] = useState(); const { setCustomType } = useCustomTypeState(); const { libraries: allLibraries } = useSelector( (store: SliceMachineStoreType) => ({ libraries: getLibraries(store), }), ); const sliceName = slice.value.type === "Slice" ? slice.value.fieldset ?? slice.key : slice.key; const libraries = allLibraries.filter((library) => library.isLocal); const localSharedSlices = libraries .map((library) => library.components) .flat(); const identicalSlices = useIdenticalSlices( slice, sliceName, localSharedSlices, ); const convertLegacySliceAndTrack = async ( args: ConvertLegacySliceAndTrackArgs, ) => { if (!dialog) { return; } setIsLoading(true); void telemetry.track({ event: "legacy-slice:converted", id: args.sliceID, variation: args.variationID ?? "default", library: args.libraryID, conversionType: dialog, }); const { errors } = await managerClient.slices.convertLegacySliceToSharedSlice({ model: slice.value, src: { ...path, sliceID: slice.key, }, dest: { libraryID: args.libraryID, sliceID: args.sliceID, variationName: args.variationName ?? "Default", variationID: args.variationID ?? "default", }, }); if (errors.length) { console.error(`Could not convert slice \`${sliceName}\``, errors); toast.error(`Could not convert slice \`${sliceName}\``); throw errors; } const { model: customType, errors: customTypeReadErrors } = await managerClient.customTypes.readCustomType({ id: path.customTypeID, }); if (customTypeReadErrors.length || !customType) { console.error( `Could not refresh custom type view \`${path.customTypeID}\``, customTypeReadErrors, ); toast.error( `Could not refresh custom type view \`${path.customTypeID}\``, ); return; } // TODO(DT-1453): Remove the need of the global getState const serverState = await getState(); // Update Redux store refreshState(serverState); setIsLoading(false); setDialog(undefined); switch (dialog) { case "as_new_slice": toast.success( `${sliceName} has been upgraded to a new slice ${args.libraryID} > ${args.sliceID}`, ); break; case "as_new_variation": toast.success( `${sliceName} has been converted as a variation of ${args.libraryID} > ${args.sliceID}`, ); break; case "merge_with_identical": default: toast.success( `${sliceName} has been merged with ${args.libraryID} > ${args.sliceID}`, ); break; } setCustomType({ customType }); }; const formProps = { path, slice, sliceName, libraries, localSharedSlices, identicalSlices, close: () => setDialog(undefined), onSubmit: convertLegacySliceAndTrack, isLoading, }; return ( <> } description="Use it with new types" onSelect={() => setDialog("as_new_slice")} > Upgrade slice } description="Add it to another slice" onSelect={() => setDialog("as_new_variation")} disabled={!localSharedSlices.length} > Convert to slice variation } description="Combine identical slices" onSelect={() => setDialog("merge_with_identical")} disabled={!identicalSlices.length} > Merge with another slice ); }; const useIdenticalSlices = ( slice: NonSharedSliceInSliceZone, sliceName: string, localSharedSlices: ComponentUI[], ) => { return useMemo(() => { const results: IdenticalSlice[] = []; const sliceFields = getFieldMappingFingerprint(slice.value, sliceName); for (const sharedSlice of localSharedSlices) { for (const variation of sharedSlice.model.variations) { const variationFields = getFieldMappingFingerprint( variation, sharedSlice.model.name, ); if ( sliceFields.primary === variationFields.primary && sliceFields.items === variationFields.items ) { results.push({ libraryID: sharedSlice.from, sliceID: sharedSlice.model.id, variationID: variation.id, path: `${sharedSlice.from}::${sharedSlice.model.id}::${variation.id}`, }); } } } return results; }, [slice, sliceName, localSharedSlices]); };