UNPKG

@plone/volto

Version:
484 lines (463 loc) 15.3 kB
import React, { useEffect } from 'react'; import useDeepCompareEffect from 'use-deep-compare-effect'; import { FormattedMessage, useIntl } from 'react-intl'; import { useSelector, useDispatch } from 'react-redux'; import { toast } from 'react-toastify'; import uniqBy from 'lodash/uniqBy'; import { Checkbox, Message } from 'semantic-ui-react'; import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels'; import Toast from '@plone/volto/components/manage/Toast/Toast'; import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink'; import { createRelations, deleteRelations, queryRelations, } from '@plone/volto/actions/relations/relations'; import { resetSearchContent, searchContent, } from '@plone/volto/actions/search/search'; const RelationsListing = ({ relationtype, query_source, query_target, potential_sources_path, potential_targets_path, // target_filter, }) => { const intl = useIntl(); const dispatch = useDispatch(); const MAX = 40; // Maximum of rows and columns const MAX_RELATIONS = 1000; const stats = useSelector((state) => state.relations?.stats?.data || null); let relations = useSelector( (state) => state.relations?.relations?.data?.[relationtype]?.items || [], ); let potential_targets_objects = useSelector( (state) => state.search.subrequests.potential_targets?.items || [], ); let potential_sources_objects = useSelector( (state) => state.search.subrequests.potential_sources?.items || [], ); const staticCatalogVocabularyQuery = useSelector( (state) => state.relations?.relations?.data?.[relationtype] ?.staticCatalogVocabularyQuery || {}, ); // Editable if plone.api.relations available const editable = useSelector( (state) => state.relations?.relations?.data?.[relationtype]?.readonly !== true, ); let relationMatrix = {}; relations.map((tpl) => { if (relationMatrix[tpl.source.UID]) { relationMatrix[tpl.source.UID].targets.push(tpl.target); } else { relationMatrix[tpl.source.UID] = { source: tpl.source, targets: [tpl.target], }; } return relationMatrix; }); // x-axis: relation targets // ************************ let matrix_options = relations.map((relation) => ({ value: relation.target.UID, label: relation.target.title, url: relation.target['@id'], review_state: relation.target.review_state, uid: relation.target.UID, })); matrix_options = uniqBy(matrix_options, function (el) { return el.value; }); // Add potential targets const potential_targets = potential_targets_objects.map((obj) => ({ value: obj.UID, label: obj.title, url: obj['@id'], review_state: obj.review_state, uid: obj.UID, })); // Just show potential targets if no querying matrix_options = query_source === '' && query_target === '' && potential_sources_path !== '' && potential_targets_path !== '' ? potential_targets : [...matrix_options, ...potential_targets]; matrix_options = uniqBy(matrix_options, function (el) { return el.value; }); matrix_options.sort(function (a, b) { var labelA = a.label.toUpperCase(); var labelB = b.label.toUpperCase(); if (labelA < labelB) { return -1; } if (labelA > labelB) { return 1; } return 0; }); // y-axis: relation sources // ************************ let items = Object.keys(relationMatrix).map((key) => ({ value: key, label: relationMatrix[key].source.title, targets: relationMatrix[key].targets.map((el) => el.UID), url: relationMatrix[key].source['@id'], review_state: relationMatrix[key].source.review_state, })); // Add potential sources const potential_sources = potential_sources_objects.map((obj) => ({ value: obj.UID, label: obj.title, targets: relationMatrix[obj.UID]?.targets?.map((el) => el.UID) || [], url: obj['@id'], review_state: obj.review_state, })); items = query_source === '' && query_target === '' && potential_sources_path !== '' && potential_targets_path !== '' ? potential_sources : [...items, ...potential_sources]; items = uniqBy(items, function (el) { return el.value; }); items.sort(function (a, b) { var labelA = a.label.toUpperCase(); var labelB = b.label.toUpperCase(); if (labelA < labelB) { return -1; } if (labelA > labelB) { return 1; } return 0; }); useEffect(() => { // If many relations, then fetch relations only with search query on source or target if (stats?.stats[relationtype] <= MAX_RELATIONS) { dispatch(queryRelations(relationtype)); } else { dispatch( queryRelations( relationtype, false, null, null, null, query_source ? query_source.startsWith('/') ? query_source : `${query_source}*` : null, query_target ? query_target.startsWith('/') ? query_target : `${query_target}*` : null, ), ); } }, [dispatch, stats, relationtype, query_source, query_target]); // Get potential source and target objects useDeepCompareEffect(() => { // Fetch fresh potential targets if (potential_targets_path !== '/' && potential_targets_path !== '') { dispatch( searchContent( potential_targets_path, { SearchableText: query_target, metadata_fields: ['UID'], sort_on: 'sortable_title', ...staticCatalogVocabularyQuery, }, 'potential_targets', ), ); } else { dispatch(resetSearchContent('potential_targets')); } // Fetch fresh potential sources if (potential_sources_path !== '/' && potential_sources_path !== '') { dispatch( searchContent( potential_sources_path, { SearchableText: query_source, metadata_fields: ['UID'], sort_on: 'sortable_title', // No need to restrict here. ...staticCatalogVocabularyQuery, }, 'potential_sources', ), ); } else { dispatch(resetSearchContent('potential_sources')); } }, [ dispatch, potential_targets_path, potential_sources_path, staticCatalogVocabularyQuery, query_source, query_target, ]); function fetchRelations() { dispatch( queryRelations( relationtype, false, null, null, null, query_source ? query_source.startsWith('/') ? query_source : `${query_source}*` : null, query_target ? query_target.startsWith('/') ? query_target : `${query_target}*` : null, ), ); } const onSelectOptionHandler = (relation, selectedvalue, checked) => { let source = selectedvalue.y; let target = selectedvalue.x; const relation_data = [ { source: source, target: target, relation: relation, }, ]; dispatch( checked ? createRelations(relation_data) : deleteRelations(relation_data), ) .then((resp) => { fetchRelations(); }) .then(() => { toast.success( <Toast success title={intl.formatMessage(messages.success)} content={intl.formatMessage(messages.relationsUpdated)} />, ); }); }; const onSelectAllHandler = (target, items_ids, checked) => { let relation_data = []; items_ids.forEach((el) => { relation_data.push({ source: el, target: target, relation: relationtype, }); }); dispatch( checked ? createRelations(relation_data) : deleteRelations(relation_data), ) .then((resp) => { fetchRelations(); }) .then(() => { toast.success( <Toast success title={intl.formatMessage(messages.success)} content={intl.formatMessage(messages.relationsUpdated)} />, ); }); }; return ( <> {/* <div> <div> <div>{items.length} sources</div> <div>{matrix_options.length} targets</div> </div> <div> <div>query_source <b>{query_source}</b></div> <div>query_target <b>{query_target}</b></div> <div>potential_sources_path <b>{potential_sources_path}</b></div> <div>potential_targets_path <b>{potential_targets_path}</b></div> </div> </div> */} {matrix_options.length <= MAX && (items.length <= MAX) & (matrix_options.length > 0) && items.length > 0 ? ( <div className="administration_matrix"> <div className="label-options"> <div className="target-labels"> <div></div> <div> {matrix_options?.map((matrix_option) => ( <div className="label-options-label inclined" id={`label-options-label-${matrix_option.value}`} key={matrix_option.value} > <div> <UniversalLink href={matrix_option.url} className={ matrix_option.review_state !== 'published' ? 'not-published' : '' } target="_blank" > <span className="label" title={matrix_option.label}> {matrix_option.label.length > 30 ? matrix_option.label.slice(0, 27) + '...' : matrix_option.label} </span> </UniversalLink> </div> </div> ))} </div> </div> <div className="listing-row selectall" key="selectall"> <div className="listing-item"> <div /> <div className="matrix_options"> {!( relationtype === 'isReferencing' || relationtype === 'iterate-working-copy' || !editable ) ? ( matrix_options?.map((matrix_option) => ( <div key={matrix_option.value} title={ intl.formatMessage( messages.createOrDeleteRelationsToTarget, ) + ` '${matrix_option.label}'` } > <Checkbox className="toggle-target" defaultChecked={false} onChange={(event, { checked }) => onSelectAllHandler( matrix_option.value, items.map((el) => el.value), checked, ) } /> </div> )) ) : ( <FormattedMessage id="Read only for this type of relation." defaultMessage="Read only for this type of relation." /> )} </div> </div> </div> </div> <div className="items" key="items"> <> {!editable && ( <Message warning> <FormattedMessage id="Relations are editable with plone.api >= 2.0.3." defaultMessage="Relations are editable with plone.api >= 2.0.3." /> </Message> )} {items.map((item) => ( <div className="listing-row" key={item.id} id={`source-row-${item.value}`} > <div className="listing-item" key={item['@id']}> <div> <span title={item.label} className="item-title"> <UniversalLink href={item.url} className={ item.review_state !== 'published' ? 'not-published' : '' } target="_blank" > {item.label.length > 25 ? item.label.slice(0, 22) + '...' : item.label} </UniversalLink> {/* <span>targets: {item.targets.join(', ')}</span> */} </span> </div> <div className="matrix_options"> {matrix_options?.map((matrix_option) => ( <React.Fragment key={matrix_option.value}> <Checkbox className={`checkbox_${matrix_option.value}`} key={matrix_option.value} title={matrix_option.title} disabled={ relationtype === 'isReferencing' || relationtype === 'iterate-working-copy' || !editable } checked={item.targets.includes(matrix_option.value)} onChange={(event, { checked }) => { onSelectOptionHandler( relationtype, { x: matrix_option.value, y: item.value }, checked, ); }} /> </React.Fragment> ))} </div> </div> </div> ))} </> </div> </div> ) : ( <div className="administration_matrix"> {matrix_options.length > MAX || items.length > MAX ? ( <FormattedMessage id="narrowDownRelations" defaultMessage="Found {sources} sources and {targets} targets. Narrow down to {max}!" values={{ sources: items.length, targets: matrix_options.length, max: MAX, }} /> ) : query_source || query_target ? ( <div>{intl.formatMessage(messages.norelationfound)}</div> ) : ( <div>{intl.formatMessage(messages.toomanyrelationsfound)}</div> )} </div> )} </> ); }; export default RelationsListing;