diff --git a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx index 17177dac..b14c557f 100644 --- a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx +++ b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx @@ -151,9 +151,8 @@ const OpenlayersMapView = (props) => { ToggleSidebarControl.current = /*@__PURE__*/ (function (Control) { function ToggleSidebarControl(opt_options) { const options = opt_options || {}; - const buttonContainer = document.getElementById( - 'dynamic-filter-toggle', - ); + const buttonContainer = document.createElement('div'); + buttonContainer.setAttribute('id', 'map-sidebar-button'); Control.call(this, { element: buttonContainer, target: options.target, @@ -577,8 +576,10 @@ const OpenlayersMapView = (props) => { } // Make dynamic filters overlay if (hasSidebar) { + const sideBar = document.createElement('div'); + sideBar.setAttribute('id', 'map-sidebar'); dynamicFilters = makeOverlay( - document.getElementById(`dynamic-filter`), + sideBar, 'ol-dynamic-filter', 'center-center', true, diff --git a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/style.css b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/style.css index 5c20bf7e..cdb4659f 100644 --- a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/style.css +++ b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/style.css @@ -11,6 +11,10 @@ height: 90%; } +#map-sidebar { + height: 100%; +} + #map-loader.ui.active { position: absolute !important; pointer-events: auto !important; diff --git a/src/components/manage/Blocks/FiltersBlock/View.jsx b/src/components/manage/Blocks/FiltersBlock/View.jsx index 544fe831..422560de 100644 --- a/src/components/manage/Blocks/FiltersBlock/View.jsx +++ b/src/components/manage/Blocks/FiltersBlock/View.jsx @@ -13,6 +13,7 @@ import { Radio, List, } from 'semantic-ui-react'; +import { Portal } from 'react-portal'; import { Icon } from '@plone/volto/components'; import { setQueryParam } from 'volto-datablocks/actions'; import { settings } from '~/config'; @@ -54,6 +55,7 @@ const View = ({ content, ...props }) => { firstLoad: false, searchResultsActive: false, }); + const [loadingData, setLoadingData] = useState(false); const [factsData, setFactsData] = useState({}); const [alphaFeature, setAlphaFeature] = useState({}); const [sitesResults, setSitesResults] = useState([]); @@ -65,6 +67,12 @@ const View = ({ content, ...props }) => { const modalButtonTitle = props.data.modalButtonTitle?.value; const searchButtonTitle = props.data.searchButtonTitle?.value; const locationResultsTexts = locationResults.map((result) => result.text); + const mapSidebarExists = document?.getElementById('map-sidebar'); + + useEffect(() => { + console.log(mapSidebarExists); + /* eslint-disable-next-line */ + }, [mapSidebarExists]) const searchResults = [ ...sitesResults.slice( 0, @@ -150,17 +158,16 @@ const View = ({ content, ...props }) => { useEffect(function () { setState({ ...state, mounted: true }); updateFactsData(true); - document - .getElementById(`dynamic-filter`) - .addEventListener('featurechange', (e) => { - if ( - JSON.stringify(e.detail.features?.[0]?.getProperties?.()?.country) !== - JSON.stringify(alphaFeature?.getProperties?.()?.country) - ) { - setAlphaFeature(e.detail.features?.[0]); - } - }); - + // document + // .getElementById(`dynamic-filter`) + // .addEventListener('featurechange', (e) => { + // if ( + // JSON.stringify(e.detail.features?.[0]?.getProperties?.()?.country) !== + // JSON.stringify(alphaFeature?.getProperties?.()?.country) + // ) { + // setAlphaFeature(e.detail.features?.[0]); + // } + // }); return () => { setState({ ...state, mounted: false }); }; @@ -190,7 +197,7 @@ const View = ({ content, ...props }) => { }, [state]); useEffect(() => { - if (state.mounted) { + if (state.mounted && __CLIENT__) { let promises = []; let metadata = []; const siteCountryFilters = @@ -426,20 +433,23 @@ const View = ({ content, ...props }) => { }, ], }; - onMountRequests.sqls.forEach((sql, index) => { - if (sql && onMountRequests.meta[index]) { - if (!state.firstLoad) { + if (!loadingData) { + onMountRequests.sqls.forEach((sql, index) => { + if (sql && onMountRequests.meta[index]) { + if (!state.firstLoad) { + promises.push(axios.get(makeUrl(providerUrl, sql))); + metadata.push(onMountRequests.meta[index]); + } + } + }); + dynamicRequests.sqls.forEach((sql, index) => { + if (sql && dynamicRequests.meta[index]) { promises.push(axios.get(makeUrl(providerUrl, sql))); - metadata.push(onMountRequests.meta[index]); + metadata.push(dynamicRequests.meta[index]); } - } - }); - dynamicRequests.sqls.forEach((sql, index) => { - if (sql && dynamicRequests.meta[index]) { - promises.push(axios.get(makeUrl(providerUrl, sql))); - metadata.push(dynamicRequests.meta[index]); - } - }); + }); + setLoadingData(true); + } Promise.all(promises) .then((response) => { if (state.mounted) { @@ -480,6 +490,7 @@ const View = ({ content, ...props }) => { ], }; }); + setLoadingData(false); setState({ ...state, filtersMeta, @@ -487,7 +498,13 @@ const View = ({ content, ...props }) => { }); } }) - .catch((error) => {}); + .catch((error) => { + setLoadingData(false); + setState({ + ...state, + ...(state.firstLoad === false ? { firstLoad: true } : {}), + }); + }); } /* eslint-disable-next-line */ }, [ @@ -945,81 +962,96 @@ const View = ({ content, ...props }) => { {searchButtonTitle ? searchButtonTitle : 'Search'} -
- -
-
-
-
Dynamic filter
-
-
-
Reporting year
-
- { - changeFilter(data, state.filtersMeta['industries'], 0, true); - }} - placeholder={state.filtersMeta['industries']?.placeholder} - options={state.filtersMeta['industries']?.options} - value={state.filters['EEASector']?.[0]} - /> -
-
-
+ +
-
Quick facts
- {state.factsDataOrder && - state.factsDataOrder.map((key) => { - return factsData[key] ? ( - - {factsData[key]?.title && ( -
{factsData[key].title}
- )} - {factsData[key]?.description && - factsData[key].description.map((description, index) => { - return

{description}

; - })} -
- ) : ( - '' - ); - })}
-
+ + {mapSidebarExists ? ( + +
+
+
Dynamic filter
+
+
+
Reporting year
+
+ { + changeFilter( + data, + state.filtersMeta['industries'], + 0, + true, + ); + }} + placeholder={state.filtersMeta['industries']?.placeholder} + options={state.filtersMeta['industries']?.options} + value={state.filters['EEASector']?.[0]} + /> +
+
+
+ +
Quick facts
+ {state.factsDataOrder && + state.factsDataOrder.map((key) => { + return factsData[key] ? ( + + {factsData[key]?.title && ( +
{factsData[key].title}
+ )} + {factsData[key]?.description && + factsData[key].description.map((description, index) => { + return

{description}

; + })} +
+ ) : ( + '' + ); + })} +
+
+
+ ) : ( + '' + )}
); }; diff --git a/src/components/manage/Blocks/LinkButton/Edit.jsx b/src/components/manage/Blocks/LinkButton/Edit.jsx new file mode 100644 index 00000000..a1f6b6c8 --- /dev/null +++ b/src/components/manage/Blocks/LinkButton/Edit.jsx @@ -0,0 +1,74 @@ +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import _uniqueId from 'lodash/uniqueId'; +import RenderFields from 'volto-addons/Widgets/RenderFields'; +import View from './View'; +import { settings } from '~/config'; + +const getSchema = (props) => { + return { + component: { + title: 'Title component type', + type: 'array', + choices: [ + ['h1', 'H1'], + ['h2', 'H2'], + ['h3', 'H3'], + ['p', 'Paragraph'], + ], + }, + title: { + title: 'Title', + type: 'text', + }, + description: { + title: 'Description', + type: 'text', + }, + linkText: { + title: 'Link text', + type: 'text', + }, + internalLink: { + title: 'Internal link', + widget: 'object_by_path', + }, + outsideLink: { + title: 'Outside link', + type: 'text', + }, + linkType: { + title: 'Use', + type: 'array', + choices: [ + ['internalLink', 'Internal link'], + ['outsideLink', 'Outside link'], + ], + }, + }; +}; + +const Edit = (props) => { + const [state, setState] = useState({ + schema: getSchema({ ...props, providerUrl: settings.providerUrl }), + id: _uniqueId('block_'), + }); + return ( +
+ + +
+ ); +}; + +export default compose( + connect((state, props) => ({ + pathname: state.router.location.pathname, + })), +)(Edit); diff --git a/src/components/manage/Blocks/LinkButton/View.jsx b/src/components/manage/Blocks/LinkButton/View.jsx new file mode 100644 index 00000000..d46cc65f --- /dev/null +++ b/src/components/manage/Blocks/LinkButton/View.jsx @@ -0,0 +1,58 @@ +/* REACT */ +import React, { useState } from 'react'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { useHistory } from 'react-router-dom'; + +const View = ({ content, ...props }) => { + const history = useHistory(); + const { data } = props; + const { + queryParam = '', + leftText = '', + rightText = '', + className = '', + inlineStyle = '', + page = '', + } = data; + const queryText = props.search[queryParam]; + + const text = `${leftText} ${queryText} ${rightText}`; + + let parsedInlineStyle; + try { + parsedInlineStyle = JSON.parse(inlineStyle); + } catch { + parsedInlineStyle = {}; + } + + return ( +
+ {props.mode === 'edit' ? !queryText ?

Query param button

: '' : ''} + {queryText ? ( + + ) : ( + '' + )} +
+ ); +}; + +export default compose( + connect((state, props) => ({ + query: state.router.location.search, + content: + state.prefetch?.[state.router.location.pathname] || state.content.data, + search: state.discodata_query.search, + })), +)(View); diff --git a/src/components/manage/Blocks/QueryParamButton/Edit.jsx b/src/components/manage/Blocks/QueryParamButton/Edit.jsx index a29bd239..36fce7da 100644 --- a/src/components/manage/Blocks/QueryParamButton/Edit.jsx +++ b/src/components/manage/Blocks/QueryParamButton/Edit.jsx @@ -45,7 +45,7 @@ const Edit = (props) => { diff --git a/src/components/manage/Blocks/QueryParamText/Edit.jsx b/src/components/manage/Blocks/QueryParamText/Edit.jsx index d772cf6a..71d6a738 100644 --- a/src/components/manage/Blocks/QueryParamText/Edit.jsx +++ b/src/components/manage/Blocks/QueryParamText/Edit.jsx @@ -47,7 +47,7 @@ const Edit = (props) => { diff --git a/src/components/theme/View/DefaultView.jsx b/src/components/theme/View/DefaultView.jsx index 303f0ba1..ba56ad65 100644 --- a/src/components/theme/View/DefaultView.jsx +++ b/src/components/theme/View/DefaultView.jsx @@ -35,7 +35,13 @@ const messages = defineMessages({ const DefaultView = ({ content, intl, location }) => { const blocksFieldname = getBlocksFieldname(content); const blocksLayoutFieldname = getBlocksLayoutFieldname(content); - + React.useEffect(() => { + console.log('MOUNTING DEFAULT VIEW'); + return () => { + console.log('UNMOUNTING DEFAULT VIEW'); + }; + /* eslint-disable-next-line */ + }, []) return hasBlocksData(content) ? (
{map(content[blocksLayoutFieldname].items, (block) => { diff --git a/src/components/theme/View/RedirectView.jsx b/src/components/theme/View/RedirectView.jsx index 8681d601..8272d645 100644 --- a/src/components/theme/View/RedirectView.jsx +++ b/src/components/theme/View/RedirectView.jsx @@ -16,7 +16,7 @@ const RedirectView = (props) => { setMounted(true); /* eslint-disable-next-line */ }, []) - if (mounted && !redirect) { + if (mounted && !redirect && !props.navigation.loading) { if (redirectPage) { const currentPath = getBasePath(currentPage); const redirectPath = getBasePath(redirectPage); @@ -28,7 +28,7 @@ const RedirectView = (props) => { } return (
- +

Redirecting...

); }; @@ -36,4 +36,5 @@ const RedirectView = (props) => { export default connect((state, props) => ({ content: state.prefetch?.[state.router.location.pathname] || state.content.data, + navigation: state.navigation, }))(RedirectView); diff --git a/src/customizations/volto/components/theme/App/App.jsx b/src/customizations/volto/components/theme/App/App.jsx new file mode 100644 index 00000000..0d677a50 --- /dev/null +++ b/src/customizations/volto/components/theme/App/App.jsx @@ -0,0 +1,238 @@ +/** + * App container. + * @module components/theme/App/App + */ + +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { asyncConnect } from 'redux-connect'; +import { Segment } from 'semantic-ui-react'; +import { renderRoutes } from 'react-router-config'; +import { Slide, ToastContainer, toast } from 'react-toastify'; +import split from 'lodash/split'; +import join from 'lodash/join'; +import trim from 'lodash/trim'; +import cx from 'classnames'; +import loadable from '@loadable/component'; + +import { settings, views } from '~/config'; + +import Error from '@plone/volto/error'; + +import { + Breadcrumbs, + Footer, + Header, + Icon, + OutdatedBrowser, + AppExtras, +} from '@plone/volto/components'; +import { BodyClass, getBaseUrl, getView, isCmsUi } from '@plone/volto/helpers'; +import { + getBreadcrumbs, + getContent, + getNavigation, + getTypes, + getWorkflow, +} from '@plone/volto/actions'; + +import clearSVG from '@plone/volto/icons/clear.svg'; +import MultilingualRedirector from '../MultilingualRedirector/MultilingualRedirector'; + +const RenderRoutes = React.memo(({ routes }) => { + return renderRoutes(routes); +}); + +/** + * @export + * @class App + * @extends {Component} + */ +class App extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + pathname: PropTypes.string.isRequired, + }; + + state = { + hasError: false, + error: null, + errorInfo: null, + }; + + /** + * ComponentDidMount + * @method ComponentDidMount + * @param {string} error The error + * @param {string} info The info + * @returns {undefined} + */ + componentDidMount() { + if (__CLIENT__ && process.env.SENTRY_DSN) { + const Raven = loadable(() => import('raven-js')); + Raven.config(process.env.SENTRY_DSN).install(); + } + } + + /** + * @method componentWillReceiveProps + * @param {Object} nextProps Next properties + * @returns {undefined} + */ + UNSAFE_componentWillReceiveProps(nextProps) { + if (nextProps.pathname !== this.props.pathname) { + if (this.state.hasError) { + this.setState({ hasError: false }); + } + } + } + + /** + * ComponentDidCatch + * @method ComponentDidCatch + * @param {string} error The error + * @param {string} info The info + * @returns {undefined} + */ + componentDidCatch(error, info) { + this.setState({ hasError: true, error, errorInfo: info }); + if (__CLIENT__ && process.env.SENTRY_DSN) { + const Raven = loadable(() => import('raven-js')); + Raven.captureException(error, { extra: info }); + } + } + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { + const path = getBaseUrl(this.props.pathname); + const action = getView(this.props.pathname); + const isCmsUI = isCmsUi(this.props.pathname); + const ConnectionRefusedView = views.errorViews.ECONNREFUSED; + + return ( + + + + {/* Body class depending on content type */} + {this.props.content && this.props.content['@type'] && ( + + )} + + {/* Body class depending on sections */} + +
+ + + +
+ + {this.props.connectionRefused ? ( + + ) : this.state.hasError ? ( + + ) : ( + + )} +
+
+
+