UNPKG

@shopgate/engage

Version:
98 lines • 12.3 kB
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);