UNPKG

@silexlabs/grapesjs-data-source

Version:
228 lines 10.2 kB
"use strict"; /* * Silex website builder, free/libre no-code tool for makers. * Copyright (c) 2023 lexoyo and Silex Labs foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getPageExpressions = getPageExpressions; exports.getTrees = getTrees; exports.isRelative = isRelative; exports.toTrees = toTrees; exports.mergeTrees = mergeTrees; const state_1 = require("./state"); const token_1 = require("./token"); const utils_1 = require("../utils"); const expressionEvaluator_1 = require("./expressionEvaluator"); // Pure functions for data operations /** * Get all expressions used in a page */ function getPageExpressions(manager, page) { const result = []; const mainComponent = page.getMainComponent(); if (mainComponent) { mainComponent.onAll(component => { // Get expressions used by the component from states const states = (0, state_1.getStates)(component, true).concat((0, state_1.getStates)(component, false)); states.forEach(state => { if (state.expression) { result.push({ expression: state.expression, component, }); } }); // Get expressions used by the component from attributes Object.values(component.getAttributes()).forEach((value) => { const expression = (0, utils_1.toExpression)(value); if (expression) { result.push({ expression, component, }); } }); }); } return result; } /** * Build a tree of expressions */ function getTrees(manager, { expression, component }, dataSourceId) { if (expression.length === 0) return []; const next = expression[0]; switch (next.type) { case 'property': { if (next.dataSourceId !== dataSourceId) return []; const trees = getTrees(manager, { expression: expression.slice(1), component }, dataSourceId); if (trees.length === 0) return [{ token: next, children: [], }]; return trees .flatMap(tree => { // Check if this is a "relative" property or "absolute" (a root type) if (isRelative(manager, next, tree.token, dataSourceId)) { return { token: next, children: [tree], }; } else { return [{ token: next, children: [], }, tree]; } }); } case 'filter': { const options = Object.values(next.options) .map((value) => (0, utils_1.toExpression)(value)) .filter((exp) => !!exp && exp.length > 0) .flatMap(exp => getTrees(manager, { expression: exp, component }, dataSourceId)); const trees = getTrees(manager, { expression: expression.slice(1), component }, dataSourceId); if (trees.length === 0) return options; return trees.flatMap(tree => [tree, ...options]); } case 'state': { const resolved = (0, expressionEvaluator_1.resolveStateExpression)(next, component, manager); if (!resolved) { manager.editor.runCommand('notifications:add', { type: 'error', group: utils_1.NOTIFICATION_GROUP, message: `Unable to resolve state <pre>${JSON.stringify(next)}</pre>`, componentId: component.getId(), }); throw new Error(`Unable to resolve state ${JSON.stringify(next)}. State defined on component ${(0, utils_1.getComponentDebug)(component)}`); } return getTrees(manager, { expression: resolved, component }, dataSourceId); } default: manager.editor.runCommand('notifications:add', { type: 'error', group: utils_1.NOTIFICATION_GROUP, message: `Invalid expression <pre>${JSON.stringify(expression)}</pre>`, componentId: component.getId(), }); throw new Error(`Invalid expression ${JSON.stringify(expression)}. Expression used on component ${(0, utils_1.getComponentDebug)(component)}`); } } /** * Check if a property is relative to a type */ function isRelative(manager, parent, child, dataSourceId) { const ds = manager.dataSources.find((dataSource) => dataSource.id === dataSourceId); if (!ds) throw new Error(`Data source not found ${dataSourceId}`); if (!ds.isConnected()) throw new Error(`Data source ${dataSourceId} is not ready (not connected)`); const parentTypes = ds.getTypes().filter(t => parent.typeIds.includes(t.id)); const parentFieldsTypes = parentTypes.flatMap(t => t.fields.map(f => f.typeIds).flat()); return parentFieldsTypes.length > 0 && child.typeIds.some(typeId => parentFieldsTypes.includes(typeId)); } /** * From expressions to a tree */ function toTrees(manager, expressions, dataSourceId) { if (expressions.length === 0) return []; return expressions // From Expression to Tree .flatMap(expression => getTrees(manager, expression, dataSourceId)) // Group by root token .reduce((acc, tree) => { const existing = acc.find(t => t[0].token.fieldId === tree.token.fieldId && (!tree.token.dataSourceId || t[0].token.dataSourceId === tree.token.dataSourceId)); if (existing) { existing.push(tree); } else { acc.push([tree]); } return acc; }, []) // Merge all trees from the root .map((grouped) => { try { const merged = grouped.reduce((acc, tree) => mergeTrees(acc, tree)); return merged; } catch (e) { manager.editor.runCommand('notifications:add', { type: 'error', group: utils_1.NOTIFICATION_GROUP, message: `Unable to merge trees <pre>${JSON.stringify(grouped)}</pre>`, componentId: expressions[0].component.getId(), }); throw e; } }); } /** * Recursively merge two trees */ function mergeTrees(tree1, tree2) { // Check if the trees have the same fieldId if (tree1.token.dataSourceId !== tree2.token.dataSourceId // Don't check for kind because it can be different for the same fieldId // For example `blog` collection (kind: list) for a loop/repeat // and `blog` item (kind: object) from inside the loop // FIXME: why is this? // || tree1.token.kind !== tree2.token.kind ) { console.error('Unable to merge trees', tree1, tree2); throw new Error(`Unable to build GraphQL query: unable to merge trees ${JSON.stringify(tree1)} and ${JSON.stringify(tree2)}`); } // Check if there are children with the same fieldId but different options // FIXME: we should use graphql aliases: https://graphql.org/learn/queries/#aliases but then it changes the variable name in the result const errors = tree1.children .filter(child1 => tree2.children.find(child2 => child1.token.fieldId === child2.token.fieldId && (0, token_1.getOptionObject)(child1.token.options, child2.token.options).error)) .map(child1 => { const child2 = tree2.children.find(child2 => child1.token.fieldId === child2.token.fieldId); return `${child1.token.fieldId} appears twice with different options: ${JSON.stringify(child1.token.options)} vs ${JSON.stringify(child2 === null || child2 === void 0 ? void 0 : child2.token.options)}`; }); if (errors.length > 0) { console.error('Unable to merge trees', errors); throw new Error(`Unable to build GraphQL query: unable to merge trees: \n* ${errors.join('\n* ')}`); } const different = tree1.children .filter(child1 => !tree2.children.find(child2 => child1.token.fieldId === child2.token.fieldId && child1.token.typeIds.join(',') === child2.token.typeIds.join(',') && !(0, token_1.getOptionObject)(child1.token.options, child2.token.options).error)) .concat(tree2.children .filter(child2 => !tree1.children.find(child1 => child1.token.fieldId === child2.token.fieldId && child1.token.typeIds.join(',') === child2.token.typeIds.join(',') && !(0, token_1.getOptionObject)(child1.token.options, child2.token.options).error))); const same = tree1.children .filter(child1 => tree2.children.find(child2 => child1.token.fieldId === child2.token.fieldId && child1.token.typeIds.join(',') === child2.token.typeIds.join(',') && !(0, token_1.getOptionObject)(child1.token.options, child2.token.options).error)); return { token: tree1.token, children: different .concat(same .map(child1 => { const child2 = tree2.children.find(child2 => child1.token.fieldId === child2.token.fieldId); return mergeTrees(child1, child2); })), }; } //# sourceMappingURL=ExpressionTree.js.map