import {ComponentRuntimeDataWithParentType, getDefaultRuntimeComponentData} from "./components"; import {PopupWindowStateContext, TreeContextData} from "./atoms"; import {convertTestableData, TestableCompositeData, TestableData, TestablePartial, useNodesStore} from "./store"; import {cloneDeep, update} from "lodash"; import {ComponentType} from "./ComponentsMeta"; import {QuantityMeta, QuantityOptions, QuantityType} from "./cards/Quantity"; import { generateDefaultNestedSelectActionsBranchData, wrapTestablesInDefaultGroup } from "./NodeHelpers"; import {AnyProductsMeta} from "./cards/AnyProducts"; import {getLazyTestables} from "./hooks"; import {Testables} from "./testables"; import {TestableGroupTypeId} from "./testableGroupTypes"; export class TestablesSaver { private readonly context: PopupWindowStateContext; private testableType = 'filter' constructor(context: PopupWindowStateContext) { this.context = context; } add(filters: ComponentRuntimeDataWithParentType[], extraData?: {suggestedScope?: string}) { const { addTreeNode, addTestables, addTestable, wrapAllContexts, write, testables, testableRelations, testablePartials } = useNodesStore.getState() filters = convertTestableData(filters) const selectedFilters = filters.map(filter => ({ ...filter, testableType: this.context.data.componentType, })); if (this.context.data.targetType === 'ghost') { // just add them directly to the memory, not to the tree addTestable(wrapTestablesInDefaultGroup(selectedFilters), id => { }) } else if (this.context.data.targetType === 'root') { let selectedFiltersWithAnyProductsMaybeRemoved = cloneDeep(selectedFilters); // so here's the thing, If the user has selected AnyProducts and a Quantity or another filter, eg: they changed they mind // and added a Categories filter from the "Narrow down selection" menu, then // we should not add AnyProducts as its redundant // we ONLY add AnyProducts when the user has not selected a Quantity or any other filter (meaning any products with any quantity) if (selectedFilters.length > 1) { if (selectedFilters.find(({type}) => type === 'AnyProducts')) { // remove AnyProducts from the selected filters selectedFiltersWithAnyProductsMaybeRemoved = selectedFilters.filter(({type}) => type !== 'AnyProducts'); } } if (this.shouldRemoveDefaultQuantityFilter(this.context.data.componentType as ComponentType, selectedFilters)) { selectedFiltersWithAnyProductsMaybeRemoved = selectedFilters.filter(({type}) => type !== QuantityMeta.id) } addTreeNode({ testable: wrapTestablesInDefaultGroup( [ ...selectedFiltersWithAnyProductsMaybeRemoved, .../*extraFiltersToAdd*/[].map(filter => ({ // @ts-ignore ...filter, testableType: this.context.data.componentType })) ] ), branch: generateDefaultNestedSelectActionsBranchData(), extraData }) } else { const testablesManager = new Testables({testables, testableRelations, testablePartials}) const targetId = this.context.data.targetId const groupType = this.context.data.groupType // is this a context or a testableComposite (group)? if (testablesManager.isContext(targetId as string)) { if (!groupType) { addTestables([convertTestableData(filters), targetId as string]) return } // here we have to create a parent group if the context is not a default AND (is an AND and only has 1 context) const parentTestableGroup = testablesManager.getParent(targetId as string)!; let parentGroupToUse: TestableCompositeData if (testablesManager.isDefaultGroup(parentTestableGroup.id)) { parentGroupToUse = parentTestableGroup as TestableCompositeData addTestables([convertTestableData(filters), parentTestableGroup.id], (a, testables) => { testables.changeTestableGroupOptions(parentGroupToUse.id, { type: groupType, }) }) } else { wrapAllContexts([parentTestableGroup.id], (a, testables) => { // after the warp, the parent of the target context will be a new group (that has been placed inside the parent group) const newParentGroup = testables.getParent(targetId as string); testables.addTestables(...[convertTestableData(filters), newParentGroup!.id]) parentGroupToUse = newParentGroup as TestableCompositeData testables.changeTestableGroupOptions(parentGroupToUse.id, { type: groupType, }) }) } } else { if (!groupType || !testablesManager.isTestableCompositeGroup(targetId as string)) { addTestables([convertTestableData(filters), targetId as string]) return } write(state => { const writableTestables = new Testables(state) const targetGroup = writableTestables.getNode(targetId as string) as TestableCompositeData | undefined if (!targetGroup) { throw new Error(`Expected "${targetId}" to be an existing testable group.`) } writableTestables.wrapChildrenInNewGroup(targetId as string) writableTestables.changeTestableGroupOptions(targetId as string, {type: groupType}) const newGroup = writableTestables.createGroup(targetId as string, TestableGroupTypeId.AND, targetGroup.mode) writableTestables.addTestables(convertTestableData(filters), newGroup.id) }) } } } update(filters: TestableDataType[]) { const {removeTestable, updateTestables, updateTestablePartial, updateTestable} = useNodesStore.getState() const testables = getLazyTestables() if (filters.length === 0) { // remove all filters removeTestable(testables.getParent(this.context.data.targetId as string)!.id) } else { if (this.context.scope === 'tier-subchild') { updateTestablePartial([filters[0] as TestablePartial]) } else if (this.context.scope === 'tier-base') { updateTestable([filters[0] as TestableData]) } else { // commit the changes to the store updateTestables(testables => { testables.updateTestables( convertTestableData( filters.map(filter => (testables.getWithParentType(filter, this.context.data.componentType))) ), testables.getParent(this.context.data.targetId as string)!.id, true ) }) } } } shouldRemoveDefaultQuantityFilter(testableType: ComponentType, selectedFilters: ComponentRuntimeDataWithParentType[]) { // if quantity is the only filter, keep it, regardless of what the user selected. if (selectedFilters.length === 1 && selectedFilters[0].type === QuantityMeta.id) { // matter of fact, add an "any products" if the quantity type is any if (selectedFilters[0].options.type === QuantityType.Any) { // add an "any products" filter selectedFilters.push({ parentType: testableType, ...getDefaultRuntimeComponentData(AnyProductsMeta) }); return true } else { return false; } } const quantityFilter = selectedFilters.find(({type}) => type === QuantityMeta.id) // if has a quantity filter and it's any quantity type if (quantityFilter && (quantityFilter.options as QuantityOptions).type === QuantityType.Any) { // remove the default quantity filter return true; } } }