diff --git a/src/ui/components/editor/EditBox.vue b/src/ui/components/editor/EditBox.vue index 50bc87118..97f3d1487 100644 --- a/src/ui/components/editor/EditBox.vue +++ b/src/ui/components/editor/EditBox.vue @@ -464,7 +464,6 @@ import StoreUtils from '../../store/StoreUtils'; import StrokePattern from './items/StrokePattern'; import myMath from '../../myMath'; import { itemCompleteTransform, worldPointOnItem } from '../../scheme/ItemMath'; -import { compileItemTemplate } from './items/ItemTemplate'; import EditorEventBus from './EditorEventBus'; import utils from '../../utils'; import { jsonDiff } from '../../json-differ'; diff --git a/src/ui/components/editor/items/ItemTemplate.js b/src/ui/components/editor/items/ItemTemplate.js index a1e757a20..8564a5997 100644 --- a/src/ui/components/editor/items/ItemTemplate.js +++ b/src/ui/components/editor/items/ItemTemplate.js @@ -47,8 +47,9 @@ function enrichPanelItem(item) { * @param {Item} templateRootItem * @param {Object} data * @param {Array} selectedItemIds + * @param {Map} cachedCompiledExpressions */ -function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, data, selectedItemIds) { +function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, data, selectedItemIds, cachedCompiledExpressions) { // cloning selected items to make sure that the script cannot mutate items const extraData = { selectedItemIds: new List(...selectedItemIds), @@ -73,9 +74,14 @@ function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, d } let click = null; if (panel.click) { - const clickCallback = compileTemplateExpressions(initBlock.concat([panel.click]), { - context: new TemplateContext(ContextPhases.EVENT, 'panel-click', panel.id) - }); + const clickExpression = initBlock.concat([panel.click]).join('\n'); + + let clickCallback = cachedCompiledExpressions.get(clickExpression); + if (!clickCallback) { + clickCallback = compileTemplateExpressions(clickExpression); + cachedCompiledExpressions.set(clickExpression, clickCallback); + } + click = (panelItem) => { // panel click callback is supposed to return the object that contains the top-level scope fields // This can be used in order to update template args in the template root item @@ -90,7 +96,8 @@ function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, d width: templateRootItem.area.w, height: templateRootItem.area.h, ...extraData, - panelItem + panelItem, + context: new TemplateContext(ContextPhases.EVENT, 'panel-click', panel.id) }; return clickCallback(clickData); }; @@ -167,6 +174,19 @@ export function compileItemTemplate(editorId, template, templateRef) { defaultArgs[argName] = arg; }); + const cachedCompiledExpressions = new Map(); + + const buildTemplateExpression = (expressionScript) => { + let cachedExpression = cachedCompiledExpressions.get(expressionScript); + if (cachedExpression) { + return cachedExpression; + } + + const compiledExpression = compileTemplateExpressions(expressionScript); + cachedCompiledExpressions.set(expressionScript, compiledExpression); + return compiledExpression; + }; + return { name : template.name, description: template.description, @@ -246,32 +266,39 @@ export function compileItemTemplate(editorId, template, templateRef) { }, buildEditor: (templateRootItem, args, width, height, selectedItemIds) => { - return buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, {...args, width, height}, selectedItemIds); + return buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, {...args, width, height}, selectedItemIds, cachedCompiledExpressions); }, - buildControls: (args, width, height) => compiledControlBuilder({ - ...args, width, height, - context: new TemplateContext(ContextPhases.EVENT, 'control', '') - }).controls.map(control => { - const controlExpressions = [].concat(initBlock).concat(toExpressionBlock(control.click)); - const clickExecutor = compileTemplateExpressions(controlExpressions, { - ...args, width, height, - context: new TemplateContext(ContextPhases.EVENT, 'control', control.id) - }); - return { - ...control, - - /** - * @param {Item} item - * @returns {Object} updated data object which can be used to update the template args. - * Keep in mind that this object contains not only template args, - * but everything that was declared in the global scope of the template script - */ - click: (item) => { - return clickExecutor({control, ...createTemplateFunctions(editorId, item)}); + buildControls: (args, width, height) => { + return compiledControlBuilder({ + ...args, width, height, + context: new TemplateContext(ContextPhases.EVENT, 'control', '') + }).controls.map(control => { + + const controlExpressions = [].concat(initBlock).concat(toExpressionBlock(control.click)); + const fullScript = controlExpressions.join('\n'); + + const clickExecutor = buildTemplateExpression(fullScript); + return { + ...control, + + /** + * @param {Item} item + * @returns {Object} updated data object which can be used to update the template args. + * Keep in mind that this object contains not only template args, + * but everything that was declared in the global scope of the template script + */ + click: (item) => { + return clickExecutor({ + control, + ...createTemplateFunctions(editorId, item), + ...args, width, height, + context: new TemplateContext(ContextPhases.EVENT, 'control', control.id) + }); + } } - } - }), + }); + }, getDefaultArgs() { const args = {}; diff --git a/src/ui/templater/templater.js b/src/ui/templater/templater.js index 553b7e6c5..aaa5b25d5 100644 --- a/src/ui/templater/templater.js +++ b/src/ui/templater/templater.js @@ -42,17 +42,12 @@ export function processJSONTemplate(obj, data) { /** * This function is used when user clicks on template controls. * It executes init and control expressions and returns the updated template argument values - * @param {Array} expressions - an array of strings which represent template expressions + * @param {String} expression - a template expression in SchemioScript * @param {Object} data - an object with initial arguments * @returns {function(Object|undefined): Object} - a function that takes extra data object as an argument, that should be added to the scope and, when invoked, will execute the expressions and will return the updated data with arguments */ -export function compileTemplateExpressions(expressions, data) { - if (!Array.isArray(expressions)) { - expressions = [expressions]; - } - - const fullScript = expressions.join('\n'); - const expressionNode = parseExpression(fullScript); +export function compileTemplateExpressions(expression, data = {}) { + const expressionNode = parseExpression(expression); return (extraData) => { const scope = new Scope({...data, ...(extraData || {})});