From ba356c3878298df5b1412bde76a8f415e4aa1af6 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 26 Feb 2024 15:42:36 +0100 Subject: [PATCH] Add navRoot to the equation, this enables Add form correct checks, and Slots under that route. --- packages/registry/src/index.ts | 7 +- packages/types/src/config/Slots.d.ts | 25 ++ packages/types/src/config/index.d.ts | 16 +- packages/types/src/content/common.d.ts | 2 + .../volto/src/components/manage/Form/Form.jsx | 258 ++++++++++-------- .../theme/SlotRenderer/SlotRenderer.test.jsx | 56 ++++ .../theme/SlotRenderer/SlotRenderer.tsx | 9 +- packages/volto/src/config/index.js | 2 +- packages/volto/src/helpers/Slots/index.tsx | 2 +- 9 files changed, 236 insertions(+), 141 deletions(-) create mode 100644 packages/types/src/config/Slots.d.ts create mode 100644 packages/volto/src/components/theme/SlotRenderer/SlotRenderer.test.jsx diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index 83dc0d5ba4..9592bc5a06 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -5,6 +5,8 @@ import type { ComponentsConfig, ExperimentalConfig, SettingsConfig, + GetSlotArgs, + GetSlotReturn, SlotComponent, SlotPredicate, SlotsConfig, @@ -184,10 +186,7 @@ class Config { } } - getSlot( - name: string, - args: T, - ): { component: SlotComponent['component']; name: string }[] | undefined { + getSlot(name: string, args: GetSlotArgs): GetSlotReturn { if (!this._data.slots[name]) { return; } diff --git a/packages/types/src/config/Slots.d.ts b/packages/types/src/config/Slots.d.ts new file mode 100644 index 0000000000..87952c3f7d --- /dev/null +++ b/packages/types/src/config/Slots.d.ts @@ -0,0 +1,25 @@ +import type { Content } from '../content'; + +export type SlotPredicate = (args: any) => boolean; + +export type GetSlotArgs = { + content: Content; + pathname: string; + navRoot?: Content; +}; + +export type GetSlotReturn = + | { component: SlotComponent['component']; name: string }[] + | undefined; + +export type SlotComponent = { + component: React.ComponentType; + predicates?: SlotPredicate[]; +}; + +export type SlotManager = { + slots: string[]; + data: Record; +}; + +export type SlotsConfig = Record; diff --git a/packages/types/src/config/index.d.ts b/packages/types/src/config/index.d.ts index 6a0dc0e0fc..a62e18c96d 100644 --- a/packages/types/src/config/index.d.ts +++ b/packages/types/src/config/index.d.ts @@ -2,6 +2,7 @@ import type { SettingsConfig } from './Settings'; import type { BlocksConfig } from './Blocks'; import type { ViewsConfig } from './Views'; import type { WidgetsConfig } from './Widgets'; +import type { SlotsConfig } from './Slots'; export type AddonReducersConfig = Record; @@ -11,20 +12,6 @@ export type AddonRoutesConfig = { component: React.ComponentType; }[]; -export type SlotPredicate = (args: any) => boolean; - -export type SlotComponent = { - component: React.ComponentType; - predicates?: SlotPredicate[]; -}; - -export type SlotManager = { - slots: string[]; - data: Record; -}; - -export type SlotsConfig = Record; - export type ComponentsConfig = Record< string, { component: React.ComponentType } @@ -46,3 +33,4 @@ export type ConfigData = { export { SettingsConfig, BlocksConfig, ViewsConfig, WidgetsConfig }; export * from './Blocks'; +export * from './Slots'; diff --git a/packages/types/src/content/common.d.ts b/packages/types/src/content/common.d.ts index b1b91edf84..de8922c49f 100644 --- a/packages/types/src/content/common.d.ts +++ b/packages/types/src/content/common.d.ts @@ -2,6 +2,7 @@ import type { BreadcrumbsResponse } from '../services/breadcrumbs'; import type { NavigationResponse } from '../services/navigation'; import type { ActionsResponse } from '../services/actions'; import type { GetTypesResponse } from '../services/types'; +import type { GetNavrootResponse } from '../services/navroot'; import type { GetAliasesResponse } from '../services/aliases'; import type { ContextNavigationResponse } from '../services/contextnavigation'; import type { WorkflowResponse } from '../services/workflow'; @@ -13,6 +14,7 @@ export interface Expanders { breadcrumbs: BreadcrumbsResponse; contextnavigation: ContextNavigationResponse; navigation: NavigationResponse; + navroot: GetNavrootResponse; types: GetTypesResponse; workflow: WorkflowResponse; } diff --git a/packages/volto/src/components/manage/Form/Form.jsx b/packages/volto/src/components/manage/Form/Form.jsx index 2e7c4ff4e6..6ffc4020b0 100644 --- a/packages/volto/src/components/manage/Form/Form.jsx +++ b/packages/volto/src/components/manage/Form/Form.jsx @@ -53,6 +53,7 @@ import { } from '@plone/volto/actions'; import { compose } from 'redux'; import config from '@plone/volto/registry'; +import SlotRenderer from '../../theme/SlotRenderer/SlotRenderer'; /** * Form container class. @@ -641,131 +642,147 @@ class Form extends Component { // Removing this from SSR is important, since react-beautiful-dnd supports SSR, // but draftJS don't like it much and the hydration gets messed up this.state.isClient && ( - - { - const newFormData = { - ...formData, - ...newBlockData, - }; - this.setState({ - formData: newFormData, - }); - if (this.props.global) { - this.props.setFormData(newFormData); - } - }} - onSetSelectedBlocks={(blockIds) => - this.setState({ multiSelected: blockIds }) - } - onSelectBlock={this.onSelectBlock} - /> - { - if (this.props.global) { - this.props.setFormData(state.formData); - } - return this.setState(state); - }} - /> - { - const newFormData = { - ...formData, - ...newData, - }; - this.setState({ - formData: newFormData, - }); - if (this.props.global) { - this.props.setFormData(newFormData); - } - }} - onChangeField={this.onChangeField} - onSelectBlock={this.onSelectBlock} - properties={formData} + <> + - {this.state.isClient && this.props.editable && ( - - 0} + + + { + const newFormData = { + ...formData, + ...newBlockData, + }; + this.setState({ + formData: newFormData, + }); + if (this.props.global) { + this.props.setFormData(newFormData); + } + }} + onSetSelectedBlocks={(blockIds) => + this.setState({ multiSelected: blockIds }) + } + onSelectBlock={this.onSelectBlock} + /> + { + if (this.props.global) { + this.props.setFormData(state.formData); + } + return this.setState(state); + }} + /> + { + const newFormData = { + ...formData, + ...newData, + }; + this.setState({ + formData: newFormData, + }); + if (this.props.global) { + this.props.setFormData(newFormData); + } + }} + onChangeField={this.onChangeField} + onSelectBlock={this.onSelectBlock} + properties={formData} + navRoot={navRoot} + type={type} + pathname={this.props.pathname} + selectedBlock={this.state.selected} + multiSelected={this.state.multiSelected} + manage={this.props.isAdminForm} + allowedBlocks={this.props.allowedBlocks} + showRestricted={this.props.showRestricted} + editable={this.props.editable} + isMainForm={this.props.editable} + /> + {this.state.isClient && this.props.editable && ( + - {schema && - map(schema.fieldsets, (fieldset) => ( - -
0} + > + {schema && + map(schema.fieldsets, (fieldset) => ( + - - {fieldset.title} - {metadataFieldsets.includes(fieldset.id) ? ( - - ) : ( - - )} - - - - {map(fieldset.fields, (field, index) => ( - - ))} - - -
-
- ))} -
-
- )} -
+ + {fieldset.title} + {metadataFieldsets.includes(fieldset.id) ? ( + + ) : ( + + )} + + + + {map(fieldset.fields, (field, index) => ( + + ))} + + + + + ))} + + + )} + + + + ) ) : ( @@ -931,6 +948,7 @@ const FormIntl = injectIntl(Form, { forwardRef: true }); export default compose( connect( (state, props) => ({ + content: state.content.data, globalData: state.form?.global, metadataFieldsets: state.sidebar?.metadataFieldsets, metadataFieldFocus: state.sidebar?.metadataFieldFocus, diff --git a/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.test.jsx b/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.test.jsx new file mode 100644 index 0000000000..0f09535e5a --- /dev/null +++ b/packages/volto/src/components/theme/SlotRenderer/SlotRenderer.test.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import '@testing-library/jest-dom/extend-expect'; +import { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import SlotRenderer from './SlotRenderer'; +import config from '@plone/volto/registry'; + +describe('SlotRenderer Component', () => { + const RouteConditionTrue = () => () => true; + const RouteConditionFalse = () => () => false; + const ContentTypeConditionTrue = () => () => true; + const ContentTypeConditionFalse = () => () => false; + + test('renders a SlotRenderer component for the aboveContentTitle with two slots in the root', () => { + config.registerSlotComponent({ + slot: 'toolbar', + name: 'save', + component: (props) =>
, + predicates: [RouteConditionTrue()], + }); + + config.registerSlotComponent({ + slot: 'toolbar', + name: 'save', + component: (props) =>
, + predicates: [RouteConditionFalse(), ContentTypeConditionFalse()], + }); + + config.registerSlotComponent({ + slot: 'toolbar', + name: 'edit', + component: (props) =>
, + predicates: [RouteConditionFalse()], + }); + + config.registerSlotComponent({ + slot: 'toolbar', + name: 'edit', + component: (props) => ( +