UNPKG

@react-awesome-query-builder/core

Version:
567 lines (524 loc) 18.3 kB
import pick from "lodash/pick"; import {widgetDefKeysToOmit, omit} from "./stuff"; export const _widgetDefKeysToOmit = widgetDefKeysToOmit; // for ui export const configKeys = ["conjunctions", "fields", "types", "operators", "widgets", "settings", "funcs", "ctx"]; export const selectTypes = [ "select", "multiselect", "treeselect", "treemultiselect", ]; export function* iterateFuncs(config) { yield* _iterateFields(config, config.funcs || {}, []); } export function* iterateFields(config) { yield* _iterateFields(config, config.fields || {}, []); } function* _iterateFields(config, subfields, path, subfieldsKey = "subfields") { const fieldSeparator = config?.settings?.fieldSeparator || "."; for (const fieldKey in subfields) { const fieldConfig = subfields[fieldKey]; if (fieldConfig[subfieldsKey]) { yield* _iterateFields(config, fieldConfig[subfieldsKey], [...path, fieldKey], subfieldsKey); } else { yield [ [...path, fieldKey].join(fieldSeparator), fieldConfig, fieldKey ]; } } } export const getFieldRawConfig = (config, field, fieldsKey = "fields", subfieldsKey = "subfields") => { if (!field) return null; if (field === "!case_value") { return config?.settings?.caseValueField; } const fieldSeparator = config?.settings?.fieldSeparator || "."; const parts = getFieldParts(field, config); const targetFields = config[fieldsKey]; if (!targetFields) return null; let fields = targetFields; let fieldConfig = null; let path = []; for (let i = 0 ; i < parts.length ; i++) { const part = parts[i]; path.push(part); const pathKey = path.join(fieldSeparator); fieldConfig = fields[pathKey]; if (i < parts.length-1) { if (fieldConfig && fieldConfig[subfieldsKey]) { fields = fieldConfig[subfieldsKey]; path = []; } else { fieldConfig = null; } } } return fieldConfig; }; // if `field` is alias (fieldName), convert to original full path export const normalizeField = (config, field, parentField = null) => { // tip: if parentField is present, field is not full path const fieldSeparator = config.settings.fieldSeparator; const path = [ parentField, ...field.split(fieldSeparator) ].filter(f => f != null); const findStr = field; const normalizedPath = config.__fieldNames[findStr]?.find?.(({inGroup}) => { if (inGroup) return parentField?.startsWith(inGroup); return true; })?.fullPath; return (normalizedPath || path).join(fieldSeparator); }; export const getFuncSignature = (config, func) => { if (!func) return null; const funcConfig = getFieldRawConfig(config, func, "funcs", "subfields"); if (!funcConfig) return null; const { returnType, args, } = funcConfig; const argsSignature = Object.fromEntries(Object.entries(args || {}).map(([k, v]) => { const argSignature = pick(v, [ "type", "valueSources", "defaultValue", "fieldSettings", // "asyncListValues", // not supported "isOptional", // to get proper caching key "_funcKey", "_argKey", "_isFuncArg", ]); return [k, argSignature]; })); const signature = { returnType, args: argsSignature, }; return signature; }; export const getFuncConfig = (config, func) => { if (!func) return null; const funcConfig = getFieldRawConfig(config, func, "funcs", "subfields"); if (!funcConfig) return null; //throw new Error("Can't find func " + func + ", please check your config"); return funcConfig; }; export const getFuncArgConfig = (config, funcKey, argKey) => { const funcConfig = getFuncConfig(config, funcKey); if (!funcConfig) return null; //throw new Error(`Can't find func ${funcKey}, please check your config`); const argConfig = funcConfig.args && funcConfig.args[argKey] || null; if (!argConfig) return null; //throw new Error(`Can't find arg ${argKey} for func ${funcKey}, please check your config`); return argConfig; }; export const isFieldDescendantOfField = (field, parentField, config = null) => { if (!parentField) return false; const fieldSeparator = config?.settings?.fieldSeparator || "."; const path = getFieldPath(field, config); const parentPath = getFieldPath(parentField, config); return path.startsWith(parentPath + fieldSeparator); }; export const getFieldPath = (field, config = null) => { if (typeof field === "string") return field; const fieldSeparator = config?.settings?.fieldSeparator || "."; return getFieldParts(field, config).join(fieldSeparator); }; export const getFieldParts = (field, config = null) => { if (!field) return []; if (Array.isArray(field)) return field; const fieldSeparator = config?.settings?.fieldSeparator || "."; if (field?.func) { return Array.isArray(field.func) ? field.func : field.func.split(fieldSeparator); } if (field?.get?.("func")) { // immutable return field?.get?.("func").split(fieldSeparator); } return field?.split?.(fieldSeparator) || []; }; export const getFieldPathParts = (field, config, onlyKeys = false) => { if (!field) return null; const fieldSeparator = config.settings.fieldSeparator; const parts = getFieldParts(field, config); if (onlyKeys) return parts; else return parts .map((_curr, ind, arr) => arr.slice(0, ind+1)) .map((parts) => parts.join(fieldSeparator)); }; export const getFieldId = (field) => { if (typeof field === "string" || Array.isArray(field)) { return `field:${getFieldPath(field)}`; } if (typeof field === "object" && field) { if (field._funcKey && field._argKey) { // it's func arg config return `arg:${getFieldPath(field._funcKey)}__${field._argKey}`; } if (field._funcKey) { // it's func config return `func:${getFieldPath(field._funcKey)}`; } if (field.func && field.arg) { // it's func arg return `arg:${getFieldPath(field.func)}__${field.arg}`; } if (field.func) { // it's field func return `func:${getFieldPath(field.func)}`; } if (field.type) { // it's already a config return null; } } if (field?.get?.("func")) { // immutable if (field?.get("arg")) { // it's func arg return `arg:${getFieldPath(field.get("func"))}__${field.get("arg")}`; } else { // it's field func return `func:${getFieldPath(field.get("func"))}`; } } return null; }; export const _getFromConfigCache = (config, bucketKey, cacheKey) => { return config.__cache?.[bucketKey]?.[cacheKey]; }; export const _saveToConfigCache = (config, bucketKey, cacheKey, value) => { if (!config.__cache || !cacheKey) { return; } if (!config.__cache[bucketKey]) { config.__cache[bucketKey] = {}; } config.__cache[bucketKey][cacheKey] = value; }; export const getFieldSrc = (field) => { if (!field) return null; if (typeof field === "object") { // should not be possible // if (field._isFuncArg) { // // it's func arg // return null; // } // if (field._isFunc) { // // it's field func // return "func"; // } if (!field.func && field.type) { // it's already a config return "field"; } if (field.func) { if (field.func && field.arg) { // it's func arg return null; } else { // it's field func return "func"; } } } if (field?.get?.("func")) { // immutable if (field?.get("arg")) { // it's func arg return null; } else { // it's field func return "func"; } } return "field"; }; export const getFieldConfig = (config, field) => { if (!field) return null; if (typeof field == "object") { if (!field.func && !!field.type && !!field.widgets) { // it's already a config // but don't mess up with obj from `getFuncSignature`, it has `type` but no `widgets` and other keys ! return field; } if (field._isFuncArg) { // it's func arg return getFuncArgConfig(config, field._funcKey, field._argKey); } if (field._isFunc) { // it's a func return getFuncConfig(config, field._funcKey); } if (field.func) { if (field.func && field.arg) { // it's func arg return getFuncArgConfig(config, field.func, field.arg); } else { // it's a func return getFuncConfig(config, field.func); } } } if (field?.get?.("func")) { // immutable if (field?.get("arg")) { // it's func arg return getFuncArgConfig(config, field.get("func"), field.get("arg")); } else { // it's field func return getFuncConfig(config, field.get("func")); } } const fieldConfig = getFieldRawConfig(config, field); if (!fieldConfig) return null; //throw new Error("Can't find field " + field + ", please check your config"); return fieldConfig; }; export const getOperatorConfig = (config, operator, field = null) => { if (!operator) return null; const opConfig = config.operators[operator]; if (field) { const fieldCacheKey = getFieldId(field); const cacheKey = fieldCacheKey ? `${fieldCacheKey}__${operator}` : null; const cached = _getFromConfigCache(config, "getOperatorConfig", cacheKey); if (cached) return cached; const fieldConfig = getFieldConfig(config, field); const widget = getWidgetForFieldOp(config, field, operator, null); const widgetConfig = config.widgets[widget] || {}; const fieldWidgetConfig = (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || {}; const widgetOpProps = widgetConfig.opProps?.[operator] || {}; const fieldWidgetOpProps = fieldWidgetConfig.opProps?.[operator] || {}; const mergedConfig = { ...opConfig, ...widgetOpProps, ...fieldWidgetOpProps, }; _saveToConfigCache(config, "getOperatorConfig", cacheKey, mergedConfig); return mergedConfig; } else { return opConfig; } }; export const getFieldWidgetConfig = (config, field, operator = null, widget = null, valueSrc = null, meta = {}) => { if (!field) return null; const fieldConfig = getFieldConfig(config, field); const fieldCacheKey = getFieldId(field); if (!widget) { widget = getWidgetForFieldOp(config, field, operator, valueSrc); } const cacheKey = fieldCacheKey ? `${fieldCacheKey}__${operator}__${widget}__${valueSrc}` : null; const cached = _getFromConfigCache(config, "getFieldWidgetConfig", cacheKey); if (cached) return cached; const widgetConfig = config.widgets[widget] || {}; const fieldWidgetConfig = fieldConfig?.widgets?.[widget] || {}; const fieldWidgetProps = fieldWidgetConfig.widgetProps || {}; const valueFieldSettings = (valueSrc === "value" || !valueSrc) ? fieldConfig?.fieldSettings : {}; // useful to take 'validateValue' let mergedConfig = { ...widgetConfig, ...fieldWidgetConfig, ...fieldWidgetProps, ...valueFieldSettings, }; _saveToConfigCache(config, "getFieldWidgetConfig", cacheKey, mergedConfig); if (meta.forExport) { mergedConfig = omit(mergedConfig, "factory"); } return mergedConfig; }; export const getFirstField = (config, parentRuleGroupField = null) => { const fieldSeparator = config.settings.fieldSeparator; const parentPathArr = getFieldParts(parentRuleGroupField, config); const parentField = parentRuleGroupField ? getFieldRawConfig(config, parentRuleGroupField) : config; let firstField = parentField, key = null, keysPath = []; do { const subfields = firstField === config ? config.fields : firstField?.subfields; if (!subfields || !Object.keys(subfields).length) { firstField = key = null; break; } key = Object.keys(subfields)[0]; keysPath.push(key); firstField = subfields[key]; } while (firstField.type == "!struct" || firstField.type == "!group"); return (parentPathArr || []).concat(keysPath).join(fieldSeparator); }; export function _getWidgetsAndSrcsForFieldOp (config, field, operator = null, valueSrc = null) { let widgets = []; let valueSrcs = []; if (!field) return {widgets, valueSrcs}; const fieldCacheKey = getFieldId(field); const cacheKey = fieldCacheKey ? `${fieldCacheKey}__${operator}__${valueSrc}` : null; const cached = _getFromConfigCache(config, "_getWidgetsAndSrcsForFieldOp", cacheKey); if (cached) return cached; const isFuncArg = typeof field === "object" && (!!field.func && !!field.arg || field._isFuncArg); const fieldConfig = getFieldConfig(config, field); const opConfig = operator ? config.operators[operator] : null; if (fieldConfig?.widgets) { for (const widget in fieldConfig.widgets) { const widgetConfig = fieldConfig.widgets[widget]; if (!config.widgets[widget]) { continue; } const widgetValueSrc = config.widgets[widget].valueSrc || "value"; let canAdd = true; if (widget === "field") { canAdd = canAdd && filterValueSourcesForField(config, ["field"], fieldConfig, operator).length > 0; } if (widget === "func") { canAdd = canAdd && filterValueSourcesForField(config, ["func"], fieldConfig, operator).length > 0; } // If can't check operators, don't add // Func args don't have operators if (valueSrc === "value" && !widgetConfig.operators && !isFuncArg && field !== "!case_value") canAdd = false; if (widgetConfig.operators && operator) canAdd = canAdd && widgetConfig.operators.indexOf(operator) != -1; if (valueSrc && valueSrc != widgetValueSrc && valueSrc !== "const") canAdd = false; if (opConfig && opConfig.cardinality == 0 && (widgetValueSrc !== "value")) canAdd = false; if (canAdd) { widgets.push(widget); let canAddValueSrc = fieldConfig.valueSources?.indexOf(widgetValueSrc) != -1; if (opConfig?.valueSources?.indexOf(widgetValueSrc) == -1) canAddValueSrc = false; if (canAddValueSrc && !valueSrcs.find(v => v == widgetValueSrc)) valueSrcs.push(widgetValueSrc); } } } const widgetWeight = (w) => { let wg = 0; if (fieldConfig.preferWidgets) { if (fieldConfig.preferWidgets.includes(w)) wg += (10 - fieldConfig.preferWidgets.indexOf(w)); } else if (w == fieldConfig.mainWidget) { wg += 100; } if (w === "field") { wg -= 1; } if (w === "func") { wg -= 2; } return wg; }; widgets.sort((w1, w2) => (widgetWeight(w2) - widgetWeight(w1))); const res = { widgets, valueSrcs }; _saveToConfigCache(config, "_getWidgetsAndSrcsForFieldOp", cacheKey, res); return res; } export const filterValueSourcesForField = (config, valueSrcs, fieldDefinition, operator = null) => { if (!fieldDefinition) return valueSrcs; let fieldType = fieldDefinition.type ?? fieldDefinition.returnType; if (fieldType === "!group") { // todo: aggregation can be not only number? fieldType = "number"; } let isOtherType = false; if (operator) { const opConfig = config.operators[operator]; if (opConfig?.valueTypes) { // Important: for "select" field and "select_any_in" op valueTypes are ["multiselect"] fieldType = opConfig.valueTypes[0]; isOtherType = true; } } // const { _isCaseValue } = fieldDefinition; if (!valueSrcs) valueSrcs = Object.keys(config.settings.valueSourcesInfo); return valueSrcs.filter(vs => { let canAdd = true; if (vs === "field") { if (config.__fieldsCntByType) { // tip: LHS field can be used as arg in RHS function const minCnt = fieldDefinition._isFuncArg || isOtherType ? 0 : 1; canAdd = canAdd && config.__fieldsCntByType[fieldType] > minCnt; } } if (vs === "func") { if (fieldDefinition.funcs) { canAdd = canAdd && fieldDefinition.funcs.length > 0; } if (config.__funcsCntByType) { canAdd = canAdd && config.__funcsCntByType[fieldType] > 0; } } return canAdd; }); }; export const getWidgetForFieldOp = (config, field, operator, valueSrc = null) => { const {widgets} = _getWidgetsAndSrcsForFieldOp(config, field, operator, valueSrc); let widget = null; if (widgets.length) widget = widgets[0]; return widget; }; export const getValueSourcesForFieldOp = (config, field, operator, fieldDefinition = null) => { const {valueSrcs} = _getWidgetsAndSrcsForFieldOp(config, field, operator, null); const filteredValueSrcs = filterValueSourcesForField(config, valueSrcs, fieldDefinition, operator); return filteredValueSrcs; }; export const getWidgetsForFieldOp = (config, field, operator, valueSrc = null) => { const {widgets} = _getWidgetsAndSrcsForFieldOp(config, field, operator, valueSrc); return widgets; }; export const getOperatorsForType = (config, fieldType) => { return config.types[fieldType]?.operators || null; }; export const getOperatorsForField = (config, field) => { const fieldConfig = getFieldConfig(config, field); const fieldOps = fieldConfig ? fieldConfig.operators : []; return fieldOps; }; export const getFirstOperator = (config, field) => { const fieldOps = getOperatorsForField(config, field); return fieldOps?.[0] ?? null; }; export const getFieldPartsConfigs = (field, config, parentField = null) => { if (!field) return null; const parentFieldDef = parentField && getFieldRawConfig(config, parentField) || null; const fieldSeparator = config.settings.fieldSeparator; const parts = getFieldParts(field, config); const isDescendant = isFieldDescendantOfField(field, parentField, config); const parentParts = !isDescendant ? [] : getFieldParts(parentField, config); return parts .slice(parentParts.length) .map((_curr, ind, arr) => arr.slice(0, ind+1)) .map((parts) => ({ part: [...parentParts, ...parts].join(fieldSeparator), key: parts[parts.length - 1] })) .map(({part, key}) => { const cnf = getFieldRawConfig(config, part); return {key, cnf}; }) .map(({key, cnf}, ind, arr) => { const parentCnf = ind > 0 ? arr[ind - 1].cnf : parentFieldDef; return [key, cnf, parentCnf]; }); };