custom variable type widget: single resource collection (1/3)#49
custom variable type widget: single resource collection (1/3)#49
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.
| { 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
…eature/resource
| { | ||
| label: "Link", | ||
| name: "link", | ||
| type: "input", | ||
| hint: "https://... ", | ||
| }, |
There was a problem hiding this comment.
does it make more sense for Link to live only in certain resources within VARIABLE_TYPE_RESOURCE_CONFIG instead of here for all resources, with a clearer hint?
For example, when I selected Protocol(File), the Link field feels a bit unnecessary
There was a problem hiding this comment.
Fair point.
There is definitely a second development pass we need to take on this code to ask in more detail what fields each type should have. If you can live with it, I might just leave it for now . It is a bit unnecessary here, but makes the code DRYer in all other configs.
| label: "File Path", | ||
| name: "file", | ||
| type: "file", | ||
| hint: "Use Media Library to upload, then paste path here", |
There was a problem hiding this comment.
totally fine for now, but we could make Media Library a clickable link down the line
| CMS.registerEventListener({ | ||
| name: "preSave", | ||
| handler: copyResourceNameHandler, | ||
| }); |
There was a problem hiding this comment.
nice use of the hook! I just learned about it, seems really smart
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.