diff --git a/platypush/backend/http/webapp/src/components/Action/ActionsBlock.vue b/platypush/backend/http/webapp/src/components/Action/ActionsBlock.vue index d5f5084872..315bc46a65 100644 --- a/platypush/backend/http/webapp/src/components/Action/ActionsBlock.vue +++ b/platypush/backend/http/webapp/src/components/Action/ActionsBlock.vue @@ -14,6 +14,7 @@ :dragging="dragging" :has-else="hasElse" :indent="indent" + :is-inside-loop="isInsideLoop" :parent="value" :read-only="readOnly" @add-else="$emit('add-else')" @@ -84,6 +85,11 @@ export default { default: 0, }, + isInsideLoop: { + type: Boolean, + default: false, + }, + readOnly: { type: Boolean, default: false, diff --git a/platypush/backend/http/webapp/src/components/Action/ActionsList.vue b/platypush/backend/http/webapp/src/components/Action/ActionsList.vue index d366e87f6d..4efbb453f9 100644 --- a/platypush/backend/http/webapp/src/components/Action/ActionsList.vue +++ b/platypush/backend/http/webapp/src/components/Action/ActionsList.vue @@ -25,6 +25,24 @@ :is-else="true" v-else-if="elses[index]" /> + + + + + + + +
+ +
+ +
+ +
+ +
+ +
@@ -76,8 +106,11 @@ import ActionsListItem from "./ActionsListItem" import ActionTile from "./ActionTile" import AddTile from "./AddTile" +import BreakTile from "./BreakTile" import ConditionBlock from "./ConditionBlock" +import ContinueTile from "./ContinueTile" import ListItem from "./ListItem" +import LoopBlock from "./LoopBlock" import Mixin from "./Mixin" import ReturnTile from "./ReturnTile" import Utils from "@/Utils" @@ -103,8 +136,11 @@ export default { ActionsListItem, ActionTile, AddTile, + BreakTile, ConditionBlock, + ContinueTile, ListItem, + LoopBlock, ReturnTile, }, @@ -119,12 +155,17 @@ export default { default: false, }, + hasElse: { + type: Boolean, + default: false, + }, + indent: { type: Number, default: 0, }, - hasElse: { + isInsideLoop: { type: Boolean, default: false, }, @@ -187,6 +228,7 @@ export default { props: { value: action, active: this.isDragging, + isInsideLoop: !!(this.isInsideLoop || this.getFor(action) || this.getWhile(action)), readOnly: this.readOnly, ref: `action-tile-${index}`, spacerBottom: this.visibleBottomSpacers[index], @@ -223,6 +265,11 @@ export default { data.props.indent = this.indent + 1 } + const loop = this.getFor(action) + if (loop) { + data.props.async = loop.async + } + return data }) }, @@ -281,6 +328,16 @@ export default { }, {}) || {} }, + fors() { + return this.newValue?.reduce?.((acc, action, index) => { + if (this.getFor(action)) { + acc[index] = action + } + + return acc + }, {}) || {} + }, + hasChanges() { return this.newStringValue !== this.stringValue }, @@ -301,18 +358,16 @@ export default { return this.dragIndices?.[0] }, + breakIndex() { + return this.getTileIndex((action) => this.isBreak(action)) + }, + + continueIndex() { + return this.getTileIndex((action) => this.isContinue(action)) + }, + returnIndex() { - const ret = this.newValue?.reduce?.((acc, action, index) => { - if (acc >= 0) - return acc - - if (this.isReturn(action)) - return index - - return acc - }, -1) - - return ret >= 0 ? ret : null + return this.getTileIndex((action) => this.isReturn(action)) }, returnValue() { @@ -349,7 +404,22 @@ export default { }, stopIndex() { - return this.returnIndex + if (this.breakIndex != null) + return this.breakIndex + if (this.continueIndex != null) + return this.continueIndex + if (this.returnIndex != null) + return this.returnIndex + + return null + }, + + allowAddButtons() { + return ( + !this.readOnly && + !this.collapsed && + this.stopIndex == null + ) }, visibleActions() { @@ -360,8 +430,11 @@ export default { if ( this.conditions[index] || this.elses[index] || + this.fors[index] || this.isAction(action) || - this.isReturn(action) + this.isReturn(action) || + this.isBreak(action) || + this.isContinue(action) ) { acc[index] = action } @@ -372,16 +445,23 @@ export default { visibleAddButtons() { return { - action: !this.readOnly && !this.collapsed && this.stopIndex == null, - return: !this.readOnly && !this.collapsed && this.stopIndex == null, - condition: !this.readOnly && !this.collapsed && this.stopIndex == null, + action: this.allowAddButtons, + return: this.allowAddButtons, + condition: this.allowAddButtons, + loop: this.allowAddButtons, else: ( - !this.readOnly && - !this.collapsed && + this.allowAddButtons && this.parent && this.getCondition(this.parent) && - !this.hasElse && - this.stopIndex == null + !this.hasElse + ), + break: ( + this.allowAddButtons && + this.isInsideLoop + ), + continue: ( + this.allowAddButtons && + this.isInsideLoop ), } }, @@ -583,6 +663,19 @@ export default { this.selectLastExprEditor() }, + addLoop() { + this.newValue.push({ 'for _ in ${range(10)}': [] }) + this.selectLastExprEditor() + }, + + addBreak() { + this.newValue.push('break') + }, + + addContinue() { + this.newValue.push('continue') + }, + addReturn() { this.newValue.push({ 'return': null }) this.selectLastExprEditor() @@ -606,7 +699,7 @@ export default { newTileElement.click() this.$nextTick(() => { - const exprEditor = newTile.$el?.querySelector('.expr-editor-container') + const exprEditor = newTile.$el?.querySelector('.editor-container') if (!exprEditor) { return } @@ -644,6 +737,20 @@ export default { } }, + getTileIndex(callback) { + const ret = this.newValue?.reduce?.((acc, action, index) => { + if (acc >= 0) + return acc + + if (callback(action)) + return index + + return acc + }, -1) + + return ret >= 0 ? ret : null + }, + syncSpacers() { this.$nextTick(() => { this.spacerElements = Object.keys(this.newValue).reduce((acc, index) => { diff --git a/platypush/backend/http/webapp/src/components/Action/BreakTile.vue b/platypush/backend/http/webapp/src/components/Action/BreakTile.vue new file mode 100644 index 0000000000..4c4fc1cc89 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/Action/BreakTile.vue @@ -0,0 +1,66 @@ + + + diff --git a/platypush/backend/http/webapp/src/components/Action/ConditionBlock.vue b/platypush/backend/http/webapp/src/components/Action/ConditionBlock.vue index d0f6f6e0cc..40fb29189a 100644 --- a/platypush/backend/http/webapp/src/components/Action/ConditionBlock.vue +++ b/platypush/backend/http/webapp/src/components/Action/ConditionBlock.vue @@ -4,6 +4,7 @@ :collapsed="collapsed" :dragging="isDragging" :has-else="hasElse" + :is-inside-loop="isInsideLoop" :indent="indent" :read-only="readOnly" @input="onActionsChange" @@ -107,6 +108,11 @@ export default { default: false, }, + isInsideLoop: { + type: Boolean, + default: false, + }, + readOnly: { type: Boolean, default: false, diff --git a/platypush/backend/http/webapp/src/components/Action/ConditionTile.vue b/platypush/backend/http/webapp/src/components/Action/ConditionTile.vue index 4493b34c38..f33f7c7cb8 100644 --- a/platypush/backend/http/webapp/src/components/Action/ConditionTile.vue +++ b/platypush/backend/http/webapp/src/components/Action/ConditionTile.vue @@ -21,7 +21,6 @@ if [ ] - then diff --git a/platypush/backend/http/webapp/src/components/Action/ContinueTile.vue b/platypush/backend/http/webapp/src/components/Action/ContinueTile.vue new file mode 100644 index 0000000000..1df7dc8654 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/Action/ContinueTile.vue @@ -0,0 +1,66 @@ + + + diff --git a/platypush/backend/http/webapp/src/components/Action/EndBlockTile.vue b/platypush/backend/http/webapp/src/components/Action/EndBlockTile.vue index 4ae3cee4ab..5e3f56f5be 100644 --- a/platypush/backend/http/webapp/src/components/Action/EndBlockTile.vue +++ b/platypush/backend/http/webapp/src/components/Action/EndBlockTile.vue @@ -12,7 +12,7 @@
- + diff --git a/platypush/backend/http/webapp/src/components/Action/ExpressionEditor.vue b/platypush/backend/http/webapp/src/components/Action/ExpressionEditor.vue index 9b1c71ee42..bdf9b9661e 100644 --- a/platypush/backend/http/webapp/src/components/Action/ExpressionEditor.vue +++ b/platypush/backend/http/webapp/src/components/Action/ExpressionEditor.vue @@ -29,7 +29,7 @@ export default { props: { value: { - type: String, + type: [String, Number, Boolean, Object, Array], default: '', }, @@ -83,7 +83,7 @@ export default { mounted() { this.hasChanges = false - if (!this.value?.trim()?.length) { + if (!this.value?.trim?.()?.length) { this.hasChanges = this.allowEmpty } diff --git a/platypush/backend/http/webapp/src/components/Action/ListItem.vue b/platypush/backend/http/webapp/src/components/Action/ListItem.vue index f9354f8696..e938f5b187 100644 --- a/platypush/backend/http/webapp/src/components/Action/ListItem.vue +++ b/platypush/backend/http/webapp/src/components/Action/ListItem.vue @@ -87,7 +87,7 @@ export default { }, value: { - type: [String, Object], + type: [String, Number, Boolean, Object, Array], required: true, }, }, diff --git a/platypush/backend/http/webapp/src/components/Action/LoopBlock.vue b/platypush/backend/http/webapp/src/components/Action/LoopBlock.vue new file mode 100644 index 0000000000..56c5ecdc7f --- /dev/null +++ b/platypush/backend/http/webapp/src/components/Action/LoopBlock.vue @@ -0,0 +1,204 @@ + + + diff --git a/platypush/backend/http/webapp/src/components/Action/LoopEditor.vue b/platypush/backend/http/webapp/src/components/Action/LoopEditor.vue new file mode 100644 index 0000000000..d54803a7de --- /dev/null +++ b/platypush/backend/http/webapp/src/components/Action/LoopEditor.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/Action/LoopTile.vue b/platypush/backend/http/webapp/src/components/Action/LoopTile.vue new file mode 100644 index 0000000000..475bede105 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/Action/LoopTile.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/Action/Mixin.vue b/platypush/backend/http/webapp/src/components/Action/Mixin.vue index 4911c9a3d2..e4f2cc196e 100644 --- a/platypush/backend/http/webapp/src/components/Action/Mixin.vue +++ b/platypush/backend/http/webapp/src/components/Action/Mixin.vue @@ -6,16 +6,42 @@ export default { return value?.trim?.()?.match(/^if\s*\$\{(.*)\}\s*$/i)?.[1]?.trim?.() }, + getFor(value) { + value = this.getKey(value) || value + let m = value?.trim?.()?.match(/^for(k?)\s*(.*)\s*in\s*\$\{(.*)\}\s*$/i) + if (!m) + return null + + return { + async: m[1].length > 0, + iterator: m[2].trim(), + iterable: m[3].trim(), + } + }, + getKey(value) { return this.isKeyword(value) ? Object.keys(value)[0] : null }, + getWhile(value) { + value = this.getKey(value) || value + return value?.trim?.()?.match(/^while\s*\$\{(.*)\}\s*$/i)?.[1]?.trim?.() + }, + isAction(value) { return typeof value === 'object' && !Array.isArray(value) && (value.action || value.name) }, isActionsBlock(value) { - return this.getCondition(value) || this.isElse(value) + return this.getCondition(value) || this.isElse(value) || this.getFor(value) + }, + + isBreak(value) { + return value?.toLowerCase?.()?.trim?.() === 'break' + }, + + isContinue(value) { + return value?.toLowerCase?.()?.trim?.() === 'continue' }, isKeyword(value) { diff --git a/platypush/backend/http/webapp/src/components/Action/Response.vue b/platypush/backend/http/webapp/src/components/Action/Response.vue index 55ff647fff..93f63b96bf 100644 --- a/platypush/backend/http/webapp/src/components/Action/Response.vue +++ b/platypush/backend/http/webapp/src/components/Action/Response.vue @@ -32,7 +32,7 @@ export default { mixins: [Utils], props: { response: { - type: [String, Object], + type: [String, Object, Array, Number, Boolean], }, error: { @@ -51,7 +51,7 @@ export default { jsonResponse() { if (this.isJSON) { - return hljs.highlight(this.response, {language: 'json'}).value + return hljs.highlight(this.response.toString(), {language: 'json'}).value } return null diff --git a/platypush/backend/http/webapp/src/components/Action/ReturnTile.vue b/platypush/backend/http/webapp/src/components/Action/ReturnTile.vue index a4fce9f216..cd9134c27d 100644 --- a/platypush/backend/http/webapp/src/components/Action/ReturnTile.vue +++ b/platypush/backend/http/webapp/src/components/Action/ReturnTile.vue @@ -19,7 +19,7 @@
-
+
@@ -59,7 +59,7 @@ export default { props: { value: { - type: String, + type: [String, Number, Boolean, Object, Array], default: '', }, @@ -68,11 +68,6 @@ export default { default: false, }, - isElse: { - type: Boolean, - default: false, - }, - readOnly: { type: Boolean, default: false, diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Procedure.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Procedure.vue index 69dac41ca8..f02715c964 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/Procedure.vue +++ b/platypush/backend/http/webapp/src/components/panels/Entities/Procedure.vue @@ -126,7 +126,7 @@
-
Type
+
Source
  {{ value.procedure_type }} @@ -323,7 +323,10 @@ export default { return 'fab fa-python' if (this.value.procedure_type === 'config') - return 'fas fa-rectangle-list' + return 'fas fa-file' + + if (this.value.procedure_type === 'db') + return 'fas fa-database' return this.defaultIconClass }, diff --git a/platypush/backend/http/webapp/src/utils/Text.vue b/platypush/backend/http/webapp/src/utils/Text.vue index 67dfd9d01b..c87ebde287 100644 --- a/platypush/backend/http/webapp/src/utils/Text.vue +++ b/platypush/backend/http/webapp/src/utils/Text.vue @@ -37,6 +37,16 @@ export default { formatNumber(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }, + + escapeHTML(value) { + return value + ?.toString?.() + ?.replace?.(/&/g, "&") + ?.replace?.(//g, ">") + ?.replace?.(/"/g, """) + ?.replace?.(/'/g, "'") || '' + }, }, }