/* eslint-disable @typescript-eslint/no-explicit-any */ import get from 'lodash-es/get'; import { FieldString } from '@/TailwindPlay/Fields/String.js'; import { FieldS3Image } from '@/TailwindPlay/Fields/S3Image.js'; import { FieldContent } from '@/TailwindPlay/Fields/Content.js'; import { FieldLocalImage } from '@/TailwindPlay/Fields/LocalImage.js'; import { FieldTitle } from '@/TailwindPlay/Fields/Title.js'; import { FieldDate } from '@/TailwindPlay/Fields/Date.js'; import { FieldBoolean } from '@/TailwindPlay/Fields/Boolean.js'; import { TailwindPlayCommonParams } from '@/TailwindPlay/Fields/types.js'; import { FieldSelect } from '@/TailwindPlay/Fields/Select.js'; import { FieldRelation } from '@/TailwindPlay/Fields/Relation.js'; import { FieldNumber } from '@/TailwindPlay/Fields/Number.js'; import { FieldFile } from '@/TailwindPlay/Fields/File.js'; import { FieldS3Video } from '@/TailwindPlay/Fields/S3Video.js'; import { FieldInput } from '@/TailwindPlay/Fields/Input.js'; import { FieldButton } from '@/TailwindPlay/Fields//Button.js'; import { FieldCheckbox } from '@/TailwindPlay/Fields/Checkbox.js'; import { FieldVariable } from '@/TailwindPlay/Fields/Variable.js'; import { FieldLink } from '@/TailwindPlay/Fields/Link.js'; import { FieldScript } from '@/TailwindPlay/Fields/Script.js'; import type { FieldFrontType } from '@/graphql/core/selectors.js'; import React, { createElement } from 'react'; import { ActionType, CMSType } from '@/zeus/index.js'; import { extractHusarData } from '@/TailwindPlay/shared/extract.js'; import { evaluateConditionNode, evaluateSetValueRules } from '@/TailwindPlay/shared/rules.js'; export const TailwindPlayOuter: React.FC< Omit & { fields: FieldFrontType[]; mainBackground?: string; containerBackground?: string; } > = ({ fields, mainBackground, containerBackground, ...commonParams }) => { return (
); }; const shouldShowField = ( f: FieldFrontType, state: Record, modelData: Record, prefix: string, ): boolean => { if (!f.visual?.rules || f.visual.rules.length === 0) { return true; } const allRulesWithoutActions = f.visual.rules.every((rule) => !rule.actions || rule.actions.length === 0); if (allRulesWithoutActions) { return f.visual.rules.every((rule) => rule.conditions.every((condition) => evaluateConditionNode(f, condition, state, modelData, prefix)), ); } return true; }; export function TailwindPlay({ fields, ...commonParams }: Omit & { fields: T[]; }) { // If rootFields is not set, this is the root level, so use these fields const rootFields = commonParams.rootFields || fields; const visibleFields = fields .filter((f) => !!(f.visual || (!f.visual && f.type !== CMSType.STRING))) .filter((f) => shouldShowField(f, commonParams.state || {}, commonParams.modelData || {}, commonParams.prefix || ''), ); return ( <> {visibleFields.map((f) => ( ))} ); } export const ListField: React.FC = ({ f, index, ...commonProps }) => { const { modelData } = commonProps; const key = `${commonProps.prefix || ''}${f.name}${typeof index === 'number' ? `[${index}]` : ''}`; const values: Array = get(modelData, key) || []; if (!Array.isArray(values)) { return null; } return ( <> {values.map((_, i) => ( ))} ); }; export const FieldSingle: React.FC = (props) => { const { f } = props; const { v, attributes, overrideKey } = extractHusarData(props); const componentOverride = props.components?.[overrideKey]?.({ data: v, attrs: attributes, f, component: , }); return componentOverride || ; }; // Helper: Check if field should be hidden based on SET_DISABLED actions const isFieldHidden = ( f: FieldFrontType, state: Record, modelData: Record, prefix: string, ): boolean => { const hiddenRules = (f.visual?.rules || []).filter( (rule) => rule.actions && rule.actions.some((a) => a.type === ActionType.SET_DISABLED && a.value === 'hidden'), ); return hiddenRules.some((rule) => rule.conditions?.every((condition) => evaluateConditionNode(f, condition, state, modelData, prefix)), ); }; export const FieldSingleInner: React.FC = (props) => { const { f, prefix = '', ...commonParams } = props; const { key, attributes, overrideKey } = extractHusarData(props); if (isFieldHidden(f, commonParams.state || {}, commonParams.modelData || {}, prefix)) { return null; } if (f.list) { return ; } // Helper: Find a field by path in the field tree const findFieldPath = (fieldName: string, fields: FieldFrontType[], currentPrefix: string = ''): string | null => { for (const field of fields) { const fieldPath = currentPrefix ? `${currentPrefix}${field.name}` : field.name; if (field.name === fieldName) { return fieldPath; } // Recursively search in nested fields if (field.fields && field.fields.length > 0) { const found = findFieldPath(fieldName, field.fields as FieldFrontType[], `${fieldPath}.`); if (found) { return found; } } } return null; }; const applyPrefixToPath = (p: string | undefined): string => { if (!p || !prefix || p.startsWith(prefix)) { return p as string; } if (commonParams.rootFields) { const targetFieldName = p.split('.')[0]; const foundPath = findFieldPath(targetFieldName, commonParams.rootFields); if (foundPath) { const pathSegments = p.split('.'); const result = pathSegments.length > 1 ? `${foundPath}.${pathSegments.slice(1).join('.')}` : foundPath; return result; } } const firstSegment = p.split('.')[0]; const prefixSegments = prefix.split('.').filter(Boolean); const prefixFirstSegment = prefixSegments[0]; const stateKeys = Object.keys(commonParams.state || {}); if ( firstSegment !== prefixFirstSegment && (firstSegment in (commonParams.state || {}) || firstSegment in (commonParams.modelData || {})) ) { return p; } if (prefixSegments.length > 0) { for (let i = 1; i <= prefixSegments.length; i++) { const ancestorPrefix = prefixSegments.slice(0, i).join('.'); const testPath = `${ancestorPrefix}.${p}`; if ( testPath in (commonParams.state || {}) || stateKeys.some((k) => k === testPath || k.startsWith(`${testPath}.`)) ) { return testPath; } } } const normPrefix = prefix.endsWith('.') ? prefix.slice(0, -1) : prefix; const lastSeg = normPrefix.split('.').filter(Boolean).pop(); if (lastSeg && p.startsWith(`${lastSeg}.`)) { const result = `${prefix}${p.slice(lastSeg.length + 1)}`; return result; } const result = `${prefix}${p}`; return result; }; const onChange = (key: string, value: any) => { const actionsToApply = f.visual?.rules ?.filter((rule) => { if (!rule.actions || rule.actions.length === 0) return false; const conditionsMet = rule.conditions?.every((condition) => evaluateConditionNode( f, condition, { ...commonParams.state, [key]: value }, commonParams.modelData || {}, prefix, ), ); return conditionsMet; }) .flatMap((rule) => rule.actions) .filter((a) => typeof a.path === 'string') .map((a) => { const prefixedPath = applyPrefixToPath(a.path); return { ...a, path: prefixedPath }; }) || []; const uniqueActions = Array.from( new Map(actionsToApply.map((a) => [a.path + JSON.stringify(a.value), a])).values(), ); const actions = [...uniqueActions, { path: key, value }]; const newData = { ...commonParams.state, [key]: value }; actions.forEach((action) => { if (action.value === null || action.value === undefined) { delete newData[action.path]; } else { newData[action.path] = action.value; } }); commonParams.setState?.(newData); }; const interactive = { ...props, value: commonParams.state?.[key] || '', onChange }; const nonInteractive = { ...props, rootFields: undefined, host: undefined }; // Handle nested shapes/objects if (f.fields && (f.type === CMSType.SHAPE || f.type === CMSType.OBJECT || f.type === CMSType.OBJECT_TABS)) { const openValue = evaluateSetValueRules( f, ActionType.SET_VALUE, commonParams.state || {}, commonParams.modelData || {}, prefix, ); const open = openValue === true || openValue === 'true' ? true : false; const elementProps = { ...attributes, ...(open !== undefined ? { open } : {}), id: f.name, 'data-field-name': key, }; const newDataPrefix = `${overrideKey}.`; const newPrefix = `${key}.`; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { index: _i, ...commonParamsRest } = commonParams; return createElement( f.visual?.component || 'div', elementProps, , ); } return ( <> {/* CAN BE SSR? */} {f.type === CMSType.CONTENT && } {f.type === CMSType.STRING && } {f.type === CMSType.NUMBER && } {f.type === CMSType.SELECT && } {f.type === CMSType.RELATION && } {f.type === CMSType.BOOLEAN && } {f.type === CMSType.DATE && } {f.type === CMSType.TITLE && } {f.type === CMSType.IMAGE_URL && } {f.type === CMSType.IMAGE && } {f.type === CMSType.VIDEO && } {f.type === CMSType.FILE && } {f.type === CMSType.VARIABLE && } {f.type === CMSType.LINK && } {/* NEED TO BE CSR? */} {f.type === CMSType.SCRIPT && } {f.type === CMSType.BUTTON && } {f.type === CMSType.INPUT && } {f.type === CMSType.CHECKBOX && } ); };