Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-9322: ezobjectrelationlist field allows selecting the same content multiple times #1409

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
const itemNodeNameCell = itemNode.querySelector('.ibexa-relations__item-name');

itemNode.dataset.contentId = contentId;
itemNode.dataset.locationId = locationId;
itemNode.querySelector('.ibexa-relations__table-action--remove-item').addEventListener('click', removeItem, false);

itemNodeNameCell.dataset.ibexaUpdateContentId = contentId;
Expand All @@ -113,12 +114,12 @@
sourceInput.dispatchEvent(new CustomEvent(EVENT_CUSTOM));
};
const onConfirm = (items) => {
items = excludeDuplicatedItems(items);
const itemsWithoutDuplicate = excludeDuplicatedItems(items);

renderRows(items);
renderRows(itemsWithoutDuplicate);
attachRowsEventHandlers();

selectedItems = [...selectedItems, ...items.map((item) => item.ContentInfo.Content._id)];
selectedItems = [...selectedItems, ...itemsWithoutDuplicate.map((item) => item.ContentInfo.Content._id)];

updateInputValue(selectedItems);
closeUDW();
Expand All @@ -128,9 +129,15 @@
};
const openUDW = (event) => {
event.preventDefault();

const selectedItemsRow = fieldContainer.querySelectorAll(SELECTOR_ROW);
const config = JSON.parse(event.currentTarget.dataset.udwConfig);
const limit = parseInt(event.currentTarget.dataset.limit, 10);
const selectedLocations = [...selectedItemsRow].reduce((locationsIds, selectedItemRow) => {
const { locationId } = selectedItemRow.dataset;
const parsedLocationId = parseInt(locationId, 10);

return isNaN(parsedLocationId) ? locationsIds : [...locationsIds, parsedLocationId];
}, []);
const title =
limit === 1
? Translator.trans(
Expand All @@ -151,17 +158,15 @@
onCancel: closeUDW,
title,
startingLocationId,
selectedLocations,
isInitLocationsDeselectionBlocked: true,
...config,
multiple: isSingle ? false : selectedItemsLimit !== 1,
multipleItemsLimit: selectedItemsLimit > 1 ? selectedItemsLimit - selectedItems.length : selectedItemsLimit,
}),
);
};
const excludeDuplicatedItems = (items) => {
selectedItemsMap = items.reduce((total, item) => ({ ...total, [item.ContentInfo.Content._id]: item }), selectedItemsMap);

return items.filter((item) => selectedItemsMap[item.ContentInfo.Content._id]);
};
const excludeDuplicatedItems = (items) => items.filter((item) => !selectedItems.includes(item.ContentInfo.Content._id));
const renderRow = (item, index) => {
const { escapeHTML } = ibexa.helpers.text;
const { formatShortDateTime } = ibexa.helpers.timezone;
Expand Down Expand Up @@ -304,7 +309,6 @@
updateInputValue(selectedItems);
};
let selectedItems = [...fieldContainer.querySelectorAll(SELECTOR_ROW)].map((row) => parseInt(row.dataset.contentId, 10));
let selectedItemsMap = selectedItems.reduce((total, item) => ({ ...total, [item]: item }), {});

updateAddBtnState();
attachRowsEventHandlers();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.c-grid {
height: 100%;
width: 100%;

&__items-wrapper {
padding: 0 calculateRem(16px) calculateRem(16px);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,11 @@
.ibexa-icon {
fill: $ibexa-color-dark;
}

&:disabled {
.ibexa-icon {
fill: $ibexa-color-dark-300;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@
flex: 3;
background-color: $ibexa-color-white;
overflow: auto;

&--init-locations-alert-visible {
flex-wrap: wrap;

.ibexa-alert {
margin: 0;
}

.c-udw-tab__main-children {
height: calc(100% - calculateRem(48px));
}
}
}

&__main-children {
display: flex;
}

&__init-locations-alert-container {
width: 100%;
}

&__right-sidebar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@
<target state="new">Filters</target>
<note>key: filters.title</note>
</trans-unit>
<trans-unit id="94d848778c1cab60409c69dcff7e1cb8ea415ffc" resname="init_selected_locations.alert.title">
<source>Items already added to the list are marked as selected and unable to deselect.</source>
<target state="new">Items already added to the list are marked as selected and unable to deselect.</target>
<note>key: init_selected_locations.alert.title</note>
</trans-unit>
<trans-unit id="2d4febe56a341671c069ff47b6e6adfd2bba005c" resname="limitation.pick.error">
<source>Could not fetch content names</source>
<target state="new">Could not fetch content names</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@
{% set body_rows = body_rows|merge([{
cols: body_row_cols,
class: 'ibexa-relations__item',
attr: { 'data-content-id': relation.contentInfo.id },
attr: {
'data-content-id': relation.contentInfo.id,
'data-location-id': relation.contentInfo.mainLocationId,
},
}]) %}
{% elseif relation.unauthorized %}
{% set col_raw_unauthorised %}
Expand All @@ -194,7 +197,10 @@
{ content: col_raw_order_input, raw: true, attr: { class: 'ibexa-relations__order-wrapper' } }
],
class: 'ibexa-relations__item',
attr: { 'data-content-id': relation.contentId },
attr: {
'data-content-id': relation.contentId,
'data-location-id': relation.contentInfo.mainLocationId,
},
}]) %}
{% endif %}
{% endfor %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ const ContentTableItem = ({ location }) => {
const [multiple] = useContext(MultipleConfigContext);
const rootLocationId = useContext(RootLocationIdContext);
const contentTypeInfo = contentTypesMap[location.ContentInfo.Content.ContentType._href];
const { checkIsSelectable, checkIsSelectionBlocked } = useSelectedLocationsHelpers();
const { checkIsSelectable, checkIsSelectionBlocked, checkIsDeselectionBlocked } = useSelectedLocationsHelpers();
const isNotSelectable = !checkIsSelectable(location);
const isSelectionBlocked = checkIsSelectionBlocked(location);
const isDeselectionBlocked = checkIsDeselectionBlocked(location);
const className = createCssClassNames({
'ibexa-table__row c-content-table-item': true,
'c-content-table-item--marked': markedLocationId === location.id,
Expand Down Expand Up @@ -82,7 +83,14 @@ const ContentTableItem = ({ location }) => {
}
};
const renderToggleSelection = () => {
return <ToggleSelection location={location} multiple={multiple} isDisabled={isSelectionBlocked} isHidden={isNotSelectable} />;
return (
<ToggleSelection
location={location}
multiple={multiple}
isDisabled={isSelectionBlocked || isDeselectionBlocked}
isHidden={isNotSelectable}
/>
);
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ const FinderLeaf = ({ location }) => {
const contentTypesMap = useContext(ContentTypesMapContext);
const [, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const [multiple] = useContext(MultipleConfigContext);
const { checkIsSelectable, checkIsSelected, checkIsSelectionBlocked } = useSelectedLocationsHelpers();
const { checkIsSelectable, checkIsSelected, checkIsSelectionBlocked, checkIsDeselectionBlocked } = useSelectedLocationsHelpers();
const isSelected = checkIsSelected(location);
const isNotSelectable = !checkIsSelectable(location);
const isSelectionBlocked = checkIsSelectionBlocked(location);
const isDeselectionBlocked = checkIsDeselectionBlocked(location);
const markLocation = ({ nativeEvent }) => {
const isSelectionButtonClicked = nativeEvent.target.closest('.c-udw-toggle-selection');
const isMarkedLocationClicked = location.id === markedLocationId;
Expand All @@ -49,7 +50,14 @@ const FinderLeaf = ({ location }) => {
}
};
const renderToggleSelection = () => {
return <ToggleSelection location={location} multiple={multiple} isDisabled={isSelectionBlocked} isHidden={isNotSelectable} />;
return (
<ToggleSelection
location={location}
multiple={multiple}
isDisabled={isSelectionBlocked || isDeselectionBlocked}
isHidden={isNotSelectable}
/>
);
};
const className = createCssClassNames({
'c-finder-leaf': true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ const GridViewItem = ({ location, version }) => {
const containersOnly = useContext(ContainersOnlyContext);
const contentTypeInfo = contentTypesMap[location.ContentInfo.Content.ContentType._href];
const { isContainer } = contentTypeInfo;
const { checkIsSelectable, checkIsSelected, checkIsSelectionBlocked } = useSelectedLocationsHelpers();
const { checkIsSelectable, checkIsSelected, checkIsSelectionBlocked, checkIsDeselectionBlocked } = useSelectedLocationsHelpers();
const isSelected = checkIsSelected(location);
const isNotSelectable = !checkIsSelectable(location);
const isSelectionBlocked = checkIsSelectionBlocked(location);
const isDeselectionBlocked = checkIsDeselectionBlocked(location);
const className = createCssClassNames({
'ibexa-grid-view-item': true,
'ibexa-grid-view-item--marked': markedLocationId === location.id,
Expand Down Expand Up @@ -70,7 +71,12 @@ const GridViewItem = ({ location, version }) => {
const renderToggleSelection = () => {
return (
<div className="ibexa-grid-view-item__checkbox">
<ToggleSelection location={location} multiple={multiple} isDisabled={isSelectionBlocked} isHidden={isNotSelectable} />
<ToggleSelection
location={location}
multiple={multiple}
isDisabled={isSelectionBlocked || isDeselectionBlocked}
isHidden={isNotSelectable}
/>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import Thumbnail from '../../../common/thumbnail/thumbnail';

import { SelectedLocationsContext, ContentTypesMapContext } from '../../universal.discovery.module';
import { getAdminUiConfig, getTranslator } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper';
import { useSelectedLocationsHelpers } from '../../hooks/useSelectedLocationsHelpers';

const SelectedLocationsItem = ({ location, permissions }) => {
const adminUiConfig = getAdminUiConfig();
const Translator = getTranslator();
const refSelectedLocationsItem = useRef(null);
const [, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const { checkIsDeselectionBlocked } = useSelectedLocationsHelpers();
const isDeselectionBlocked = checkIsDeselectionBlocked(location);
const contentTypesMap = useContext(ContentTypesMapContext);
const clearLabel = Translator.trans(
/*@Desc("Clear selection")*/ 'selected_locations.clear_selection',
Expand Down Expand Up @@ -65,6 +68,7 @@ const SelectedLocationsItem = ({ location, permissions }) => {
onClick={removeFromSelection}
title={clearLabel}
data-tooltip-container-selector=".c-udw-tab"
disabled={isDeselectionBlocked}
>
<Icon name="discard" extraClasses="ibexa-icon--tiny-small" />
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import Icon from '../../../common/icon/icon';
import SelectedLocationsItem from './selected.locations.item';
import { createCssClassNames } from '../../../common/helpers/css.class.names';

import { SelectedLocationsContext, AllowConfirmationContext } from '../../universal.discovery.module';
import { SelectionConfigContext, SelectedLocationsContext, AllowConfirmationContext } from '../../universal.discovery.module';

const SelectedLocations = () => {
const Translator = getTranslator();
const refSelectedLocations = useRef(null);
const refTogglerButton = useRef(null);
const { isInitLocationsDeselectionBlocked, initSelectedLocationsIds } = useContext(SelectionConfigContext);
const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const allowConfirmation = useContext(AllowConfirmationContext);
const [isComponentHidden, setIsComponentHidden] = useState(true);
const [initSelectedLocations, setInitSelectedLocations] = useState([]);
const [selectedLocationsWithoutInit, setSelectedLocationsWithoutInit] = useState([]);
const [isExpanded, setIsExpanded] = useState(false);
const className = createCssClassNames({
'c-selected-locations': true,
Expand All @@ -36,16 +40,24 @@ const SelectedLocations = () => {
const togglerLabel = isExpanded ? collapseLabel : expandLabel;
const clearSelection = () => {
hideAllTooltips(refSelectedLocations.current);

if (isInitLocationsDeselectionBlocked) {
dispatchSelectedLocationsAction({ type: 'REPLACE_SELECTED_LOCATIONS', locations: initSelectedLocations });

return;
}

dispatchSelectedLocationsAction({ type: 'CLEAR_SELECTED_LOCATIONS' });
};
const toggleExpanded = () => {
setIsExpanded(!isExpanded);
};
const renderSelectionCounter = () => {
const selectedLocationsCount = isInitLocationsDeselectionBlocked ? selectedLocationsWithoutInit.length : selectedLocations.length;
const selectedLabel = Translator.transChoice(
/*@Desc("{1}%count% selected item|[2,Inf]%count% selected items")*/ 'selected_locations.selected_items',
selectedLocations.length,
{ count: selectedLocations.length },
{ count: selectedLocationsCount },
'ibexa_universal_discovery_widget',
);

Expand Down Expand Up @@ -92,11 +104,13 @@ const SelectedLocations = () => {
return null;
}

const selectedLocationsToIterate = isInitLocationsDeselectionBlocked ? selectedLocationsWithoutInit : selectedLocations;

return (
<div className="c-selected-locations__items-wrapper">
{renderActionButtons()}
<div className="c-selected-locations__items-list">
{selectedLocations.map((selectedLocation) => (
{selectedLocationsToIterate.map((selectedLocation) => (
<SelectedLocationsItem
key={selectedLocation.location.id}
location={selectedLocation.location}
Expand All @@ -109,7 +123,7 @@ const SelectedLocations = () => {
};

useEffect(() => {
if (!allowConfirmation) {
if (isComponentHidden) {
return;
}

Expand All @@ -122,7 +136,29 @@ const SelectedLocations = () => {
toggleButtonTooltip.setContent({ '.tooltip-inner': togglerLabel });
}, [isExpanded]);

if (!allowConfirmation) {
useEffect(() => {
if (isInitLocationsDeselectionBlocked) {
const initSelectedLocationsTemp = [];
const selectedLocationsWithoutInitTemp = [];

selectedLocations.forEach((selectedLocation) => {
if (initSelectedLocationsIds.includes(selectedLocation.location.id)) {
initSelectedLocationsTemp.push(selectedLocation);
} else {
selectedLocationsWithoutInitTemp.push(selectedLocation);
}
});

setInitSelectedLocations(initSelectedLocationsTemp);
setSelectedLocationsWithoutInit(selectedLocationsWithoutInitTemp);
}

const onlyInitSelectedLocationsAreSelected = initSelectedLocationsIds.length === selectedLocations.length;

setIsComponentHidden(!allowConfirmation || (onlyInitSelectedLocationsAreSelected && isInitLocationsDeselectionBlocked));
}, [selectedLocations, isInitLocationsDeselectionBlocked, allowConfirmation]);

if (isComponentHidden) {
return null;
}

Expand Down
Loading
Loading