forked from platypush/platypush
[#341] Added for for/break/continue statements in procedure editor.
This commit is contained in:
parent
c4610254f8
commit
156a6379d0
17 changed files with 892 additions and 39 deletions
|
@ -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,
|
||||
|
|
|
@ -25,6 +25,24 @@
|
|||
:is-else="true"
|
||||
v-else-if="elses[index]" />
|
||||
|
||||
<LoopBlock v-bind="componentsData[index].props"
|
||||
v-on="componentsData[index].on"
|
||||
:collapsed="collapsedBlocks[index]"
|
||||
:dragging="isDragging"
|
||||
v-else-if="fors[index]" />
|
||||
|
||||
<BreakTile :active="isDragging"
|
||||
:readOnly="readOnly"
|
||||
:spacerTop="componentsData[index].props.spacerTop"
|
||||
@delete="deleteAction(index)"
|
||||
v-else-if="isBreak(action)" />
|
||||
|
||||
<ContinueTile :active="isDragging"
|
||||
:readOnly="readOnly"
|
||||
:spacerTop="componentsData[index].props.spacerTop"
|
||||
@delete="deleteAction(index)"
|
||||
v-else-if="isContinue(action)" />
|
||||
|
||||
<ReturnTile v-bind="componentsData[index].props"
|
||||
:value="returnValue"
|
||||
@change="editReturn($event)"
|
||||
|
@ -67,6 +85,18 @@
|
|||
<div class="row item action add-else-container" v-if="visibleAddButtons.else">
|
||||
<AddTile icon="fas fa-question" title="Add Else" @click="$emit('add-else')" />
|
||||
</div>
|
||||
|
||||
<div class="row item action add-for-container" v-if="visibleAddButtons.loop">
|
||||
<AddTile icon="fas fa-arrow-rotate-left" title="Add Loop" @click="addLoop" />
|
||||
</div>
|
||||
|
||||
<div class="row item action add-break-container" v-if="visibleAddButtons.break">
|
||||
<AddTile icon="fas fa-hand" title="Add Break" @click="addBreak" />
|
||||
</div>
|
||||
|
||||
<div class="row item action add-continue-container" v-if="visibleAddButtons.continue">
|
||||
<AddTile icon="fas fa-rotate" title="Add Continue" @click="addContinue" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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() {
|
||||
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) => {
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<ListItem class="break-tile"
|
||||
value="break"
|
||||
:active="active"
|
||||
:read-only="readOnly"
|
||||
:spacer-bottom="spacerBottom"
|
||||
:spacer-top="spacerTop">
|
||||
<Tile :value="value"
|
||||
class="keyword"
|
||||
:draggable="false"
|
||||
:read-only="readOnly"
|
||||
:with-delete="!readOnly"
|
||||
@click.stop
|
||||
@delete="$emit('delete')">
|
||||
<div class="tile-name">
|
||||
<span class="icon">
|
||||
<i class="fas fa-hand" />
|
||||
</span>
|
||||
<span class="name">
|
||||
<span class="keyword">break</span>
|
||||
</span>
|
||||
</div>
|
||||
</Tile>
|
||||
</ListItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItem from "./ListItem"
|
||||
import Tile from "@/components/elements/Tile"
|
||||
|
||||
export default {
|
||||
emits: ['delete'],
|
||||
|
||||
components: {
|
||||
ListItem,
|
||||
Tile,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: 'break',
|
||||
},
|
||||
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
spacerBottom: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
spacerTop: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -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,
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<span class="name">
|
||||
<span class="keyword">if</span> [
|
||||
<span class="code" v-text="value" /> ]
|
||||
<span class="keyword">then</span>
|
||||
</span>
|
||||
</div>
|
||||
</Tile>
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<ListItem class="continue-tile"
|
||||
value="continue"
|
||||
:active="active"
|
||||
:read-only="readOnly"
|
||||
:spacer-bottom="spacerBottom"
|
||||
:spacer-top="spacerTop">
|
||||
<Tile :value="value"
|
||||
class="keyword"
|
||||
:draggable="false"
|
||||
:read-only="readOnly"
|
||||
:with-delete="!readOnly"
|
||||
@click.stop
|
||||
@delete="$emit('delete')">
|
||||
<div class="tile-name">
|
||||
<span class="icon">
|
||||
<i class="fas fa-rotate" />
|
||||
</span>
|
||||
<span class="name">
|
||||
<span class="keyword">continue</span>
|
||||
</span>
|
||||
</div>
|
||||
</Tile>
|
||||
</ListItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItem from "./ListItem"
|
||||
import Tile from "@/components/elements/Tile"
|
||||
|
||||
export default {
|
||||
emits: ['delete'],
|
||||
|
||||
components: {
|
||||
ListItem,
|
||||
Tile,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: 'continue',
|
||||
},
|
||||
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
spacerBottom: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
spacerTop: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -12,7 +12,7 @@
|
|||
<Tile class="keyword" :draggable="false" :read-only="true">
|
||||
<div class="tile-name">
|
||||
<span class="icon">
|
||||
<i class="fas fa-question" />
|
||||
<i :class="icon" />
|
||||
</span>
|
||||
<span class="name">
|
||||
<span class="keyword" v-text="value" />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ export default {
|
|||
},
|
||||
|
||||
value: {
|
||||
type: [String, Object],
|
||||
type: [String, Number, Boolean, Object, Array],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<div class="loop-block">
|
||||
<ActionsBlock :value="value"
|
||||
:collapsed="collapsed"
|
||||
:dragging="isDragging"
|
||||
:indent="indent"
|
||||
:is-inside-loop="true"
|
||||
:read-only="readOnly"
|
||||
@input="onActionsChange"
|
||||
@drag="$emit('drag', $event)"
|
||||
@dragend="$emit('dragend', $event)"
|
||||
@dragenter="$emit('dragenter', $event)"
|
||||
@dragleave="$emit('dragleave', $event)"
|
||||
@dragover="$emit('dragover', $event)"
|
||||
@drop="$emit('drop', $event)">
|
||||
<template #before>
|
||||
<LoopTile v-bind="loopTileConf.props"
|
||||
v-on="loopTileConf.on"
|
||||
@input.prevent.stop
|
||||
:spacer-top="spacerTop"
|
||||
:spacer-bottom="false" />
|
||||
</template>
|
||||
|
||||
<template #after>
|
||||
<EndBlockTile value="end for"
|
||||
icon="fas fa-arrow-rotate-right"
|
||||
:active="active"
|
||||
:spacer-bottom="spacerBottom"
|
||||
@drop="onDrop" />
|
||||
</template>
|
||||
</ActionsBlock>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionsBlock from "./ActionsBlock"
|
||||
import EndBlockTile from "./EndBlockTile"
|
||||
import LoopTile from "./LoopTile"
|
||||
import Mixin from "./Mixin"
|
||||
|
||||
export default {
|
||||
name: 'LoopBlock',
|
||||
mixins: [Mixin],
|
||||
emits: [
|
||||
'delete',
|
||||
'drag',
|
||||
'dragend',
|
||||
'dragenter',
|
||||
'dragleave',
|
||||
'dragover',
|
||||
'drop',
|
||||
'input',
|
||||
],
|
||||
|
||||
components: {
|
||||
ActionsBlock,
|
||||
LoopTile,
|
||||
EndBlockTile,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
async: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
dragging: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
indent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
isInsideLoop: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
spacerBottom: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
spacerTop: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
dragging_: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
loop() {
|
||||
return this.getFor(this.key)
|
||||
},
|
||||
|
||||
loopTileConf() {
|
||||
return {
|
||||
props: {
|
||||
...this.loop,
|
||||
active: this.active,
|
||||
readOnly: this.readOnly,
|
||||
spacerBottom: this.spacerBottom,
|
||||
spacerTop: this.spacerTop,
|
||||
},
|
||||
|
||||
on: {
|
||||
change: this.onLoopChange,
|
||||
delete: (event) => this.$emit('delete', event),
|
||||
drag: this.onDragStart,
|
||||
dragend: this.onDragEnd,
|
||||
dragenterspacer: (event) => this.$emit('dragenter', event),
|
||||
dragleavespacer: (event) => this.$emit('dragleave', event),
|
||||
dragover: (event) => this.$emit('dragover', event),
|
||||
dragoverspacer: (event) => this.$emit('dragoverspacer', event),
|
||||
drop: this.onDrop,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
isDragging() {
|
||||
return this.dragging_ || this.dragging
|
||||
},
|
||||
|
||||
key() {
|
||||
return this.getKey(this.value)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onActionsChange(value) {
|
||||
if (!this.key || this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('input', { [this.key]: value })
|
||||
},
|
||||
|
||||
onLoopChange(loop) {
|
||||
const iterable = loop?.iterable?.trim()
|
||||
const iterator = loop?.iterator?.trim()
|
||||
const async_ = loop?.async || false
|
||||
|
||||
if (!this.key || this.readOnly || !iterable?.length || !iterator?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const keyword = 'for' + (async_ ? 'k' : '')
|
||||
loop = `${keyword} ${iterator} in \${${iterable}}`
|
||||
this.$emit('input', { [loop]: this.value[this.key] })
|
||||
},
|
||||
|
||||
onDragStart(event) {
|
||||
if (this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.dragging_ = true
|
||||
this.$emit('drag', event)
|
||||
},
|
||||
|
||||
onDragEnd(event) {
|
||||
this.dragging_ = false
|
||||
this.$emit('dragend', event)
|
||||
},
|
||||
|
||||
onDrop(event) {
|
||||
if (this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.dragging_ = false
|
||||
this.$emit('drop', event)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<form class="loop-editor" @submit.prevent.stop="onSubmit">
|
||||
for
|
||||
<label for="iterator">
|
||||
<input type="text"
|
||||
name="iterator"
|
||||
autocomplete="off"
|
||||
:autofocus="true"
|
||||
placeholder="Iterator"
|
||||
:value="iterator"
|
||||
ref="iterator"
|
||||
@input.stop="onInput('iterator', $event)" />
|
||||
</label>
|
||||
|
||||
in
|
||||
|
||||
<label for="iterable">
|
||||
<input type="text"
|
||||
name="iterable"
|
||||
autocomplete="off"
|
||||
:autofocus="true"
|
||||
placeholder="Iterable"
|
||||
:value="iterable"
|
||||
ref="iterable"
|
||||
@input.stop="onInput('iterable', $event)" />
|
||||
</label>
|
||||
|
||||
<label class="async">
|
||||
<input class="checkbox"
|
||||
type="checkbox"
|
||||
name="async"
|
||||
:checked="async"
|
||||
@input.stop="onInput('async', $event)" />
|
||||
Run in parallel
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<button type="submit" :disabled="!hasChanges">
|
||||
<i class="fas fa-check" /> Save
|
||||
</button>
|
||||
</label>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: [
|
||||
'change',
|
||||
'input',
|
||||
],
|
||||
|
||||
props: {
|
||||
async: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
iterable: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
iterator: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
hasChanges: true,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSubmit() {
|
||||
const iterator = this.$refs.iterator.value.trim()
|
||||
const iterable = this.$refs.iterable.value.trim()
|
||||
if (!iterator.length || !iterable.length) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('change', { iterator, iterable })
|
||||
},
|
||||
|
||||
onInput(target, event) {
|
||||
const value = '' + event.target.value
|
||||
if (!value?.trim()?.length) {
|
||||
this.hasChanges = false
|
||||
} else {
|
||||
if (target === 'iterator') {
|
||||
this.hasChanges = value !== this.iterator
|
||||
}
|
||||
|
||||
if (!this.hasChanges && target === 'iterable') {
|
||||
this.hasChanges = value !== this.iterable
|
||||
}
|
||||
|
||||
if (!this.hasChanges && target === 'async') {
|
||||
this.hasChanges = value !== this.async
|
||||
}
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
event.target.value = value
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.hasChanges = false
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.iterator.focus()
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "common";
|
||||
|
||||
.loop-editor {
|
||||
min-width: 40em;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
&.async {
|
||||
justify-content: flex-start;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
212
platypush/backend/http/webapp/src/components/Action/LoopTile.vue
Normal file
212
platypush/backend/http/webapp/src/components/Action/LoopTile.vue
Normal file
|
@ -0,0 +1,212 @@
|
|||
<template>
|
||||
<ListItem class="loop-tile"
|
||||
:value="value"
|
||||
:active="active"
|
||||
:read-only="readOnly"
|
||||
:spacer-bottom="spacerBottom"
|
||||
:spacer-top="spacerTop"
|
||||
v-on="dragListeners"
|
||||
@input="$emit('input', $event)">
|
||||
<div class="drag-spacer" v-if="dragging && !spacerTop"> </div>
|
||||
|
||||
<Tile v-bind="tileConf.props"
|
||||
v-on="tileConf.on"
|
||||
:draggable="!readOnly"
|
||||
@click.stop="showLoopEditor = true">
|
||||
<div class="tile-name">
|
||||
<span class="icon">
|
||||
<i class="fas fa-arrow-rotate-left" />
|
||||
</span>
|
||||
<span class="name">
|
||||
<span class="keyword">for<span v-if="async">k</span></span>
|
||||
<span class="code" v-text="iterator" />
|
||||
<span class="keyword"> in </span> [
|
||||
<span class="code" v-text="iterable" /> ]
|
||||
</span>
|
||||
</div>
|
||||
</Tile>
|
||||
|
||||
<div class="editor-container" v-if="showLoopEditor && !readOnly">
|
||||
<Modal title="Edit Loop"
|
||||
:visible="true"
|
||||
@close="showLoopEditor = false">
|
||||
<LoopEditor :iterator="iterator"
|
||||
:iterable="iterable"
|
||||
:async="async"
|
||||
ref="loopEditor"
|
||||
@change="onLoopChange"
|
||||
v-if="showLoopEditor">
|
||||
Loop
|
||||
</LoopEditor>
|
||||
</Modal>
|
||||
</div>
|
||||
</ListItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItem from "./ListItem"
|
||||
import LoopEditor from "./LoopEditor"
|
||||
import Modal from "@/components/Modal"
|
||||
import Tile from "@/components/elements/Tile"
|
||||
|
||||
export default {
|
||||
emits: [
|
||||
'change',
|
||||
'click',
|
||||
'delete',
|
||||
'drag',
|
||||
'dragend',
|
||||
'dragenter',
|
||||
'dragleave',
|
||||
'dragover',
|
||||
'drop',
|
||||
'input',
|
||||
],
|
||||
|
||||
components: {
|
||||
LoopEditor,
|
||||
ListItem,
|
||||
Modal,
|
||||
Tile,
|
||||
},
|
||||
|
||||
props: {
|
||||
iterator: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
iterable: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
async: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
isElse: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
spacerBottom: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
spacerTop: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
dragListeners() {
|
||||
return this.readOnly ? {} : {
|
||||
drag: this.onDragStart,
|
||||
dragend: this.onDragEnd,
|
||||
dragenter: (event) => this.$emit('dragenter', event),
|
||||
dragleave: (event) => this.$emit('dragleave', event),
|
||||
dragover: (event) => this.$emit('dragover', event),
|
||||
drop: this.onDrop,
|
||||
}
|
||||
},
|
||||
|
||||
tileConf() {
|
||||
return {
|
||||
props: {
|
||||
value: this.value,
|
||||
class: 'keyword',
|
||||
readOnly: this.readOnly,
|
||||
withDelete: !this.readOnly,
|
||||
},
|
||||
|
||||
on: {
|
||||
...this.dragListeners,
|
||||
delete: () => this.$emit('delete'),
|
||||
input: this.onInput,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
value() {
|
||||
return `for ${this.iterator} in ${this.iterable}`
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
showLoopEditor: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onLoopChange(event) {
|
||||
this.showLoopEditor = false
|
||||
if (this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('change', event)
|
||||
},
|
||||
|
||||
onInput(value) {
|
||||
if (!value || this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('input', value)
|
||||
},
|
||||
|
||||
onDragStart(event) {
|
||||
if (this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.dragging = true
|
||||
this.$emit('drag', event)
|
||||
},
|
||||
|
||||
onDragEnd(event) {
|
||||
this.dragging = false
|
||||
this.$emit('dragend', event)
|
||||
},
|
||||
|
||||
onDrop(event) {
|
||||
this.dragging = false
|
||||
if (this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('drop', event)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "common";
|
||||
|
||||
.action-tile {
|
||||
.condition {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.drag-spacer {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
</Tile>
|
||||
|
||||
<div class="expr-editor-container" v-if="showExprEditor && !readOnly">
|
||||
<div class="editor-container" v-if="showExprEditor && !readOnly">
|
||||
<Modal title="Edit Return"
|
||||
:visible="true"
|
||||
@close="showExprEditor = false">
|
||||
|
@ -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,
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
|
||||
<div class="info-body" v-if="!infoCollapsed">
|
||||
<div class="item">
|
||||
<div class="label">Type</div>
|
||||
<div class="label">Source</div>
|
||||
<div class="value">
|
||||
<i :class="procedureTypeIconClass" />
|
||||
{{ 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
|
||||
},
|
||||
|
|
|
@ -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, """)
|
||||
?.replace?.(/'/g, "'") || ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue