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'}
-
{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 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ }
+ />
+
+
+ );
+ }
+}
+
+export const __test__ = connect(
+ (state, props) => ({
+ pathname: props.location.pathname,
+ token: state.userSession.token,
+ content: state.content.data,
+ apiError: state.apierror.error,
+ connectionRefused: state.apierror.connectionRefused,
+ }),
+ {},
+)(App);
+
+export default compose(
+ asyncConnect([
+ {
+ key: 'breadcrumbs',
+ promise: ({ location, store: { dispatch } }) =>
+ __SERVER__ && dispatch(getBreadcrumbs(getBaseUrl(location.pathname))),
+ },
+ {
+ key: 'content',
+ promise: ({ location, store: { dispatch } }) =>
+ __SERVER__ && dispatch(getContent(getBaseUrl(location.pathname))),
+ },
+ {
+ key: 'navigation',
+ promise: ({ location, store: { dispatch } }) =>
+ __SERVER__ &&
+ dispatch(
+ getNavigation(getBaseUrl(location.pathname), settings.navDepth),
+ ),
+ },
+ {
+ key: 'types',
+ promise: ({ location, store: { dispatch } }) =>
+ __SERVER__ && dispatch(getTypes(getBaseUrl(location.pathname))),
+ },
+ {
+ key: 'workflow',
+ promise: ({ location, store: { dispatch } }) =>
+ __SERVER__ && dispatch(getWorkflow(getBaseUrl(location.pathname))),
+ },
+ ]),
+ connect(
+ (state, props) => ({
+ pathname: props.location.pathname,
+ token: state.userSession.token,
+ content: state.content.data,
+ apiError: state.apierror.error,
+ connectionRefused: state.apierror.connectionRefused,
+ }),
+ {},
+ ),
+)(App);
diff --git a/src/customizations/volto/components/theme/View/View.jsx b/src/customizations/volto/components/theme/View/View.jsx
new file mode 100644
index 00000000..4f16224e
--- /dev/null
+++ b/src/customizations/volto/components/theme/View/View.jsx
@@ -0,0 +1,312 @@
+/**
+ * View container.
+ * @module components/theme/View/View
+ */
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { compose } from 'redux';
+import { Portal } from 'react-portal';
+import { injectIntl } from 'react-intl';
+import { Helmet } from '@plone/volto/helpers';
+import qs from 'query-string';
+import { views } from '~/config';
+
+import { Comments, Tags, Toolbar } from '@plone/volto/components';
+import { listActions, getContent } from '@plone/volto/actions';
+import {
+ BodyClass,
+ getBaseUrl,
+ getLayoutFieldname,
+} from '@plone/volto/helpers';
+import { Dimmer, Loader } from 'semantic-ui-react';
+import { getBasePath } from '~/helpers';
+
+/**
+ * View container class.
+ * @class View
+ * @extends Component
+ */
+class View extends Component {
+ /**
+ * Property types.
+ * @property {Object} propTypes Property types.
+ * @static
+ */
+ static propTypes = {
+ actions: PropTypes.shape({
+ object: PropTypes.arrayOf(PropTypes.object),
+ object_buttons: PropTypes.arrayOf(PropTypes.object),
+ user: PropTypes.arrayOf(PropTypes.object),
+ }),
+ listActions: PropTypes.func.isRequired,
+ /**
+ * Action to get the content
+ */
+ getContent: PropTypes.func.isRequired,
+ /**
+ * Pathname of the object
+ */
+ pathname: PropTypes.string.isRequired,
+ location: PropTypes.shape({
+ search: PropTypes.string,
+ pathname: PropTypes.string,
+ }).isRequired,
+ /**
+ * Version id of the object
+ */
+ versionId: PropTypes.string,
+ /**
+ * Content of the object
+ */
+ content: PropTypes.shape({
+ /**
+ * Layout of the object
+ */
+ layout: PropTypes.string,
+ /**
+ * Allow discussion of the object
+ */
+ allow_discussion: PropTypes.bool,
+ /**
+ * Title of the object
+ */
+ title: PropTypes.string,
+ /**
+ * Description of the object
+ */
+ description: PropTypes.string,
+ /**
+ * Type of the object
+ */
+ '@type': PropTypes.string,
+ /**
+ * Subjects of the object
+ */
+ subjects: PropTypes.arrayOf(PropTypes.string),
+ is_folderish: PropTypes.bool,
+ }),
+ error: PropTypes.shape({
+ /**
+ * Error type
+ */
+ status: PropTypes.number,
+ }),
+ };
+
+ /**
+ * Default properties.
+ * @property {Object} defaultProps Default properties.
+ * @static
+ */
+ static defaultProps = {
+ actions: null,
+ content: null,
+ versionId: null,
+ error: null,
+ };
+
+ state = {
+ hasObjectButtons: null,
+ isClient: false,
+ RenderedView: null,
+ };
+
+ /**
+ * Component will mount
+ * @method componentWillMount
+ * @returns {undefined}
+ */
+ UNSAFE_componentWillMount() {
+ this.props.listActions(getBaseUrl(this.props.pathname));
+ this.props.getContent(
+ getBaseUrl(this.props.pathname),
+ this.props.versionId,
+ );
+ }
+
+ componentDidMount() {
+ const RenderedView = this.getRenderedView();
+ this.setState({ isClient: true, RenderedView });
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.content['@id'] !== this.props.content['@id']) {
+ const RenderedView = this.getRenderedView();
+ this.setState({ RenderedView });
+ }
+ }
+
+ /**
+ * Component will receive props
+ * @method componentWillReceiveProps
+ * @param {Object} nextProps Next properties
+ * @returns {undefined}
+ */
+ UNSAFE_componentWillReceiveProps(nextProps) {
+ if (nextProps.pathname !== this.props.pathname) {
+ this.props.listActions(getBaseUrl(nextProps.pathname));
+ this.props.getContent(
+ getBaseUrl(nextProps.pathname),
+ this.props.versionId,
+ );
+ }
+
+ if (nextProps.actions.object_buttons) {
+ const objectButtons = nextProps.actions.object_buttons;
+ this.setState({
+ hasObjectButtons: !!objectButtons.length,
+ });
+ }
+ }
+
+ /**
+ * Default fallback view
+ * @method getViewDefault
+ * @returns {string} Markup for component.
+ */
+ getViewDefault = () => views.defaultView;
+
+ /**
+ * Get view by content type
+ * @method getViewByType
+ * @returns {string} Markup for component.
+ */
+ getViewByType = () =>
+ views.contentTypesViews[this.props.content['@type']] || null;
+
+ /**
+ * Get view by content layout property
+ * @method getViewByLayout
+ * @returns {string} Markup for component.
+ */
+ getViewByLayout = () =>
+ views.layoutViews[
+ this.props.content[getLayoutFieldname(this.props.content)]
+ ] || null;
+
+ getRenderedView = () =>
+ getBasePath(this.props.content['@id']) === this.props.pathname
+ ? this.getViewByType() || this.getViewByLayout() || this.getViewDefault()
+ : null;
+
+ /**
+ * Cleans the component displayName (specially for connected components)
+ * which have the Connect(componentDisplayName)
+ * @method cleanViewName
+ * @param {string} dirtyDisplayName The displayName
+ * @returns {string} Clean displayName (no Connect(...)).
+ */
+ cleanViewName = (dirtyDisplayName) =>
+ dirtyDisplayName
+ .replace('Connect(', '')
+ .replace('injectIntl(', '')
+ .replace(')', '')
+ .replace('connect(', '')
+ .toLowerCase();
+
+ /**
+ * Render method.
+ * @method render
+ * @returns {string} Markup for the component.
+ */
+ render() {
+ if (this.props.error && !this.props.connectionRefused) {
+ let FoundView;
+ if (this.props.error.status === undefined) {
+ // For some reason, while development and if CORS is in place and the
+ // requested resource is 404, it returns undefined as status, then the
+ // next statement will fail
+ FoundView = views.errorViews.corsError;
+ } else {
+ FoundView = views.errorViews[this.props.error.status.toString()];
+ }
+ if (!FoundView) {
+ FoundView = views.errorViews['404']; // default to 404
+ }
+ return (
+
+
+
+ );
+ }
+ if (!this.props.content) {
+ return
;
+ }
+ const RenderedView = this.state.RenderedView;
+
+ return (
+
+
+ {this.props.content.language && (
+
+ )}
+
{this.props.content.title}
+
+
+ {/* Body class if displayName in component is set */}
+
+ {RenderedView ? (
+
+ ) : (
+
+ European Environment Agency
+
+ )}
+ {this.props.content.subjects &&
+ this.props.content.subjects.length > 0 && (
+
+ )}
+ {/* Add opt-in social sharing if required, disabled by default */}
+ {/* In the future this might be parameterized from the app config */}
+ {/*
*/}
+ {this.props.content.allow_discussion && (
+
+ )}
+ {this.state.isClient && (
+
+ } />
+
+ )}
+
+ );
+ }
+}
+
+export default compose(
+ injectIntl,
+ connect(
+ (state, props) => ({
+ actions: state.actions.actions,
+ token: state.userSession.token,
+ content: state.content.data,
+ error: state.content.get.error,
+ apiError: state.apierror.error,
+ connectionRefused: state.apierror.connectionRefused,
+ pathname: props.location.pathname,
+ versionId:
+ qs.parse(props.location.search) &&
+ qs.parse(props.location.search).version_id,
+ }),
+ {
+ listActions,
+ getContent,
+ },
+ ),
+)(View);
diff --git a/src/localconfig.js b/src/localconfig.js
index ddd30969..b21f63d9 100644
--- a/src/localconfig.js
+++ b/src/localconfig.js
@@ -34,6 +34,9 @@ import QueryParamTextView from '~/components/manage/Blocks/QueryParamText/View';
import QueryParamButtonEdit from '~/components/manage/Blocks/QueryParamButton/Edit';
import QueryParamButtonView from '~/components/manage/Blocks/QueryParamButton/View';
+// import QueryParamButtonEdit from '~/components/manage/Blocks/LinkButton/Edit';
+// import QueryParamButtonView from '~/components/manage/Blocks/QueryParamButton/View';
+
import BlocksWidget from '~/components/manage/Widgets/BlocksWidget';
import { addCustomGroup } from '~/helpers';
diff --git a/theme/site/globals/site.overrides b/theme/site/globals/site.overrides
index b4185ea5..29b0e5ec 100644
--- a/theme/site/globals/site.overrides
+++ b/theme/site/globals/site.overrides
@@ -991,6 +991,8 @@ button.solid,
a.solid {
margin: 0;
+ max-width: unset;
+ width: fit-content;
}
button.outline,
@@ -1358,4 +1360,8 @@ h3 {
.documentFirstHeading {
display: none;
}
+}
+
+.ui.inverted.dimmer {
+ background-color: transparent !important;
}
\ No newline at end of file