UNPKG

@silexlabs/grapesjs-data-source

Version:
166 lines (162 loc) 6.27 kB
import { Component } from 'grapesjs' import { Context, Expression, Field, Filter, Property, State, StateId, Token, Type, TypeId } from '../types' import { DataTree } from './DataTree' import { getOrCreatePersistantId, getState, getStateIds } from './state' import { getExpressionResultType, getTokenOptions } from './token' import { getFixedToken } from '../utils' /** * Get the context of a component * This includes all parents states, data sources queryable values, values provided in the options */ export function getContext(component: Component, dataTree: DataTree, currentStateId?: StateId, hideLoopData = false): Context { if (!component) { console.error('Component is required for context') throw new Error('Component is required for context') } // Get all queryable values from all data sources const queryable: Property[] = dataTree.queryables .map((field: Field) => { if (!field.dataSourceId) throw new Error(`Type ${field.id} has no data source`) return fieldToToken(field) }) // Get all states in the component scope const states: State[] = [] const loopProperties: Token[] = [] let parent = component while (parent) { // Get explicitely set states states .push(...(getStateIds(parent, true, parent === component ? currentStateId : undefined) .map((stateId: StateId): State => ({ type: 'state', storedStateId: stateId, label: getState(parent, stateId, true)?.label || stateId, componentId: getOrCreatePersistantId(parent), exposed: true, })))) // Get states from loops if (parent !== component || !hideLoopData) { // If it is a loop on the parent or if we don't hide the loop data const loopDataState = getState(parent, '__data', false) if (loopDataState) { try { const loopDataField = getExpressionResultType(loopDataState.expression, parent, dataTree) if (loopDataField) { const displayName = (label: string) => `${parent.getName() ?? 'Unknown'}'s ${loopDataField.label} ${label}` if (loopDataField.kind === 'list') { loopProperties.push({ type: 'state', storedStateId: '__data', componentId: getOrCreatePersistantId(parent), exposed: false, forceKind: 'object', // FIXME: this may be a scalar label: `Loop data (${loopDataField.label})`, }, { type: 'property', propType: 'field', fieldId: 'forloop.index0', label: displayName('forloop.index0'), kind: 'scalar', typeIds: ['number'], }, { type: 'property', propType: 'field', fieldId: 'forloop.index', label: displayName('forloop.index'), kind: 'scalar', typeIds: ['number'], }) } else { console.warn('Loop data is not a list for component', parent, 'and state', loopDataState) } } else { console.warn('Loop data type not found for component', parent, 'and state', loopDataState) } } catch { console.error('Error while getting loop data for component', parent, 'and state', loopDataState) } } } // Go up to parent parent = parent.parent() as Component } // Get filters which accept no input const filters: Filter[] = dataTree.filters .filter(filter => filter.validate(null)) // Add a fixed value const fixedValue = getFixedToken('') // Return the context return [ ...queryable, ...states, ...loopProperties, ...filters, fixedValue, ] } /** * Create a property token from a field */ export function fieldToToken(field: Field): Property { if (!field) throw new Error('Field is required for token') if (!field.dataSourceId) throw new Error(`Field ${field.id} has no data source`) return { type: 'property', propType: 'field', fieldId: field.id, label: field.label, typeIds: field.typeIds, dataSourceId: field.dataSourceId, kind: field.kind, ...getTokenOptions(field) ?? {}, } } /** * Auto complete an expression * @returns a list of possible tokens to add to the expression */ export function getCompletion(options: { component: Component, expression: Expression, dataTree: DataTree, rootType?: TypeId, currentStateId?: StateId, hideLoopData?: boolean}): Context { const { component, expression, dataTree, rootType, currentStateId, hideLoopData } = options if (!component) throw new Error('Component is required for completion') if (!expression) throw new Error('Expression is required for completion') if (expression.length === 0) { if (rootType) { const type = dataTree.getType(rootType, null, component.getId()) if (!type) { console.warn('Root type not found', rootType) return [] } return type.fields .map((field: Field) => fieldToToken(field)) } return getContext(component, dataTree, currentStateId, hideLoopData) } const field = getExpressionResultType(expression, component, dataTree) if (!field) { console.warn('Result type not found for expression', expression) return [] } return ([] as Token[]) // Add fields if the kind is object .concat(field.kind === 'object' ? field.typeIds // Find possible types .map((typeId: TypeId) => dataTree.getType(typeId, field.dataSourceId ?? null, component.getId())) // Add all of their fields .flatMap((type: Type | null) => type?.fields ?? []) // To token .flatMap( (fieldOfField: Field): Token[] => { // const t: Type | null = this.findType(field.typeIds, field.dataSourceId) // if(!t) throw new Error(`Type ${field.typeIds} not found`) return fieldOfField.typeIds.map((typeId: TypeId) => ({ ...fieldToToken(fieldOfField), typeIds: [typeId], })) } ) : []) // Add filters .concat( dataTree.filters // Match input type .filter(filter => filter.validate(field)) ) }