diff --git a/jsconfig.json b/jsconfig.json index 2ab0e90d..d979573b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,11 +2,14 @@ "compilerOptions": { "paths": { "@eeacms/volto-tabs-block": [ - "develop/volto-tabsblock/src" + "develop/volto-tabs-block/src" ], "volto-tabsview": [ "develop/volto-tabsview/src" ], + "volto-grid-block": [ + "develop/volto-grid-block/src" + ], "volto-datablocks": [ "develop/volto-datablocks/src" ], diff --git a/mrs.developer.json b/mrs.developer.json index 185e832f..d531575f 100644 --- a/mrs.developer.json +++ b/mrs.developer.json @@ -10,6 +10,11 @@ "branch": "master", "path": "src" }, + "volto-grid-block": { + "url": "https://github.com/eea/volto-grid-block.git", + "branch": "master", + "path": "src" + }, "volto-datablocks": { "url": "https://github.com/eea/volto-datablocks.git", "branch": "master", diff --git a/package.json b/package.json index eea213a2..a1c264a2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "@eeacms/volto-metadata-block", "@eeacms/volto-tabs-block", "volto-addons", - "volto-datablocks" + "volto-datablocks", + "volto-grid-block" ], "scripts": { "start": "razzle start", @@ -103,7 +104,7 @@ "@eeacms/volto-metadata-block": "github:eea/volto-metadata-block#0.2.0", "@eeacms/volto-slate-metadata-mentions": "github:eea/volto-slate-metadata-mentions#0.2.0", "@eeacms/volto-widgets-view": "github:eea/volto-widgets-view#0.2.4", - "@plone/volto": "github:eea/volto#7.8.2-beta.3", + "@plone/volto": "github:eea/volto#7.11.1-beta.1", "axios": "^0.20.0", "jsonp": "^0.2.1", "ol": "^6.4.3", @@ -115,7 +116,7 @@ "react-loadable": "^5.5.0", "react-sizeme": "^2.6.12", "react-visibility-sensor": "^5.1.1", - "volto-slate": "github:eea/volto-slate#0.5.2" + "volto-slate": "github:eea/volto-slate#0.6.0.beta.1" }, "devDependencies": { "@babel/cli": "^7.8.4", diff --git a/src/components/manage/Blocks/DetailedLink/Edit.jsx b/src/components/manage/Blocks/DetailedLink/Edit.jsx index 3db6def1..f8cd0538 100644 --- a/src/components/manage/Blocks/DetailedLink/Edit.jsx +++ b/src/components/manage/Blocks/DetailedLink/Edit.jsx @@ -31,7 +31,9 @@ const Edit = (props) => { }, [props.pages]) useEffect(() => { - props.dispatch(getPage(pageLink)); + if (pageLink) { + props.dispatch(getPage(pageLink)); + } /* eslint-disable-next-line */ }, [pageLink]) diff --git a/src/components/manage/Blocks/NavigationBlock/Edit.jsx b/src/components/manage/Blocks/NavigationBlock/Edit.jsx index 3725e37a..af212dc3 100644 --- a/src/components/manage/Blocks/NavigationBlock/Edit.jsx +++ b/src/components/manage/Blocks/NavigationBlock/Edit.jsx @@ -10,7 +10,7 @@ const getSchema = (props) => { return { parent: { title: 'Parent page', - type: 'link', + widget: 'object_by_path', }, className: { title: 'Classname', diff --git a/src/components/manage/Blocks/NavigationBlock/View.jsx b/src/components/manage/Blocks/NavigationBlock/View.jsx index 9ddc2b4f..0ce74a6f 100644 --- a/src/components/manage/Blocks/NavigationBlock/View.jsx +++ b/src/components/manage/Blocks/NavigationBlock/View.jsx @@ -58,7 +58,10 @@ const View = ({ content, ...props }) => { ) : ( - '' +

+ There are no pages inside of selected page. Make sure you add pages or + delete the block +

); }; diff --git a/src/components/manage/Blocks/SidebarBlock/Edit.jsx b/src/components/manage/Blocks/SidebarBlock/Edit.jsx index 96548d26..5e3fde02 100644 --- a/src/components/manage/Blocks/SidebarBlock/Edit.jsx +++ b/src/components/manage/Blocks/SidebarBlock/Edit.jsx @@ -1,319 +1,57 @@ -import { v4 as uuid } from 'uuid'; +import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; -import React from 'react'; -import { SidebarPortal } from '@plone/volto/components'; -import InlineForm from '@plone/volto/components/manage/Form/InlineForm'; -import { getBlocks } from '@plone/volto/helpers'; -import { SIDEBAR } from '~/constants/Blocks.js'; -import { FormStateContext } from '@plone/volto/components/manage/Form/FormContext'; -import { - resetContentForEdit, - resetTabs, -} from '@eeacms/volto-tabs-block/actions'; // reflowBlocksLayout, setToEditMode -import { - globalDeriveTabsFromState, - tabsLayoutToBlocksLayout, - isEqual, -} from '@eeacms/volto-tabs-block/Tabs/utils'; +import { compose } from 'redux'; +import _uniqueId from 'lodash/uniqueId'; +import RenderFields from 'volto-addons/Widgets/RenderFields'; import View from './View'; -import getSchema from './schema'; import { settings } from '~/config'; -const J = JSON.stringify; // TODO: should use something from lodash - -class EditSidebarBlock extends React.Component { - static contextType = FormStateContext; - - constructor(props, context) { - super(props); - this.state = { - blocksLayout: context.contextData.formData.blocks_layout.items, - }; - - this.saveGlobalLayout = this.saveGlobalLayout.bind(this); - this.handleChangeBlock = this.handleChangeBlock.bind(this); - this.isFirstTabsBlock = this.isFirstTabsBlock.bind(this); - this.schema = getSchema(props); - } - - isFirstTabsBlock() { - const blocks = getBlocks(this.context.contextData?.formData || {}); - const [id] = blocks.find(([, value]) => value['@type'] === SIDEBAR); - return id === this.props.id; - } - - componentDidMount() { - const { contextData } = this.context; - let formData = this.context.contextData.formData; - if ( - Object.keys(formData || {}).includes('_original_items') && - !contextData.fixed_for_edit - ) { - // We're coming from the View page with a layout that already has tabs, - // so it was changed. In this case restore the original_items as items - formData = { - ...formData, - blocks_layout: { - ...formData.blocks_layout, - items: tabsLayoutToBlocksLayout(getBlocks(formData), {}), - }, - }; - this.props.resetContentForEdit(true, formData); // isEditView = true - this.globalRelayout({ defaultFormData: formData, fixed_for_edit: true }); - // this.props.resetTabs(); - } - - const blocks = getBlocks(formData); - const res = blocks.find(([, value]) => value['@type'] === SIDEBAR); - - if (!res) { - // the component did not exist in the edit value, it is new - // so we should use the contextData instead - this.props.resetContentForEdit(true, formData); - this.globalRelayout({ defaultFormData: formData, fixed_for_edit: true }); - } else { - // if this is the first tabs block, relayout the whole form - const [id] = res; - if (id === this.props.id) { - this.props.resetContentForEdit(true, formData); - this.globalRelayout({ - defaultFormData: formData, - fixed_for_edit: true, - }); - } - } - } - - componentWillUnmount() { - const blocks = getBlocks(this.context.contextData?.formData || {}); - const index = blocks.findIndex(([, value]) => value['@type'] === SIDEBAR); - if (index === -1) { - // this.props.setToEditMode(false); - // TODO: might need to do some stuff here - } - } - - componentDidUpdate(prevProps) { - const { contextData } = this.context; - const { data, tabsState } = this.props; - const { formData } = contextData; - - const blocksLayout = formData.blocks_layout.items; - - // TODO: this needs to be finished - const { tabs, tabsLayout } = this.props.data; - if ( - tabs?.length && - tabs?.length < tabsLayout?.length - // || (tabs.length === 0 && tabsLayout.length === 0) // Might cause problems when section is last block - ) { - // TODO: handle the case when all tabs are deleted; - const index = prevProps.data.tabs.findIndex((v, i) => tabs[i] !== v); - const blockIds = tabsLayout[index]; - const next = index > 0 ? index - 1 : 0; - tabsLayout.splice(index, 1); - tabsLayout[next] = [...tabsLayout[next].concat(blockIds)]; - - // Did the change involve the current tab? - if (index === tabsState[this.props.block]) { - tabsState[this.props.block] = next; - } - - const defaultFormData = { - ...formData, - blocks: { - ...formData.blocks, - [this.props.block]: { - ...formData.blocks[this.props.block], - tabs, - tabsLayout, - }, - }, - }; - - const blocks = getBlocks(defaultFormData) || []; - const new_layout = tabsLayoutToBlocksLayout(blocks, tabsState); - const { contextData, setContextData } = this.context; - // Update schema - let schema = { ...this.state.schema }; - if ( - JSON.stringify(prevProps.data.tabs) !== - JSON.stringify(this.props.data.tabs) - ) { - schema = { ...getSchema(this.props) }; - } - this.setState({ blocksLayout: new_layout, schema }); - setContextData({ - ...contextData, - formData: { - ...formData, - blocks: { - ...formData.blocks, - // We need to create new objects because we mutate in place some arrays - ...Object.fromEntries(JSON.parse(JSON.stringify(blocks))), - }, - blocks_layout: { - ...formData.blocks_layout, - items: new_layout, - }, - }, - }); - return; - } - - const isTabsChanged = - data.tabsLayout && J(prevProps.tabsState) !== J(tabsState); - // && isEqual(blocksLayout, this.state.blocksLayout); - - if (isTabsChanged) { - if (this.isFirstTabsBlock()) { - return this.globalRelayout({ tabChanged: true }); - } - } - - const isBlocksChanged = - data.tabsLayout && - J(prevProps.tabsState) === J(tabsState) && - !isEqual(blocksLayout, this.state.blocksLayout); - - if (isBlocksChanged) { - if (this.isFirstTabsBlock()) { - return this.globalRelayout({}); - } - } - } - - createDefaultBlock() { - const idTrailingBlock = uuid(); - const res = [ - idTrailingBlock, - { - '@type': settings.defaultBlockType, - }, - ]; - return res; - } - - globalRelayout({ - defaultFormData, - tabChanged = false, - fixed_for_edit = false, - }) { - const { contextData, setContextData } = this.context; - const { tabsState } = this.props; - const formData = defaultFormData || contextData.formData; - - const blocks = getBlocks(formData) || []; - const globalState = globalDeriveTabsFromState({ - blocks, - tabsState, - tabChanged, - }); - - blocks.forEach(([id, block]) => { - if (block['@type'] === SIDEBAR) { - block.tabsLayout = globalState[id]; // [activeTab] - // Create placeholder tabs for "empty" pages in the tabs - block.tabsLayout.forEach((page, index) => { - if (!page?.length) { - const extra = this.createDefaultBlock(); - formData.blocks[extra[0]] = extra[1]; - block.tabsLayout[index] = [extra[0]]; - } - }); - const { tabs = [], tabsLayout = [] } = block; - if (tabs.length < tabsLayout.length) { - const start = tabs.length - 1; - tabs.length = tabsLayout.length; - block.tabs = [...tabs].fill({}, start); - } - } +const getSchema = (props) => { + return { + parent: { + title: 'Parent page', + widget: 'object_by_path', + }, + className: { + title: 'Classname', + type: 'text', + }, + preset: { + type: 'array', + title: 'Preset navigation', + choices: [ + ['facilities', 'Facilities'], + ['installations', 'Installations'], + ['lcps', 'Lcps'], + ], + }, + }; +}; + +const Edit = (props) => { + const [state, setState] = useState({ + schema: getSchema({ ...props, providerUrl: settings.providerUrl }), + id: _uniqueId('block_'), + }); + useEffect(() => { + setState({ + ...state, + schema: getSchema({ + ...props, + }), }); - - const new_layout = tabsLayoutToBlocksLayout(blocks, tabsState); - - this.setState({ blocksLayout: new_layout }); - setContextData({ - ...contextData, - fixed_for_edit: fixed_for_edit || contextData.fixed_for_edit, // keep true - formData: { - ...formData, - blocks: { - ...formData.blocks, - // We need to create new objects because we mutate in place some arrays - ...Object.fromEntries(JSON.parse(JSON.stringify(blocks))), - }, - blocks_layout: { - ...formData.blocks_layout, - items: new_layout, - }, - }, - }); - } - - handleChangeBlock(id, value) { - const { data } = this.props; - this.props.onChangeBlock(this.props.block, { - ...data, - [id]: value, - }); - } - - saveGlobalLayout(new_layout) { - const { contextData, setContextData } = this.context; - const data = { - ...contextData, - formData: { - ...contextData.formData, - blocks_layout: { - ...contextData.formData.blocks_layout, - items: new_layout, - }, - }, - }; - return setContextData(data); - } - - render() { - const { data } = this.props; - return ( -
{ - this.props.onSelectBlock(this.props.block); - }} - > -
- -
- - - -
- ); - } -} - -export default connect( - (state) => { - return { - tabsState: state.tabs_block[state.router.location.key] || {}, - content: state.content, - }; - }, - { - resetContentForEdit, - resetTabs, - }, -)(EditSidebarBlock); + /* eslint-disable-next-line */ + }, [state.item, props.data.components]) + return ( +
+ + +
+ ); +}; + +export default compose( + connect((state, props) => ({ + pathname: state.router.location.pathname, + })), +)(Edit); diff --git a/src/components/manage/Blocks/SidebarBlock/View.jsx b/src/components/manage/Blocks/SidebarBlock/View.jsx index 3249a6dc..bea43512 100644 --- a/src/components/manage/Blocks/SidebarBlock/View.jsx +++ b/src/components/manage/Blocks/SidebarBlock/View.jsx @@ -1,77 +1,24 @@ -import React, { useState } from 'react'; +/* REACT */ +import React, { useEffect, useState } from 'react'; +import { useHistory } from 'react-router-dom'; import { compose } from 'redux'; import { connect } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { Tab, Menu } from 'semantic-ui-react'; -import { useDispatch, useSelector } from 'react-redux'; -import { NavLink } from 'react-router-dom'; -import { setActiveTab } from '@eeacms/volto-tabs-block/actions'; -import { blocks, settings } from '~/config'; -import { defineMessages, injectIntl } from 'react-intl'; -import cx from 'classnames'; -import { getBlocksFieldname, getBaseUrl } from '@plone/volto/helpers'; -import { isEqual } from '@eeacms/volto-tabs-block/Tabs/utils'; -import { arrayToTree } from 'performant-array-to-tree'; -import '@eeacms/volto-tabs-block/Tabs/public.less'; - -import { isObject, isArray } from 'lodash'; - -import qs from 'query-string'; -import { getNavigationByParent } from 'volto-tabsview/helpers'; +/* SEMANTIC UI */ +import { Menu } from 'semantic-ui-react'; +/* HELPERS */ import { - getDiscodataResource, - setQueryParam, - deleteQueryParam, -} from 'volto-datablocks/actions'; + isActive, + getNavigationByParent, + getBasePath, +} from 'volto-tabsview/helpers'; -const messages = defineMessages({ - unknownBlock: { - id: 'Unknown Block', - defaultMessage: 'Unknown Block {block}', - }, -}); +import { setQueryParam, deleteQueryParam } from 'volto-datablocks/actions'; -const flattenArray = (array, depth = 0) => { - let flattenedArray = []; - array.forEach((item) => { - if (item.children?.length > 0) { - flattenedArray.push({ ...item, depth }); - flattenedArray = [ - ...flattenedArray, - ...flattenArray(item.children, depth + 1), - ]; - } else { - flattenedArray.push({ ...item, depth }); - } - }); - return flattenedArray; -}; +import { getFacilities, getInstallations, getLcps } from '~/helpers/api'; -const hasChildActive = (tabs, tab, activeTabIndex) => { - const activeTab = tabs[activeTabIndex]; - if (activeTab?.parentTitle === tab?.title) return true; - if (tab.children) { - let active = false; - tab.children.forEach((child) => { - active = hasChildActive(tabs, child, activeTabIndex); - }); - return active; - } - return false; -}; +import './style.css'; -const isHidden = (tabs, tab, activeTabIndex) => { - if (!hasChildActive(tabs, tab, activeTabIndex)) { - const activeTab = tabs[activeTabIndex]; - if (tab.title === activeTab?.title) return false; - if (tab.parentTitle === activeTab?.title) return false; - if (tab.depth > 0) return true; - return false; - } - return false; -}; - -const _flattenArray = (array, prevItem = {}, depth = 0) => { +const flattenArray = (array, prevItem = {}, depth = 0, maxDepth) => { let flattenedArray = []; if (!array) return flattenedArray; array.forEach((item) => { @@ -79,7 +26,11 @@ const _flattenArray = (array, prevItem = {}, depth = 0) => { flattenedArray.push({ ...item, parent: prevItem.url || null, depth }); flattenedArray = [ ...flattenedArray, - ..._flattenArray(item.items, item, depth + 1), + ...(typeof maxDepth === 'number' && depth < maxDepth + ? flattenArray(item.items, item, depth + 1, maxDepth) + : typeof maxDepth !== 'number' && !maxDepth + ? flattenArray(item.items, item, depth + 1, maxDepth) + : []), ]; } else { flattenedArray.push({ ...item, parent: prevItem.url || null, depth }); @@ -88,269 +39,520 @@ const _flattenArray = (array, prevItem = {}, depth = 0) => { return flattenedArray; }; -const _hasChildActive = (tabs, tab, pathname) => { - const activeChildTab = tabs.filter( - (child) => child.parent === tab.url && child.url === pathname, - )[0]; - if (!activeChildTab) return false; - return true; -}; - -const _hasParentActive = (tabs, tab, pathname) => { - if (!tab.parent) return -1; +const hasAscendentActive = (tabs, tab, pathname) => { + if (!tab.parent || pathname === tab.url) return -1; const parentTab = tabs.filter((parentTab) => parentTab.url === tab.parent)[0]; - if (parentTab && pathname === parentTab.url) return 1; + if (parentTab && pathname === parentTab.url) { + return 1; + } else if (parentTab.parent) { + return hasAscendentActive(tabs, parentTab, pathname); + } return 0; }; -const _isHidden = (tabs, tab, pathname) => { - if (!_hasChildActive(tabs, tab, pathname)) { - if (tab.url === pathname) return false; - if (_hasParentActive(tabs, tab, pathname) === 1) return false; - if (tab.depth > 0) return true; - return false; +const hasDescendentActive = (tab, pathname) => { + let ok = false; + if (pathname === tab.url) return true; + if (tab.items) { + tab.items.forEach((item) => { + if (hasDescendentActive(item, pathname) && !ok) { + ok = true; + } + }); + } + return ok; +}; + +const makeNewNavigation = ( + items, + preset, + collection, + search, + history, + dispatch, +) => { + if (preset === 'facilities') { + return items.map((item) => ({ + ...item, + onClick: () => { + dispatch(deleteQueryParam({ queryParam: ['facilityInspireId'] })); + history.push(item.url); + }, + items: item.items + ? collection.map((facility) => ({ + title: facility.facilityInspireId, + url: facility.facilityInspireId, + presetItem: true, + onClick: (pathname) => { + if (facility.facilityInspireId !== search.facilityInspireId) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + }, + }), + ); + } + if (pathname !== item.items[0].url) { + history.push(item.items[0].url); + } + }, + active: (pathname) => { + return ( + search.facilityInspireId === facility.facilityInspireId && + pathname.includes(item.url) + ); + }, + items: [ + ...item.items.map((child) => ({ + ...child, + redirect: (pathname) => { + // if ( + // search.facilityInspireId !== facility.facilityInspireId && + // pathname === child.url + // ) { + // history.push(item.url); + // } + }, + active: (pathname) => { + return ( + search.facilityInspireId === facility.facilityInspireId && + pathname.includes(child.url) + ); + }, + onClick: (pathname) => { + if (facility.facilityInspireId !== search.facilityInspireId) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + }, + }), + ); + } + if (pathname !== child.url) { + history.push(child.url); + } + }, + })), + ], + })) + : [], + })); + } else if (preset === 'installations') { + return items.map((item) => ({ + ...item, + onClick: () => { + dispatch( + deleteQueryParam({ + queryParam: ['facilityInspireId', 'installationInspireId'], + }), + ); + history.push(item.url); + }, + items: item.items + ? collection.map((facility) => ({ + title: facility.facilityInspireId, + url: facility.facilityInspireId, + presetItem: true, + onClick: (pathname) => { + if (facility.facilityInspireId !== search.facilityInspireId) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + installationInspireId: facility.installations[0], + }, + }), + ); + } + if (pathname !== item.items[0].url) { + history.push(item.items[0].url); + } + }, + active: (pathname) => { + return ( + search.facilityInspireId === facility.facilityInspireId && + pathname.includes(item.url) + ); + }, + items: [ + ...facility.installations.map((installation) => ({ + title: installation, + url: installation, + presetItem: true, + onClick: (pathname) => { + if ( + installation !== search.installationInspireId || + facility.facilityInspireId !== search.facilityInspireId + ) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + installationInspireId: installation, + }, + }), + ); + } + if (pathname !== item.items[0].url) { + history.push(item.items[0].url); + } + }, + active: (pathname) => { + return ( + search.facilityInspireId === facility.facilityInspireId && + pathname.includes(item.url) + ); + }, + items: [ + ...item.items.map((child) => ({ + ...child, + redirect: (pathname) => { + // if ( + // search.facilityInspireId !== facility.facilityInspireId && + // pathname === child.url + // ) { + // history.push(item.url); + // } + }, + active: (pathname) => { + return ( + search.facilityInspireId === + facility.facilityInspireId && + pathname.includes(child.url) + ); + }, + onClick: (pathname) => { + if ( + facility.facilityInspireId !== search.facilityInspireId + ) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + }, + }), + ); + } + if (pathname !== child.url) { + history.push(child.url); + } + }, + })), + ], + })), + ], + })) + : [], + })); + } else if (preset === 'lcps') { + console.log(collection); + return items.map((item) => ({ + ...item, + onClick: () => { + dispatch( + deleteQueryParam({ + queryParam: [ + 'facilityInspireId', + 'installationInspireId', + 'lcpInspireId', + ], + }), + ); + history.push(item.url); + }, + items: item.items + ? collection.map((facility) => ({ + title: facility.facilityInspireId, + url: facility.facilityInspireId, + presetItem: true, + onClick: (pathname) => { + if (facility.facilityInspireId !== search.facilityInspireId) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + installationInspireId: + facility.installations[0].installationInspireId, + lcpInspireId: facility.installations[0].lcps[0], + }, + }), + ); + } + if (pathname !== item.items[0].url) { + history.push(item.items[0].url); + } + }, + active: (pathname) => { + return ( + search.facilityInspireId === facility.facilityInspireId && + pathname.includes(item.url) + ); + }, + items: [ + ...facility.installations.map((installation) => ({ + title: installation.installationInspireId, + url: installation.installationInspireId, + presetItem: true, + onClick: (pathname) => { + if ( + installation.installationInspireId !== + search.installationInspireId || + facility.facilityInspireId !== search.facilityInspireId + ) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + installationInspireId: + installation.installationInspireId, + lcpInspireId: installation.lcps[0], + }, + }), + ); + } + if (pathname !== item.items[0].url) { + history.push(item.items[0].url); + } + }, + active: (pathname) => { + return ( + search.installationInspireId === + installation.installationInspireId && + pathname.includes(item.url) + ); + }, + items: [ + ...installation.lcps.map((lcp) => ({ + title: lcp, + url: lcp, + presetItem: true, + onClick: (pathname) => { + if ( + lcp !== search.lcpInspireId || + installation.installationInspireId !== + search.installationInspireId || + facility.facilityInspireId !== search.facilityInspireId + ) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + installationInspireId: + installation.installationInspireId, + lcpInspireId: lcp, + }, + }), + ); + } + if (pathname !== item.items[0].url) { + history.push(item.items[0].url); + } + }, + active: (pathname) => { + return ( + search.lcpInspireId === lcp && + pathname.includes(item.url) + ); + }, + items: [ + ...item.items.map((child) => ({ + ...child, + redirect: (pathname) => { + // if ( + // search.facilityInspireId !== facility.facilityInspireId && + // pathname === child.url + // ) { + // history.push(item.url); + // } + }, + active: (pathname) => { + return ( + search.facilityInspireId === + facility.facilityInspireId && + search.installationInspireId === + installation.installationInspireId && + search.lcpInspireId === lcp && + pathname.includes(child.url) + ); + }, + onClick: (pathname) => { + if ( + lcp !== search.lcpInspireId || + installation.installationInspireId !== + search.installationInspireId || + facility.facilityInspireId !== + search.facilityInspireId + ) { + dispatch( + setQueryParam({ + queryParam: { + facilityInspireId: facility.facilityInspireId, + installationInspireId: + installation.installationInspireId, + lcpInspireId: lcp, + }, + }), + ); + } + if (pathname !== child.url) { + history.push(child.url); + } + }, + })), + ], + })), + ], + })), + ], + })) + : [], + })); } - return false; }; -const View = ({ - id, - onTabChange, - data, - mode = 'view', - properties, - intl, - location, - navigation, - ...rest -}) => { +const View = ({ content, ...props }) => { + const customPresets = { + facilities: { + get: getFacilities, + key: 'facilities', + }, + installations: { + get: getInstallations, + key: 'installations', + }, + lcps: { + get: getLcps, + key: 'lcps', + }, + }; const history = useHistory(); - const dispatch = useDispatch(); - const pathKey = useSelector((state) => state.router.location.key); - const tabsState = useSelector((state) => state.tabs_block[pathKey] || {}); - const mounted = React.useRef(); - const saved_blocks_layout = React.useRef([]); - const blocks_layout = properties.blocks_layout?.items; - const pathname = location.pathname.replace(/\/$/, ''); - const [navTabs, setNavTabs] = useState(_flattenArray(navigation.items)); - const { useNavigation = false, tabsLayout = [] } = data; - const globalActiveTab = tabsState[id] || 0; - const tabs = data.tabs - ? [ - ...data.tabs.map((tab, index) => ({ - ...tab, - index, - id: tab.title, - parentId: - tab.parentTitle !== tab.title ? tab.parentTitle || null : null, - })), - ] - : []; - const orderedTabs = flattenArray(arrayToTree(tabs, { dataField: null })); - // We have the following "racing" condition: - // The tabsblockview is mounted sometime before the GET_CONTENT_SUCCESS - // action is triggered, so even if we "fix" the display, - // the global state.data.blocks_layout will be overwritten with the "wrong" - // content. So we need to watch if the blocks_layout is rewritten, to trigger - // the tab change again - React.useEffect(() => { - if (!mounted.current && mode === 'view') { - const newTabsState = {}; - Object.keys(tabsState).forEach((blockid) => { - newTabsState[blockid] = 0; - }); - dispatch(setActiveTab(id, 0, mode, newTabsState, pathKey)); - mounted.current = true; - } - if ( - mode === 'view' && - !isEqual(blocks_layout, saved_blocks_layout.current) - ) { - const newTabsState = {}; - saved_blocks_layout.current = blocks_layout; - Object.keys(tabsState).forEach((blockid) => { - newTabsState[blockid] = 0; - }); - dispatch(setActiveTab(id, 0, mode, newTabsState, pathKey)); + const { data } = props; + const [collection, setCollection] = useState([]); + const pathname = props.pathname; + const preset = customPresets[data.preset?.value] || {}; + const parent = data.parent?.value; + let navigation = []; + + const updateCollcetion = () => { + const newCollection = + props.discodata_resources.data[preset?.key]?.[ + props.search.siteInspireId + ] || []; + if (JSON.stringify(newCollection) !== JSON.stringify(collection)) { + setCollection(newCollection); } - }, [dispatch, id, mode, tabsState, blocks_layout, pathKey]); + }; - React.useEffect(() => { - setNavTabs(_flattenArray(navigation.items)); - }, [navigation]); + useEffect(() => { + updateCollcetion(); + /* eslint-disable-next-line */ + }, []) - const blocksFieldname = getBlocksFieldname(properties); + useEffect(() => { + if (preset.get && preset.key) { + const key = `${preset.key}-${props.search.siteInspireId}`; + if ( + !props.discodata_resources.data[preset.key]?.[ + props.search.siteInspireId + ] && + !props.discodata_resources.pendingRequests[key] + ) { + preset.get(props.dispatch, props.search.siteInspireId); + } + } + /* eslint-disable-next-line */ + }, [preset]) + + useEffect(() => { + updateCollcetion(); + /* eslint-disable-next-line */ + }, [props.discodata_resources.data[preset?.key]?.[props.search.siteInspireId]]) - const renderTab = React.useCallback( - (tab, index = 0) => { - const blockIds = tabsLayout[index] || []; - return ( - - {blockIds.map((block) => { - const Block = - blocks.blocksConfig[ - properties[blocksFieldname]?.[block]?.['@type'] - ]?.['view'] || null; - return Block !== null ? ( - <> - - - ) : ( -
- {intl.formatMessage(messages.unknownBlock, { - block: properties[blocksFieldname]?.[block]?.['@type'], - })} -
- ); - })} -
+ if (props.navigation?.items?.length && parent) { + if (preset.key && collection.length) { + navigation = flattenArray( + makeNewNavigation( + props.navigation.items, + preset.key, + collection, + props.search, + history, + props.dispatch, + ), ); - }, - [tabsLayout, blocksFieldname, intl, location?.pathname, properties], // TODO: fill in the rest of the array - ); + console.log(navigation); + } else if (preset.key && !collection.length) { + navigation = flattenArray(props.navigation.items, {}, 0, 0); + } else { + navigation = flattenArray(props.navigation.items); + } + } - const menu = { pointing: true }; - const grid = { paneWidth: 9, tabWidth: 3, stackable: true }; - const position = data?.position || 'top'; - if (mode === 'edit') { - menu.attached = false; - menu.tabular = false; - } else { - switch (position) { - case 'top': - break; - case 'bottom': - menu.attached = 'bottom'; - break; - case 'left': - menu.fluid = true; - menu.vertical = true; - menu.tabular = true; - break; - case 'right': - menu.fluid = true; - menu.vertical = true; - menu.tabular = 'right'; + useEffect(() => { + for (let i = 0; i < navigation.length; i++) { + if (navigation[i].redirect) { + navigation[i].redirect(pathname); break; - default: + } } - } - if (!useNavigation) { - return ( -
-
- {tabs.length ? ( - { - dispatch( - setActiveTab(id, activeIndex, mode, tabsState, pathKey), - ); + /* eslint-disable-next-line */ + }, [navigation]) + + return navigation.length ? ( +
+ + {navigation.map((item, index) => { + const url = !item.presetItem ? getBasePath(item.url) : ''; + const name = item.title; + const active = pathname.includes(item.url); + const hasDescendents = hasDescendentActive(item, pathname); + const hasAscendents = + hasAscendentActive(navigation, item, pathname) === 1; + const isHidden = false; + // !active && item.depth > 0 + // ? !hasAscendents && !hasDescendents + // : false; + return ( + { + item.onClick ? item.onClick(pathname) : history.push(url); }} - activeIndex={globalActiveTab} - panes={orderedTabs.map((child, index) => { - const menuItemClassName = `${position} ${mode} ${ - hasChildActive(orderedTabs, child, globalActiveTab) - ? 'active by-child' - : '' - } ${ - isHidden(orderedTabs, child, globalActiveTab) ? 'hidden' : '' - } depth_${child.depth || 0}`; - return { - render: () => - mode === 'view' && renderTab(child, child.index), - menuItem: ( - - {child.title} - - ), - }; - })} /> - ) : ( - <> -
- {mode === 'view' ? renderTab(0, {}) : ''} - - )} -
-
- ); - } - return ( -
-
- {navTabs.length ? ( - { - const item = navTabs[activeIndex]; - history.push(item.url === '' ? '/' : item.url); - // dispatch(setActiveTab(id, activeIndex, mode, tabsState, pathKey)); - }} - activeIndex={globalActiveTab} - panes={navTabs.map((child, index) => { - const menuItemClassName = `${position} ${mode} ${ - _isHidden(navTabs, child, pathname) ? 'hidden' : '' - } depth_${child.depth || 0}`; - return { - render: () => mode === 'view' && renderTab(child), - menuItem: ( - - {child.title} - - ), - }; - })} - /> - ) : ( - <> -
- {mode === 'view' ? renderTab({}) : ''} - - )} -
+ ); + })} +
+ ) : props.mode === 'edit' ? ( +

+ There are no pages inside of selected page. Make sure you add pages or + delete the block +

+ ) : ( + '' ); }; export default compose( - connect( - (state, props) => ({ - location: state.router.location, - content: - state.prefetch?.[state.router.location.pathname] || state.content.data, - lang: state.intl.locale, - navigation: getNavigationByParent( - state.navigation.items, - props.data?.parent, - ), - discodata_resources: state.discodata_resources, - discodata_query: state.discodata_query, - }), - { - getDiscodataResource, - setQueryParam, - deleteQueryParam, - }, - ), + connect((state, props) => ({ + query: state.router.location.search, + content: + state.prefetch?.[state.router.location.pathname] || state.content.data, + pathname: state.router.location.pathname, + search: state.discodata_query.search, + discodata_resources: state.discodata_resources, + navigation: getNavigationByParent( + state.navigation.items, + props.data?.parent?.value, + ), + })), )(View); diff --git a/src/components/manage/Blocks/SidebarBlock/schema.jsx b/src/components/manage/Blocks/SidebarBlock/schema.jsx deleted file mode 100644 index 0e0bd8f2..00000000 --- a/src/components/manage/Blocks/SidebarBlock/schema.jsx +++ /dev/null @@ -1,87 +0,0 @@ -export const Tab = (props) => { - return { - title: 'Tab', - fieldsets: [ - { - id: 'default', - title: 'Default', - fields: ['title', 'parentTitle'], - }, - ], - - properties: { - title: { - type: 'string', - title: 'Title', - }, - parentTitle: { - factory: 'Choice', - type: 'string', - choices: [ - ['', 'No value'], - ...(props.data.tabs?.map((tab) => [tab.title, tab.title]) || []), - ], - title: 'Parent', - }, - }, - - required: ['title'], - }; -}; - -export const Tabs = (props) => { - return { - title: 'Tabs', - - fieldsets: [ - { - id: 'default', - title: 'Default', - fields: ['tabs', 'parent'], - }, - { - id: 'settings', - title: 'Settings', - fields: ['useNavigation', 'position', 'css_class'], - }, - ], - - properties: { - css_class: { - title: 'CSS Class', - default: 'default-tabsblock', - widget: 'string', - }, - tabs: { - widget: 'object_list', - title: 'Tabs', - // this is an invention, should confront with dexterity serializer - schema: Tab(props), - }, - parent: { - widget: 'object_by_path', - title: 'Parent page', - }, - useNavigation: { - type: 'boolean', - title: 'Use navigation', - }, - position: { - title: 'Position', - description: 'Position of the tabs, content related', - factory: 'Choice', - type: 'string', - choices: [ - ['top', 'Top'], - ['bottom', 'Bottom'], - ['left', 'Left'], - ['right', 'Right'], - ], - }, - }, - - required: ['display', 'cards'], - }; -}; - -export default Tabs; diff --git a/src/components/manage/Blocks/SidebarBlock/style.css b/src/components/manage/Blocks/SidebarBlock/style.css index 4f149c02..0293dddf 100644 --- a/src/components/manage/Blocks/SidebarBlock/style.css +++ b/src/components/manage/Blocks/SidebarBlock/style.css @@ -1,83 +1,58 @@ -.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; +.sidebar-block .ui.menu { + background: transparent; + border-bottom: none; flex-flow: column; - max-width: 220px; -} - -.sidebar-block-container .sidebar.hidden { - width: 0; - overflow: hidden; } -.sidebar-block-container .sidebar.show { - width: 100%; +.sidebar-block .ui.menu .item { + background: transparent; } -.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 { +.sidebar-block .ui.menu .item, +.sidebar-block .ui.menu .item.active { background-color: transparent; border: none; text-align: left; - color: #333333; + color: #333333 !important; 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 { +.sidebar-block .ui.menu .item.hidden { display: none; } -.sidebar-block-container .sidebar .tabs .tabs__item.show { +.sidebar-block .ui.menu .item.show { display: block; } -.sidebar-block-container .sidebar .tabs .tabs__item.depth__2 { - padding-left: 1em; +.sidebar-block .ui.menu .item.depth__1 { + margin-left: 1em; +} + +.sidebar-block .ui.menu .item.depth__2 { + margin-left: 2em; } -.sidebar-block-container .sidebar .tabs .tabs__item.depth__3 { - padding-left: 2em; +.sidebar-block .ui.menu .item.depth__3 { + margin-left: 3em; } -.sidebar-block-container .sidebar .tabs .tabs__item.depth__4 { - padding-left: 3em; +.sidebar-block .ui.menu .item.depth__4 { + margin-left: 4em; } -.sidebar-block-container .sidebar .tabs .tabs__item_active { - color: #ED776A +.sidebar-block .ui.menu .item.depth__5 { + margin-left: 5em; } -.sidebar-block-container .sidebar .tabs .tabs__item_active.depth__2 { - color: #ED776A +.sidebar-block .ui.menu .item.active { + color: #ED776A!important; + background-color: transparent!important; } -.sidebar-block-container .sidebar .tabs .tabs__item_active.depth__3 { +.sidebar-block .ui.menu .item.active.depth__3 { color: #333333; - font-weight: bold;; + /* font-weight: bold;; */ } diff --git a/src/helpers/api.jsx b/src/helpers/api.jsx new file mode 100644 index 00000000..968363a1 --- /dev/null +++ b/src/helpers/api.jsx @@ -0,0 +1,146 @@ +import { settings } from '~/config'; +import { + setDiscodataResource, + setDiscodataResourcePending, +} from 'volto-datablocks/actions'; +import axios from 'axios'; + +export const parseResponse = (response) => { + try { + return JSON.parse(response.request.response); + } catch { + return {}; + } +}; + +export const getFacilities = (dispatch, siteInspireId) => { + const sql = encodeURI(`SELECT DISTINCT facilityInspireId + FROM [IED].[latest].[Browse6Header] as R + WHERE R.[siteInspireId] = '${siteInspireId}' + GROUP BY R.[facilityInspireId]`); + const url = `${settings.providerUrl}?query=${sql}`; + return new Promise((resolve, reject) => { + dispatch( + setDiscodataResourcePending({ key: `facilities-${siteInspireId}` }), + ); + axios + .get(url) + .then((response) => { + dispatch( + setDiscodataResource({ + collection: parseResponse(response).results, + resourceKey: 'facilities', + key: siteInspireId, + }), + ); + resolve(parseResponse(response).results); + }) + .catch((error) => { + reject(error); + }); + }); +}; + +export const getInstallations = (dispatch, siteInspireId) => { + const sql = encodeURI(`SELECT DISTINCT + facilityInspireId, + string_agg(concat(installationInspireId, ''), ',') as installations + FROM [IED].[latest].[Browse8Header] as Results + WHERE siteInspireId LIKE 'UK.CAED/NRW170267.SITE' + GROUP BY facilityInspireId`); + const url = `${settings.providerUrl}?query=${sql}`; + return new Promise((resolve, reject) => { + dispatch( + setDiscodataResourcePending({ key: `installations-${siteInspireId}` }), + ); + axios + .get(url) + .then((response) => { + dispatch( + setDiscodataResource({ + collection: parseResponse(response).results.map((item) => ({ + ...item, + installations: [...new Set(item.installations.split(','))], + })), + resourceKey: 'installations', + key: siteInspireId, + }), + ); + resolve(parseResponse(response).results); + }) + .catch((error) => { + reject(error); + }); + }); +}; + +export const getLcps = (dispatch, siteInspireId) => { + const sql = encodeURI(`SELECT DISTINCT + facilityInspireId, + installationInspireId, + string_agg(concat(lcpInspireId, ''), ',') as lcps + FROM [IED].[latest].[vw_Browse10_Header] as Results + WHERE siteInspireId LIKE '${siteInspireId}' + GROUP BY facilityInspireId, installationInspireId`); + const url = `${settings.providerUrl}?query=${sql}`; + return new Promise((resolve, reject) => { + dispatch(setDiscodataResourcePending({ key: `lcps-${siteInspireId}` })); + axios + .get(url) + .then((response) => { + const parsedResponse = parseResponse(response).results; + const collection = []; + parsedResponse.forEach((item) => { + const facilityIndex = collection + .map((facility) => facility.facilityInspireId) + .indexOf(item.facilityInspireId); + if (facilityIndex > -1) { + const installationIndex = collection[facilityIndex].installations + .map((installation) => installation.installationInspireId) + .indexOf(item.installationInspireId); + if (installationIndex > -1) { + collection[facilityIndex].installations[installationIndex] = { + ...collection[facilityIndex].installations[installationIndex], + lcps: [ + ...collection[facilityIndex].installations[installationIndex] + .lcps, + [...new Set(item.lcps.split(','))], + ], + }; + } + // else { + // collection[facilityIndex].installations[installationIndex] = { + // ...collection[facilityIndex].installations[installationIndex], + // lcps: [ + // ...collection[facilityIndex].installations[installationIndex] + // .lcps, + // [...new Set(item.lcps.split(','))], + // ], + // }; + // } + } else { + collection.push({ + facilityInspireId: item.facilityInspireId, + installations: [ + { + installationInspireId: item.installationInspireId, + lcps: [...new Set(item.lcps.split(','))], + }, + ], + }); + } + }); + dispatch( + setDiscodataResource({ + collection: [...collection], + resourceKey: 'lcps', + key: siteInspireId, + }), + ); + resolve(parseResponse(response).results); + }) + .catch((error) => { + reject(error); + }); + }); +}; diff --git a/src/localconfig.js b/src/localconfig.js index 3f8dd552..8f700fe8 100644 --- a/src/localconfig.js +++ b/src/localconfig.js @@ -16,17 +16,17 @@ import ArticlesListEdit from '~/components/manage/Blocks/ArticlesList/Edit'; import ChildrenLinksView from '~/components/manage/Blocks/ChildrenLinks/View'; import ChildrenLinksEdit from '~/components/manage/Blocks/ChildrenLinks/Edit'; -import EprtrSidebarBlockEdit from '~/components/manage/Blocks/SidebarBlock/Edit'; -import EprtrSidebarBlockView from '~/components/manage/Blocks/SidebarBlock/View'; - import EprtrFiltersBlockEdit from '~/components/manage/Blocks/FiltersBlock/Edit'; import EprtrFiltersBlockView from '~/components/manage/Blocks/FiltersBlock/View'; import DiscodataOpenlayersMapBlockEdit from '~/components/manage/Blocks/DiscodataOpenlayersMapBlock/Edit'; import DiscodataOpenlayersMapBlockView from '~/components/manage/Blocks/DiscodataOpenlayersMapBlock/View'; -import NavigationBlockEdit from 'volto-tabsview/components/theme/NavigationBlock/Edit'; -import NavigationBlockView from 'volto-tabsview/components/theme/NavigationBlock/View'; +import NavigationBlockEdit from '~/components/manage/Blocks/NavigationBlock/Edit'; +import NavigationBlockView from '~/components/manage/Blocks/NavigationBlock/View'; + +import SidebarBlockEdit from '~/components/manage/Blocks/SidebarBlock/Edit'; +import SidebarBlockView from '~/components/manage/Blocks/SidebarBlock/View'; import BlocksWidget from '~/components/manage/Widgets/BlocksWidget'; @@ -38,8 +38,6 @@ import linkSVG from '@plone/volto/icons/link.svg'; import listSVG from '@plone/volto/icons/content-listing.svg'; import worldSVG from '@plone/volto/icons/world.svg'; -import { SIDEBAR } from '~/constants/Blocks'; - export function applyConfig(voltoConfig) { const config = { ...voltoConfig }; addCustomGroup(config, { id: 'eprtr_blocks', title: 'Eprtr Blocks' }); @@ -109,13 +107,13 @@ export function applyConfig(voltoConfig) { icon: linkSVG, }; - config.blocks.blocksConfig[SIDEBAR] = { - id: SIDEBAR, - title: 'Eprtr sidebar block', + config.blocks.blocksConfig.sidebar_block = { + id: 'sidebar_block', + title: 'Sidebar Block', group: 'eprtr_blocks', - view: EprtrSidebarBlockView, - edit: EprtrSidebarBlockEdit, - icon: packSVG, + view: SidebarBlockView, + edit: SidebarBlockEdit, + icon: linkSVG, }; config.blocks.blocksConfig.eprtr_filters_block = { diff --git a/theme/site/globals/site.overrides b/theme/site/globals/site.overrides index 28193976..15afe8e1 100644 --- a/theme/site/globals/site.overrides +++ b/theme/site/globals/site.overrides @@ -1050,12 +1050,12 @@ body.has-sidebar { &.red-menu { .item { color: #D63D27; - background-color: transparent; + background-color: transparent !important; padding: 0 2em !important; border-radius: 2em; &:before { width: 0; - background-color: transparent; + background-color: transparent !important; } } .active.item { @@ -1070,8 +1070,8 @@ body.has-sidebar { } &:before { width: 0; - background: transparent; - background-color: transparent; + background: transparent !important; + background-color: transparent !important; } } } @@ -1081,11 +1081,12 @@ body.has-sidebar { color: #000 !important; &:before { width: 0; - background-color: transparent; + background-color: transparent !important; } } .active.item { - background-color: transparent; + border: none !important; + background-color: transparent !important; } } &.item { @@ -1116,7 +1117,7 @@ body.has-sidebar { } } .active.item { - background: #fff; + background-color: transparent !important; color: #4296B3; font-weight: bold; border-bottom: 2px solid #4296B3; @@ -1316,13 +1317,11 @@ h3 { } } -.section-browse-the-data { - #page-document.ui.container { - width: 100%!important; - margin: 0!important; - margin-left: 0!important; - margin-right: 0!important; - margin-top: 0!important; - margin-bottom: 0!important; - } +#page-document.ui.container { + width: 100%!important; + margin: 0!important; + margin-left: 0!important; + margin-right: 0!important; + margin-top: 0!important; + margin-bottom: 0!important; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3934cf31..b0ffdbbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1800,9 +1800,9 @@ dependencies: "@types/node" ">= 8" -"@plone/volto@github:eea/volto#7.8.2-beta.3": - version "7.8.2" - resolved "https://codeload.github.com/eea/volto/tar.gz/0eb86d05cd9fc3bdc5da8023bf8c8d36f7e3e42d" +"@plone/volto@github:eea/volto#7.11.1-beta.1": + version "7.11.1" + resolved "https://codeload.github.com/eea/volto/tar.gz/3c10221aa77f906bb1febcb2ae78d5b73a18ac70" dependencies: "@babel/core" "7.9.6" "@babel/plugin-proposal-decorators" "7.8.3" @@ -16430,9 +16430,9 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -"volto-slate@github:eea/volto-slate#0.5.2": +"volto-slate@github:eea/volto-slate#0.6.0.beta.1": version "0.4.1" - resolved "https://codeload.github.com/eea/volto-slate/tar.gz/a5f73d0d8bb10866dd54f680034f90b16f17bbb6" + resolved "https://codeload.github.com/eea/volto-slate/tar.gz/ba95e06754410479cd229d877d24473718b9391f" dependencies: "@types/jest" "^25.2.3" classnames "2.2.6"