@plone/volto
Version:
Volto
484 lines (463 loc) • 15.3 kB
JSX
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;