diff --git a/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss b/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss
index 022c324534..264ea71e46 100644
--- a/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss
+++ b/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss
@@ -15,6 +15,8 @@
@import 'universal-discovery/finder.branch';
@import 'universal-discovery/finder.leaf';
@import 'universal-discovery/content.meta.preview';
+@import 'universal-discovery/selected.items.panel.item';
+@import 'universal-discovery/selected.items.panel';
@import 'universal-discovery/selected.locations';
@import 'universal-discovery/selected.locations.item';
@import 'universal-discovery/grid';
diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss
index 102cc9048f..ed8dc3ce20 100644
--- a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss
+++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss
@@ -19,13 +19,15 @@
height: 100%;
}
- &__sidebar {
- display: flex;
+ &__content-meta-preview:not(:empty) {
+ flex: 1;
height: 100%;
- min-width: calculateRem(270px);
- margin-right: calculateRem(24px);
- border-right: calculateRem(1px) solid $ibexa-color-light;
- background-color: $ibexa-color-white;
+ overflow: auto;
+ border-left: calculateRem(1px) solid $ibexa-color-light;
+ }
+
+ &__filters {
+ border-left: calculateRem(1px) solid $ibexa-color-light;
}
&__spinner-wrapper {
@@ -41,12 +43,13 @@
}
&__content {
+ flex: 2;
display: flex;
flex-direction: column;
overflow: auto;
width: 100%;
flex-shrink: 1;
- padding: 0 calculateRem(8px);
+ padding: calculateRem(24px);
background-color: $ibexa-color-white;
position: relative;
}
@@ -73,7 +76,6 @@
display: grid;
grid-template: 'title clear-search-btn' 'subtitle subtitle';
justify-content: start;
- margin-top: calculateRem(16px);
}
&__table-tile {
diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.item.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.item.scss
new file mode 100644
index 0000000000..7e48caf675
--- /dev/null
+++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.item.scss
@@ -0,0 +1,56 @@
+.c-selected-items-panel-item {
+ display: flex;
+ align-items: center;
+ padding: calculateRem(5px);
+ margin-bottom: calculateRem(8px);
+ border: calculateRem(1px) solid $ibexa-color-light;
+ border-radius: $ibexa-border-radius;
+ box-shadow: calculateRem(4px) calculateRem(2px) calculateRem(17px) 0 rgba($ibexa-color-black, 0.05);
+
+ &__image-wrapper {
+ width: calculateRem(42px);
+ height: calculateRem(42px);
+ background-color: $ibexa-color-light-300;
+ border-radius: $ibexa-border-radius-small;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ &__info {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ flex: 1;
+ width: calculateRem(185px);
+ padding: 0 calculateRem(15px);
+ }
+
+ &__info-name {
+ font-size: $ibexa-text-font-size;
+ }
+
+ &__info-description {
+ font-size: $ibexa-text-font-size-small;
+ color: $ibexa-color-dark-400;
+ }
+
+ &__actions-wrapper {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ }
+
+ &__remove-button {
+ width: calculateRem(32px);
+ height: calculateRem(32px);
+ padding: 0;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+
+ .ibexa-icon {
+ fill: $ibexa-color-dark;
+ }
+ }
+}
diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.scss
new file mode 100644
index 0000000000..647d12b53e
--- /dev/null
+++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.scss
@@ -0,0 +1,75 @@
+.c-selected-items-panel {
+ background-color: $ibexa-color-white;
+ position: fixed;
+ top: calc(100vh - calculateRem(98px));
+ bottom: calculateRem(31px);
+ left: calculateRem(16px);
+ min-height: calculateRem(60px);
+ border: calculateRem(1px) solid $ibexa-color-light;
+ border-top-right-radius: $ibexa-border-radius;
+ border-bottom-right-radius: $ibexa-border-radius;
+ box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.15);
+ z-index: 1;
+ transition: all $ibexa-admin-transition-duration $ibexa-admin-transition;
+
+ &__header {
+ display: flex;
+ justify-content: start;
+ align-items: center;
+ padding: calculateRem(16px);
+ }
+
+ &__selection-counter {
+ color: $ibexa-color-dark;
+ font-size: calculateRem(22px);
+ font-weight: 600;
+ padding-right: calculateRem(16px);
+ }
+
+ &--expanded {
+ bottom: calculateRem(16px);
+ top: calculateRem(88px);
+ min-width: calculateRem(491px);
+ overflow: hidden;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: $ibexa-border-radius;
+
+ .c-selected-items-panel {
+ &__items-wrapper {
+ display: block;
+ }
+
+ &__toggle-button-icon {
+ transform: rotate(0);
+ }
+ }
+ }
+
+ &__items-wrapper {
+ display: none;
+ overflow: auto;
+ padding: 0 calculateRem(38px) calculateRem(16px) calculateRem(22px);
+ border-top: calculateRem(1px) solid $ibexa-color-light;
+ height: calc(100% - calculateRem(70px));
+ }
+
+ &__actions {
+ padding: calculateRem(16px) 0;
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ &__toggle-button {
+ display: flex;
+ width: calculateRem(32px);
+ height: calculateRem(32px);
+ justify-content: center;
+ align-items: center;
+ margin-right: calculateRem(32px);
+ }
+
+ &__toggle-button-icon {
+ transform: rotate(180deg);
+ }
+}
diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss
index f4f33d1e42..06401f0910 100644
--- a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss
+++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss
@@ -1,22 +1,22 @@
.c-selected-locations {
background-color: $ibexa-color-white;
position: fixed;
- top: calculateRem(95px);
- right: calculateRem(16px);
+ top: calc(100vh - calculateRem(98px));
+ bottom: calculateRem(31px);
+ left: calculateRem(16px);
min-height: calculateRem(60px);
- min-width: calculateRem(390px);
border: calculateRem(1px) solid $ibexa-color-light;
- border-top-left-radius: $ibexa-border-radius;
- border-bottom-left-radius: $ibexa-border-radius;
+ border-top-right-radius: $ibexa-border-radius;
+ border-bottom-right-radius: $ibexa-border-radius;
box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.15);
z-index: 1;
transition: all $ibexa-admin-transition-duration $ibexa-admin-transition;
&__header {
display: flex;
- justify-content: space-between;
+ justify-content: start;
align-items: center;
- padding: calculateRem(7px) calculateRem(12px) calculateRem(7px) calculateRem(22px);
+ padding: calculateRem(16px);
}
&__selection-counter {
@@ -27,14 +27,22 @@
}
&--expanded {
- bottom: calculateRem(100px);
+ bottom: calculateRem(16px);
+ top: calculateRem(88px);
min-width: calculateRem(491px);
overflow: hidden;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: $ibexa-border-radius;
.c-selected-locations {
&__items-wrapper {
display: block;
}
+
+ &__toggle-button-icon {
+ transform: rotate(0);
+ }
}
}
@@ -51,4 +59,17 @@
display: flex;
justify-content: flex-end;
}
+
+ &__toggle-button {
+ display: flex;
+ width: calculateRem(32px);
+ height: calculateRem(32px);
+ justify-content: center;
+ align-items: center;
+ margin-right: calculateRem(32px);
+ }
+
+ &__toggle-button-icon {
+ transform: rotate(180deg);
+ }
}
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.js
index 062add5f88..cf7fac210e 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.js
@@ -2,6 +2,9 @@ import React, { useContext, useState, useEffect, useCallback, useRef } from 'rea
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
+import FiltersPanel from './filters.panel';
+import FiltersRow from './filters.row';
+
import {
SelectedContentTypesContext,
SelectedSectionContext,
@@ -69,7 +72,7 @@ const Filters = ({ search }) => {
const makeSearch = useCallback(() => {
prevSelectedLanguage.current = selectedLanguage;
- search(0);
+ search();
}, [search, selectedLanguage]);
const isApplyButtonEnabled =
!!selectedContentTypes.length || !!selectedSection || !!selectedSubtree || prevSelectedLanguage.current !== selectedLanguage;
@@ -115,12 +118,9 @@ const Filters = ({ search }) => {
);
};
- const filtersLabel = Translator.trans(/*@Desc("Filters")*/ 'filters.title', {}, 'ibexa_universal_discovery_widget');
const languageLabel = Translator.trans(/*@Desc("Language")*/ 'filters.language', {}, 'ibexa_universal_discovery_widget');
const sectionLabel = Translator.trans(/*@Desc("Section")*/ 'filters.section', {}, 'ibexa_universal_discovery_widget');
const subtreeLabel = Translator.trans(/*@Desc("Subtree")*/ 'filters.subtree', {}, 'ibexa_universal_discovery_widget');
- const clearLabel = Translator.trans(/*@Desc("Clear")*/ 'filters.clear', {}, 'ibexa_universal_discovery_widget');
- const applyLabel = Translator.trans(/*@Desc("Apply")*/ 'filters.apply', {}, 'ibexa_universal_discovery_widget');
const languageOptions = Object.values(adminUiConfig.languages.mappings)
.filter((language) => language.enabled)
.map((language) => ({
@@ -150,25 +150,8 @@ const Filters = ({ search }) => {
return (
<>
{isNestedUdwOpened && ReactDOM.createPortal(, nestedUdwContainer.current)}
-
-
-
{filtersLabel}
-
-
-
-
-
-
-
{languageLabel}
+
+
{
options={languageOptions}
extraClasses="c-udw-dropdown"
/>
-
+
-
-
{sectionLabel}
+
{
options={sectionOptions}
extraClasses="c-udw-dropdown"
/>
-
-
-
{subtreeLabel}
+
+
{renderSubtreeBreadcrumbs()}
{renderSelectContentButton()}
-
-
+
+
>
);
};
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.panel.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.panel.js
new file mode 100644
index 0000000000..9d43101d5a
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.panel.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { getTranslator } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper';
+
+const FiltersPanel = ({ children, isApplyButtonEnabled, makeSearch, clearFilters }) => {
+ const Translator = getTranslator();
+ const filtersLabel = Translator.trans(/*@Desc("Filters")*/ 'filters.title', {}, 'ibexa_universal_discovery_widget');
+ const clearLabel = Translator.trans(/*@Desc("Clear")*/ 'filters.clear', {}, 'ibexa_universal_discovery_widget');
+ const applyLabel = Translator.trans(/*@Desc("Apply")*/ 'filters.apply', {}, 'ibexa_universal_discovery_widget');
+
+ return (
+
+
+
{filtersLabel}
+
+
+
+
+
+ {children}
+
+ );
+};
+
+FiltersPanel.propTypes = {
+ children: PropTypes.node.isRequired,
+ isApplyButtonEnabled: PropTypes.bool.isRequired,
+ makeSearch: PropTypes.func.isRequired,
+ clearFilters: PropTypes.func.isRequired,
+};
+
+export default FiltersPanel;
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.row.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.row.js
new file mode 100644
index 0000000000..4beff1df96
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.row.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { createCssClassNames } from '../../../common/helpers/css.class.names';
+
+const FiltersRow = ({ children, title, extraClasses }) => {
+ const className = createCssClassNames({
+ 'c-filters__row': true,
+ [extraClasses]: true,
+ });
+
+ return (
+
+ );
+};
+
+FiltersRow.propTypes = {
+ children: PropTypes.node.isRequired,
+ extraClasses: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+};
+
+export default FiltersRow;
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js
index 1b316dab8a..2e352cd08a 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js
@@ -11,6 +11,7 @@ import Icon from '../../../common/icon/icon';
import Spinner from '../../../common/spinner/spinner';
import ContentTable from '../content-table/content.table';
import Filters from '../filters/filters';
+import ContentMetaPreview from '../../content.meta.preview.module';
import SearchTags from './search.tags';
import { useSearchByQueryFetch } from '../../hooks/useSearchByQueryFetch';
import { ActiveTabContext, AllowedContentTypesContext, MarkedLocationIdContext, SearchTextContext } from '../../universal.discovery.module';
@@ -193,15 +194,18 @@ const Search = ({ itemsPerPage }) => {
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js
new file mode 100644
index 0000000000..e593de19a7
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js
@@ -0,0 +1,71 @@
+import React, { useContext, useEffect, useMemo, useRef } from 'react';
+
+import {
+ parse as parseTooltip,
+ hideAll as hideAllTooltips,
+} from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/tooltips.helper';
+
+import Icon from '../../../common/icon/icon';
+import Thumbnail from '../../../common/thumbnail/thumbnail';
+
+import { SelectedItemsContext } from '../../universal.discovery.module';
+import { getAdminUiConfig, getTranslator } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper';
+import { REMOVE_SELECTED_ITEMS } from '../../hooks/useSelectedItemsReducer';
+
+const SelectedItemsPanelItem = ({ item, thumbnailData, name, description }) => {
+ const adminUiConfig = getAdminUiConfig();
+ const Translator = getTranslator();
+ const refSelectedLocationsItem = useRef(null);
+ const { dispatchSelectedItemsAction } = useContext(SelectedItemsContext);
+ const removeItemLabel = Translator.trans(
+ /*@Desc("Clear selection")*/ 'selected_items_panel.item.remove_item',
+ {},
+ 'ibexa_universal_discovery_widget',
+ );
+ const removeFromSelection = () => {
+ hideAllTooltips(refSelectedLocationsItem.current);
+ dispatchSelectedItemsAction({ type: REMOVE_SELECTED_ITEMS, ids: [{ id: item.id, type: item.type }] });
+ };
+ const sortedActions = useMemo(() => {
+ const { universalSelectItemActions } = adminUiConfig.universalDiscoveryWidget;
+ const actions = universalSelectItemActions ? [...universalSelectItemActions] : [];
+
+ return actions.sort((actionA, actionB) => {
+ return actionB.priority - actionA.priority;
+ });
+ }, []);
+
+ useEffect(() => {
+ parseTooltip(refSelectedLocationsItem.current);
+ }, []);
+
+ return (
+
+
+
+
+
+ {name}
+ {description}
+
+
+ {sortedActions.map((action) => {
+ const Component = action.component;
+
+ return ;
+ })}
+
+
+
+ );
+};
+
+export default SelectedItemsPanelItem;
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.js
new file mode 100644
index 0000000000..00050d6ed8
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.js
@@ -0,0 +1,152 @@
+import React, { useContext, useState, useEffect, useRef, useMemo } from 'react';
+
+import {
+ parse as parseTooltip,
+ hideAll as hideAllTooltips,
+} from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/tooltips.helper';
+import {
+ getBootstrap,
+ getAdminUiConfig,
+ getTranslator,
+} from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper';
+
+import Icon from '../../../common/icon/icon';
+import { createCssClassNames } from '../../../common/helpers/css.class.names';
+
+import { AllowConfirmationContext, SelectedItemsContext } from '../../universal.discovery.module';
+import { CLEAR_SELECTED_ITEMS } from '../../hooks/useSelectedItemsReducer';
+
+const SelectedItemsPanel = () => {
+ const Translator = getTranslator();
+ const adminUiConfig = getAdminUiConfig();
+ // TODO: fill dependency array
+ const itemsComponentsMap = useMemo(() => {
+ const { universalSelectItemsComponentsConfigs } = adminUiConfig.universalDiscoveryWidget;
+ const configsArray = universalSelectItemsComponentsConfigs ? [...universalSelectItemsComponentsConfigs] : [];
+
+ return configsArray.reduce((configsMap, config) => {
+ configsMap[config.itemType] = config;
+
+ return configsMap;
+ }, {});
+ }, [adminUiConfig]);
+
+ const refSelectedLocations = useRef(null);
+
+ const { selectedItems, dispatchSelectedItemsAction } = useContext(SelectedItemsContext);
+ const allowConfirmation = useContext(AllowConfirmationContext);
+ const [isExpanded, setIsExpanded] = useState(false);
+ const className = createCssClassNames({
+ 'c-selected-items-panel': true,
+ 'c-selected-items-panel--expanded': isExpanded,
+ });
+ const expandLabel = Translator.trans(
+ /*@Desc("Expand sidebar")*/ 'selected_locations.expand.sidebar',
+ {},
+ 'ibexa_universal_discovery_widget',
+ );
+ const collapseLabel = Translator.trans(
+ /*@Desc("Collapse sidebar")*/ 'selected_locations.collapse.sidebar',
+ {},
+ 'ibexa_universal_discovery_widget',
+ );
+ const togglerLabel = isExpanded ? collapseLabel : expandLabel;
+ const clearSelection = () => {
+ hideAllTooltips(refSelectedLocations.current);
+ dispatchSelectedItemsAction({ type: CLEAR_SELECTED_ITEMS });
+ };
+ const toggleExpanded = () => {
+ setIsExpanded(!isExpanded);
+ };
+ const renderSelectionCounter = () => {
+ const selectedLabel = Translator.transChoice(
+ /*@Desc("{1}%count% selected item|[2,Inf]%count% selected items")*/ 'selected_locations.selected_items',
+ selectedItems.length,
+ { count: selectedItems.length },
+ 'ibexa_universal_discovery_widget',
+ );
+
+ return {selectedLabel}
;
+ };
+ const renderToggleButton = () => {
+ return (
+
+ );
+ };
+ const renderActionButtons = () => {
+ const removeLabel = Translator.transChoice(
+ /*@Desc("{1}Deselect|[2,Inf]Deselect all")*/ 'selected_locations.deselect_all',
+ selectedItems.length,
+ {},
+ 'ibexa_universal_discovery_widget',
+ );
+
+ return (
+
+
+
+ );
+ };
+ const renderLocationsList = () => {
+ if (!isExpanded) {
+ return null;
+ }
+
+ return (
+
+ {renderActionButtons()}
+
+ {selectedItems.map((selectedItem) => {
+ const ItemComponent = itemsComponentsMap[selectedItem.type].component;
+
+ return ItemComponent && ;
+ })}
+
+
+ );
+ };
+
+ useEffect(() => {
+ if (!allowConfirmation) {
+ return;
+ }
+
+ parseTooltip(refSelectedLocations.current);
+ hideAllTooltips();
+
+ const bootstrap = getBootstrap();
+ const toggleButtonTooltip = bootstrap.Tooltip.getOrCreateInstance('.c-selected-items-panel__toggle-button');
+
+ toggleButtonTooltip.setContent({ '.tooltip-inner': togglerLabel });
+ }, [isExpanded]);
+
+ if (!allowConfirmation) {
+ return null;
+ }
+
+ return (
+
+
+ {renderToggleButton()}
+ {renderSelectionCounter()}
+
+ {renderLocationsList()}
+
+ );
+};
+
+export default SelectedItemsPanel;
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js
index 25ecc75839..7f809b2365 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js
@@ -15,7 +15,6 @@ import { SelectedLocationsContext, AllowConfirmationContext } from '../../univer
const SelectedLocations = () => {
const Translator = getTranslator();
const refSelectedLocations = useRef(null);
- const refTogglerButton = useRef(null);
const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext);
const allowConfirmation = useContext(AllowConfirmationContext);
const [isExpanded, setIsExpanded] = useState(false);
@@ -52,18 +51,15 @@ const SelectedLocations = () => {
return {selectedLabel}
;
};
const renderToggleButton = () => {
- const iconName = isExpanded ? 'caret-double-next' : 'caret-double-back';
-
return (
);
};
@@ -129,8 +125,8 @@ const SelectedLocations = () => {
return (
- {renderSelectionCounter()}
{renderToggleButton()}
+ {renderSelectionCounter()}
{renderLocationsList()}
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js
index c398082678..e41a5580b3 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js
@@ -1,10 +1,12 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
+import { parse as parseTooltip } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/tooltips.helper';
+
import SimpleDropdown from '../../../common/simple-dropdown/simple.dropdown';
import { SortingContext, SortOrderContext, SORTING_OPTIONS } from '../../universal.discovery.module';
-const SortSwitcher = ({ isDisabled }) => {
+const SortSwitcher = ({ isDisabled = false, disabledConfig = null }) => {
const [sorting, setSorting] = useContext(SortingContext);
const [sortOrder, setSortOrder] = useContext(SortOrderContext);
const selectedOption = SORTING_OPTIONS.find((option) => option.sortClause === sorting && option.sortOrder === sortOrder);
@@ -13,8 +15,19 @@ const SortSwitcher = ({ isDisabled }) => {
setSortOrder(option.sortOrder);
};
+ const disabledParams = {};
+
+ if (isDisabled && disabledConfig) {
+ disabledParams.title = disabledConfig.disabledInfoTooltipLabel;
+ }
+
return (
-
+
parseTooltip(node)}
+ className="c-sort-switcher"
+ data-tooltip-container-selector=".c-udw-tab"
+ {...disabledParams}
+ >
{
SortSwitcher.propTypes = {
isDisabled: PropTypes.bool,
-};
-
-SortSwitcher.defaultProps = {
- isDisabled: false,
+ disabledConfig: PropTypes.object,
};
export const SortSwitcherMenuButton = {
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js
index 2ac0160ae2..85e93d62dd 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js
@@ -8,15 +8,16 @@ import SelectedLocations from '../selected-locations/selected.locations';
import ContentCreateWidget from '../content-create-widget/content.create.widget';
import ContentMetaPreview from '../../content.meta.preview.module';
-import { SelectedLocationsContext, DropdownPortalRefContext } from '../../universal.discovery.module';
+import { SelectedLocationsContext, DropdownPortalRefContext, SelectedItemsContext } from '../../universal.discovery.module';
+import SelectedItemsPanel from '../selected-items/selected.items.panel';
-const Tab = ({ children, actionsDisabledMap }) => {
+const Tab = ({ children, actionsDisabledMap, isRightSidebarHidden }) => {
const topBarRef = useRef();
const bottomBarRef = useRef();
const [contentHeight, setContentHeight] = useState('100%');
const [selectedLocations] = useContext(SelectedLocationsContext);
+ const { selectedItems, } = useContext(SelectedItemsContext);
const dropdownPortalRef = useContext(DropdownPortalRefContext);
- const selectedLocationsComponent = !!selectedLocations.length ? : null;
const contentStyles = {
height: contentHeight,
};
@@ -40,12 +41,15 @@ const Tab = ({ children, actionsDisabledMap }) => {
{children}
-
- {ContentMetaPreview && }
- {selectedLocationsComponent}
-
+ {!isRightSidebarHidden && (
+
+
+
+ )}
+ {!!selectedLocations.length &&
}
+ {!!selectedItems.length &&
}
@@ -56,6 +60,7 @@ const Tab = ({ children, actionsDisabledMap }) => {
Tab.propTypes = {
children: PropTypes.any.isRequired,
actionsDisabledMap: PropTypes.object,
+ isRightSidebarHidden: PropTypes.bool,
};
Tab.defaultProps = {
@@ -64,6 +69,7 @@ Tab.defaultProps = {
'sort-switcher': false,
'view-switcher': false,
},
+ isRightSidebarHidden: false,
};
export default Tab;
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js
new file mode 100644
index 0000000000..b58a477ce2
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js
@@ -0,0 +1,28 @@
+import React, { useContext } from 'react';
+import PropTypes from 'prop-types';
+
+import { createCssClassNames } from '../../../common/helpers/css.class.names';
+import { SelectedItemsContext } from '../../universal.discovery.module';
+import { TOGGLE_SELECTED_ITEMS } from '../../hooks/useSelectedItemsReducer';
+
+const ToggleItemSelection = ({ multiple, item, isHidden = false }) => {
+ const { selectedItems, dispatchSelectedItemsAction } = useContext(SelectedItemsContext);
+ const isSelected = selectedItems.some((selectedItem) => selectedItem.type === item.type && selectedItem.id === item.id);
+ const className = createCssClassNames({
+ 'c-udw-toggle-selection ibexa-input': true,
+ 'ibexa-input--checkbox': multiple,
+ 'c-udw-toggle-selection--hidden': isHidden,
+ });
+ const inputType = multiple ? 'checkbox' : 'radio';
+
+ console.log(isSelected, selectedItems, item.type, item.id);
+ return ;
+};
+
+ToggleItemSelection.propTypes = {
+ item: PropTypes.object.isRequired,
+ multiple: PropTypes.bool.isRequired,
+ isHidden: PropTypes.bool,
+};
+
+export default ToggleItemSelection;
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js
index 4e6555c1cb..a04dc24624 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js
@@ -36,8 +36,12 @@ const TopMenu = ({ actionsDisabledMap }) => {
{sortedActions.map((action) => {
const Component = action.component;
+ const disabledData = actionsDisabledMap[action.id];
+ const hasDisabledConfig = disabledData instanceof Object;
- return ;
+ return (
+
+ );
})}
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js
index d66c4dbe7a..6df58e9c82 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js
@@ -4,14 +4,12 @@ import PropTypes from 'prop-types';
import { createCssClassNames } from '../../../common/helpers/css.class.names';
import Icon from '../../../common/icon/icon';
-import { ActiveTabContext, SearchTextContext } from '../../universal.discovery.module';
+import { SearchTextContext } from '../../universal.discovery.module';
const ENTER_CHAR_CODE = 13;
-const SEARCH_TAB_ID = 'search';
const TopMenuSearchInput = ({ isSearchOpened, setIsSearchOpened }) => {
- const [activeTab, setActiveTab] = useContext(ActiveTabContext);
- const [searchText, setSearchText] = useContext(SearchTextContext);
+ const [searchText, setSearchText, makeSearch] = useContext(SearchTextContext);
const [inputValue, setInputValue] = useState(searchText);
const inputRef = useRef();
const className = createCssClassNames({
@@ -24,16 +22,9 @@ const TopMenuSearchInput = ({ isSearchOpened, setIsSearchOpened }) => {
'ibexa-btn--tertiary': !isSearchOpened,
});
const updateInputValue = ({ target: { value } }) => setInputValue(value);
- const search = (value) => {
- if (activeTab !== SEARCH_TAB_ID) {
- setActiveTab('search');
- }
-
- setSearchText(value);
- };
const handleSearchBtnClick = () => {
if (isSearchOpened) {
- search(inputValue);
+ makeSearch(inputValue);
setIsSearchOpened(false);
} else {
setIsSearchOpened(true);
@@ -41,7 +32,7 @@ const TopMenuSearchInput = ({ isSearchOpened, setIsSearchOpened }) => {
};
const handleKeyPressed = ({ charCode }) => {
if (charCode === ENTER_CHAR_CODE) {
- search(inputValue);
+ makeSearch(inputValue);
}
};
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js
index 770e5df0fc..e4ef9a5178 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js
@@ -3,13 +3,14 @@ import PropTypes from 'prop-types';
import SimpleDropdown from '../../../common/simple-dropdown/simple.dropdown';
import { getTranslator } from '../../../../../../Resources/public/js/scripts/helpers/context.helper';
-import { CurrentViewContext, VIEWS } from '../../universal.discovery.module';
+import { CurrentViewContext, ViewContext } from '../../universal.discovery.module';
const ViewSwitcher = ({ isDisabled }) => {
const Translator = getTranslator();
const viewLabel = Translator.trans(/*@Desc("View")*/ 'view_switcher.view', {}, 'ibexa_universal_discovery_widget');
const [currentView, setCurrentView] = useContext(CurrentViewContext);
- const selectedOption = VIEWS.find((option) => option.value === currentView);
+ const { views } = useContext(ViewContext);
+ const selectedOption = views.find((option) => option.value === currentView);
const onOptionClick = ({ value }) => {
setCurrentView(value);
};
@@ -17,7 +18,7 @@ const ViewSwitcher = ({ isDisabled }) => {
return (
{
+ switch (action.type) {
+ case FETCH_START:
+ return {
+ ...state,
+ data: null,
+ isLoading: true,
+ };
+ case FETCH_END:
+ return { ...state, data: action.data, isLoading: false };
+ case CHANGE_PAGE: {
+ const isCurrentPageIndex = action.pageIndex === state.pageIndex;
+
+ if (isCurrentPageIndex) {
+ return state;
+ }
+
+ return {
+ ...state,
+ data: null,
+ pageIndex: action.pageIndex,
+ };
+ }
+ default:
+ throw new Error();
+ }
+};
+
+export const usePaginableFetch = ({ itemsPerPage, extraFetchParams }, fetchFunction) => {
+ const restInfo = useContext(RestInfoContext);
+ const [state, dispatch] = useReducer(fetchReducer, fetchInitialState);
+ const changePage = (pageIndex) => dispatch({ type: CHANGE_PAGE, pageIndex });
+
+ useEffect(() => {
+ dispatch({ type: FETCH_START });
+ const offset = state.pageIndex * itemsPerPage;
+ const { abortController } = fetchFunction({ ...restInfo, limit: itemsPerPage, offset, ...extraFetchParams }, (data) =>
+ dispatch({ type: FETCH_END, data }),
+ );
+
+ return () => {
+ if (abortController) {
+ abortController.abort();
+ }
+ };
+ }, [state.pageIndex, restInfo, itemsPerPage, extraFetchParams]);
+
+ return [state.data, state.isLoading, state.pageIndex, changePage];
+};
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js b/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js
new file mode 100644
index 0000000000..1b230c878d
--- /dev/null
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js
@@ -0,0 +1,77 @@
+import { useReducer } from 'react';
+
+export const ADD_SELECTED_ITEMS = 'ADD_SELECTED_ITEMS';
+export const REMOVE_SELECTED_ITEMS = 'REMOVE_SELECTED_ITEMS';
+export const TOGGLE_SELECTED_ITEMS = 'TOGGLE_SELECTED_ITEMS';
+export const CLEAR_SELECTED_ITEMS = 'CLEAR_SELECTED_ITEMS';
+export const CHANGE_MULTIPLE_SETTING = 'CHANGE_MULTIPLE_SETTING';
+
+const checkIsItemSelected = (selectedItems, item) =>
+ selectedItems.some((selectedItem) => selectedItem.type === item.type && selectedItem.id === item.id);
+
+const filterOutSelectedItems = (selectedItems, items) => items.filter((item) => !checkIsItemSelected(selectedItems, item));
+
+const selectedItemsReducer = (state, action) => {
+ const { isMultiple, items } = state;
+
+ switch (action.type) {
+ case ADD_SELECTED_ITEMS: {
+ const oldItemsWithoutNewItems = filterOutSelectedItems(action.items, items);
+ const newItems = [...oldItemsWithoutNewItems, ...action.items];
+
+ if (!isMultiple && newItems.length > 1) {
+ throw new Error('useSelectedItemsReducer ADD_SELECTED_ITEMS: cannot select more than one item with single select.');
+ }
+
+ return {
+ ...state,
+ items: newItems,
+ };
+ }
+ case REMOVE_SELECTED_ITEMS:
+ return filterOutSelectedItems(action.itemsIdsWithTypes, items);
+ case TOGGLE_SELECTED_ITEMS: {
+ const oldItemsWithoutDeselectedItems = filterOutSelectedItems(action.items, items);
+ const newItemsWithoutDeselectedItems = filterOutSelectedItems(items, action.items);
+ const newItems = [...oldItemsWithoutDeselectedItems, ...newItemsWithoutDeselectedItems];
+
+ if (!isMultiple && newItems.length > 1) {
+ throw new Error('useSelectedItemsReducer ADD_SELECTED_ITEMS: cannot select more than one item with single select.');
+ }
+
+ return {
+ ...state,
+ items: newItems,
+ };
+ }
+ case CLEAR_SELECTED_ITEMS:
+ return {
+ ...state,
+ items: [],
+ };
+ case CHANGE_MULTIPLE_SETTING:
+ if (!action.isMultiple && items.length > 1) {
+ throw new Error(
+ 'useSelectedItemsReducer CHANGE_MULTIPLE_SETTING: cannot set to single select when multiple items are selected.',
+ );
+ }
+
+ return {
+ ...state,
+ isMultiple: action.isMultiple,
+ };
+ default:
+ throw new Error();
+ }
+};
+
+export const useSelectedItemsReducer = ({ items = [], isMultiple, multipleItemsLimit }) => {
+ const initialState = {
+ isMultiple,
+ multipleItemsLimit,
+ items,
+ };
+ const [{ items: selectedItems }, dispatchSelectedItemsAction] = useReducer(selectedItemsReducer, initialState);
+
+ return { selectedItems, dispatchSelectedItemsAction };
+};
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js b/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js
index 226936e185..930afaa736 100644
--- a/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js
+++ b/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js
@@ -42,7 +42,7 @@ const SearchTabModule = () => {
return (
-
+
@@ -61,4 +61,4 @@ const SearchTab = {
isHiddenOnList: true,
};
-export { SearchTabModule as ValueTypeDefault, SearchTab };
+export { SearchTabModule as default, SearchTab };
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 cec9ce9c2b..92827f0e66 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
@@ -23,9 +23,11 @@ import {
getTranslator,
SYSTEM_ROOT_LOCATION_ID,
} from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper';
+import { useSelectedItemsReducer } from './hooks/useSelectedItemsReducer';
const { document } = window;
const CLASS_SCROLL_DISABLED = 'ibexa-scroll-disabled';
+const SEARCH_TAB_ID = 'search';
const defaultRestInfo = {
accsessToken: null,
instanceUrl: window.location.origin,
@@ -186,6 +188,7 @@ export const StartingLocationIdContext = createContext();
export const LoadedLocationsMapContext = createContext();
export const RootLocationIdContext = createContext();
export const SelectedLocationsContext = createContext();
+export const SelectedItemsContext = createContext();
export const CreateContentWidgetContext = createContext();
export const ContentOnTheFlyDataContext = createContext();
export const ContentOnTheFlyConfigContext = createContext();
@@ -196,6 +199,7 @@ export const DropdownPortalRefContext = createContext();
export const SuggestionsStorageContext = createContext();
export const GridActiveLocationIdContext = createContext();
export const SnackbarActionsContext = createContext();
+export const ViewContext = createContext();
const UniversalDiscoveryModule = (props) => {
const { restInfo } = props;
@@ -231,6 +235,10 @@ const UniversalDiscoveryModule = (props) => {
{ parentLocationId: props.rootLocationId, subitems: [] },
]);
const [selectedLocations, dispatchSelectedLocationsAction] = useSelectedLocationsReducer();
+ const { selectedItems, dispatchSelectedItemsAction } = useSelectedItemsReducer({
+ isMultiple: true,//props.multiple,
+ multipleItemsLimit: props.multipleItemsLimit,
+ });
const activeTabConfig = tabs.find((tab) => tab.id === activeTab);
const Tab = activeTabConfig.component;
const className = createCssClassNames({
@@ -310,6 +318,13 @@ const UniversalDiscoveryModule = (props) => {
},
[selectedLocations, contentTypesInfoMap],
);
+ const makeSearch = (value) => {
+ if (activeTab !== SEARCH_TAB_ID) {
+ setActiveTab('search');
+ }
+
+ setSearchText(value);
+ };
useEffect(() => {
const addContentTypesInfo = (contentTypes) => {
@@ -475,7 +490,7 @@ const UniversalDiscoveryModule = (props) => {
-
+
@@ -494,90 +509,106 @@ const UniversalDiscoveryModule = (props) => {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/Behat/Component/UniversalDiscoveryWidget.php b/src/lib/Behat/Component/UniversalDiscoveryWidget.php
index 28585e6dea..f44aa40206 100644
--- a/src/lib/Behat/Component/UniversalDiscoveryWidget.php
+++ b/src/lib/Behat/Component/UniversalDiscoveryWidget.php
@@ -218,7 +218,7 @@ protected function specifyLocators(): array
new CSSLocator('confirmButton', '.c-actions-menu__confirm-btn'),
new CSSLocator('cancelButton', '.c-top-menu__cancel-btn'),
new CSSLocator('mainWindow', '.m-ud'),
- new CSSLocator('selectedLocationsTab', '.c-selected-locations'),
+ new CSSLocator('selectedLocationsTab', '.c-selected-items-panel'),
new CSSLocator('categoryTabSelector', '.c-tab-selector__item'),
new CSSLocator('selectedTab', '.c-tab-selector__item--selected'),
new VisibleCSSLocator('contentIframe', '.c-content-edit__iframe, .m-content-create__iframe'),