custom variable type widget: single resource collection (1/3)#49
custom variable type widget: single resource collection (1/3)#49interim17 wants to merge 10 commits intorun-formatfrom
Conversation
✅ Deploy Preview for project-idea-board ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
| - { | ||
| label: "Datasets", | ||
| name: "datasets", | ||
| widget: "relation", | ||
| collection: "resources", | ||
| search_fields: ["name"], | ||
| value_field: "{{slug}}", | ||
| display_fields: ["name"], | ||
| multiple: true, | ||
| required: false, | ||
| hint: "Select datasets from the Resources collection, if missing, please add to the Resources collection and then finish editing this entry.", | ||
| filters: [{ field: "resource.type", values: ["dataset"] }], | ||
| } | ||
| - { | ||
| label: "Resources", | ||
| name: "resources", | ||
| widget: "relation", | ||
| collection: "resources", | ||
| search_fields: ["resource.name", "resource.type"], | ||
| value_field: "{{slug}}", | ||
| display_fields: ["resource.type", "resource.name"], | ||
| multiple: true, | ||
| required: false, | ||
| hint: "Select other resources from the Resources collection, if missing, please add to the Resources collection and then finish editing this entry.", | ||
| } |
There was a problem hiding this comment.
One select for any resource at all, and one filtered to just datasets. We can provide as many as desired/useful.
There was a problem hiding this comment.
Pull request overview
Adds a new CMS-managed “Resources” collection backed by a custom union widget, and wires it into the Decap CMS admin so “Ideas” can reference datasets/resources from a single collection.
Changes:
- Extend
static/admin/config.ymlto add a newresourcescollection and new relation fields on Ideas. - Add a configurable union-style Decap CMS control (
resource_union) to author different resource types. - Treat
templateKey: resourcemarkdown as “data-only” content in Gatsby page creation.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| static/admin/config.yml | Adds “Datasets/Resources” relation fields to Ideas and introduces a new “Resources” collection using the custom union widget. |
| src/cms/widgets/VariableTypeWidget/types.ts | Introduces shared config typings for the union widget. |
| src/cms/widgets/VariableTypeWidget/VariableTypeWidgetControl.tsx | Implements the generic config-driven union widget control (including file field support). |
| src/cms/widgets/VariableResourceWidget/constants.ts | Defines the resource-type configuration used by the union widget. |
| src/cms/widgets/VariableResourceWidget/VariableResourceUnionControl.tsx | Binds the generic union widget to the “resource” use-case with concrete type config. |
| src/cms/widgets/VariableResourceWidget/copyResourceNameHandler.ts | Adds a preSave hook to copy nested resource name into a required top-level field. |
| src/cms/cms.js | Registers the custom widget and the preSave event handler with Decap CMS. |
| gatsby-node.js | Adds resource to the list of data-only templateKeys to prevent page creation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - { | ||
| label: "Resources", | ||
| name: "resources", | ||
| widget: "relation", | ||
| collection: "resources", | ||
| search_fields: ["resource.name", "resource.type"], | ||
| value_field: "{{slug}}", | ||
| display_fields: ["resource.type", "resource.name"], | ||
| multiple: true, | ||
| required: false, | ||
| hint: "Select other resources from the Resources collection, if missing, please add to the Resources collection and then finish editing this entry.", | ||
| } |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
src/cms/widgets/VariableResourceWidget/copyResourceNameHandler.ts
Outdated
Show resolved
Hide resolved
| { label: "Name", name: "name", type: "input" }, | ||
| { | ||
| label: "Description", | ||
| name: "description", | ||
| type: "textarea", |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| Certain fields are required to be top-level by the CMS, which clutters the UI | ||
| when we have a redundant field in widget. This preSave hook copies the resource.name | ||
| field to the top-level from the nested widget. | ||
| */ |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| }; | ||
|
|
||
| const handleTypeChange = (newType: string) => { | ||
| onChange({ type: newType }); |
There was a problem hiding this comment.
handleTypeChange calls onChange({ type: newType }), which drops all existing fields in the object whenever the user changes the type (e.g., name/description/link will be lost). Preserve the current normalized value when switching types (and optionally clear only the previous type-specific fields) so changing the selector doesn't wipe user input.
| onChange({ type: newType }); | |
| const current = normalizeValue(); | |
| onChange({ ...current, type: newType }); |
There was a problem hiding this comment.
Since fields won't match between types necessarily, I think we should drop them.
| { label: "Name", name: "name", type: "input" }, | ||
| { | ||
| label: "Description", | ||
| name: "description", | ||
| type: "textarea", |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| import CMS from "decap-cms-core" | ||
|
|
||
| // FRor some reason I could not import CmsEventListener as as a type | ||
| // and it was resolving to the global CMS object, so this is the workaround. | ||
| type CmsEventListenerHandler = Parameters<typeof CMS.registerEventListener>[0]['handler']; | ||
| type CmsEventListenerHandlerArg = Parameters<CmsEventListenerHandler>[0]; | ||
|
|
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| { label: "Name", name: "name", type: "input" }, | ||
| { | ||
| label: "Description", | ||
| name: "description", | ||
| type: "textarea", |
There was a problem hiding this comment.
cellLine repeats name/description even though the widget's default base fields already include them, causing duplicate inputs bound to the same keys. Remove the duplicates or override baseFields for this type.
| /** | ||
| * Implementation of VariableTypeWidgetControl for a union of different | ||
| * resource types (software tools, datasets, etc.). | ||
| * Config defined in RESOURCE_TYPES. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…eature/resource
…into feature/resource
Problem
Advances #40
Decap CMS does not natively support a variable type object widget.
A fairly limited variable type list widget exists, but doesn't quite meet our needs, and it's use leads to significantly cluttered UI with deeply nested fields.
In this case we want a single collection for resources (datasets, cell lines, software tools) to associate with an idea, rather than individual collections for each of those resource types.
https://decapcms.org/docs/variable-type-widgets/
Solution
Built a custom widget to handle a variable type, in this case, a resource object that can be one of several varieties and will render unique fields in the UI based on the type selected.
This PR does not address fully migrating all existing resources, or even the desired final set of fields for any resource types, as its a fairly big lift to just get the custom widget built, styled and registered.
In
cms/widgetswe define aVariableTypeWidgetControlEven though we only have one implementation, I figured this was likely a pattern that would be repeated or built upon in any of our projects using this CMS so this abstract implementation doesn't need to know anything about the specifics of the config.
VariableResourceWidgetHold the config in
constantswhere the fields are actually defined and the React component isVariableResourceUnionControl.In
cms.jsWe register the component here, in future work we can also register a custom Preview component here.
we also register a hook in the
preSavestep of the Decap lifecycle, which allows us to use the nested "name" field for a required and hidden top-level name field, which helps to reduce UI noise.In
config.ymlThe collection
Resourcesuses the widget, and in theideascollection relational widgets are used to make a select both for the general resources, and one filtered only to datasets.