From 46dc0e6a12b2f9ff1ee37a54cf3697d3db4283ba Mon Sep 17 00:00:00 2001 From: soupette Date: Tue, 26 Jan 2021 16:18:26 +0100 Subject: [PATCH] Created PluginAPI and improve plugin registration in order to add a boot lifecycle for enabling a plugin to interact with another internal API. Update the ctb to expose a form api. Signed-off-by: soupette --- packages/strapi-admin/admin/src/app.js | 8 +++ .../strapi-admin/admin/src/utils/Plugin.js | 50 +++++++++++++++++++ .../strapi-admin/admin/src/utils/Strapi.js | 13 +++++ .../src/containers/FormModal/forms/index.js | 49 ++++++++++++++++-- .../admin/src/containers/FormModal/index.js | 16 ++++-- .../admin/src/index.js | 25 ++++++++++ .../strapi-plugin-i18n/admin/src/index.js | 22 ++++++++ 7 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 packages/strapi-admin/admin/src/utils/Plugin.js diff --git a/packages/strapi-admin/admin/src/app.js b/packages/strapi-admin/admin/src/app.js index 4340ed8ab6..b8ec7d4376 100644 --- a/packages/strapi-admin/admin/src/app.js +++ b/packages/strapi-admin/admin/src/app.js @@ -62,6 +62,8 @@ const pluginsToLoad = []; Object.keys(plugins).forEach(current => { const registerPlugin = plugin => { + strapi.registerPlugin(plugin); + return plugin; }; const currentPluginFn = plugins[current]; @@ -115,6 +117,12 @@ const { dispatch } = store; // Load plugins, this will be removed in the v4, temporary fix until the plugin API // https://plugin-api-rfc.vercel.app/plugin-api/admin.html pluginsToLoad.forEach(plugin => { + const bootPlugin = plugin.boot; + + if (bootPlugin) { + bootPlugin(strapi); + } + dispatch(pluginLoaded(plugin)); }); diff --git a/packages/strapi-admin/admin/src/utils/Plugin.js b/packages/strapi-admin/admin/src/utils/Plugin.js new file mode 100644 index 0000000000..60885276fb --- /dev/null +++ b/packages/strapi-admin/admin/src/utils/Plugin.js @@ -0,0 +1,50 @@ +class Plugin { + pluginId = null; + + decorators = {}; + + injectedComponents = {}; + + internals = {}; + + constructor(pluginConf) { + this.pluginId = pluginConf.id; + this.decorators = pluginConf.decorators || {}; + this.injectedComponents = pluginConf.injectedComponents || {}; + this.internals = pluginConf.internals || {}; + } + + decorate(compoName, compo) { + if (this.decorators && this.decorators[compoName]) { + this.decorators[compoName] = compo; + } + } + + getDecorator(compoName) { + if (this.decorators) { + return this.decorators[compoName] || null; + } + + return null; + } + + getInjectedComponents(containerName, blockName) { + try { + return this.injectedComponents[containerName][blockName] || {}; + } catch (err) { + console.error('Cannot get injected component', err); + + return err; + } + } + + injectComponent(containerName, blockName, compo) { + try { + this.injectedComponents[containerName][blockName].push(compo); + } catch (err) { + console.error('Cannot inject component', err); + } + } +} + +export default pluginConf => new Plugin(pluginConf); diff --git a/packages/strapi-admin/admin/src/utils/Strapi.js b/packages/strapi-admin/admin/src/utils/Strapi.js index b3f1dcd0a9..5710d9e8b5 100644 --- a/packages/strapi-admin/admin/src/utils/Strapi.js +++ b/packages/strapi-admin/admin/src/utils/Strapi.js @@ -1,6 +1,7 @@ import ComponentApi from './ComponentApi'; import FieldApi from './FieldApi'; import MiddlewareApi from './MiddlewareApi'; +import PluginHandler from './Plugin'; class Strapi { componentApi = ComponentApi(); @@ -8,6 +9,18 @@ class Strapi { fieldApi = FieldApi(); middlewares = MiddlewareApi(); + + plugins = {}; + + getPlugin = pluginId => { + return this.plugins[pluginId]; + }; + + registerPlugin = pluginConf => { + if (pluginConf.id) { + this.plugins[pluginConf.id] = PluginHandler(pluginConf); + } + }; } export default () => { diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/forms/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/forms/index.js index d73b668a63..1e4e2a12e9 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/forms/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/forms/index.js @@ -13,7 +13,8 @@ const forms = { attributeType, reservedNames, alreadyTakenTargetContentTypeAttributes, - options + options, + extensions ) { const attributes = get(currentSchema, ['schema', 'attributes'], {}); @@ -21,22 +22,62 @@ const forms = { return attr !== options.initialData.name; }); + const validators = []; + const attributeFormExtensions = extensions.attribute[attributeType]; + + if (attributeFormExtensions) { + attributeFormExtensions.forEach(({ validator }) => { + if (validator) { + validators.push(validator); + } + }); + } + try { - return attributeTypes[attributeType]( + let shape = attributeTypes[attributeType]( usedAttributeNames, reservedNames.attributes, alreadyTakenTargetContentTypeAttributes, options ); + + validators.forEach(validator => { + console.log({ validator }); + shape = shape.shape(validator); + }); + + return shape; } catch (err) { + console.error('form', err); + return attributeTypes.default(usedAttributeNames, reservedNames.attributes); } }, form: { - advanced(data, type, step) { + advanced(data, type, step, actionType, attributes, extensions) { + const attributeFormExtensions = extensions.attribute[type]; + + let customForms = []; + + if (attributeFormExtensions) { + attributeFormExtensions.forEach(({ form }) => { + if (form.advanced) { + const blocksToAdd = form.advanced(data, type, step, actionType, attributes); + + blocksToAdd.forEach(block => { + customForms.push(block); + }); + } + }); + } + try { - return attributesForm.advanced[type](data, step); + const baseForm = attributesForm.advanced[type](data, step).items; + + return { items: [...baseForm, ...customForms] }; } catch (err) { + console.error(err); + return { items: [] }; } }, diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js index 75f8c677ad..600c34f3ca 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/FormModal/index.js @@ -10,6 +10,7 @@ import { getYupInnerErrors, useGlobalContext, useQuery, + useStrapi, InputsIndex, } from 'strapi-helper-plugin'; import { Button, Text, Padded } from '@buffetjs/core'; @@ -70,6 +71,12 @@ const FormModal = () => { const { push } = useHistory(); const { search } = useLocation(); const { emitEvent, formatMessage } = useGlobalContext(); + const { + strapi: { getPlugin }, + } = useStrapi(); + const ctbPlugin = getPlugin(pluginId); + const types = ctbPlugin.internals.forms.types; + const query = useQuery(); const attributeOptionRef = useRef(); @@ -365,8 +372,6 @@ const FormModal = () => { items: [], })); - console.log({ modifiedData }); - const headers = createHeadersArray(state); const isCreatingContentType = state.modalType === 'contentType'; @@ -458,7 +463,8 @@ const FormModal = () => { type, reservedNames, alreadyTakenTargetContentTypeAttributes, - { modifiedData, initialData } + { modifiedData, initialData }, + types ); } else if (isEditingCategory) { schema = forms.editCategory.schema(allComponentsCategories, initialData); @@ -1025,6 +1031,7 @@ const FormModal = () => { }); } catch (err) { const errors = getYupInnerErrors(err); + console.log({ err, errors }); dispatch({ type: SET_ERRORS, @@ -1199,7 +1206,8 @@ const FormModal = () => { state.attributeType, state.step, state.actionType, - attributes + attributes, + types ).items.map((row, index) => { return (
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/index.js b/packages/strapi-plugin-content-type-builder/admin/src/index.js index 84fc0fdb37..d6b0727c99 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/index.js @@ -72,6 +72,31 @@ export default strapi => { }, ], }, + // Internal APIs exposed by the CTB for the other plugins to use + internals: { + forms: { + types: { + attribute: { + test: [], + }, + contentType: {}, + component: {}, + }, + extendFields(fields, extension) { + const formType = this.types.attribute; + + fields.forEach(field => { + let currentField = formType[field]; + + if (currentField) { + currentField.push(extension); + } else { + formType[field] = [extension]; + } + }); + }, + }, + }, }; return strapi.registerPlugin(plugin); diff --git a/packages/strapi-plugin-i18n/admin/src/index.js b/packages/strapi-plugin-i18n/admin/src/index.js index 47372f10f1..f4f233c7f4 100644 --- a/packages/strapi-plugin-i18n/admin/src/index.js +++ b/packages/strapi-plugin-i18n/admin/src/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import * as yup from 'yup'; import pluginPkg from '../../package.json'; import middlewares from './middlewares'; import pluginId from './pluginId'; @@ -48,6 +49,27 @@ export default strapi => { }, }, trads, + boot(app) { + const ctbPlugin = app.getPlugin('content-type-builder'); + + if (ctbPlugin) { + ctbPlugin.internals.forms.extendFields(['text', 'string'], { + validator: { + i18n: yup.string().required(), + }, + form: { + advanced(args) { + console.log('advanced', args); + + return [[{ name: 'i18n', type: 'text', label: { id: 'i18nTest' } }]]; + }, + base(args) { + console.log('base', args); + }, + }, + }); + } + }, }; return strapi.registerPlugin(plugin);