diff --git a/src/bundle/Resources/public/scss/_tables.scss b/src/bundle/Resources/public/scss/_tables.scss
index 43b518ef6b..91d18d16c3 100644
--- a/src/bundle/Resources/public/scss/_tables.scss
+++ b/src/bundle/Resources/public/scss/_tables.scss
@@ -43,6 +43,10 @@
}
}
}
+
+ &--not-selectable {
+ cursor: not-allowed;
+ }
}
&__cell:first-child {
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/content-table/content.table.item.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/content-table/content.table.item.js
index bf42a03d8b..787bc67100 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/content-table/content.table.item.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/content-table/content.table.item.js
@@ -7,6 +7,7 @@ import Icon from '../../../common/icon/icon';
import { formatShortDateTime } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/timezone.helper';
import { createCssClassNames } from '../../../common/helpers/css.class.names';
import { loadAccordionData } from '../../services/universal.discovery.service';
+import { useSelectedLocationsHelpers } from '../../hooks/useSelectedLocationsHelpers';
import {
RestInfoContext,
CurrentViewContext,
@@ -17,8 +18,6 @@ import {
ContentTypesMapContext,
SelectedLocationsContext,
MultipleConfigContext,
- ContainersOnlyContext,
- AllowedContentTypesContext,
RootLocationIdContext,
} from '../../universal.discovery.module';
@@ -33,12 +32,10 @@ const ContentTableItem = ({ location }) => {
const [, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const [multiple] = useContext(MultipleConfigContext);
const rootLocationId = useContext(RootLocationIdContext);
- const allowedContentTypes = useContext(AllowedContentTypesContext);
const contentTypeInfo = contentTypesMap[location.ContentInfo.Content.ContentType._href];
- const containersOnly = useContext(ContainersOnlyContext);
- const { isContainer } = contentTypeInfo;
- const isNotSelectable =
- (containersOnly && !isContainer) || (allowedContentTypes && !allowedContentTypes.includes(contentTypeInfo.identifier));
+ const { checkIsSelectable, checkIsSelectionBlocked } = useSelectedLocationsHelpers();
+ const isNotSelectable = !checkIsSelectable(location);
+ const isSelectionBlocked = checkIsSelectionBlocked(location);
const className = createCssClassNames({
'ibexa-table__row c-content-table-item': true,
'c-content-table-item--marked': markedLocationId === location.id,
@@ -85,7 +82,7 @@ const ContentTableItem = ({ location }) => {
}
};
const renderToggleSelection = () => {
- return ;
+ return ;
};
return (
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/finder/finder.leaf.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/finder/finder.leaf.js
index 045fd38639..08f1530919 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/finder/finder.leaf.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/finder/finder.leaf.js
@@ -7,14 +7,13 @@ import ToggleSelection from '../toggle-selection/toggle.selection';
import Icon from '../../../common/icon/icon';
import { createCssClassNames } from '../../../common/helpers/css.class.names';
+import { useSelectedLocationsHelpers } from '../../hooks/useSelectedLocationsHelpers';
import {
MarkedLocationIdContext,
LoadedLocationsMapContext,
ContentTypesMapContext,
SelectedLocationsContext,
MultipleConfigContext,
- ContainersOnlyContext,
- AllowedContentTypesContext,
} from '../../universal.discovery.module';
const { document } = window;
@@ -23,15 +22,12 @@ const FinderLeaf = ({ location }) => {
const [markedLocationId, setMarkedLocationId] = useContext(MarkedLocationIdContext);
const [loadedLocationsMap, dispatchLoadedLocationsAction] = useContext(LoadedLocationsMapContext);
const contentTypesMap = useContext(ContentTypesMapContext);
- const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
+ const [, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const [multiple] = useContext(MultipleConfigContext);
- const containersOnly = useContext(ContainersOnlyContext);
- const allowedContentTypes = useContext(AllowedContentTypesContext);
- const contentTypeInfo = contentTypesMap[location.ContentInfo.Content.ContentType._href];
- const { isContainer } = contentTypeInfo;
- const isSelected = selectedLocations.some((selectedLocation) => selectedLocation.location.id === location.id);
- const isNotSelectable =
- (containersOnly && !isContainer) || (allowedContentTypes && !allowedContentTypes.includes(contentTypeInfo.identifier));
+ const { checkIsSelectable, checkIsSelected, checkIsSelectionBlocked } = useSelectedLocationsHelpers();
+ const isSelected = checkIsSelected(location);
+ const isNotSelectable = !checkIsSelectable(location);
+ const isSelectionBlocked = checkIsSelectionBlocked(location);
const markLocation = ({ nativeEvent }) => {
const isSelectionButtonClicked = nativeEvent.target.closest('.c-udw-toggle-selection');
const isMarkedLocationClicked = location.id === markedLocationId;
@@ -53,7 +49,7 @@ const FinderLeaf = ({ location }) => {
}
};
const renderToggleSelection = () => {
- return ;
+ return ;
};
const className = createCssClassNames({
'c-finder-leaf': true,
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/grid-view/grid.view.item.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/grid-view/grid.view.item.js
index 8752c8c1ad..45abf7930e 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/grid-view/grid.view.item.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/grid-view/grid.view.item.js
@@ -5,6 +5,7 @@ import ToggleSelection from '../toggle-selection/toggle.selection';
import Thumbnail from '../../../common/thumbnail/thumbnail';
import { createCssClassNames } from '../../../common/helpers/css.class.names';
+import { useSelectedLocationsHelpers } from '../../hooks/useSelectedLocationsHelpers';
import {
LoadedLocationsMapContext,
MarkedLocationIdContext,
@@ -12,7 +13,6 @@ import {
SelectedLocationsContext,
MultipleConfigContext,
ContainersOnlyContext,
- AllowedContentTypesContext,
GridActiveLocationIdContext,
} from '../../universal.discovery.module';
@@ -25,15 +25,15 @@ const GridViewItem = ({ location, version }) => {
const [markedLocationId, setMarkedLocationId] = useContext(MarkedLocationIdContext);
const [, dispatchLoadedLocationsAction] = useContext(LoadedLocationsMapContext);
const contentTypesMap = useContext(ContentTypesMapContext);
- const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
+ const [, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const [multiple] = useContext(MultipleConfigContext);
const containersOnly = useContext(ContainersOnlyContext);
- const allowedContentTypes = useContext(AllowedContentTypesContext);
const contentTypeInfo = contentTypesMap[location.ContentInfo.Content.ContentType._href];
const { isContainer } = contentTypeInfo;
- const isSelected = selectedLocations.some((selectedLocation) => selectedLocation.location.id === location.id);
- const isNotSelectable =
- (containersOnly && !isContainer) || (allowedContentTypes && !allowedContentTypes.includes(contentTypeInfo.identifier));
+ const { checkIsSelectable, checkIsSelected, checkIsSelectionBlocked } = useSelectedLocationsHelpers();
+ const isSelected = checkIsSelected(location);
+ const isNotSelectable = !checkIsSelectable(location);
+ const isSelectionBlocked = checkIsSelectionBlocked(location);
const className = createCssClassNames({
'ibexa-grid-view-item': true,
'ibexa-grid-view-item--marked': markedLocationId === location.id,
@@ -68,7 +68,7 @@ const GridViewItem = ({ location, version }) => {
const renderToggleSelection = () => {
return (
-
+
);
};
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.selection.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.selection.js
index a61c219559..1bb40a3fce 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.selection.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.selection.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { createCssClassNames } from '../../../common/helpers/css.class.names';
import { SelectedLocationsContext } from '../../universal.discovery.module';
-const ToggleSelection = ({ multiple, location, isHidden }) => {
+const ToggleSelection = ({ multiple, location, isDisabled, isHidden }) => {
const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const isSelected = selectedLocations.some((selectedItem) => selectedItem.location.id === location.id);
const className = createCssClassNames({
@@ -22,17 +22,21 @@ const ToggleSelection = ({ multiple, location, isHidden }) => {
return null;
}
- return ;
+ return (
+
+ );
};
ToggleSelection.propTypes = {
location: PropTypes.object.isRequired,
multiple: PropTypes.bool.isRequired,
isHidden: PropTypes.bool,
+ isDisabled: PropTypes.bool,
};
ToggleSelection.defaultProps = {
isHidden: false,
+ isDisabled: false,
};
export default ToggleSelection;
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/tree-item-toggle-selection/tree.item.toggle.selection.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/tree-item-toggle-selection/tree.item.toggle.selection.js
index 6fbea50985..04b21ffe80 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/tree-item-toggle-selection/tree.item.toggle.selection.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/tree-item-toggle-selection/tree.item.toggle.selection.js
@@ -28,12 +28,14 @@ const TreeItemToggleSelection = ({ locationId, isContainer, contentTypeIdentifie
}
const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
- const [multiple] = useContext(MultipleConfigContext);
+ const [multiple, multipleItemsLimit] = useContext(MultipleConfigContext);
const containersOnly = useContext(ContainersOnlyContext);
const allowedContentTypes = useContext(AllowedContentTypesContext);
const restInfo = useContext(RestInfoContext);
+ const isSelected = selectedLocations.some((selectedLocation) => selectedLocation.location.id === locationId);
const isNotSelectable =
(containersOnly && !isContainer) || (allowedContentTypes && !allowedContentTypes.includes(contentTypeIdentifier));
+ const isSelectionBlocked = multipleItemsLimit !== 0 && !isSelected && selectedLocations.length >= multipleItemsLimit;
const location = {
id: locationId,
};
@@ -49,7 +51,7 @@ const TreeItemToggleSelection = ({ locationId, isContainer, contentTypeIdentifie
return (
-
+
{isNotSelectable && }
);
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/helpers/selected.locations.helper.js b/src/bundle/ui-dev/src/modules/universal-discovery/helpers/selected.locations.helper.js
new file mode 100644
index 0000000000..272cfc2a87
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/helpers/selected.locations.helper.js
@@ -0,0 +1,13 @@
+export const checkIsSelectable = ({ location, contentTypesMap, allowedContentTypes, containersOnly }) => {
+ const contentType = contentTypesMap[location.ContentInfo.Content.ContentType._href];
+ const { isContainer, identifier } = contentType;
+ const isAllowedContentType = allowedContentTypes?.includes(identifier) ?? true;
+
+ return (!containersOnly || isContainer) && isAllowedContentType;
+};
+
+export const checkIsSelected = ({ location, selectedLocations }) =>
+ selectedLocations.some((selectedLocation) => selectedLocation.location.id === location.id);
+
+export const checkIsSelectionBlocked = ({ location, selectedLocations, multipleItemsLimit }) =>
+ multipleItemsLimit !== 0 && selectedLocations.length >= multipleItemsLimit && !checkIsSelected({ location, selectedLocations });
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedLocationsHelpers.js b/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedLocationsHelpers.js
new file mode 100644
index 0000000000..b8400a849c
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedLocationsHelpers.js
@@ -0,0 +1,33 @@
+import { useCallback, useContext } from 'react';
+
+import { checkIsSelectable, checkIsSelected, checkIsSelectionBlocked } from '../helpers/selected.locations.helper';
+import {
+ AllowedContentTypesContext,
+ ContainersOnlyContext,
+ ContentTypesMapContext,
+ MultipleConfigContext,
+ SelectedLocationsContext,
+} from '../universal.discovery.module';
+
+export const useSelectedLocationsHelpers = () => {
+ const [, multipleItemsLimit] = useContext(MultipleConfigContext);
+ const contentTypesMap = useContext(ContentTypesMapContext);
+ const [selectedLocations] = useContext(SelectedLocationsContext);
+ const containersOnly = useContext(ContainersOnlyContext);
+ const allowedContentTypes = useContext(AllowedContentTypesContext);
+ const checkIsSelectableWrapped = useCallback(
+ (location) => checkIsSelectable({ location, contentTypesMap, allowedContentTypes, containersOnly }),
+ [contentTypesMap, allowedContentTypes, containersOnly],
+ );
+ const checkIsSelectedWrapped = useCallback((location) => checkIsSelected({ location, selectedLocations }), [selectedLocations]);
+ const checkIsSelectionBlockedWrapped = useCallback(
+ (location) => checkIsSelectionBlocked({ location, selectedLocations, multipleItemsLimit }),
+ [selectedLocations, multipleItemsLimit],
+ );
+
+ return {
+ checkIsSelectable: checkIsSelectableWrapped,
+ checkIsSelected: checkIsSelectedWrapped,
+ checkIsSelectionBlocked: checkIsSelectionBlockedWrapped,
+ };
+};
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js b/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js
index 7c63e58203..cec9ce9c2b 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useCallback, useState, createContext, useRef } from 'react';
+import React, { useEffect, useCallback, useState, createContext, useRef, useMemo } from 'react';
import PropTypes from 'prop-types';
import Icon from '../common/icon/icon';
@@ -268,13 +268,17 @@ const UniversalDiscoveryModule = (props) => {
loadContentInfo({ ...restInfo, contentId, signal }, (response) => resolve(response));
});
};
- const contentTypesMapGlobal = Object.values(adminUiConfig.contentTypes).reduce((contentTypesMap, contentTypesGroup) => {
- contentTypesGroup.forEach((contentType) => {
- contentTypesMap[contentType.href] = contentType;
- });
+ const contentTypesMapGlobal = useMemo(
+ () =>
+ Object.values(adminUiConfig.contentTypes).reduce((contentTypesMap, contentTypesGroup) => {
+ contentTypesGroup.forEach((contentType) => {
+ contentTypesMap[contentType.href] = contentType;
+ });
- return contentTypesMap;
- }, {});
+ return contentTypesMap;
+ }, {}),
+ [adminUiConfig.contentTypes],
+ );
const onConfirm = useCallback(
(selectedItems = selectedLocations) => {
loadVersions().then((locationsWithVersions) => {