import React, {FC, useEffect, useMemo, useRef, useState} from "react"; import {TestableCompositeData, TestableCompositeDataWithChildren, TestableData, useNodesStore} from "./store"; import {CardItemProps} from "./CardItem"; import {colors, ConditionColorPalette, defaultConditionColorPalette} from "./Colors"; import classNames from "classnames"; import {__} from "../globals"; import {RightContextButtons, RightContextButtonsProps} from "./RightContextButtons"; import {Color, ColorWithCustomTint, request} from "./Color"; import {useChildrenIds, useNodeData, useParentId, useTestableTypeFromContext} from "./testables"; import {atom, useAtomValue, useSetAtom} from "jotai"; import {HoveredContextIDAtom, PopupWindowStateContext, TestablesPopupWindowStateAtom, TreeContextData} from "./atoms"; import {RemoveButton} from "./RemoveButton"; import {useOpenEditTestablePopup} from "./hooks"; import {ContextCardItem} from "./ContextCardItem"; import {Card, CardProps} from "./Card"; import {RightContextButtonProps} from "./RightContextButton"; import {motion} from 'motion/react'; import {TestablesSaver} from "./TestablesSaver"; import {useOpenTestablesPopupWindow} from "./useOpenTestablesPopupWindow"; import {getSuggestedScopeForTestable} from "./getSuggestedScopeForTestable"; import {TargetedConfirmDialog} from "../TargetedConfirmDialog"; import {TestableGroupTypeId} from "./testableGroupTypes"; import {applyConditionContextCardTheme, getConditionContextCardTheme} from "./conditionCardTheme"; export type ContextProps = /* Pick */ & Pick & Pick & { id?: string, // only needed when data is not provided data: TestableCompositeData | TestableCompositeDataWithChildren, buttonsAreEnabled?: boolean, removable?: boolean, confirmRemoval?: boolean, showRemoveButtonLabel?: boolean, buttonsDisplay?: RightContextButtonsProps['displayType'], onRightButtonsClick?: RightContextButtonsProps['onClick'], showRightContextButtonsLabel?: boolean, // defaults to true highlight?: boolean, onItemClick?: (id: string | TestableData) => void, onRemoveClick?: (id: string | TestableData) => void, activeItemIds?: string[], supportsMultipleComponents?: boolean, // for the testables popup when clicking an existing context, setting this to false will make the popup not show the add another filter/condition- defaults to unsupportedComponents?: [], // true floatButtons?: boolean, conditionColorPalette?: ConditionColorPalette, popupScope?: PopupWindowStateContext['scope'] | 'popup-sidebar', popupSupportedComponents?: string[], popupShowTabs?: boolean, popupConditionColorPalette?: ConditionColorPalette, } export const Context: FC = ({ id, data, buttonsDisplay, highlight, onRightButtonHoverStart, onRightButtonHoverEnd, removable = true, confirmRemoval = true, buttonsAreEnabled = true, // ideally this should be false but this was added after it was implemented in several places already so the default has to be true onItemClick, onRemoveClick, activeItemIds = [], fullWidth = false, color = colors.purple, scaleOnHover, animateLayout = true, supportsMultipleComponents = true, unsupportedComponents = [], showRemoveButtonLabel = true, onRightButtonsClick, floatButtons = false, showRightContextButtonsLabel = true, conditionColorPalette, popupScope = 'tree-node', popupSupportedComponents, popupShowTabs = false, popupConditionColorPalette, }) => { id = id || data.id let useDataObjectInsteadOfID = !!data?.children; const conditionOrFilterIds: (TestableData['id'] | TestableData)[] = useChildrenIds(useDataObjectInsteadOfID ? data : id) // in case data is stale const testableType = useTestableTypeFromContext(useDataObjectInsteadOfID ? data : id) const parentId = useParentId(id) const requestedConditionPalette = conditionColorPalette || data?.conditionColorPalette || popupConditionColorPalette const shouldUseConditionCardTheme = testableType === 'condition' || popupScope === 'preconditions' || popupScope === 'predates' || Boolean(requestedConditionPalette) const conditionThemePlace = ( popupScope === 'preconditions' || popupScope === 'predates' || popupScope === 'tier-base' || popupScope === 'tier-subchild' || popupScope === 'popup-sidebar' ) ? popupScope : 'context' const conditionCardTheme = shouldUseConditionCardTheme ? getConditionContextCardTheme(requestedConditionPalette || defaultConditionColorPalette, conditionThemePlace) : undefined const contextCardColor: Color | ColorWithCustomTint = useMemo( () => applyConditionContextCardTheme(color, conditionCardTheme), [color, conditionCardTheme] ) const parentTestableData = useNodeData(parentId) const isInsideAND = parentTestableData?.type === TestableGroupTypeId.AND const setHoveredContextID = useSetAtom(HoveredContextIDAtom) const isHoveredAtom = useMemo(() => atom(get => get(HoveredContextIDAtom) === id), [id]) const isHovered = useAtomValue(isHoveredAtom) const [isHighlighted, setHighlighted] = useState(false) const shouldBeHighlighted = highlight || isHighlighted; const removeTestable = useNodesStore(store => store.removeTestable) const setTestablesPopupWindowState = useSetAtom(TestablesPopupWindowStateAtom) const openTestablePopupWindow = useOpenTestablesPopupWindow(); const openEditTestablePopup = useOpenEditTestablePopup({ testableType, supportsMultipleComponents, unsupportedComponents, scope: popupScope, supportedComponents: popupSupportedComponents, conditionColorPalette: popupConditionColorPalette || conditionColorPalette, showTabs: popupShowTabs, }) const [isRemoveConfirmOpen, setIsRemoveConfirmOpen] = useState(false) const [removeOriginRect, setRemoveOriginRect] = useState(null) const removeTitle = testableType === 'condition' ? __('Delete this group of conditions?') : __('Delete this group of product filters?') const removeDescription = testableType === 'condition' ? __('This will remove this group and all its conditions.') : __('This will remove this group and all its product filters.') const confirmRemoveContext = () => { removeTestable(id) setHoveredContextID(null) setIsRemoveConfirmOpen(false) } const rightContextButtons = buttonsAreEnabled && { if (context === 'button.label.background' && !showRightContextButtonsLabel) { return color.background(60) } } } as ColorWithCustomTint} contextID={id} displayType={buttonsDisplay} isOpened={true} parentData={parentTestableData as TestableCompositeData} onRightButtonHoverStart={onRightButtonHoverStart} onRightButtonHoverEnd={onRightButtonHoverEnd} onClick={onRightButtonsClick} showLabel={showRightContextButtonsLabel} /> const containerRef = useRef(null) let [height, setHeight] = useState('auto') const [cardItemHeights, setCardItemHeights] = useState>({}) const cardItemsSignature = conditionOrFilterIds.map((conditionOrFilterId) => { return typeof conditionOrFilterId === 'object' ? conditionOrFilterId.id : conditionOrFilterId }).join('|') useEffect(() => { if (containerRef.current) { const resizeObserver = new ResizeObserver((entries) => { // We only have one entry, so we can use entries[0]. const observedHeight = entries[0].contentRect.height setHeight(observedHeight) }) resizeObserver.observe(containerRef.current) return () => { // Cleanup the observer when the component is unmounted resizeObserver.disconnect() } } }, []) useEffect(() => { setCardItemHeights({}) }, [id, cardItemsSignature]) if (isHovered && typeof height === 'number') { //height = height + 100 } return setHoveredContextID(id)} onMouseLeave={() => setHoveredContextID(null)} >
setHoveredContextID.bind(null, id)} onHoverEnd={() => setHoveredContextID.bind(null, null)} outerRing={data?.mode === 'filter'} fullWidth={fullWidth} scaleOnHover={scaleOnHover} animateLayout={animateLayout} bottomButton={false ? { icon: <>{false && } {true && }, text: __('Add filter'), onClick: () => { } } : undefined} afterContent={<> {removable && { if (confirmRemoval) { setRemoveOriginRect(event.currentTarget.getBoundingClientRect()) setIsRemoveConfirmOpen(true) } else { confirmRemoveContext() } }} entityID={id} show={isHovered} onRequestToHighlight={setHighlighted.bind(this, true)} onRequestToUnHighlight={setHighlighted.bind(this, false)} showLabel={showRemoveButtonLabel} />} {floatButtons &&
{rightContextButtons &&
{rightContextButtons}
}
} } > {conditionOrFilterIds.map((conditionOrFilterId, index) => { const previousCardItemHeight = cardItemHeights[index - 1] const currentCardItemHeight = cardItemHeights[index] const topConnectorMarkerCenterOffset = previousCardItemHeight && currentCardItemHeight ? (previousCardItemHeight + currentCardItemHeight) / 4 : undefined return { setCardItemHeights((previousHeights) => { if (previousHeights[index] === measuredHeight) { return previousHeights } return { ...previousHeights, [index]: measuredHeight, } }) }} onClick={async () => { const testableId = typeof conditionOrFilterId === 'object' ? conditionOrFilterId.id : conditionOrFilterId; const suggestedScope = await getSuggestedScopeForTestable(testableId); if (onItemClick) { onItemClick(conditionOrFilterId) return } openEditTestablePopup(testableId, suggestedScope!) return const context = { id: 'context-edit', scope: 'tree-node', data: { componentType: testableType, targetType: 'testableComposite', supportsMultipleComponents, unsupportedComponents, mode: 'edit', targetId: conditionOrFilterId, showTabs: false, extraData: { suggestedScope, } } } as PopupWindowStateContext openTestablePopupWindow({ context, onClose: (data) => { if (data.status !== 'success' || !data.components) { return } // @ts-ignore const saver = new TestablesSaver(context) saver.update(data.components) } }); /* setTestablesPopupWindowState({ isOpen: true, context: { scope: 'tree-node', data: { targetType: 'testableComposite', mode: 'edit', targetId: conditionOrFilterId } } }) */ }} onRemoveClick={ /*onRemoveClick ? (() => onRemoveClick?.(conditionOrFilterId)) : undefined*/ removable? (() => { if(onRemoveClick) { onRemoveClick(conditionOrFilterId) } else if(removable) { typeof conditionOrFilterId === 'string' && removeTestable(conditionOrFilterId) } }) : undefined } dynamicRemoveButton={!removable? false : (removable || !!onRemoveClick) } topConnectorMarkerCenterOffset={topConnectorMarkerCenterOffset} topConnectorMarker={ testableType === 'condition' && conditionOrFilterIds.length > 1 && index > 0 ? ( + ) : undefined } position={classNames({ 'first': index === 0, 'last': index + 1 === conditionOrFilterIds.length, 'middle': index > 0 && (index + 1) < conditionOrFilterIds.length }) as CardItemProps['position']} /> })}
setIsRemoveConfirmOpen(false)} />
{!floatButtons && rightContextButtons}
; }