@shopgate/engage
Version:
Shopgate's ENGAGE library.
98 lines • 12.3 kB
JavaScript
function _typeof(obj){if(typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"){_typeof=function _typeof(obj){return typeof obj;};}else{_typeof=function _typeof(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};}return _typeof(obj);}function _objectWithoutProperties(source,excluded){if(source==null)return{};var target=_objectWithoutPropertiesLoose(source,excluded);var key,i;if(Object.getOwnPropertySymbols){var sourceSymbolKeys=Object.getOwnPropertySymbols(source);for(i=0;i<sourceSymbolKeys.length;i++){key=sourceSymbolKeys[i];if(excluded.indexOf(key)>=0)continue;if(!Object.prototype.propertyIsEnumerable.call(source,key))continue;target[key]=source[key];}}return target;}function _objectWithoutPropertiesLoose(source,excluded){if(source==null)return{};var target={};var sourceKeys=Object.keys(source);var key,i;for(i=0;i<sourceKeys.length;i++){key=sourceKeys[i];if(excluded.indexOf(key)>=0)continue;target[key]=source[key];}return target;}function _toPropertyKey(arg){var key=_toPrimitive(arg,"string");return _typeof(key)==="symbol"?key:String(key);}function _toPrimitive(input,hint){if(_typeof(input)!=="object"||input===null)return input;var prim=input[Symbol.toPrimitive];if(prim!==undefined){var res=prim.call(input,hint||"default");if(_typeof(res)!=="object")return res;throw new TypeError("@@toPrimitive must return a primitive value.");}return(hint==="string"?String:Number)(input);}function _defineProperty(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else{obj[key]=value;}return obj;}function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}function _slicedToArray(arr,i){return _arrayWithHoles(arr)||_iterableToArrayLimit(arr,i)||_nonIterableRest();}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance");}function _iterableToArrayLimit(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"]!=null)_i["return"]();}finally{if(_d)throw _e;}}return _arr;}function _arrayWithHoles(arr){if(Array.isArray(arr))return arr;}import React,{useMemo,useState,useEffect,useCallback}from'react';import PropTypes from'prop-types';import{connect}from'react-redux';import debounce from'lodash/debounce';import isEqual from'lodash/isEqual';import{router}from'@shopgate/engage/core';import{getFiltersByHash}from'@shopgate/engage/filter';// eslint-disable-next-line no-unused-vars, import/named
import Context,{APIFilter,RouteFilters}from"./FilterPageProvider.context";import{buildInitialFilters,buildUpdatedFilters}from"../helpers";/**
* @param {Object} state The application state.
* @param {Object} props The component props.
* @returns {Object}
*/var mapStateToProps=function mapStateToProps(state,props){return{filters:getFiltersByHash(state,props)};};/**
* @param {Object} next The next component props.
* @param {Object} prev The previous component props.
* @returns {boolean}
*/var areStatePropsEqual=function areStatePropsEqual(next,prev){if(!prev.filters&&next.filters||!isEqual(prev.filters,next.filters)){return false;}return true;};/**
* The FilterPageProvider component provides all relevant data and callbacks to represent and modify
* the current state of the "filter" page.
* @param {Object} props Provider props
* @param {APIFilter[]} props.filters Array of available filters
* @param {RouteFilters} props.activeFilters Object with the active filters for a filtered product
* list
* @param {string} props.parentRouteId Id of the route with the product list that's supposed to be
* filtered
* @param {Function} [props.onApply] Callback invoked when users pressed the apply button
* @param {string} [props.categoryId] A category to be used for filter selection from Redux
* @param {string} [props.searchPhrase] A search phrase to be used for filter selection from Redux
* @param {NodeList} children Provider children
* @returns {JSX.Element}
*/var FilterPageProvider=function FilterPageProvider(_ref){var filtersProp=_ref.filters,activeFiltersProp=_ref.activeFilters,parentRouteId=_ref.parentRouteId,onApply=_ref.onApply,children=_ref.children;var _useState=useState(activeFiltersProp||{}),_useState2=_slicedToArray(_useState,2),currentFilters=_useState2[0],setCurrentFilters=_useState2[1];/**
* Storage that hosts an object that represents the initial state of the filters page.
* It's created from the "filters" array that contains all available filters, and the
* "activeFilters" object that represents filters with an active value selection.
*/var _useState3=useState(buildInitialFilters(filtersProp,activeFiltersProp)),_useState4=_slicedToArray(_useState3,2),initialFilters=_useState4[0],setInitialFilters=_useState4[1];/**
* Storage that hosts an object that represents the a partial state of the filters page with
* all filters that where modified since the filters page was opened.
*/var _useState5=useState({}),_useState6=_slicedToArray(_useState5,2),changedFilters=_useState6[0],setChangedFilters=_useState6[1];// Object that represents the current state of all filters
var mergedFilters=useMemo(function(){return _extends({},initialFilters,{},changedFilters);},[changedFilters,initialFilters]);/**
* Effect that updates the "initialFilters" state when it doesn't have content yet
*/useEffect(function(){setInitialFilters(function(currentState){if(Object.keys(currentState).length>0){return currentState;}return buildInitialFilters(filtersProp,activeFiltersProp);});},[activeFiltersProp,filtersProp]);/**
* Whether a reset of the active filters is possible.
*
* Reset is possible whenever filters where selected by the user before, or when filters where
* modified since the filters page was opened.
* @type {boolean}
*/var resetPossible=useMemo(function(){return!!(Object.keys(currentFilters).length||Object.keys(changedFilters).length);},[changedFilters,currentFilters]);/**
* Whether the filter selection has changed since the filters page was opened
* @type {boolean}
*/var hasChanged=useMemo(function(){return Object.keys(changedFilters).length>0||!!(Object.keys(currentFilters).length===0&&activeFiltersProp);},[activeFiltersProp,changedFilters,currentFilters]);/**
* Retrieves a list of currently selected values for a filter
* @callback getSelectedFilterValues
* @param {string} filterId The id of the filter
* @returns {string[]}
*/var getSelectedFilterValues=useCallback(/**
* @param {string} filterId The id of the filter
* @returns {string[]}
*/function(filterId){var _initialFilters$filte;var value=changedFilters[filterId]?changedFilters[filterId].value:((_initialFilters$filte=initialFilters[filterId])===null||_initialFilters$filte===void 0?void 0:_initialFilters$filte.value)||[];return value.map(function(entry){return entry.id||entry;});},[changedFilters,initialFilters]);/**
* Resets all filters which have been changed by the user
*/var resetAllFilters=useCallback(function(){setInitialFilters(buildInitialFilters(filtersProp,{}));setCurrentFilters({});setChangedFilters({});},[filtersProp]);/**
* Resets all filters which have been changed by the user since the filters page was opened
*/var resetChangedFilters=useCallback(function(){setChangedFilters({});},[]);/**
* Adds or updates the selection for a changed filter
* @callback updateChangedFilterInternal
* @param {string} filterId The id of the filter to be updated
* @param {string[]} selectedValues The updated selected values
*/var updateChangedFilterInternal=useCallback(/**
* @param {string} filterId The id of the filter to be updated
* @param {string[]} selectedValues The updated selected values
*/function(filterId,selectedValues){setChangedFilters(function(currentState){return _extends({},currentState,_defineProperty({},filterId,selectedValues));});},[]);/**
* Removes a changed filter
* @callback removeChangedFilterInternal
* @param {string} filterId The id of the filter to be updated
* @param {string[]} selectedValues The updated selected values
*/var removeChangedFilterInternal=useCallback(/**
* @param {string} filterId The id of the filter to be removed
*/function(filterId){setChangedFilters(function(currentState){// Separate the given id from the other set filters.
var removed=currentState[filterId],remainingFilters=_objectWithoutProperties(currentState,[filterId].map(_toPropertyKey));return remainingFilters;});},[]);/**
* Updates the selection for a filter
*
* @param {string} filterId The id of the filter to be updated
* @param {string[]} selectedValues The updated selected values
*/var updateSelectedFilterValues=useCallback(debounce(/**
* @param {string} filterId The id of the filter to be updated
* @param {string[]} selectedValues The updated selected values
*/function(filterId,selectedValues){// Retrieve data of filter to be updated from the filters array.
var filter=filtersProp.find(function(entry){return entry.id===filterId;});// Retrieve the values for the updated filter that where set when the filter page was opened
var initialValues=initialFilters[filterId].value;// Prepare the update payload
var stateValue=[].concat(selectedValues);/**
* No initial values where set for this filter, and the update contains no values. So we
* can remove the filter from the changedFilters storage.
*/if(initialValues.length===0&&selectedValues.length===0){removeChangedFilterInternal(filterId);return;}/**
* When the filter update would recreate the state that the filter initially had, we
* remove the filter from the changedFilters storage.
*
* That enables proper behavior for the "reset" and "update" button states.
*/if(initialValues.length!==0&&selectedValues.length!==0){if(initialValues.every(function(initial,i){return initial===selectedValues[i];})){removeChangedFilterInternal(filterId);return;}}if(Array.isArray(filter.values)){/**
* The selectedValues array only contains a list of ids.
* For the getProducts request that's dispatched after the current filter selection was
* applied, id and label is required at the filter values level.
*/stateValue=selectedValues.map(function(valueId){var match=filter.values.find(function(entry){return entry.id===valueId;});return{id:match.id,label:match.label};});}updateChangedFilterInternal(filterId,_extends({id:filterId,type:filter.type,label:filter.label,value:stateValue},filter.source&&{source:filter.source}));},50),[filtersProp,initialFilters,removeChangedFilterInternal,updateChangedFilterInternal]);/**
* Applies the current filter selection to the parent route with a product list to be filtered
*/var applyFilters=useCallback(function(){var filters=buildUpdatedFilters(currentFilters,changedFilters);router.update(parentRouteId,{filters:filters});onApply(filters);},[changedFilters,currentFilters,onApply,parentRouteId]);var value=useMemo(function(){return{resetPossible:resetPossible,hasChanged:hasChanged,apiFilters:filtersProp||[],filters:mergedFilters,resetAllFilters:resetAllFilters,resetChangedFilters:resetChangedFilters,getSelectedFilterValues:getSelectedFilterValues,updateSelectedFilterValues:updateSelectedFilterValues,applyFilters:applyFilters};},[hasChanged,resetPossible,filtersProp,mergedFilters,resetAllFilters,resetChangedFilters,getSelectedFilterValues,updateSelectedFilterValues,applyFilters]);return React.createElement(Context.Provider,{value:value},children);};FilterPageProvider.defaultProps={children:null,activeFilters:null,parentRouteId:null,filters:null,onApply:function onApply(){return setTimeout(router.pop,250);}};/**
* @type FilterPageProvider
*/export default connect(mapStateToProps,null,null,{areStatePropsEqual:areStatePropsEqual})(FilterPageProvider);