diff --git a/.eslintrc b/.eslintrc index f5c858df..89d062e7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -53,8 +53,8 @@ "./src/develop/volto-embed/src" ], [ - "volto-sidebar", - "./src/develop/volto-sidebar/src" + "volto-tabsview", + "./src/develop/volto-tabsview/src" ] ] } diff --git a/jsconfig.json b/jsconfig.json index a6393519..c5ba58c7 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -7,6 +7,7 @@ "volto-datablocks", "volto-drafteditor", "volto-mosaic", + "volto-tabsview", "volto-plotlycharts" ], "compilerOptions": { @@ -15,6 +16,9 @@ "volto-mosaic": [ "develop/volto-mosaic/src" ], + "volto-tabsview": [ + "develop/volto-tabsview/src" + ], "volto-datablocks": [ "develop/volto-datablocks/src" ], diff --git a/mrs.developer.json b/mrs.developer.json index 799ee242..856e0773 100644 --- a/mrs.developer.json +++ b/mrs.developer.json @@ -39,5 +39,9 @@ "volto-gridlayout": { "url": "https://github.com/eea/volto-gridlayout.git", "path": "src" + }, + "volto-tabsview": { + "url": "https://github.com/eea/volto-tabsview.git", + "path": "src" } } diff --git a/src/components/manage/Blocks/SidebarBlock/Edit.jsx b/src/components/manage/Blocks/SidebarBlock/Edit.jsx new file mode 100644 index 00000000..9f7063bf --- /dev/null +++ b/src/components/manage/Blocks/SidebarBlock/Edit.jsx @@ -0,0 +1,84 @@ +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'; + +const makeChoices = keys => keys && keys.map(k => [k, k]); + +const getSchema = props => { + const { search, key, resourceKey } = props.discodata_query.data; + const discodataResources = Object.keys(props.discodata_resources.data) || []; + const selectedDiscodataResource = + props.discodata_resources.data?.[resourceKey]?.[search?.[key]] || null; + return { + parent: { + type: 'link', + title: 'Parent page', + }, + multiply_second_level: { + type: 'boolean', + title: 'Multiply second level', + }, + discodata_resource: { + type: 'array', + title: 'Discodata resource', + choices: makeChoices(discodataResources), + }, + discodata_resource_property: { + type: 'array', + title: 'Source', + // items: { + choices: selectedDiscodataResource + ? makeChoices(Object.keys(selectedDiscodataResource)) + : [], + // }, + }, + query_parameter: { + type: 'text', + title: 'Query to set', + }, + }; +}; + +const Edit = props => { + const [state, setState] = useState({ + schema: getSchema({ ...props }), + id: _uniqueId('block_'), + }); + useEffect(() => { + props.onChangeBlock(props.block, { + ...props.data, + hide_block: { + selector: '.sidebar-block-container .sidebar', + hiddenClassName: 'hidden', + event: 'sidebarToggle', + }, + }); + /* eslint-disable-next-line */ + }, []) + useEffect(() => { + setState({ + ...state, + schema: getSchema({ + ...props, + }), + }); + /* eslint-disable-next-line */ + }, [state.item, props.data, props.discodata_resources, props.discodata_query]) + return ( +
+ + +
+ ); +}; + +export default compose( + connect((state, props) => ({ + pathname: state.router.location.pathname, + discodata_resources: state.discodata_resources, + discodata_query: state.discodata_query, + })), +)(Edit); diff --git a/src/components/manage/Blocks/SidebarBlock/View.jsx b/src/components/manage/Blocks/SidebarBlock/View.jsx new file mode 100644 index 00000000..114245d9 --- /dev/null +++ b/src/components/manage/Blocks/SidebarBlock/View.jsx @@ -0,0 +1,206 @@ +/* REACT */ +import React, { useState, useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { NavLink } from 'react-router-dom'; +/* ROOT */ +import { settings } from '~/config'; +/* HELPERS */ +import { getNavigationByParent } from 'volto-tabsview/helpers'; +import { + getDiscodataResource, + setDiscodataQuery, +} from 'volto-datablocks/actions'; + +import './style.css'; +const sidebarRef = React.createRef(); +let unlisten; +const View = ({ content, ...props }) => { + const { data } = props; + const [state, setState] = useState({ + sidebar: [], + sidebarOpened: true, + }); + const history = useHistory(); + const { search, key, resourceKey } = props.discodata_query.data; + const parent = data.parent?.value; + const activeItem = search?.[props.data.discodata_resource_property?.value]; + + // useEffect(() => { + // unlisten = this.props.history.listen((location, action) => { + // if (action === 'PUSH') { + // const nextPathname = location.pathname.split('/'); + // const prevPathname = props.location.pathname.split('/'); + // // if ( + // // props.location.search && + // // props.location.search !== location.search && + // // (location.pathname.includes(props.location.pathname) || + // // nextPathname[1] === prevPathname[1]) + // // ) { + // // props.history.push( + // // `${location.pathname}${this.props.location.search}`, + // // ); + // // } + // } + // }); + // return () => { + // unlisten(); + // }; + // /* eslint-disable-next-line */ + // }, []) + + useEffect(() => { + if (props.navigation) { + const sidebar = []; + sidebar.push(...getSidebar(props.navigation, 1)); + setState({ + ...state, + sidebar, + }); + } + /* eslint-disable-next-line */ + }, [ props.data, props.navigation, props.discodata_resources, props.discodata_query?.search]); + + const getSidebar = (item, depth) => { + const sidebar = []; + if (depth === 2 && props.data?.multiply_second_level?.value === true) { + const selectedDiscodataResource = + props.discodata_resources.data?.[resourceKey]?.[search?.[key]] || null; + const selectedDiscodataResourceProperty = + selectedDiscodataResource?.[ + props.data.discodata_resource_property?.value + ]; + selectedDiscodataResourceProperty && + item?.items?.length && + Object.entries(selectedDiscodataResourceProperty).forEach( + ([key, value]) => { + sidebar.push( + , + ); + item?.items?.length && + item.items.forEach(nextItem => { + sidebar.push( + , + ); + sidebar.push(...getSidebar(nextItem, depth + 2)); + }); + }, + ); + } else { + item?.items?.length && + item.items.forEach(nextItem => { + sidebar.push( + + {nextItem.title} + , + ); + sidebar.push(...getSidebar(nextItem, depth + 1)); + }); + } + return sidebar; + }; + return ( +
+ {/* */} +
+ {props.navigation?.items?.length && parent ? ( + + ) : ( + '' + )} +
+
+ ); +}; + +export default compose( + connect( + (state, props) => ({ + router: state.router, + query: state.router.location.search, + location: state.router.location, + content: + state.prefetch?.[state.router.location.pathname] || state.content.data, + pathname: state.router.location.pathname, + lang: state.intl.locale, + navigation: getNavigationByParent( + state.navigation.items, + props.data?.parent?.value, + ), + discodata_resources: state.discodata_resources, + discodata_query: state.discodata_query, + }), + { getDiscodataResource, setDiscodataQuery }, + ), +)(View); diff --git a/src/components/manage/Blocks/SidebarBlock/style.css b/src/components/manage/Blocks/SidebarBlock/style.css new file mode 100644 index 00000000..4f149c02 --- /dev/null +++ b/src/components/manage/Blocks/SidebarBlock/style.css @@ -0,0 +1,83 @@ +.react-grid-item .block-container, +.react-grid-item .block-container .block-wrapper, +.sidebar-block-container, +.sidebar-block-container .sidebar { + height: 100%; +} + +.sidebar-block-container .sidebar { + background-color: #f3efee; + display: flex; + transition: width 0.2s; +} + +.sidebar-block-container .sidebar .tabs { + display: flex; + flex-flow: column; + max-width: 220px; +} + +.sidebar-block-container .sidebar.hidden { + width: 0; + overflow: hidden; +} + +.sidebar-block-container .sidebar.show { + width: 100%; +} + +.sidebar-block-container .sidebar .tabs { + flex-direction: column; + display: inline-flex; + position: relative; + margin: 2em auto; + padding: 0 1em; +} + +.sidebar-block-container .sidebar .tabs .tabs__item, +.sidebar-block-container .sidebar .tabs .tabs__item_active { + background-color: transparent; + border: none; + text-align: left; + color: #333333; + padding-bottom: 1rem; + cursor: pointer; +} + +.sidebar-block-container .sidebar .tabs .tabs__item:focus, +.sidebar-block-container .sidebar .tabs .tabs__item_active:focus { + outline: none; +} + +.sidebar-block-container .sidebar .tabs .tabs__item.hidden { + display: none; +} + +.sidebar-block-container .sidebar .tabs .tabs__item.show { + display: block; +} + +.sidebar-block-container .sidebar .tabs .tabs__item.depth__2 { + padding-left: 1em; +} + +.sidebar-block-container .sidebar .tabs .tabs__item.depth__3 { + padding-left: 2em; +} + +.sidebar-block-container .sidebar .tabs .tabs__item.depth__4 { + padding-left: 3em; +} + +.sidebar-block-container .sidebar .tabs .tabs__item_active { + color: #ED776A +} + +.sidebar-block-container .sidebar .tabs .tabs__item_active.depth__2 { + color: #ED776A +} + +.sidebar-block-container .sidebar .tabs .tabs__item_active.depth__3 { + color: #333333; + font-weight: bold;; +} diff --git a/src/components/theme/View/DiscodataView.jsx b/src/components/theme/View/DiscodataView.jsx new file mode 100644 index 00000000..b2d0c506 --- /dev/null +++ b/src/components/theme/View/DiscodataView.jsx @@ -0,0 +1,78 @@ +/* REACT IMPORTS */ +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import qs from 'query-string'; +/* ROOT IMPORTS */ +import MosaicView from 'volto-mosaic/components/theme/View'; +import DB from 'volto-datablocks/DataBase/DB'; +// SVGS +/* LOCAL IMPORTS */ +import { + getDiscodataResource, + setDiscodataQuery, +} from 'volto-datablocks/actions'; +/* =================================================== */ + +const DiscodataView = props => { + const query = qs.parse(props.location.search); + + useEffect(() => { + const { sql_query, endpoint_url } = props.content; + const { + search, + key, + resourceKey, + where, + groupBy, + } = props.discodata_query.data; + const whereStatements = + where?.length > 0 && + where.map(param => { + return { + discodataKey: param, + value: props.discodata_query.data.search?.[param], + }; + }); + const url = DB.table(sql_query, endpoint_url, { + p: query.p, + nrOfHits: query.nrOfHits, + }) + .where(whereStatements) + .encode() + .get(); + if (!props.discodata_resources.loading) { + const request = { + url, + search: search || {}, + resourceKey: resourceKey || '', + key: key || '', + groupBy: groupBy || [], + }; + if ( + request.url && + !props.discodata_resources.data?.[key]?.[ + props.discodata_query.data.search?.[key] + ] + ) { + props.getDiscodataResource(request); + } + } + /* eslint-disable-next-line */ + }, [props.discodata_query.data]) + + return ( +
+ +
+ ); +}; + +export default connect( + (state, props) => ({ + discodata_query: state.discodata_query, + discodata_resources: state.discodata_resources, + content: + state.prefetch?.[state.router.location.pathname] || state.content.data, + }), + { getDiscodataResource, setDiscodataQuery }, +)(DiscodataView); diff --git a/src/config.js b/src/config.js index 6f0c913c..fa80660e 100644 --- a/src/config.js +++ b/src/config.js @@ -12,6 +12,7 @@ import { applyConfig as mosaicConfig } from 'volto-mosaic/config'; import { applyConfig as plotlyConfig } from 'volto-plotlycharts/config'; // import { applyConfig as installEPRTRFrontend } from './localconfig'; import { applyConfig as gridLayoutConfig } from 'volto-gridlayout/config'; +import { applyConfig as tabsViewConfig } from 'volto-tabsview/config'; // import { applyConfig as installEPRTRFrontend } from './localconfig'; import installEPRTR from './localconfig'; @@ -22,6 +23,7 @@ const config = [ installTableau, plotlyConfig, // ckeditorConfig, + tabsViewConfig, mosaicConfig, blocksConfig, dataBlocksConfig, diff --git a/src/customizations/volto/helpers/Api/Api.js b/src/customizations/volto/helpers/Api/Api.js new file mode 100644 index 00000000..0fcd7412 --- /dev/null +++ b/src/customizations/volto/helpers/Api/Api.js @@ -0,0 +1,81 @@ +/** + * Api helper. + * @module helpers/Api + */ + +import superagent from 'superagent'; +import cookie from 'react-cookie'; + +import { settings } from '~/config'; + +const methods = ['get', 'post', 'put', 'patch', 'del']; + +/** + * Format the url. + * @function formatUrl + * @param {string} path Path (or URL) to be formatted. + * @returns {string} Formatted path. + */ +function formatUrl(path) { + if (path.startsWith('http://') || path.startsWith('https://')) return path; + + const adjustedPath = path[0] !== '/' ? `/${path}` : path; + let apiPath = ''; + if (settings.internalApiPath && __SERVER__) { + apiPath = settings.internalApiPath; + } else { + apiPath = settings.apiPath; + } + return `${apiPath}${adjustedPath}`; +} + +/** + * Api class. + * @class Api + */ +class Api { + /** + * Constructor + * @method constructor + * @constructs Api + */ + constructor() { + methods.forEach(method => { + this[method] = (path, { params, data, type, headers = {} } = {}) => { + let request; + let promise = new Promise((resolve, reject) => { + request = superagent[method](formatUrl(path)); + + if (params) { + request.query(params); + } + + const authToken = cookie.load('auth_token'); + if (authToken) { + request.set('Authorization', `Bearer ${authToken}`); + } + + request.set('Accept', 'application/json'); + + if (type) { + request.type(type); + } + + Object.keys(headers).forEach(key => request.set(key, headers[key])); + + if (data) { + request.send(data); + } + + request.end((err, { body } = {}) => + err ? reject(err) : resolve(body), + ); + }); + promise.request = request; + return promise; + }; + }); + } +} + +export default Api; diff --git a/src/localconfig.js b/src/localconfig.js index ab8af831..eb74ae4a 100644 --- a/src/localconfig.js +++ b/src/localconfig.js @@ -3,6 +3,9 @@ import TabsView from '~/components/theme/View/TabsView'; import RedirectView from '~/components/theme/View/RedirectView'; import TabsChildView from '~/components/theme/View/TabsChildView'; import BrowseView from '~/components/theme/View/BrowseView/BrowseView'; +import DiscodataView from '~/components/theme/View/DiscodataView'; + +import MosaicForm from 'volto-mosaic/components/manage/Form'; import DetailedLinkView from '~/components/manage/Blocks/DetailedLink/View'; import DetailedLinkEdit from '~/components/manage/Blocks/DetailedLink/Edit'; @@ -25,8 +28,10 @@ import RegulatoryInformationBlockView from '~/components/manage/Blocks/Regulator import CompanyHeaderEdit from '~/components/manage/Blocks/CompanyHeader/Edit'; import CompanyHeaderView from '~/components/manage/Blocks/CompanyHeader/View'; +import EprtrSidebarBlockEdit from '~/components/manage/Blocks/SidebarBlock/Edit'; +import EprtrSidebarBlockView from '~/components/manage/Blocks/SidebarBlock/View'; + const applyConfig = config => { - console.log('config', config); config.views = { ...config.views, layoutViews: { @@ -35,6 +40,15 @@ const applyConfig = config => { glossaryview: TabsChildView, redirect_view: RedirectView, browse_view: BrowseView, + discodata_view: DiscodataView, + }, + }; + + config.editForms = { + ...config.editForms, + byLayout: { + ...config.editForms?.byLayout, + discodata_view: MosaicForm, }, }; @@ -101,6 +115,15 @@ const applyConfig = config => { group: 'data_blocks', }; + config.blocks.blocksConfig.eprtr_sidebar_block = { + id: 'eprtr_sidebar_block', + title: 'Eprtr sidebar block', + view: EprtrSidebarBlockView, + edit: EprtrSidebarBlockEdit, + icon: chartIcon, + group: 'data_blocks', + }; + return config; }; diff --git a/theme/site/globals/site.overrides b/theme/site/globals/site.overrides index e8dc3635..8e87227c 100644 --- a/theme/site/globals/site.overrides +++ b/theme/site/globals/site.overrides @@ -831,11 +831,13 @@ space-around { top: auto; } } - .sidebar-container { z-index: 9999; } +} +.sidebar-container .ui.raised.segments { + height: fit-content !important; } .block.maps iframe { @@ -1002,4 +1004,127 @@ body.has-sidebar { left: auto !important; transform: none !important; top: auto; +} + +/* Tabs view nav */ +.tabs-view-menu { + padding-bottom: 0; + height: 100%; + .scroll-container { + height: 100%; + .ui.menu { + height: 100%; + } + } + .ui.menu { + border: none; + box-shadow: none; + &.item { + text-align: left !important; + justify-content: start !important; + margin-left: auto !important; + margin-right: auto !important; + &::-webkit-scrollbar { + height: 6px; + } + &::-webkit-scrollbar-track { + border-radius: 10em; + } + &::-webkit-scrollbar-thumb { + background-color: darkgrey; + outline: 1px solid slategrey; + } + .item { + width: fit-content !important; + margin-right: 1em !important; + margin-left: 1em !important; + } + } + .item { + font-size: 18px; + &:before { + background: #fff; + } + } + .active.item { + background: #fff; + color: #4296B3; + font-weight: bold; + border-bottom: 2px solid #4296B3; + &:hover { + background: transparent; + color: #3b849e; + } + } + } + + // @media(min-width: 1300px) { + // .ui.item.menu { + // width: 1200px !important; + // } + // } + + // @media(min-width: 1000px) { + // .ui.item.menu { + // width: 900px !important; + // } + // } + + // @media(max-width: 999px) { + // .ui.item.menu { + // width: fit-content !important; + // } + // } +} + +.react-grid-item { + .block-container { + .block-wrapper { + &.grey { + background-color: #F6F6F6; + } + .sidebar { + .tabs { + + } + } + } + } +} + + +.view-navgation-container { + height: 100%; + .tabs-view-menu { + padding-bottom: 0; + } + .view-sidebar-container { + display: grid; + grid-template-columns: 300px 1fr; + height: calc(100% - 40px); + .sidebar { + background: #F3EFEE; + padding: 20px 0; + } + .view { + padding: 20px; + } + } +} + + +.section-industrial-site main { + height: 100%; +} + +h2 { + color: #4296B3; +} + +h3 { + color: #EC776A; +} + +.blocks-chooser { + top: -500px !important; } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4f583f22..cc953dc3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,6 +32,9 @@ ], "volto-embed": [ "develop/volto-embed" + ], + "volto-tabsview": [ + "develop/volto-tabsview" ] }, "baseUrl": "src"