diff --git a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx index ce18a00a..1dcd93dd 100644 --- a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx +++ b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx @@ -1185,15 +1185,17 @@ const OpenlayersMapView = (props) => { - {state.popupDetails.properties.nLCP || 0} Large - combustion plants + {state.popupDetails.properties.nInstallations || 0}{' '} + Installations

@@ -1202,17 +1204,15 @@ const OpenlayersMapView = (props) => { - {state.popupDetails.properties.nInstallations || 0}{' '} - Installations + {state.popupDetails.properties.nLCP || 0} Large + combustion plants

diff --git a/src/components/manage/Blocks/GlossarySearchBlock/Edit.jsx b/src/components/manage/Blocks/GlossarySearchBlock/Edit.jsx new file mode 100644 index 00000000..8097f625 --- /dev/null +++ b/src/components/manage/Blocks/GlossarySearchBlock/Edit.jsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { injectIntl } from 'react-intl'; +import View from './View'; + +import RenderFields from 'volto-addons/Widgets/RenderFields'; + +const schema = { + title: { + title: 'Title', + type: 'text', + }, + description: { + title: 'Description', + type: 'text', + }, + placeholder: { + title: 'Placeholder', + type: 'text', + }, + searchButton: { + title: 'Search button', + type: 'boolean', + }, + buttonText: { + title: 'Button text', + type: 'text', + requires: 'searchButton', + }, + className: { + title: 'Class name', + type: 'text', + }, + buttonClassName: { + title: 'Button class name', + type: 'text', + requires: 'searchButton', + }, + query: { + title: 'Query parameters', + type: 'schema', + fieldSetTitle: 'Query metadata', + fieldSetId: 'query-metadata', + fieldSetSchema: { + fieldsets: [ + { + id: 'default', + title: 'title', + fields: ['title', 'id', 'value'], + }, + ], + properties: { + title: { + type: 'string', + title: 'Query title', + }, + id: { + type: 'string', + title: 'Query id', + description: 'This will be used as query parameter key', + }, + value: { + type: 'array', + title: 'Values', + }, + }, + required: ['id', 'title', 'value'], + }, + editFieldset: false, + deleteFieldset: false, + }, +}; + +const Edit = (props) => { + if (__SERVER__) { + return
; + } + return ( +
+ + +
+ ); +}; + +export default compose( + injectIntl, + connect((state) => ({ + content: state.content.data, + })), +)(Edit); diff --git a/src/components/manage/Blocks/GlossarySearchBlock/View.jsx b/src/components/manage/Blocks/GlossarySearchBlock/View.jsx new file mode 100644 index 00000000..9d37f8e7 --- /dev/null +++ b/src/components/manage/Blocks/GlossarySearchBlock/View.jsx @@ -0,0 +1,380 @@ +import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; +import { Form, Input } from 'semantic-ui-react'; +import { compose } from 'redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import qs from 'query-string'; +import { isArray, isObject, isString } from 'lodash'; +import { Icon } from '@plone/volto/components'; +import zoomSVG from '@plone/volto/icons/zoom.svg'; +import clearSVG from '@plone/volto/icons/clear.svg'; +import { settings } from '~/config'; +import { + quickResetSearchContent, + quickSearchContent, +} from 'volto-addons/actions'; +import Highlighter from 'react-highlight-words'; +import cx from 'classnames'; +import axios from 'axios'; +import { setQueryParam, deleteQueryParam } from 'volto-datablocks/actions'; +import './style.css'; + +const messages = defineMessages({ + search: { + id: 'Search', + defaultMessage: 'Search', + }, + searchSite: { + id: 'Search Site', + defaultMessage: 'Search Site', + }, +}); + +/** + * View search block component. + * @class View + * @extends Component + */ +class View extends Component { + constructor(props) { + super(props); + this.state = { + providerUrl: settings.providerUrl, + text: '', + apiRoot: new URL(settings.apiPath).pathname, + active: false, + query: {}, + pollutants: [], + loading: false, + }; + this.linkFormContainer = React.createRef(); + this.linkInput = React.createRef(); + this.onSubmit = this.onSubmit.bind(this); + this.handleClickOutside = this.handleClickOutside.bind(this); + this.onSelectItem = this.onSelectItem.bind(this); + this.onClose = this.onClose.bind(this); + this.onChange = this.onChange.bind(this); + this.makeQuery = this.makeQuery.bind(this); + this.getPollutants = this.getPollutants.bind(this); + this.onSelectPollutant = this.onSelectPollutant.bind(this); + } + + componentDidMount() { + if ( + this.props.data?.query?.value && + isString(this.props.data.query.value) + ) { + const query = JSON.parse(this.props.data.query.value); + this.setState({ query }); + } else if ( + this.props.data?.query?.value && + isObject(this.props.data.query.value) + ) { + this.setState({ query: this.props.data.query.value }); + } + this.props.quickResetSearchContent(); + document.addEventListener('mousedown', this.handleClickOutside, false); + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.handleClickOutside, false); + } + + componentDidUpdate(prevProps) { + if (prevProps.location?.state?.text && this.props?.location?.state?.text) { + if (prevProps.location.state.text !== this.props.location.state.text) { + this.setState( + { + text: this.props?.location?.state?.text, + }, + () => { + const title = this.props.data?.title + ? `&title=${this.props.data.title.value}` + : ''; + const description = this.props.data?.description + ? `&description=${this.props.data.description.value}` + : ''; + this.props.history.push({ + pathname: `/search`, + search: `?SearchableText=${ + this.state.text + }${this.makeQuery()}${title}${description}`, + state: { text: this.state.text }, + }); + }, + ); + } + } + if (prevProps.data.query?.value !== this.props.data.query?.value) { + if ( + this.props.data.query.value && + isString(this.props.data.query.value) + ) { + const query = JSON.parse(this.props.data.query.value); + this.setState({ query }); + } else if ( + this.props.data.query.value && + isObject(this.props.data.query.value) + ) { + this.setState({ query: this.props.data.query.value }); + } + } + } + + onSubmit(event) { + const title = this.props.data?.title + ? `&title=${this.props.data.title.value}` + : ''; + const description = this.props.data?.description + ? `&description=${this.props.data.description.value}` + : ''; + this.props.history.push({ + pathname: `/search`, + search: `?SearchableText=${ + this.state.text + }${this.makeQuery()}${title}${description}`, + state: { text: this.state.text }, + }); + this.setState({ active: false }); + event && event.preventDefault(); + } + + handleClickOutside(e) { + if ( + this.linkFormContainer && + !this.linkFormContainer.current.contains(e.target) + ) { + return this.setState({ active: false }); + } else { + this.setState({ active: true }); + } + } + + onChange(event, { value }) { + if (value && value !== '') { + this.props.quickSearchContent('', { + SearchableText: `*${value}*`, + ...this.makeQueryObject(), + }); + } else { + this.props.quickResetSearchContent(); + } + this.getPollutants(value); + this.setState({ text: value }); + } + + onSelectItem(item) { + item?.['@id'] && this.props.history.push(item['@id']); + } + + onSelectPollutant(pollutant) { + this.props.setQueryParam({ + queryParam: { + index_pollutant_group_id: parseInt(pollutant.parentId), + index_pollutant_id: parseInt(pollutant.pollutantId), + }, + }); + this.props.history.push('/glossary/pollutants/pollutant-index'); + } + + onClose() { + this.props.quickResetSearchContent(); + this.setState({ active: false }); + } + + makeQuery() { + let query = ''; + this.state.query.properties && + isObject(this.state.query.properties) && + Object.entries(this.state.query.properties).forEach(([itemKey, item]) => { + if (isArray(item.value)) { + item.value.forEach((value) => { + query += `&${itemKey}:query=${value}`; + }); + } else if (item.value) { + query += `&${itemKey}:query=${item.value}`; + } + }); + return query; + } + + makeQueryObject() { + const queryObj = {}; + this.state.query.properties && + isObject(this.state.query.properties) && + Object.entries(this.state.query.properties).forEach(([itemKey, item]) => { + if (isArray(item.value)) { + queryObj[itemKey] = item.value; + } else if (item.value) { + queryObj[itemKey] = []; + queryObj[itemKey].push(item.value); + } + }); + return queryObj; + } + + getPollutants(name) { + if (name) { + const sql = `SELECT POL.name, + POL_DET.pollutantId, + POL.parentId + FROM [IED].[latest].[LOV_POLLUTANT] as POL + LEFT JOIN [IED].[latest].[pollutants_details_table] AS POL_DET + ON POL.pollutantId = POL_DET.pollutantId + WHERE name LIKE '%${name}%' + ORDER BY name`; + this.setState({ loading: true }); + axios + .get(this.state.providerUrl + `?query=${encodeURI(sql)}`) + .then((response) => { + this.setState({ pollutants: response.data.results, loading: false }); + }) + .catch((error) => { + this.setState({ pollutants: [], loading: false }); + }); + } else { + this.setState({ pollutants: [], loading: false }); + } + } + + render() { + return ( +
+
+ +
+ + + {this.state.text.length ? ( + { + this.setState({ + text: '', + }); + this.onClose(); + }} + /> + ) : ( + '' + )} + {this.state.active && + this.props.search && + !this.state.loading && + (this.props.search.length || this.state.pollutants.length) ? ( +
    + {this.props.search.map((item, index) => { + return ( +
  • this.onSelectItem(item)} + role="presentation" + > + +
  • + ); + })} + {this.state.pollutants + .filter((pollutant) => pollutant.pollutantId) + .map((pollutant, index) => { + return ( +
  • this.onSelectPollutant(pollutant)} + role="presentation" + > + +
  • + ); + })} +
+ ) : ( + '' + )} +
+ {this.props.data?.searchButton?.value ? ( + + ) : ( + '' + )} +
+
+
+ ); + } +} + +/** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ +View.propTypes = { + // data: PropTypes.objectOf(PropTypes.any).isRequired, +}; + +export default compose( + withRouter, + injectIntl, + connect( + (state, props) => ({ + search: state.quicksearch.items, + path: qs.parse(props.location.search).path, + }), + { + quickResetSearchContent, + quickSearchContent, + setQueryParam, + deleteQueryParam, + }, + ), +)(View); diff --git a/src/components/manage/Blocks/GlossarySearchBlock/style.css b/src/components/manage/Blocks/GlossarySearchBlock/style.css new file mode 100644 index 00000000..6561cab2 --- /dev/null +++ b/src/components/manage/Blocks/GlossarySearchBlock/style.css @@ -0,0 +1,74 @@ +.searchbox { + padding: 0 !important; + border: none !important; +} + +.searchbox div.input:not(.glossary) { + padding: 0 3rem; + border: 1px solid #606060; + height: 30px; + font-weight: 300; + font-size: 18px; + color: rgb(0, 0, 0); + background: rgb(255, 255, 255); +} + +.searchbox .icon { + fill: #606060; + position: absolute; + z-index: 1; + left: 1rem; + top: 50%; + transform: translateY(-50%); +} + +.searchbox .searchIcon { + cursor: pointer; +} + +.searchbox .clearIcon { + left: unset; + right: 1rem; + cursor: pointer; +} + +.searchbox .searchIcon, +.searchbox .clearIcon { + transition: fill 0.2s; +} + +.searchbox .searchIcon:hover, +.searchbox .clearIcon:hover { + fill: #000; +} + + +.searchbox .floating_search_results { + position: absolute; + z-index: 98; + top: 100%; + width: 100%; + margin: 0; + list-style-type: none; + max-height: 264px; + overflow: auto; + margin: 0; + padding: 1rem; + padding-left: 3rem; + background: white; + color: #000; + border: 1px solid #EDEDED; + border-radius: 5px; + -webkit-box-shadow: 0px 2px 4px -3px rgba(0, 0, 0, 0.75); + -moz-box-shadow: 0px 2px 4px -3px rgba(0, 0, 0, 0.75); + box-shadow: 0px 2px 4px -3px rgba(0, 0, 0.75); +} +.searchbox li { + cursor: pointer; + font-size: .8rem; + line-height: 1.9; + margin-bottom: .5rem; +} +.searchbox li:hover { + font-weight: bold; +} \ No newline at end of file diff --git a/src/components/manage/Blocks/Iframe/Edit.jsx b/src/components/manage/Blocks/Iframe/Edit.jsx index 0f0c6533..316bcfa3 100644 --- a/src/components/manage/Blocks/Iframe/Edit.jsx +++ b/src/components/manage/Blocks/Iframe/Edit.jsx @@ -9,8 +9,16 @@ import './style.css'; const getSchema = (props) => { return { - url: { - title: 'Url', + desktopUrl: { + title: 'Desktop url', + type: 'text', + }, + tabletUrl: { + title: 'Tablet url', + type: 'text', + }, + mobileUrl: { + title: 'Mobile url', type: 'text', }, title: { @@ -71,6 +79,42 @@ const getSchema = (props) => { editFieldset: false, deleteFieldset: false, }, + flags: { + title: 'Flags', + type: 'schema', + fieldSetTitle: 'Flags', + fieldSetId: 'flags_parameters', + fieldSetSchema: { + fieldsets: [ + { + id: 'default', + title: 'title', + fields: ['title', 'id', 'packageName', 'flag'], + }, + ], + properties: { + title: { + title: 'Title', + type: 'text', + }, + id: { + title: 'Id', + type: 'text', + }, + packageName: { + title: 'Package name', + type: 'text', + }, + flag: { + title: 'Flag name', + type: 'text', + }, + }, + required: ['id', 'title', 'packageName', 'flag'], + }, + editFieldset: false, + deleteFieldset: false, + }, }; }; @@ -79,6 +123,9 @@ const Edit = (props) => { schema: getSchema({ ...props, providerUrl: settings.providerUrl }), id: _uniqueId('block_'), }); + // useEffect(() => { + + // }, [discodata_query, flags]); return (
{ export default compose( connect((state, props) => ({ pathname: state.router.location.pathname, + flags: state.flags, + discodata_query: state.discodata_query, })), )(Edit); diff --git a/src/components/manage/Blocks/Iframe/View.jsx b/src/components/manage/Blocks/Iframe/View.jsx index 20f2598a..7b6f7e52 100644 --- a/src/components/manage/Blocks/Iframe/View.jsx +++ b/src/components/manage/Blocks/Iframe/View.jsx @@ -1,16 +1,48 @@ /* REACT */ -import React, { useState, useEffect } from 'react'; +import React, { useLayoutEffect, useState, useEffect } from 'react'; import { compose } from 'redux'; import { connect } from 'react-redux'; import Iframe from 'react-iframe'; import qs from 'query-string'; import './style.css'; +const useWindowSize = () => { + const [size, setSize] = useState([0, 0]); + useLayoutEffect(() => { + function updateSize() { + setSize([window.innerWidth, window.innerHeight]); + } + window.addEventListener('resize', updateSize); + updateSize(); + return () => window.removeEventListener('resize', updateSize); + }, []); + return size; +}; + const View = ({ content, ...props }) => { const [discodataQuery, setDiscodataQuery] = useState({}); + const [flags, setFlags] = useState({}); + const [windowWidth, windowHeight] = useWindowSize(); + const breakPoints = { + desktop: [Infinity, 992], + tablet: [991, 768], + mobile: [767, 0], + }; const { data } = props; - const { url = '', title = '', width = '100%', height = 'auto' } = data; + const { + desktopUrl = '', + tabletUrl = '', + mobileUrl = '', + title = '', + width = '100%', + height = 'auto', + } = data; const { hideToolbar = false, overflow = false, preset = null } = data; + const url = { + desktop: desktopUrl, + tablet: tabletUrl || desktopUrl, + mobile: mobileUrl || tabletUrl || desktopUrl, + }; useEffect(() => { if (props.data.queryParameters?.value) { @@ -38,25 +70,62 @@ const View = ({ content, ...props }) => { }, [props.data.queryParameters, props.search]) useEffect(() => { - try { - new URL(url); - const newUrl = getUrl(url); - if (props.mode === 'edit' && url !== newUrl) { - props.onChangeBlock(props.block, { - ...props.data, - url: newUrl, + if (props.data.flags?.value) { + try { + const newFlags = {}; + const properties = JSON.parse(props.data.flags?.value).properties || {}; + Object.keys(properties).forEach((flag) => { + const id = props.search[flag]; + if ( + id && + typeof props.flags.items[properties[flag].packageName]?.[id]?.[ + properties[flag].flag + ] !== 'undefined' + ) { + newFlags[properties[flag].flag] = + props.flags.items[properties[flag].packageName][id][ + properties[flag].flag + ]; + } }); + setFlags({ ...newFlags }); + } catch { + setFlags({}); } - } catch {} + } + }, [props.data.flags, props.flags, props.search]); + + const flagsState = + Object.keys(flags).filter((flag) => !flags[flag]).length > 0; + + useEffect(() => { + if (props.mode === 'edit') { + props.onChangeBlock(props.block, { + ...props.data, + desktopUrl: parseUrl(desktopUrl), + tabletUrl: parseUrl(tabletUrl), + mobileUrl: parseUrl(mobileUrl), + }); + } /* eslint-disable-next-line */ - }, [url]) + }, [desktopUrl, tabletUrl, mobileUrl]) const getUrl = (url) => { const newUrl = new URL(url); return newUrl.protocol + '//' + newUrl.host + newUrl.pathname; }; + const getUrlByWidth = () => { + return url[ + Object.keys(breakPoints).filter( + (breakPoint) => + breakPoints[breakPoint][0] >= windowWidth && + breakPoints[breakPoint][1] <= windowWidth, + )[0] + ]; + }; + const applyQueryParameters = (url, query) => { try { new URL(url); @@ -71,6 +140,19 @@ const View = ({ content, ...props }) => { } }; + const parseUrl = (url) => { + try { + new URL(url); + const newUrl = getUrl(url); + if (url !== newUrl) { + return newUrl; + } + return url; + } catch { + return ''; + } + }; + const getPresetQueries = () => { if (preset === 'site_tableau') { return { @@ -82,20 +164,29 @@ const View = ({ content, ...props }) => { return (
-