diff --git a/src/actions/index.js b/src/actions/index.js index f8575724..101af985 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -14,6 +14,7 @@ import { SET_SECTION_TABS, GET_PARENT_FOLDER_DATA, GET_PAGE, + GET_SPARQL_DATA, } from '~/constants/ActionTypes'; export function setSectionTabs(payload) { @@ -43,3 +44,14 @@ export function getPage(url) { }, }; } + +export function getSparqlData(path) { + return { + type: GET_SPARQL_DATA, + path, + request: { + op: 'get', + path: `${path}/@sparql-data`, + }, + }; +} diff --git a/src/components/manage/Blocks/ArticlesSparql/Edit.jsx b/src/components/manage/Blocks/ArticlesSparql/Edit.jsx new file mode 100644 index 00000000..e2400883 --- /dev/null +++ b/src/components/manage/Blocks/ArticlesSparql/Edit.jsx @@ -0,0 +1,40 @@ +/** + * Edit map block. + * @module components/manage/Blocks/Maps/Edit + */ +import React, { useState } from 'react'; +import InlineForm from '@plone/volto/components/manage/Form/InlineForm'; +import { SidebarPortal } from '@plone/volto/components'; +import View from './View'; +import getSchema from './schema'; + +const Edit = (props) => { + const [state, setState] = useState({ + schema: getSchema(props), + }); + + const handleChangeBlock = (id, value) => { + const { data } = props; + props.onChangeBlock(props.block, { + ...data, + [id]: value, + }); + }; + + return ( +
+ + + + +
+ ); +}; + +export default Edit; diff --git a/src/components/manage/Blocks/ArticlesSparql/View.jsx b/src/components/manage/Blocks/ArticlesSparql/View.jsx new file mode 100644 index 00000000..fe9f8cdc --- /dev/null +++ b/src/components/manage/Blocks/ArticlesSparql/View.jsx @@ -0,0 +1,145 @@ +import React, { useState, useEffect } from 'react'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import Icon from '@plone/volto/components/theme/Icon/Icon'; +import { Link } from 'react-router-dom'; +import { getSparqlData } from '~/actions'; +import { Image } from 'semantic-ui-react'; +import moment from 'moment'; +import cx from 'classnames'; +import downSVG from '@plone/volto/icons/down.svg'; +import upSVG from '@plone/volto/icons/up.svg'; +import placeholderImage from './placeholder.png'; +import './style.css'; + +const View = (props) => { + const [activeItem, setActiveItem] = useState(1); + const { page = '/', redirectPage = null, preview = false } = props.data || {}; + const items = props.sparql_data[page]?.items || []; + + useEffect(() => { + if (!props.sparql_data[page]) { + props.getSparqlData(page); + } + /* eslint-disable-next-line */ + }, []); + + const isVisible = (index) => { + if (!preview) return true; + if (activeItem === 0) { + return index < 3; + } else { + return Math.abs(index - activeItem) < 2; + } + }; + + return ( +
+ {props.mode === 'edit' && !props.data.page ? ( +

Select SPARQL data from sidebar

+ ) : ( + '' + )} + {props.mode === 'edit' && props.data.page && !items.length ? ( +

There is no SPARQL data

+ ) : ( + '' + )} + {items.length ? ( +
+ {items.map((item, index) => + isVisible(index) ? ( +
+
+ +
+
+
+

{item.title}

+
+
+

+ {moment(item.time).format('DD MMM YYYY')} +

+
+
+

{item.description}

+
+
+ + READ ARTICLE + +
+
+
+ ) : ( + '' + ), + )} +
+ ) : ( + '' + )} + {preview && redirectPage && items.length ? ( + + READ MORE + + ) : ( + '' + )} + {/* {items.length > 2 ? ( +
+ {activeItem > 1 ? ( + { + setActiveItem(activeItem - 1); + }} + name={upSVG} + size="24px" + /> + ) : ( + '' + )} + {activeItem < items.length - 1 ? ( + { + setActiveItem(activeItem + 1); + }} + name={downSVG} + size="24px" + /> + ) : ( + '' + )} +
+ ) : ( + '' + )} */} +
+ ); +}; + +export default compose( + connect( + (state, props) => ({ + sparql_data: state.sparql.items, + }), + { getSparqlData }, + ), +)(View); diff --git a/src/components/manage/Blocks/ArticlesSparql/placeholder.png b/src/components/manage/Blocks/ArticlesSparql/placeholder.png new file mode 100644 index 00000000..bf1d3104 Binary files /dev/null and b/src/components/manage/Blocks/ArticlesSparql/placeholder.png differ diff --git a/src/components/manage/Blocks/ArticlesSparql/schema.jsx b/src/components/manage/Blocks/ArticlesSparql/schema.jsx new file mode 100644 index 00000000..1e181367 --- /dev/null +++ b/src/components/manage/Blocks/ArticlesSparql/schema.jsx @@ -0,0 +1,33 @@ +export const getSchema = (props) => { + return { + title: 'Detailed Link', + + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: ['page', 'preview', 'redirectPage'], + }, + ], + + properties: { + page: { + title: 'Page', + widget: 'object_by_path', + }, + preview: { + title: 'Preview', + type: 'boolean', + }, + redirectPage: { + title: 'Redirect page', + widget: 'object_by_path', + description: 'Applies if preview is selected', + }, + }, + + required: ['page'], + }; +}; + +export default getSchema; diff --git a/src/components/manage/Blocks/ArticlesSparql/style.css b/src/components/manage/Blocks/ArticlesSparql/style.css new file mode 100644 index 00000000..5a1b64f7 --- /dev/null +++ b/src/components/manage/Blocks/ArticlesSparql/style.css @@ -0,0 +1,80 @@ +.articles-sparql { + position: relative; +} + +.articles .articles-row { + align-items: center; + height: 250px; + justify-content: center; +} + +.articles .article { + display: block; +} + +.articles .articles-row.can-be-half:last-child { + -webkit-mask-image: -webkit-gradient(linear, left top, + left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); +} +.articles .articles-row.can-be-half:last-child .article { + height: 50%; + overflow: hidden; + -webkit-mask-image: -webkit-gradient(linear, left top, + left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); +} + +.articles .article.hero img { + border-radius: 2em; +} + +.articles .article-header, +.articles .article-description { + display: block; + position: relative; +} + +.articles .article-header h3 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + line-height: 1.5em; + max-height: 1.5em; +} + +.articles .article-description p { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + line-height: 1.5em; + max-height: 4.5em; +} + +.articles-slideshow { + position: absolute; + display: flex; + flex-direction: column; + top: 50%; + right: 1em; + transform: translateY(-50%); +} + +.articles-slideshow .icon { + cursor: pointer; +} + +.articles-redirect { + position: absolute; + bottom: 7em; + left: 50%; + transform: translateX(-50%); +} + +@media (min-width: 500px) and (max-width: 768px) { + .sm-height-fit-content { + height: fit-content !important; + } +} \ No newline at end of file diff --git a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx index d3762dee..7290e8c4 100644 --- a/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx +++ b/src/components/manage/Blocks/DiscodataOpenlayersMapBlock/View.jsx @@ -502,10 +502,6 @@ const OpenlayersMapView = (props) => { callback: function () {}, }, ); - setTimeout(() => { - stateRef?.current?.map?.sitesSourceLayer && - stateRef.current.map.sitesSourceLayer.getSource().refresh(); - }, options.duration + 100); } }) .catch((error) => {}); @@ -532,10 +528,6 @@ const OpenlayersMapView = (props) => { zoom: 15, }); } - setTimeout(() => { - stateRef?.current?.map?.sitesSourceLayer && - stateRef.current.map.sitesSourceLayer.getSource().refresh(); - }, 1100); }) .catch((error) => {}); } @@ -896,7 +888,7 @@ const OpenlayersMapView = (props) => { }); } - map.once('postrender', function(event) { + map.once('postrender', function (event) { sitesSourceLayer.getSource().refresh(); }); diff --git a/src/components/manage/Blocks/FiltersBlock/View.jsx b/src/components/manage/Blocks/FiltersBlock/View.jsx index 0da3f5fc..b794a5e5 100644 --- a/src/components/manage/Blocks/FiltersBlock/View.jsx +++ b/src/components/manage/Blocks/FiltersBlock/View.jsx @@ -27,7 +27,6 @@ import circleMinus from '@plone/volto/icons/circle-minus.svg'; import clear from '@plone/volto/icons/clear.svg'; import './style.css'; -let nrOfRequests = 0; const makeUrl = (providerUrl, url) => { return encodeURI(providerUrl + `?query=${url}`); }; @@ -41,6 +40,7 @@ const keyCodes = { const View = ({ content, ...props }) => { const providerUrl = settings.providerUrl; const [state, setState] = useState({ + id: _uniqueId('block_'), open: false, filters: {}, filtersMeta: {}, @@ -59,7 +59,6 @@ const View = ({ content, ...props }) => { 'permit_years', ], factsDataOrder: ['Country_quick_facts', 'EU_quick_facts'], - mounted: false, firstLoad: false, }); const [filtersMetaReady, setFiltersMetaReady] = useState(false); @@ -74,9 +73,11 @@ const View = ({ content, ...props }) => { const [triggerSearch, setTriggerSearch] = useState(false); const [quickFactsListener, setQuickFactsListener] = useState(false); const [sidebar, setSidebar] = useState(false); + const [mountState, setMountState] = useState(false); const alphaFeatureRef = useRef({}); const searchContainerModal = useRef(null); const searchContainer = useRef(null); + const mounted = useRef(false); const modalButtonTitle = props.data.modalButtonTitle?.value; const locationResultsTexts = locationResults.map((result) => result.text); const mapSidebarExists = document?.getElementById('map-sidebar'); @@ -187,26 +188,24 @@ const View = ({ content, ...props }) => { } useEffect(function () { - setState({ ...state, mounted: true }); + mounted.current = true; + setMountState(true); updateFactsData(true); - setState({ - ...state, - mounted: true, - }); return () => { if (quickFactsListener && document.getElementById(`dynamic-filter`)) { document .getElementById(`dynamic-filter`) .removeEventListener('featurechange', onFeaturechange); } - setState({ ...state, mounted: false }); + mounted.current = false; + setMountState(false); }; /* eslint-disable-next-line */ }, []); useEffect(() => { alphaFeatureRef.current = alphaFeature; - if (state.mounted) { + if (mountState) { updateFactsData(false); } /* eslint-disable-next-line */ @@ -238,14 +237,14 @@ const View = ({ content, ...props }) => { }, [state]); useEffect(() => { - if (typeof updateFilters === 'function') { + if (mountState) { updateFilters(); - } - if (Object.keys(state.filtersMeta).length && !filtersMetaReady) { - setFiltersMetaReady(true); + if (Object.keys(state.filtersMeta).length && !filtersMetaReady) { + setFiltersMetaReady(true); + } } /* eslint-disable-next-line */ - }, [JSON.stringify(state.filters), JSON.stringify(state.filtersMeta)]) + }, [JSON.stringify(state.filtersMeta)]) useEffect(() => { if ( @@ -259,7 +258,7 @@ const View = ({ content, ...props }) => { }, [filtersMetaReady]) useEffect(() => { - if (state.mounted && __CLIENT__) { + if (mountState && __CLIENT__) { let promises = []; let metadata = []; const siteCountryFilters = @@ -559,7 +558,7 @@ const View = ({ content, ...props }) => { } Promise.all(promises) .then((response) => { - if (state.mounted) { + if (mounted.current) { const filtersMeta = { ...state.filtersMeta, }; @@ -569,7 +568,6 @@ const View = ({ content, ...props }) => { } }); response.forEach((res, index) => { - nrOfRequests++; const results = JSON.parse(res.request.response).results; let filteringInputs = []; if (state.filtersMeta[metadata[index]?.key]?.filteringInputs) { @@ -613,14 +611,14 @@ const View = ({ content, ...props }) => { } const queries = props.discodata_query.search[metadata[index].queryToSet] || []; - const filteringInptsByQuery = - queries.length > 1 - ? queries.map((query, index) => ({ - id: _uniqueId('select_'), - type: 'select', - position: index, - })) - : [metadata[index]?.firstInput]; + let filteringInptsByQuery = [metadata[index]?.firstInput]; + if (Array.isArray(queries) && queries.length > 1) { + filteringInptsByQuery = queries.map((query, index) => ({ + id: _uniqueId('select_'), + type: 'select', + position: index, + })); + } filtersMeta[metadata[index]?.key] = { filteringInputs: filteringInputs.length ? filteringInputs @@ -650,6 +648,7 @@ const View = ({ content, ...props }) => { ...(state.firstLoad === false ? { firstLoad: true } : {}), }); } + return; }) .catch((error) => { setLoadingData(false); @@ -661,7 +660,7 @@ const View = ({ content, ...props }) => { } /* eslint-disable-next-line */ }, [ - state.mounted, + mountState, state.filters?.EEAActivity && JSON.stringify(state.filters.EEAActivity), state.filters?.siteCountry && JSON.stringify(state.filters.siteCountry), state.filters?.region && JSON.stringify(state.filters.region), @@ -754,7 +753,7 @@ const View = ({ content, ...props }) => { } }); queryParamKeys.forEach((key, keyIndex) => { - if (props.discodata_query.search[key]) { + if (Array.isArray(props.discodata_query.search[key])) { newFilters[key] = props.discodata_query.search[key]; props.discodata_query.search[key].forEach((param, index) => { if ( diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js index 8bfa1a23..49c1c6d0 100644 --- a/src/constants/ActionTypes.js +++ b/src/constants/ActionTypes.js @@ -8,3 +8,4 @@ export const SET_SECTION_TABS = 'SET_SECTION_TABS'; export const GET_PARENT_FOLDER_DATA = 'GET_PARENT_FOLDER_DATA'; export const GET_NAV_ITEMS = 'GET_NAV_ITEMS'; export const GET_PAGE = 'GET_PAGE'; +export const GET_SPARQL_DATA = 'GET_SPARQL_DATA'; diff --git a/src/localconfig.js b/src/localconfig.js index d4d8228e..2a26dc54 100644 --- a/src/localconfig.js +++ b/src/localconfig.js @@ -7,6 +7,9 @@ import RedirectView from '~/components/theme/View/RedirectView'; import DetailedLinkView from '~/components/manage/Blocks/DetailedLink/View'; import DetailedLinkEdit from '~/components/manage/Blocks/DetailedLink/Edit'; +import ArticlesSparqlView from '~/components/manage/Blocks/ArticlesSparql/View'; +import ArticlesSparqlEdit from '~/components/manage/Blocks/ArticlesSparql/Edit'; + import FolderContentsBlockView from '~/components/manage/Blocks/FolderContentsBlock/View'; import FolderContentsBlockEdit from '~/components/manage/Blocks/FolderContentsBlock/Edit'; @@ -120,6 +123,15 @@ export function applyConfig(voltoConfig) { icon: listSVG, }; + config.blocks.blocksConfig.articles_sparql = { + id: 'articles_sparql', + title: 'Articles sparql', + group: 'eprtr_blocks', + view: ArticlesSparqlView, + edit: ArticlesSparqlEdit, + icon: listSVG, + }; + config.blocks.blocksConfig.detailed_link = { id: 'detailed_link', title: 'Detailed Link', diff --git a/src/reducers/index.js b/src/reducers/index.js index d4d4f8e2..39e2f1ce 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -7,6 +7,7 @@ import defaultReducers from '@plone/volto/reducers'; import section_tabs from './section_tabs'; import parent_folder_data from './parent_folder_data'; import pages from './pages'; +import sparql from './sparql'; /** * Root reducer. * @function @@ -18,6 +19,7 @@ const reducers = { section_tabs, parent_folder_data, pages, + sparql, ...defaultReducers, // Add your reducers here }; diff --git a/src/reducers/sparql.js b/src/reducers/sparql.js new file mode 100644 index 00000000..3bd878e2 --- /dev/null +++ b/src/reducers/sparql.js @@ -0,0 +1,42 @@ +import { GET_SPARQL_DATA } from '~/constants/ActionTypes'; + +const initialState = { + error: null, + items: {}, + loaded: false, + loading: false, +}; + +export default function pages(state = initialState, action = {}) { + switch (action.type) { + case `${GET_SPARQL_DATA}_PENDING`: + return { + ...state, + error: null, + loaded: false, + loading: true, + }; + case `${GET_SPARQL_DATA}_SUCCESS`: + const items = { + ...state.items, + }; + items[action.path] = action.result; + return { + ...state, + error: null, + items, + loaded: true, + loading: false, + }; + case `${GET_SPARQL_DATA}_FAIL`: + return { + ...state, + error: action.error, + items: {}, + loaded: false, + loading: false, + }; + default: + return state; + } +}