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"
|
:dragging="dragging"
|
||||||
:has-else="hasElse"
|
:has-else="hasElse"
|
||||||
:indent="indent"
|
:indent="indent"
|
||||||
|
:is-inside-loop="isInsideLoop"
|
||||||
:parent="value"
|
:parent="value"
|
||||||
:read-only="readOnly"
|
:read-only="readOnly"
|
||||||
@add-else="$emit('add-else')"
|
@add-else="$emit('add-else')"
|
||||||
|
@ -84,6 +85,11 @@ export default {
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isInsideLoop: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|
|
@ -25,6 +25,24 @@
|
||||||
:is-else="true"
|
:is-else="true"
|
||||||
v-else-if="elses[index]" />
|
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"
|
<ReturnTile v-bind="componentsData[index].props"
|
||||||
:value="returnValue"
|
:value="returnValue"
|
||||||
@change="editReturn($event)"
|
@change="editReturn($event)"
|
||||||
|
@ -67,6 +85,18 @@
|
||||||
<div class="row item action add-else-container" v-if="visibleAddButtons.else">
|
<div class="row item action add-else-container" v-if="visibleAddButtons.else">
|
||||||
<AddTile icon="fas fa-question" title="Add Else" @click="$emit('add-else')" />
|
<AddTile icon="fas fa-question" title="Add Else" @click="$emit('add-else')" />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,8 +106,11 @@
|
||||||
import ActionsListItem from "./ActionsListItem"
|
import ActionsListItem from "./ActionsListItem"
|
||||||
import ActionTile from "./ActionTile"
|
import ActionTile from "./ActionTile"
|
||||||
import AddTile from "./AddTile"
|
import AddTile from "./AddTile"
|
||||||
|
import BreakTile from "./BreakTile"
|
||||||
import ConditionBlock from "./ConditionBlock"
|
import ConditionBlock from "./ConditionBlock"
|
||||||
|
import ContinueTile from "./ContinueTile"
|
||||||
import ListItem from "./ListItem"
|
import ListItem from "./ListItem"
|
||||||
|
import LoopBlock from "./LoopBlock"
|
||||||
import Mixin from "./Mixin"
|
import Mixin from "./Mixin"
|
||||||
import ReturnTile from "./ReturnTile"
|
import ReturnTile from "./ReturnTile"
|
||||||
import Utils from "@/Utils"
|
import Utils from "@/Utils"
|
||||||
|
@ -103,8 +136,11 @@ export default {
|
||||||
ActionsListItem,
|
ActionsListItem,
|
||||||
ActionTile,
|
ActionTile,
|
||||||
AddTile,
|
AddTile,
|
||||||
|
BreakTile,
|
||||||
ConditionBlock,
|
ConditionBlock,
|
||||||
|
ContinueTile,
|
||||||
ListItem,
|
ListItem,
|
||||||
|
LoopBlock,
|
||||||
ReturnTile,
|
ReturnTile,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -119,12 +155,17 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasElse: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
indent: {
|
indent: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
hasElse: {
|
isInsideLoop: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
@ -187,6 +228,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
value: action,
|
value: action,
|
||||||
active: this.isDragging,
|
active: this.isDragging,
|
||||||
|
isInsideLoop: !!(this.isInsideLoop || this.getFor(action) || this.getWhile(action)),
|
||||||
readOnly: this.readOnly,
|
readOnly: this.readOnly,
|
||||||
ref: `action-tile-${index}`,
|
ref: `action-tile-${index}`,
|
||||||
spacerBottom: this.visibleBottomSpacers[index],
|
spacerBottom: this.visibleBottomSpacers[index],
|
||||||
|
@ -223,6 +265,11 @@ export default {
|
||||||
data.props.indent = this.indent + 1
|
data.props.indent = this.indent + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loop = this.getFor(action)
|
||||||
|
if (loop) {
|
||||||
|
data.props.async = loop.async
|
||||||
|
}
|
||||||
|
|
||||||
return data
|
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() {
|
hasChanges() {
|
||||||
return this.newStringValue !== this.stringValue
|
return this.newStringValue !== this.stringValue
|
||||||
},
|
},
|
||||||
|
@ -301,18 +358,16 @@ export default {
|
||||||
return this.dragIndices?.[0]
|
return this.dragIndices?.[0]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
breakIndex() {
|
||||||
|
return this.getTileIndex((action) => this.isBreak(action))
|
||||||
|
},
|
||||||
|
|
||||||
|
continueIndex() {
|
||||||
|
return this.getTileIndex((action) => this.isContinue(action))
|
||||||
|
},
|
||||||
|
|
||||||
returnIndex() {
|
returnIndex() {
|
||||||
const ret = this.newValue?.reduce?.((acc, action, index) => {
|
return this.getTileIndex((action) => this.isReturn(action))
|
||||||
if (acc >= 0)
|
|
||||||
return acc
|
|
||||||
|
|
||||||
if (this.isReturn(action))
|
|
||||||
return index
|
|
||||||
|
|
||||||
return acc
|
|
||||||
}, -1)
|
|
||||||
|
|
||||||
return ret >= 0 ? ret : null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
returnValue() {
|
returnValue() {
|
||||||
|
@ -349,7 +404,22 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
stopIndex() {
|
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() {
|
visibleActions() {
|
||||||
|
@ -360,8 +430,11 @@ export default {
|
||||||
if (
|
if (
|
||||||
this.conditions[index] ||
|
this.conditions[index] ||
|
||||||
this.elses[index] ||
|
this.elses[index] ||
|
||||||
|
this.fors[index] ||
|
||||||
this.isAction(action) ||
|
this.isAction(action) ||
|
||||||
this.isReturn(action)
|
this.isReturn(action) ||
|
||||||
|
this.isBreak(action) ||
|
||||||
|
this.isContinue(action)
|
||||||
) {
|
) {
|
||||||
acc[index] = action
|
acc[index] = action
|
||||||
}
|
}
|
||||||
|
@ -372,16 +445,23 @@ export default {
|
||||||
|
|
||||||
visibleAddButtons() {
|
visibleAddButtons() {
|
||||||
return {
|
return {
|
||||||
action: !this.readOnly && !this.collapsed && this.stopIndex == null,
|
action: this.allowAddButtons,
|
||||||
return: !this.readOnly && !this.collapsed && this.stopIndex == null,
|
return: this.allowAddButtons,
|
||||||
condition: !this.readOnly && !this.collapsed && this.stopIndex == null,
|
condition: this.allowAddButtons,
|
||||||
|
loop: this.allowAddButtons,
|
||||||
else: (
|
else: (
|
||||||
!this.readOnly &&
|
this.allowAddButtons &&
|
||||||
!this.collapsed &&
|
|
||||||
this.parent &&
|
this.parent &&
|
||||||
this.getCondition(this.parent) &&
|
this.getCondition(this.parent) &&
|
||||||
!this.hasElse &&
|
!this.hasElse
|
||||||
this.stopIndex == null
|
),
|
||||||
|
break: (
|
||||||
|
this.allowAddButtons &&
|
||||||
|
this.isInsideLoop
|
||||||
|
),
|
||||||
|
continue: (
|
||||||
|
this.allowAddButtons &&
|
||||||
|
this.isInsideLoop
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -583,6 +663,19 @@ export default {
|
||||||
this.selectLastExprEditor()
|
this.selectLastExprEditor()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addLoop() {
|
||||||
|
this.newValue.push({ 'for _ in ${range(10)}': [] })
|
||||||
|
this.selectLastExprEditor()
|
||||||
|
},
|
||||||
|
|
||||||
|
addBreak() {
|
||||||
|
this.newValue.push('break')
|
||||||
|
},
|
||||||
|
|
||||||
|
addContinue() {
|
||||||
|
this.newValue.push('continue')
|
||||||
|
},
|
||||||
|
|
||||||
addReturn() {
|
addReturn() {
|
||||||
this.newValue.push({ 'return': null })
|
this.newValue.push({ 'return': null })
|
||||||
this.selectLastExprEditor()
|
this.selectLastExprEditor()
|
||||||
|
@ -606,7 +699,7 @@ export default {
|
||||||
|
|
||||||
newTileElement.click()
|
newTileElement.click()
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const exprEditor = newTile.$el?.querySelector('.expr-editor-container')
|
const exprEditor = newTile.$el?.querySelector('.editor-container')
|
||||||
if (!exprEditor) {
|
if (!exprEditor) {
|
||||||
return
|
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() {
|
syncSpacers() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.spacerElements = Object.keys(this.newValue).reduce((acc, index) => {
|
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"
|
:collapsed="collapsed"
|
||||||
:dragging="isDragging"
|
:dragging="isDragging"
|
||||||
:has-else="hasElse"
|
:has-else="hasElse"
|
||||||
|
:is-inside-loop="isInsideLoop"
|
||||||
:indent="indent"
|
:indent="indent"
|
||||||
:read-only="readOnly"
|
:read-only="readOnly"
|
||||||
@input="onActionsChange"
|
@input="onActionsChange"
|
||||||
|
@ -107,6 +108,11 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isInsideLoop: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
<span class="name">
|
<span class="name">
|
||||||
<span class="keyword">if</span> [
|
<span class="keyword">if</span> [
|
||||||
<span class="code" v-text="value" /> ]
|
<span class="code" v-text="value" /> ]
|
||||||
<span class="keyword">then</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Tile>
|
</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">
|
<Tile class="keyword" :draggable="false" :read-only="true">
|
||||||
<div class="tile-name">
|
<div class="tile-name">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fas fa-question" />
|
<i :class="icon" />
|
||||||
</span>
|
</span>
|
||||||
<span class="name">
|
<span class="name">
|
||||||
<span class="keyword" v-text="value" />
|
<span class="keyword" v-text="value" />
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default {
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: [String, Number, Boolean, Object, Array],
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ export default {
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.hasChanges = false
|
this.hasChanges = false
|
||||||
if (!this.value?.trim()?.length) {
|
if (!this.value?.trim?.()?.length) {
|
||||||
this.hasChanges = this.allowEmpty
|
this.hasChanges = this.allowEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
value: {
|
value: {
|
||||||
type: [String, Object],
|
type: [String, Number, Boolean, Object, Array],
|
||||||
required: true,
|
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?.()
|
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) {
|
getKey(value) {
|
||||||
return this.isKeyword(value) ? Object.keys(value)[0] : null
|
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) {
|
isAction(value) {
|
||||||
return typeof value === 'object' && !Array.isArray(value) && (value.action || value.name)
|
return typeof value === 'object' && !Array.isArray(value) && (value.action || value.name)
|
||||||
},
|
},
|
||||||
|
|
||||||
isActionsBlock(value) {
|
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) {
|
isKeyword(value) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default {
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
props: {
|
props: {
|
||||||
response: {
|
response: {
|
||||||
type: [String, Object],
|
type: [String, Object, Array, Number, Boolean],
|
||||||
},
|
},
|
||||||
|
|
||||||
error: {
|
error: {
|
||||||
|
@ -51,7 +51,7 @@ export default {
|
||||||
|
|
||||||
jsonResponse() {
|
jsonResponse() {
|
||||||
if (this.isJSON) {
|
if (this.isJSON) {
|
||||||
return hljs.highlight(this.response, {language: 'json'}).value
|
return hljs.highlight(this.response.toString(), {language: 'json'}).value
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
</Tile>
|
</Tile>
|
||||||
|
|
||||||
<div class="expr-editor-container" v-if="showExprEditor && !readOnly">
|
<div class="editor-container" v-if="showExprEditor && !readOnly">
|
||||||
<Modal title="Edit Return"
|
<Modal title="Edit Return"
|
||||||
:visible="true"
|
:visible="true"
|
||||||
@close="showExprEditor = false">
|
@close="showExprEditor = false">
|
||||||
|
@ -59,7 +59,7 @@ export default {
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: [String, Number, Boolean, Object, Array],
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -68,11 +68,6 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
isElse: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
readOnly: {
|
readOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|
|
@ -126,7 +126,7 @@
|
||||||
|
|
||||||
<div class="info-body" v-if="!infoCollapsed">
|
<div class="info-body" v-if="!infoCollapsed">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="label">Type</div>
|
<div class="label">Source</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<i :class="procedureTypeIconClass" />
|
<i :class="procedureTypeIconClass" />
|
||||||
{{ value.procedure_type }}
|
{{ value.procedure_type }}
|
||||||
|
@ -323,7 +323,10 @@ export default {
|
||||||
return 'fab fa-python'
|
return 'fab fa-python'
|
||||||
|
|
||||||
if (this.value.procedure_type === 'config')
|
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
|
return this.defaultIconClass
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,6 +37,16 @@ export default {
|
||||||
formatNumber(number) {
|
formatNumber(number) {
|
||||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
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>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue