From fd2b3f1bb4fc5445b3e6ccade89d42c3de9a1bcf Mon Sep 17 00:00:00 2001 From: valentinab25 <30239069+valentinab25@users.noreply.github.com> Date: Tue, 7 Apr 2020 13:44:41 +0300 Subject: [PATCH 01/21] Add demo upgrade --- Jenkinsfile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 8bcccffb..0f36d94c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,6 +2,8 @@ pipeline { environment { registry = "eeacms/eprtr-frontend" template = "templates/volto-eprtr" + RANCHER_STACKID = "1st1851" + RANCHER_ENVID = "1a332957" dockerImage = '' tagName = '' } @@ -45,6 +47,21 @@ pipeline { } } + stage('Upgrade demo') { + when { + buildingTag() + } + steps { + node(label: 'docker') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'Rancher_dev_token', usernameVariable: 'RANCHER_ACCESS', passwordVariable: 'RANCHER_SECRET'],string(credentialsId: 'Rancher_dev_url', variable: 'RANCHER_URL')]) { + sh '''wget -O rancher_upgrade.sh https://github.com/raw/eea/eea.docker.gitflow/master/src/rancher_upgrade.sh''' + sh '''chmod 755 rancher_upgrade.sh''' + sh '''./rancher_upgrade.sh''' + } + } + } + } + } post { From 936a221b7661d1fc2c49c70eca00da6f5723060b Mon Sep 17 00:00:00 2001 From: Mihai Macaneata Date: Wed, 8 Apr 2020 08:11:23 +0300 Subject: [PATCH 02/21] move detailedlink to frontend, fix siblings render --- src/components/manage/Blocks/Block/Edit.jsx | 277 ++++++++++++++++++ .../Blocks/DetailedLink/AddLinkForm.jsx | 201 +++++++++++++ .../manage/Blocks/DetailedLink/Edit.jsx | 177 +++++++++++ .../manage/Blocks/DetailedLink/View.jsx | 115 ++++++++ .../manage/Blocks/DetailedLink/style.css | 68 +++++ src/components/theme/View/TabsView.jsx | 2 +- src/localconfig.js | 13 + 7 files changed, 852 insertions(+), 1 deletion(-) create mode 100644 src/components/manage/Blocks/Block/Edit.jsx create mode 100644 src/components/manage/Blocks/DetailedLink/AddLinkForm.jsx create mode 100644 src/components/manage/Blocks/DetailedLink/Edit.jsx create mode 100644 src/components/manage/Blocks/DetailedLink/View.jsx create mode 100644 src/components/manage/Blocks/DetailedLink/style.css diff --git a/src/components/manage/Blocks/Block/Edit.jsx b/src/components/manage/Blocks/Block/Edit.jsx new file mode 100644 index 00000000..64ac3abb --- /dev/null +++ b/src/components/manage/Blocks/Block/Edit.jsx @@ -0,0 +1,277 @@ +/** + * Edit block. + * @module components/manage/Blocks/Block/Edit + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { DragSource, DropTarget } from 'react-dnd'; +import { findDOMNode } from 'react-dom'; +import { defineMessages, injectIntl } from 'react-intl'; +import { blocks } from '~/config'; +import { Button } from 'semantic-ui-react'; +import includes from 'lodash/includes'; +import cx from 'classnames'; +import { setSidebarTab } from '@plone/volto/actions'; + +import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser'; +import Icon from '@plone/volto/components/theme/Icon/Icon'; +import dragSVG from '@plone/volto/icons/drag.svg'; +import trashSVG from '@plone/volto/icons/delete.svg'; + +const messages = defineMessages({ + unknownBlock: { + id: 'Unknown Block', + defaultMessage: 'Unknown Block {block}', + }, + delete: { + id: 'delete', + defaultMessage: 'delete', + }, +}); + +const itemSource = { + beginDrag(props) { + return { + id: props.id, + index: props.index, + }; + }, +}; + +const ItemTypes = { + ITEM: 'block', +}; + +const itemTarget = { + hover(props, monitor, component) { + const dragIndex = monitor.getItem().index; + const hoverIndex = props.index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + + // Determine rectangle on screen + const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); + + // Get vertical middle + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + + // Determine mouse position + const clientOffset = monitor.getClientOffset(); + + // Get pixels to the top + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + // Only perform the move when the mouse has crossed half of the items height + // When dragging downwards, only move when the cursor is below 50% + // When dragging upwards, only move when the cursor is above 50% + + // Dragging downwards + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return; + } + + // Dragging upwards + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return; + } + + // Time to actually perform the action + props.onMoveBlock(dragIndex, hoverIndex); + + // Note: we're mutating the monitor item here! + // Generally it's better to avoid mutations, + // but it's good here for the sake of performance + // to avoid expensive index searches. + monitor.getItem().index = hoverIndex; + }, +}; + +/** + * Edit block class. + * @class Edit + * @extends Component + */ +class Edit extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + type: PropTypes.string.isRequired, + data: PropTypes.objectOf(PropTypes.any).isRequired, + // properties is mapped to formData, so it's not connected to changes of the object + properties: PropTypes.objectOf(PropTypes.any).isRequired, + selected: PropTypes.bool.isRequired, + connectDragSource: PropTypes.func.isRequired, + connectDragPreview: PropTypes.func.isRequired, + connectDropTarget: PropTypes.func.isRequired, + index: PropTypes.number.isRequired, + id: PropTypes.string.isRequired, + onMoveBlock: PropTypes.func.isRequired, + onDeleteBlock: PropTypes.func.isRequired, + }; + + componentDidMount() { + const { type } = this.props; + const blockHasOwnFocusManagement = + blocks.blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null; + if ( + !blockHasOwnFocusManagement && + this.props.selected && + this.blockNode.current + ) { + this.blockNode.current.focus(); + } + if (this.props.selected) { + this.props.setSidebarTab(blocks.blocksConfig?.[type]?.sidebarBar || 0); + } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { selected, type } = this.props; + const blockHasOwnFocusManagement = + blocks.blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null; + if ( + !blockHasOwnFocusManagement && + nextProps.selected && + selected !== nextProps.selected && + this.blockNode.current + ) { + this.blockNode.current.focus(); + } + if ( + (!this.props.selected && nextProps.selected) || + type !== nextProps.type + ) { + this.props.setSidebarTab( + blocks.blocksConfig?.[nextProps.type]?.sidebarTab || 0, + ); + } + } + + blockNode = React.createRef(); + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { + const { + id, + type, + selected, + connectDragSource, + connectDropTarget, + connectDragPreview, + } = this.props; + + const Block = blocks.blocksConfig?.[type]?.['edit'] || null; + const blockHasOwnFocusManagement = + blocks.blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null; + + const hideHandler = + this.props.data['@type'] === 'text' && + (!this.props.data.text || + (this.props.data.text && + this.props.data.text.blocks && + this.props.data.text.blocks.length === 1 && + this.props.data.text.blocks[0].text === '')); + + return connectDropTarget( + connectDragPreview( +
+ {selected && + connectDragSource( +
+ +
, + )} + {Block !== null ? ( +
this.props.onSelectBlock(this.props.id)} + onKeyDown={ + !blockHasOwnFocusManagement + ? e => + this.props.handleKeyDown( + e, + this.props.index, + this.props.id, + this.blockNode.current, + ) + : null + } + className={cx(`block ${type}`, { selected: this.props.selected })} + style={{ outline: 'none' }} + ref={this.blockNode} + // The tabIndex is required for the keyboard navigation + /* eslint-disable jsx-a11y/no-noninteractive-tabindex */ + tabIndex={!blockHasOwnFocusManagement ? -1 : null} + > + +
+ ) : ( +
+ this.props.handleKeyDown( + e, + this.props.index, + this.props.id, + this.blockNode.current, + ) + } + className={cx(`block ${type}`, { selected: this.props.selected })} + style={{ outline: 'none' }} + ref={this.blockNode} + // The tabIndex is required for the keyboard navigation + tabIndex={-1} + > + {this.props.intl.formatMessage(messages.unknownBlock, { + block: type, + })} +
+ )} + {selected && !includes(blocks.requiredBlocks, type) && ( + + )} +
, + ), + ); + } +} + +export default compose( + injectIntl, + withObjectBrowser, + DropTarget(ItemTypes.ITEM, itemTarget, connect => ({ + connectDropTarget: connect.dropTarget(), + })), + DragSource(ItemTypes.ITEM, itemSource, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + connectDragPreview: connect.dragPreview(), + })), + connect(null, { setSidebarTab }), +)(Edit); \ No newline at end of file diff --git a/src/components/manage/Blocks/DetailedLink/AddLinkForm.jsx b/src/components/manage/Blocks/DetailedLink/AddLinkForm.jsx new file mode 100644 index 00000000..a753e7da --- /dev/null +++ b/src/components/manage/Blocks/DetailedLink/AddLinkForm.jsx @@ -0,0 +1,201 @@ +/** + * Add link form. + * @module components/manage/AnchorPlugin/components/LinkButton/AddLinkForm + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { compose } from 'redux'; +import unionClassNames from 'union-class-names'; +import { connect } from 'react-redux'; +import { map } from 'lodash'; +import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib'; +import { defineMessages, injectIntl } from 'react-intl'; + +import { resetSearchContent, searchContent } from '@plone/volto/actions'; +import URLUtils from '@plone/volto/components/manage/AnchorPlugin/utils/URLUtils'; + +const messages = defineMessages({ + placeholder: { + id: 'Enter URL or title', + defaultMessage: 'Enter URL or title', + }, +}); + +class AddLinkForm extends Component { + static propTypes = { + onOverrideContent: PropTypes.func.isRequired, + resetSearchContent: PropTypes.func.isRequired, + searchContent: PropTypes.func.isRequired, + search: PropTypes.arrayOf( + PropTypes.shape({ + '@id': PropTypes.string, + '@type': PropTypes.string, + title: PropTypes.string, + description: PropTypes.string, + }), + ), + }; + + static defaultProps = { + placeholder: 'Enter URL or search for content', + search: [], + }; + + constructor(props) { + super(props); + this.state = { + value: '', + isInvalid: false, + }; + this.onRef = this.onRef.bind(this); + this.onChange = this.onChange.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + this.onSubmit = this.onSubmit.bind(this); + } + + componentDidMount() { + this.input.focus(); + this.props.resetSearchContent(); + document.addEventListener('mousedown', this.handleClickOutside, false); + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.handleClickOutside, false); + } + + handleClickOutside = e => { + if ( + this.linkFormContainer.current && + doesNodeContainClick(this.linkFormContainer.current, e) + ) + return; + this.onClose(); + }; + + onClose() { + return true; + } + + onRef(node) { + this.input = node; + } + + linkFormContainer = React.createRef(); + + onChange({ target: { value } }) { + const nextState = { value }; + if (this.state.isInvalid && URLUtils.isUrl(URLUtils.normalizeUrl(value))) { + nextState.isInvalid = false; + } + this.setState(nextState); + if (value && value !== '') { + this.props.searchContent('', { + Title: `*${value}*`, + }); + } else { + this.props.resetSearchContent(); + } + } + + onSelectItem = (e, item) => { + e.preventDefault(); + const itemToSave = { + key: item['@id'], + text: item.title, + value: item['@id'], + description: item.description, + external: true, + }; + this.setState({ + value: itemToSave, + isInvalid: false, + }); + this.props.onAddLink(itemToSave) + this.props.resetSearchContent(); + this.input.blur(); + this.onClose(); + }; + + onKeyDown(e) { + if (e.key === 'Enter') { + e.preventDefault(); + this.onSubmit(); + } else if (e.key === 'Escape') { + e.preventDefault(); + this.onClose(); + } + } + + onSubmit() { + let { value: url } = this.state; + if (!URLUtils.isMail(URLUtils.normaliseMail(url))) { + url = URLUtils.normalizeUrl(url); + if (!URLUtils.isUrl(url)) { + this.setState({ isInvalid: true }); + return; + } + } else { + url = URLUtils.normaliseMail(url); + } + this.input.blur(); + this.onClose(); + } + + render() { + const { value, isInvalid } = this.state; + console.log('link value', this.props); + return ( +
+
+ + + + + + + +
+ +
+ ); + } +} + +export default compose( + injectIntl, + connect( + state => ({ + search: state.search.items, + }), + { resetSearchContent, searchContent }, + ), +)(AddLinkForm); diff --git a/src/components/manage/Blocks/DetailedLink/Edit.jsx b/src/components/manage/Blocks/DetailedLink/Edit.jsx new file mode 100644 index 00000000..20609e57 --- /dev/null +++ b/src/components/manage/Blocks/DetailedLink/Edit.jsx @@ -0,0 +1,177 @@ +/** + * Edit map block. + * @module components/manage/Blocks/Maps/Edit + */ + +import React, { Component } from 'react'; +import PropTypes, { array } from 'prop-types'; +import { Link } from 'react-router-dom'; +import { Icon, SidebarPortal, TextWidget } from '@plone/volto/components'; +import { Dropdown, Segment, Checkbox, Input, Button } from 'semantic-ui-react'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import './style.css'; +import { Card } from 'semantic-ui-react'; +import { settings } from '~/config'; +import AddLinkForm from './AddLinkForm'; + +function removeDuplicates(myArr, prop) { + return myArr.filter((obj, pos, arr) => { + return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos; + }); +} + +class Edit extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + selected: PropTypes.bool.isRequired, + block: PropTypes.string.isRequired, + index: PropTypes.number.isRequired, + data: PropTypes.objectOf(PropTypes.any).isRequired, + pathname: PropTypes.string.isRequired, + onChangeBlock: PropTypes.func.isRequired, + onSelectBlock: PropTypes.func.isRequired, + onDeleteBlock: PropTypes.func.isRequired, + onFocusPreviousBlock: PropTypes.func.isRequired, + onFocusNextBlock: PropTypes.func.isRequired, + }; + + constructor(props) { + super(props); + this.state = { + // catalogueList: this.props.data.catalogueList || [], + // catalogueSelectionList: this.props.data.catalogueSelectionList || [], + // details: [], + link: this.props.data.link + }; + } + getPath(url) { + if (!url) return ''; + return url + .replace(settings.apiPath, '') + .replace(settings.internalApiPath, ''); + } + + componentDidMount() { + console.log('-----', this.props.properties); + // this.setInitialCatalogueSelectionList(); + } + + // resetCatalogueSelectionList = () => { + // const catalogueSelectionList = + // (this.props.properties.items.length && + // this.props.properties.items.map(item => ({ + // key: this.getPath(item['@id']), + // text: item.title || item.Title, + // value: this.getPath(item['@id']), + // description: item.description, + // image: this.getPath(item.image?.download), + // }))) || + // []; + // this.setState({ catalogueSelectionList }); + // }; + + // setInitialCatalogueSelectionList = () => { + // const catalogueSelectionListFromChildren = + // (this.props.properties.items.length && + // this.props.properties.items.map(item => ({ + // key: this.getPath(item['@id']), + // text: item.title || item.Title, + // value: this.getPath(item['@id']), + // description: item.description, + // image: this.getPath(item.image?.download), + // }))) || + // []; + // const catalogueSelectionList = removeDuplicates( + // [ + // ...catalogueSelectionListFromChildren, + // ...this.state.catalogueSelectionList, + // ], + // 'key', + // ); + // console.log('initial', this.state.catalogueSelectionList); + // this.setState({ catalogueSelectionList }); + // }; + + // componentDidUpdate(prevProps, prevState) { + // if (JSON.stringify(prevState) !== JSON.stringify(this.state)) { + // this.onChangeData(); + // } + // } + + onChangeData() { + this.props.onChangeBlock(this.props.block, { + ...this.props.data, + link: this.state.link, + // catalogueList: this.state.catalogueList, + // catalogueSelectionList: this.state.catalogueSelectionList, + }); + } + + // onChangeCatalogue = (event, data) => { + // this.setState({ + // catalogue: data.checked, + // }); + // }; + + // onChangeCatalogueListing = (event, data) => { + // this.setState({ + // catalogueList: data.value, + // }); + // }; + + onAddLink = link => { + this.setState({ + link + }); + }; + + render() { + // text: item.title || item.Title, + // value: this.getPath(item['@id']), + // description: item.description, + // image: this.getPath(item.image?.download), + return ( +
+ asdasdas + {this.state.link && ( +
+
+ +
+ {this.state.link.title || this.state.link.Title} +
+ {this.state.link.description &&

{this.state.link.description}

} + {this.state.link.image && ( + icon + )} + +
+
+ ) || 'select a link from sidebar'} + {/* */} + +
+

Detailed Link

+
+ +
+

Non-children links

+ +
+
+
+ {/*
*/} +
+ ); + } +} + +export default injectIntl(Edit); diff --git a/src/components/manage/Blocks/DetailedLink/View.jsx b/src/components/manage/Blocks/DetailedLink/View.jsx new file mode 100644 index 00000000..d53b039e --- /dev/null +++ b/src/components/manage/Blocks/DetailedLink/View.jsx @@ -0,0 +1,115 @@ +/** + * Edit map block. + * @module components/manage/Blocks/Maps/Edit + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; +import { injectIntl } from 'react-intl'; +import './style.css'; +import { settings } from '~/config'; +import { BodyClass } from '@plone/volto/helpers'; + +// import { Dropdown, Segment, Checkbox, Input, Button } from 'semantic-ui-react'; +// import AddLinkForm from './AddLinkForm'; + +function removeDuplicates(myArr, prop) { + return myArr.filter((obj, pos, arr) => { + return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos; + }); +} + +class View extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + data: PropTypes.objectOf(PropTypes.any).isRequired, + // pathname: PropTypes.string.isRequired, + }; + + constructor(props) { + super(props); + this.state = { + catalogue: props.data.catalogue || false, + catalogueList: this.props.data.catalogueList || [], + catalogueSelectionList: this.props.data.catalogueSelectionList || [], + details: [], + }; + } + getPath(url) { + if (!url) return ''; + return url + .replace(settings.apiPath, '') + .replace(settings.internalApiPath, ''); + } + componentDidMount() { + this.setInitialCatalogueSelectionList(); + } + + setInitialCatalogueSelectionList = () => { + const catalogueSelectionListFromChildren = + (this.props.properties.items.length && + this.props.properties.items.map(item => ({ + key: this.getPath(item['@id']), + text: item.title || item.Title, + value: this.getPath(item['@id']), + description: item.description, + image: this.getPath(item.image?.download), + }))) || + []; + const catalogueSelectionList = removeDuplicates( + [ + ...catalogueSelectionListFromChildren, + ...this.state.catalogueSelectionList, + ], + 'key', + ); + console.log('initial', this.state.catalogueSelectionList); + this.setState({ catalogueSelectionList }); + }; + + render() { + const childrenToDisplay = this.state.catalogue + ? this.state.catalogueSelectionList.filter(item => + this.state.catalogueList.includes(item.value), + ) + : this.state.catalogueSelectionList; + return ( +
+ + {!this.state.catalogueSelectionList && + !this.state.catalogueSelectionList.length &&
No children
} + {this.state.catalogue && !this.state.catalogueList.length && ( +
Please select items to display for catalogue intro
+ )} + {childrenToDisplay.length && ( +
+ {childrenToDisplay.map((item, i) => ( +
+ +
+ {item.text} +
+ {item.description &&

{item.description}

} + {item.image && ( + icon + )} + +
+ ))} +
+ )} +
+ ); + } +} + +export default injectIntl(View); diff --git a/src/components/manage/Blocks/DetailedLink/style.css b/src/components/manage/Blocks/DetailedLink/style.css new file mode 100644 index 00000000..c2b707c1 --- /dev/null +++ b/src/components/manage/Blocks/DetailedLink/style.css @@ -0,0 +1,68 @@ +.catalogue-listing-block { + display: flex; + flex-wrap: wrap; + max-width: 800px; + margin: 0 auto; +} + +.catalogue-listing-block-item { + width: 50%; + min-height: 200px; + display: flex; + justify-content: flex-end; + padding: 1rem; + padding-left: 60px; + position: relative; +} + + +.catalogue-listing-block-item:nth-child(odd) { + justify-content: flex-start; + border-right: 1px solid #eee; + padding-left: 1rem; + padding-right: 60px; +} + +.catalogue-listing-block-item:nth-child(even) a { + text-align: right; +} + +.catalogue-listing-block-item img { + position: absolute; + width: 60px; + height: auto; + top: 1rem; + right: 1rem; + opacity: 0.5; +} + +.catalogue-listing-block-item:nth-child(even) img { + right: unset; + left: 1rem; +} + +.catalogue-listing-block-item p { + color: #444; + font-size: 16px; + position: relative; +} + +.catalogue-listing-block-item { + border-bottom: 1px solid #eee; +} + +.catalogue-listing-block-item:nth-last-child(-n+2) { + border-bottom: none; +} + +.catalogue-listing-block-item-title { + font-family: 'Roboto Condensed'; + font-size: 28px; + color: rgb(102,102,102); + font-weight: bold; + position: relative; +} + +.center-heading h1.documentFirstHeading { + text-align: center; +} \ No newline at end of file diff --git a/src/components/theme/View/TabsView.jsx b/src/components/theme/View/TabsView.jsx index 881ddaa7..280d428e 100644 --- a/src/components/theme/View/TabsView.jsx +++ b/src/components/theme/View/TabsView.jsx @@ -71,7 +71,7 @@ class DefaultView extends Component { // } computeFolderTabs = siblings => { - const tabsItems = siblings.items.map(i => { + const tabsItems = siblings && siblings.items.map(i => { return { url: flattenToAppURL(i.url), title: i.name, diff --git a/src/localconfig.js b/src/localconfig.js index 5d299e7a..77702e73 100644 --- a/src/localconfig.js +++ b/src/localconfig.js @@ -1,6 +1,9 @@ import TabsView from '~/components/theme/View/TabsView'; +import ChildrenListView from '~/components/manage/Blocks/DetailedLink/View'; +import ChildrenListEdit from '~/components/manage/Blocks/DetailedLink/Edit'; + const applyConfig = config => { console.log('config', config) config.views = { @@ -12,6 +15,16 @@ console.log('config', config) }; + + config.blocks.blocksConfig.detailedLink = { + id: 'detailedlink', + group: 'custom_addons', + title: 'Detailed Link', + view: ChildrenListView, + edit: ChildrenListEdit, + icon: config.blocks.blocksConfig.text.icon, + }; + // config.blocks.blocksConfig.collection_block = { // id: 'collection_block', // title: 'Collection Listing', From 83fc18e44ee969ba39a0d299718d124e8e547d68 Mon Sep 17 00:00:00 2001 From: Mihai Macaneata Date: Wed, 8 Apr 2020 10:42:13 +0300 Subject: [PATCH 03/21] redirect instead of showing siblings of child --- src/components/theme/View/RedirectView.jsx | 53 ++++++++++++++++++++++ src/localconfig.js | 11 ++--- 2 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/components/theme/View/RedirectView.jsx diff --git a/src/components/theme/View/RedirectView.jsx b/src/components/theme/View/RedirectView.jsx new file mode 100644 index 00000000..5ff1539c --- /dev/null +++ b/src/components/theme/View/RedirectView.jsx @@ -0,0 +1,53 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Redirect } from 'react-router-dom'; + +class RedirectView extends Component { + constructor(props) { + super(props); + this.state = { + redirect: false, + }; + console.log('topicsView'); + } + static propTypes = { + content: PropTypes.shape({ + title: PropTypes.string, + + description: PropTypes.string, + + items: PropTypes.arrayOf( + PropTypes.shape({ + title: PropTypes.string, + description: PropTypes.string, + url: PropTypes.string, + image: PropTypes.object, + image_caption: PropTypes.string, + '@type': PropTypes.string, + }), + ), + }).isRequired, + }; + + componentDidMount() { + const mainItem = this.props.content.items[0]; + const mainUrl = mainItem && mainItem.url; + console.log('mainitem,mainurl', mainItem, mainUrl); + if (__CLIENT__ && mainUrl && window) { + this.setState({ redirect: mainUrl }); + } else { + this.setState({ redirect: false }); + } + } + + render() { + console.log('redirect state', this.state.redirect); + if (this.state.redirect) { + return ; + } else { + return ''; + } + } +} + +export default RedirectView; \ No newline at end of file diff --git a/src/localconfig.js b/src/localconfig.js index 77702e73..eaa28481 100644 --- a/src/localconfig.js +++ b/src/localconfig.js @@ -1,5 +1,6 @@ import TabsView from '~/components/theme/View/TabsView'; +import RedirectView from '~/components/theme/View/RedirectView'; import ChildrenListView from '~/components/manage/Blocks/DetailedLink/View'; import ChildrenListEdit from '~/components/manage/Blocks/DetailedLink/Edit'; @@ -11,6 +12,8 @@ console.log('config', config) layoutViews: { ...config.views.layoutViews, tabs_view: TabsView, + redirect_view: RedirectView, + }, }; @@ -25,14 +28,6 @@ console.log('config', config) icon: config.blocks.blocksConfig.text.icon, }; - // config.blocks.blocksConfig.collection_block = { - // id: 'collection_block', - // title: 'Collection Listing', - // view: CollectionBlockView, - // edit: CollectionBlockEdit, - // icon: chartIcon, - // group: 'custom_addons', - // }; return config; } From 9007502a120dddbecd28853f777fb4ed85fcb04a Mon Sep 17 00:00:00 2001 From: Mihai Macaneata Date: Wed, 8 Apr 2020 11:27:42 +0300 Subject: [PATCH 04/21] section tabs --- src/actions/index.js | 11 +++++++++ src/components/theme/View/TabsView.jsx | 33 +++++--------------------- src/constants/ActionTypes.js | 1 + src/reducers/section_tabs.js | 28 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 src/reducers/section_tabs.js diff --git a/src/actions/index.js b/src/actions/index.js index e69de29b..e1b6a35e 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -0,0 +1,11 @@ +import { + SET_SECTION_TABS, +} from '~/constants/ActionTypes'; + + +export function setSectionTabs(payload) { + return { + type: SET_SECTION_TABS, + payload: payload, + }; +} \ No newline at end of file diff --git a/src/components/theme/View/TabsView.jsx b/src/components/theme/View/TabsView.jsx index 280d428e..0a9cccec 100644 --- a/src/components/theme/View/TabsView.jsx +++ b/src/components/theme/View/TabsView.jsx @@ -20,7 +20,10 @@ import { hasBlocksData, } from '@plone/volto/helpers'; import { flattenToAppURL } from '@plone/volto/helpers'; - +import { setSectionTabs } from '~/actions'; +const mapDispatchToProps = { + setSectionTabs, +}; const messages = defineMessages({ unknownBlock: { id: 'Unknown Block', @@ -77,6 +80,7 @@ class DefaultView extends Component { title: i.name, }; }); + this.props.setSectionTabs(tabsItems); return tabsItems; }; @@ -87,31 +91,6 @@ class DefaultView extends Component { const blocksLayoutFieldname = getBlocksLayoutFieldname(content); const tabs = this.computeFolderTabs(content['@components'].siblings); - // const currentUrl = this.props.content?.['@id']; - // const shouldRenderRoutes = - // typeof currentUrl !== 'undefined' && - // samePath(currentUrl, this.props.pathname) - // ? true - // : false; - // - // if (shouldRenderRoutes === false) - // return ( - //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- // ); - return ( hasBlocksData(content) && (
@@ -167,5 +146,5 @@ export default compose( pathname: props.location.pathname, content: state.prefetch?.[state.router.location.pathname] || state.content.data, - })), + }),mapDispatchToProps), )(DefaultView); \ No newline at end of file diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js index e69de29b..6c20dcc2 100644 --- a/src/constants/ActionTypes.js +++ b/src/constants/ActionTypes.js @@ -0,0 +1 @@ +export const SET_SECTION_TABS = 'SET_SECTION_TABS'; \ No newline at end of file diff --git a/src/reducers/section_tabs.js b/src/reducers/section_tabs.js new file mode 100644 index 00000000..032dbb05 --- /dev/null +++ b/src/reducers/section_tabs.js @@ -0,0 +1,28 @@ +import { SET_SECTION_TABS } from '~/constants/ActionTypes'; + +const initialState = { + error: null, + items: null, + loaded: false, + loading: false, +}; + +/** + * Navigation reducer. + * @function navigation + * @param {Object} state Current state. + * @param {Object} action Action to be handled. + * @returns {Object} New state. + */ +export default function section_tabs(state = initialState, action = {}) { + if (action.type === SET_SECTION_TABS) { + return { + ...state, + error: null, + items: action.payload, + loaded: true, + loading: false, + }; + } + return state; +} \ No newline at end of file From 688a2c59183d79bd82e5beb53f5a9fa203f865d6 Mon Sep 17 00:00:00 2001 From: Mihai Macaneata Date: Wed, 8 Apr 2020 11:34:48 +0300 Subject: [PATCH 05/21] 2nd level of tabs --- src/components/theme/View/TabsChildView.jsx | 147 ++++++++++++++++++++ src/localconfig.js | 3 + 2 files changed, 150 insertions(+) create mode 100644 src/components/theme/View/TabsChildView.jsx diff --git a/src/components/theme/View/TabsChildView.jsx b/src/components/theme/View/TabsChildView.jsx new file mode 100644 index 00000000..51abc713 --- /dev/null +++ b/src/components/theme/View/TabsChildView.jsx @@ -0,0 +1,147 @@ +/** + * Document view component. + * @module components/theme/View/DefaultView + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Helmet } from '@plone/volto/helpers'; +import { compose } from 'redux'; +import { defineMessages, injectIntl } from 'react-intl'; + +import { map } from 'lodash'; +import { connect } from 'react-redux'; +import { Link } from 'react-router-dom'; + +import { blocks } from '~/config'; +import { + getBlocksFieldname, + getBlocksLayoutFieldname, + hasBlocksData, +} from '@plone/volto/helpers'; +import { flattenToAppURL } from '@plone/volto/helpers'; + +const messages = defineMessages({ + unknownBlock: { + id: 'Unknown Block', + defaultMessage: 'Unknown Block {block}', + }, +}); + +class DefaultView extends Component { + constructor(props) { + super(props); + // this.renderTabs = this.renderTabs.bind(this); + this.state = { + tabs: null, + }; + } + + static defaultProps = { + parent: null, + }; + static propTypes = { + tabs: PropTypes.array, + content: PropTypes.shape({ + title: PropTypes.string, + description: PropTypes.string, + text: PropTypes.shape({ + data: PropTypes.string, + }), + }).isRequired, + localNavigation: PropTypes.any, + }; + + // componentWillReceiveProps(nextProps) { + // console.log('herere', nextProps.parent, this.props.parent); + // if (nextProps.parent && nextProps.parent.id !== this.props.parent?.id) { + + // const pathArr = nextProps.location.pathname.split('/'); + // pathArr.length = 4; + // const path = pathArr.join('/'); + // const tabsItems = nextProps.parent.items.map(i => { + // return { + // url: `${path}/${i.id}`, + // title: i.title, + // '@type': i['@type'], + // }; + // }); + // this.props.setFolderTabs(tabsItems); + // } + // } + + computeFolderTabs = siblings => { + const tabsItems = siblings && siblings.items.map(i => { + return { + url: flattenToAppURL(i.url), + title: i.name, + }; + }); + return tabsItems; + }; + + render() { + const content = this.props.content; + const intl = this.props.intl; + const blocksFieldname = getBlocksFieldname(content); + const blocksLayoutFieldname = getBlocksLayoutFieldname(content); + const tabs = this.computeFolderTabs(content['@components'].siblings); + console.log('section tabs', this.props.sectionTabs) + return ( + hasBlocksData(content) && ( +
+ {tabs && tabs.length ? ( + + ) : ( + '' + )} + + {map(content[blocksLayoutFieldname].items, block => { + const Block = + blocks.blocksConfig[ + (content[blocksFieldname]?.[block]?.['@type']) + ]?.['view'] || null; + return Block !== null ? ( + + ) : ( +
+ {intl.formatMessage(messages.unknownBlock, { + block: content[blocksFieldname]?.[block]?.['@type'], + })} +
+ ); + })} +
+ ) + ); + } +} + +export default compose( + injectIntl, + connect((state, props) => ({ + pathname: props.location.pathname, + content: + state.prefetch?.[state.router.location.pathname] || state.content.data, + sectionTabs: state.section_tabs + }),mapDispatchToProps), +)(DefaultView); \ No newline at end of file diff --git a/src/localconfig.js b/src/localconfig.js index eaa28481..e67ccb7c 100644 --- a/src/localconfig.js +++ b/src/localconfig.js @@ -1,6 +1,7 @@ import TabsView from '~/components/theme/View/TabsView'; import RedirectView from '~/components/theme/View/RedirectView'; +import TabsChildView from '~/components/theme/View/TabsChildView'; import ChildrenListView from '~/components/manage/Blocks/DetailedLink/View'; import ChildrenListEdit from '~/components/manage/Blocks/DetailedLink/Edit'; @@ -12,6 +13,8 @@ console.log('config', config) layoutViews: { ...config.views.layoutViews, tabs_view: TabsView, + tabs_child_view: TabsChildView, + redirect_view: RedirectView, }, From e8263de32b418966727d1dca273f67c5ba52b625 Mon Sep 17 00:00:00 2001 From: Mihai Macaneata Date: Wed, 8 Apr 2020 12:40:54 +0300 Subject: [PATCH 06/21] fix siblings by redirecting again to 3rd level --- src/actions/index.js | 1 + src/components/theme/View/TabsChildView.jsx | 90 ++++++++++++------- src/components/theme/View/TabsView.jsx | 34 ++++--- src/reducers/index.js | 2 + src/reducers/section_tabs.js | 5 +- theme/themes/pastanaga/globals/site.overrides | 53 +++++++++++ 6 files changed, 136 insertions(+), 49 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index e1b6a35e..e56facb9 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -4,6 +4,7 @@ import { export function setSectionTabs(payload) { + console.log('in action', payload) return { type: SET_SECTION_TABS, payload: payload, diff --git a/src/components/theme/View/TabsChildView.jsx b/src/components/theme/View/TabsChildView.jsx index 51abc713..d2c8b4e9 100644 --- a/src/components/theme/View/TabsChildView.jsx +++ b/src/components/theme/View/TabsChildView.jsx @@ -86,13 +86,17 @@ class DefaultView extends Component { const blocksFieldname = getBlocksFieldname(content); const blocksLayoutFieldname = getBlocksLayoutFieldname(content); const tabs = this.computeFolderTabs(content['@components'].siblings); - console.log('section tabs', this.props.sectionTabs) + const sectionTabs = this.props.sectionTabs?.items + console.log('sectionTabs in child', sectionTabs) + return ( hasBlocksData(content) && ( -
- {tabs && tabs.length ? ( -