[#341] Added for for/break/continue statements in procedure editor.

This commit is contained in:
Fabio Manganiello 2024-09-13 18:19:02 +02:00
parent c4610254f8
commit 156a6379d0
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
17 changed files with 892 additions and 39 deletions

View file

@ -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,

View file

@ -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) => {

View file

@ -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>

View file

@ -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,

View file

@ -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>

View file

@ -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>

View file

@ -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" />

View file

@ -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
} }

View file

@ -87,7 +87,7 @@ export default {
}, },
value: { value: {
type: [String, Object], type: [String, Number, Boolean, Object, Array],
required: true, required: true,
}, },
}, },

View file

@ -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>

View file

@ -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)" />&nbsp;
Run in parallel
</label>
<label>
<button type="submit" :disabled="!hasChanges">
<i class="fas fa-check" />&nbsp;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>

View 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">&nbsp;</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>&nbsp;
<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>

View file

@ -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) {

View file

@ -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

View file

@ -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,

View file

@ -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" />&nbsp; <i :class="procedureTypeIconClass" />&nbsp;
{{ 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
}, },

View file

@ -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, "&amp;")
?.replace?.(/</g, "&lt;")
?.replace?.(/>/g, "&gt;")
?.replace?.(/"/g, "&quot;")
?.replace?.(/'/g, "&#039;") || ''
},
}, },
} }
</script> </script>