diff --git a/package-lock.json b/package-lock.json index b5dec3f8..bfdf7c0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11559,9 +11559,9 @@ } }, "node_modules/vite-plugin-framer": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/vite-plugin-framer/-/vite-plugin-framer-1.0.6.tgz", - "integrity": "sha512-Y6lI/TPdvJ4fBBgq5O/9s3VpIHIO+9fAUl3K/GdGUm3og8Xgrm0xQfLSIeieI7PYSLuQM/fH5WJ2KDfHCI6Z4Q==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/vite-plugin-framer/-/vite-plugin-framer-1.0.7.tgz", + "integrity": "sha512-aUf7p6mbGOR1DlTfejSrT4yKmSmg0yr3IIAFi/V9SwWJSDI5GmVQUVStUQORFo+q9yxtfZh7kVKS4NdslxHnSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14899,7 +14899,7 @@ "name": "cms-starter", "version": "0.0.0", "dependencies": { - "framer-plugin": "^3.0.0", + "framer-plugin": "^3.1.0-beta.3", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -14919,7 +14919,7 @@ "typescript": "^5.7.3", "typescript-eslint": "^8.25.0", "vite": "^6.2.4", - "vite-plugin-framer": "^1.0.6", + "vite-plugin-framer": "^1.0.7", "vite-plugin-mkcert": "^1.17.7" } }, @@ -15114,9 +15114,9 @@ } }, "starters/cms/node_modules/framer-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/framer-plugin/-/framer-plugin-3.0.0.tgz", - "integrity": "sha512-JRHSSa/uf+LTPRfk1CQfzpU2Al0/gW5OP5CTlpotHUqyak3gGyjmVw9tmr9xE1tLXqPpkHl4mN5QEHwHPNzp4g==", + "version": "3.1.0-beta.3", + "resolved": "https://registry.npmjs.org/framer-plugin/-/framer-plugin-3.1.0-beta.3.tgz", + "integrity": "sha512-JIxc3pNNP4JLQc2bXRwI9nISUBgxEyDSeDVvHK/FVtom51mSACNUD7xmL8pJ4lXRkUFSge3+sbo6zCzARY2Gfg==", "peerDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/starters/cms/package.json b/starters/cms/package.json index f82f216b..a6091c0a 100644 --- a/starters/cms/package.json +++ b/starters/cms/package.json @@ -14,7 +14,7 @@ "typecheck": "tsc" }, "dependencies": { - "framer-plugin": "^3.0.0", + "framer-plugin": "^3.1.0-beta.3", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -34,7 +34,7 @@ "typescript": "^5.7.3", "typescript-eslint": "^8.25.0", "vite": "^6.2.4", - "vite-plugin-framer": "^1.0.6", + "vite-plugin-framer": "^1.0.7", "vite-plugin-mkcert": "^1.17.7" } } diff --git a/starters/cms/src/FieldMapping.tsx b/starters/cms/src/FieldMapping.tsx index ec1a9597..c001c893 100644 --- a/starters/cms/src/FieldMapping.tsx +++ b/starters/cms/src/FieldMapping.tsx @@ -1,6 +1,6 @@ -import { type ManagedCollectionFieldInput, framer, type ManagedCollection } from "framer-plugin" +import { type ManagedCollectionFieldInput, framer, type ManagedCollection, useIsAllowedTo } from "framer-plugin" import { useEffect, useState } from "react" -import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection } from "./data" +import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection, syncMethods } from "./data" interface FieldMappingRowProps { field: ManagedCollectionFieldInput @@ -8,6 +8,7 @@ interface FieldMappingRowProps { isIgnored: boolean onToggleDisabled: (fieldId: string) => void onNameChange: (fieldId: string, name: string) => void + disabled: boolean } function FieldMappingRow({ @@ -16,6 +17,7 @@ function FieldMappingRow({ isIgnored, onToggleDisabled, onNameChange, + disabled, }: FieldMappingRowProps) { return ( <> @@ -23,6 +25,7 @@ function FieldMappingRow({ type="button" className={`source-field ${isIgnored ? "ignored" : ""}`} onClick={() => onToggleDisabled(field.id)} + disabled={disabled} > {originalFieldName ?? field.id} @@ -39,7 +42,7 @@ function FieldMappingRow({ onNameChange(field.id, event.target.value)} @@ -135,6 +138,8 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie }) } + const isAllowedToManage = useIsAllowedTo("ManagedCollection.setFields", ...syncMethods) + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault() @@ -149,8 +154,11 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie try { setStatus("syncing-collection") - const fieldsToSync = fields.filter(field => !ignoredFieldIds.has(field.id)) + const fieldsToSync = fields + .filter(field => !ignoredFieldIds.has(field.id)) + .map(field => ({ ...field, name: field.name.trim() || field.id })) + await collection.setFields(fieldsToSync) await syncCollection(collection, dataSource, fieldsToSync, selectedSlugField) await framer.closePlugin("Synchronization successful", { variant: "success" }) } catch (error) { @@ -188,6 +196,7 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie if (!selectedField) return setSelectedSlugField(selectedField) }} + disabled={!isAllowedToManage} > {possibleSlugFields.map(possibleSlugField => { return ( @@ -210,13 +219,17 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie isIgnored={ignoredFieldIds.has(field.id)} onToggleDisabled={toggleFieldDisabledState} onNameChange={changeFieldName} + disabled={!isAllowedToManage} /> ))} diff --git a/starters/cms/src/data.ts b/starters/cms/src/data.ts index 24daa29e..8d806931 100644 --- a/starters/cms/src/data.ts +++ b/starters/cms/src/data.ts @@ -4,6 +4,7 @@ import { framer, type ManagedCollection, type ManagedCollectionItemInput, + type ProtectedMethod, } from "framer-plugin" export const PLUGIN_KEYS = { @@ -101,11 +102,6 @@ export async function syncCollection( fields: readonly ManagedCollectionFieldInput[], slugField: ManagedCollectionFieldInput ) { - const sanitizedFields = fields.map(field => ({ - ...field, - name: field.name.trim() || field.id, - })) - const items: ManagedCollectionItemInput[] = [] const unsyncedItems = new Set(await collection.getItemIds()) @@ -123,7 +119,7 @@ export async function syncCollection( const fieldData: FieldDataInput = {} for (const [fieldName, value] of Object.entries(item)) { - const field = sanitizedFields.find(field => field.id === fieldName) + const field = fields.find(field => field.id === fieldName) // Field is in the data but skipped based on selected fields. if (!field) continue @@ -141,7 +137,6 @@ export async function syncCollection( }) } - await collection.setFields(sanitizedFields) await collection.removeItems(Array.from(unsyncedItems)) await collection.addItems(items) @@ -149,6 +144,12 @@ export async function syncCollection( await collection.setPluginData(PLUGIN_KEYS.SLUG_FIELD_ID, slugField.id) } +export const syncMethods = [ + "ManagedCollection.removeItems", + "ManagedCollection.addItems", + "ManagedCollection.setPluginData", +] as const satisfies ProtectedMethod[] + export async function syncExistingCollection( collection: ManagedCollection, previousDataSourceId: string | null, @@ -162,6 +163,10 @@ export async function syncExistingCollection( return { didSync: false } } + if (!framer.isAllowedTo(...syncMethods)) { + return { didSync: false } + } + try { const dataSource = await getDataSource(previousDataSourceId) const existingFields = await collection.getFields()