[#341] Added support for setting variables in procedure editor.

This commit is contained in:
Fabio Manganiello 2024-09-16 03:08:46 +02:00
parent dfbbea93fd
commit e7e76087c0
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 408 additions and 9 deletions

View file

@ -31,6 +31,12 @@
:dragging="isDragging"
v-else-if="loops[index]" />
<SetVariablesTile v-bind="componentsData[index].props"
v-on="componentsData[index].on"
:collapsed="collapsedBlocks[index]"
:dragging="isDragging"
v-else-if="sets[index]" />
<BreakTile :active="isDragging"
:readOnly="readOnly"
:spacerTop="componentsData[index].props.spacerTop"
@ -101,6 +107,10 @@
<div class="row item action add-continue-container" v-if="visibleAddButtons.continue">
<AddTile icon="fas fa-rotate" title="Add Continue" @click="addContinue" />
</div>
<div class="row item action add-set-container" v-if="visibleAddButtons.set">
<AddTile icon="fas fa-square-root-variable" title="Set Variables" @click="addSet" />
</div>
</div>
</div>
</div>
@ -117,6 +127,7 @@ import ListItem from "./ListItem"
import LoopBlock from "./LoopBlock"
import Mixin from "./Mixin"
import ReturnTile from "./ReturnTile"
import SetVariablesTile from "./SetVariablesTile"
import Utils from "@/Utils"
export default {
@ -146,6 +157,7 @@ export default {
ListItem,
LoopBlock,
ReturnTile,
SetVariablesTile,
},
props: {
@ -280,6 +292,10 @@ export default {
data.props.type = 'while'
}
if (this.isSet(action)) {
data.props.value = action.set
}
return data
})
},
@ -365,6 +381,16 @@ export default {
}, {})
},
sets() {
return this.newValue?.reduce?.((acc, action, index) => {
if (this.isSet(action)) {
acc[index] = action
}
return acc
}, {}) || {}
},
hasChanges() {
return this.newStringValue !== this.stringValue
},
@ -463,9 +489,10 @@ export default {
this.fors[index] ||
this.whiles[index] ||
this.isAction(action) ||
this.isReturn(action) ||
this.isBreak(action) ||
this.isContinue(action)
this.isContinue(action) ||
this.isReturn(action) ||
this.isSet(action)
) {
acc[index] = action
}
@ -481,6 +508,7 @@ export default {
condition: this.allowAddButtons,
for: this.allowAddButtons,
while: this.allowAddButtons,
set: this.allowAddButtons,
else: (
this.allowAddButtons &&
this.parent &&
@ -562,7 +590,7 @@ export default {
return
}
event.stopPropagation()
event.stopPropagation?.()
this.$emit('dragenter', index)
},
@ -571,7 +599,7 @@ export default {
return
}
event.stopPropagation()
event.stopPropagation?.()
this.$emit('dragleave', index)
},
@ -584,7 +612,7 @@ export default {
return
}
event.stopPropagation()
event.stopPropagation?.()
let dropIndices = []
if (!event.detail?.length) {
@ -673,7 +701,7 @@ export default {
// otherwise the event will be caught by the parent element. If the parent
// is a modal, then the modal will be closed, making it impossible to edit
// text fields in the action tiles.
event.stopPropagation()
event.stopPropagation?.()
return
}
@ -713,6 +741,11 @@ export default {
this.newValue.push('continue')
},
addSet() {
this.newValue.push({ 'set': {} })
this.selectLastExprEditor()
},
addReturn() {
this.newValue.push({ 'return': null })
this.selectLastExprEditor()

View file

@ -29,6 +29,7 @@
<input class="checkbox"
type="checkbox"
name="async"
ref="async"
:checked="async"
@input.stop="onInput('async', $event)" />&nbsp;
Run in parallel
@ -76,11 +77,12 @@ export default {
onSubmit() {
const iterator = this.$refs.iterator.value.trim()
const iterable = this.$refs.iterable.value.trim()
const async_ = this.$refs.async.checked
if (!iterator.length || !iterable.length) {
return
}
this.$emit('change', { iterator, iterable })
this.$emit('change', { iterator, iterable, async: async_ })
},
onInput(target, event) {

View file

@ -18,8 +18,7 @@
<i class="fas fa-arrow-rotate-left" />
</span>
<span class="name" v-if="type === 'for'">
<span class="keyword">for<span v-if="async">k</span></span>&nbsp;
<span class="code" v-text="iterator" />
<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>

View file

@ -67,6 +67,10 @@ export default {
return this.getKey(value) === 'return'
},
isSet(value) {
return this.getKey(value) === 'set'
},
},
}
</script>

View file

@ -0,0 +1,361 @@
<template>
<ListItem class="set-variables-tile"
:class="{active}"
:value="value"
:active="active"
:read-only="readOnly"
:spacer-bottom="spacerBottom"
:spacer-top="spacerTop"
v-on="dragListeners"
@input="$emit('input', $event)">
<Tile v-bind="tileConf.props"
v-on="tileConf.on"
:draggable="!readOnly"
@click.stop="showEditor = true">
<div class="tile-name">
<span class="icon">
<i class="fas fa-square-root-variable" />
</span>
<span class="name">
<div class="keyword">set</div>
</span>
</div>
<div class="variables">
<div class="variable" v-for="(value, name) in value" :key="name">
<span class="code name" v-text="name" />&nbsp;=
<span class="code value" v-text="value" />
</div>
</div>
</Tile>
<div class="editor-container" v-if="showEditor && !readOnly">
<Modal title="Set Variables"
:visible="true"
@close="showEditor = false">
<form class="editor" @submit.prevent="onChange">
<div class="variable" v-for="(v, i) in newValue" :key="i">
<span class="name">
<input type="text"
placeholder="Variable Name"
@blur="onBlur(i)"
@input.prevent.stop
v-model="newValue[i][0]">&nbsp;=
</span>
<span class="value">
<input type="text"
placeholder="Value"
@input.prevent.stop
v-model="newValue[i][1]">
</span>
</div>
<div class="variable">
<span class="name">
<input type="text"
placeholder="Variable Name"
ref="newVarName"
@input.prevent.stop
v-model="newVariable.name">&nbsp;=
</span>
<span class="value">
<input type="text"
placeholder="Value"
ref="newVarValue"
@blur="onBlur(null)"
@input.prevent.stop
v-model="newVariable.value">
</span>
</div>
<div class="buttons">
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</form>
</Modal>
</div>
</ListItem>
</template>
<script>
import ListItem from "./ListItem"
import Modal from "@/components/Modal"
import Tile from "@/components/elements/Tile"
export default {
emits: [
'click',
'delete',
'drag',
'dragend',
'dragenter',
'dragleave',
'dragover',
'drop',
'input',
],
components: {
ListItem,
Modal,
Tile,
},
props: {
active: {
type: Boolean,
default: false,
},
readOnly: {
type: Boolean,
default: false,
},
spacerBottom: {
type: Boolean,
default: true,
},
spacerTop: {
type: Boolean,
default: true,
},
value: {
type: Object,
default: () => ({}),
},
},
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,
},
}
},
},
data() {
return {
dragging: false,
newValue: [],
newVariable: {
name: '',
value: '',
},
showEditor: false,
}
},
methods: {
onChange() {
this.showEditor = false
if (this.readOnly) {
return
}
const variables = this.newValue
if (this.newVariable.name?.trim?.()?.length) {
variables.push([this.newVariable.name, this.newVariable.value])
}
const args = variables.map(([name, value]) => {
name = this.sanitizeName(name)
try {
value = JSON.parse(value)
} catch (e) {
value = value?.trim()
}
return [name, value]
})
.reduce((acc, [name, value]) => {
if (!name?.length) {
return acc
}
acc[name] = value
return acc
}, {})
if (!Object.keys(args).length) {
return
}
this.onInput(args)
},
onInput(value) {
if (!value || this.readOnly) {
return
}
this.$emit('input', {set: value})
},
onBlur(index) {
if (this.readOnly) {
return
}
if (index != null) {
const name = this.sanitizeName(this.newValue[index][0])
if (!name?.length) {
this.newValue.splice(index, 1)
} else {
this.newValue[index][0] = name
}
} else {
const name = this.sanitizeName(this.newVariable.name)
const value = this.newVariable.value
if (name?.length) {
this.newValue.push([name, value])
this.newVariable = {
name: '',
value: '',
}
this.$nextTick(() => {
this.$refs.newVarName?.focus()
})
}
}
},
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)
},
sanitizeName(name) {
return name?.trim()?.replace(/[^\w_]/g, '_')
},
syncValue() {
this.newValue = Object.entries(this.value)
},
},
watch: {
showEditor(value) {
if (!value) {
this.newVariable = {
name: '',
value: '',
}
} else {
this.$nextTick(() => {
this.$refs.newVarName?.focus()
})
}
},
value: {
immediate: true,
handler() {
this.syncValue()
},
},
},
mounted() {
this.syncValue()
this.$nextTick(() => {
this.$refs.newVarName?.focus()
})
},
}
</script>
<style lang="scss" scoped>
@import "common";
.set-variables-tile {
&.active {
.spacer {
display: none;
}
}
.variables {
margin-left: 2.5em;
}
.variable {
.value {
font-style: italic;
}
}
.drag-spacer {
height: 0;
}
.editor {
display: flex;
flex-direction: column;
padding: 1em;
.variable {
display: flex;
.value {
flex: 1;
input[type="text"] {
width: 100%;
}
}
}
.buttons {
margin: 1em;
}
}
}
</style>