forked from platypush/platypush
[#341] Added support for dynamic context in procedure editor components.
This commit is contained in:
parent
0e40d77bc7
commit
6dd1d481d5
21 changed files with 694 additions and 215 deletions
|
@ -5,14 +5,15 @@
|
|||
<!-- Supported action arguments -->
|
||||
<div class="arg" :key="name" v-for="name in Object.keys(action.args)">
|
||||
<label>
|
||||
<input type="text"
|
||||
class="action-arg-value"
|
||||
:class="{required: action.args[name].required}"
|
||||
<ContextAutocomplete :value="action.args[name].value"
|
||||
:disabled="running"
|
||||
:items="contextAutocompleteItems"
|
||||
:placeholder="name"
|
||||
:value="action.args[name].value"
|
||||
:quote="true"
|
||||
:select-on-tab="false"
|
||||
@input="onArgEdit(name, $event)"
|
||||
@focus="onSelect(name)">
|
||||
@blur="onSelect(name)"
|
||||
@focus="onSelect(name)" />
|
||||
<span class="required-flag" v-if="action.args[name].required">*</span>
|
||||
</label>
|
||||
|
||||
|
@ -36,12 +37,13 @@
|
|||
@input="onExtraArgNameEdit(i, $event.target.value)">
|
||||
</label>
|
||||
<label class="col-6">
|
||||
<input type="text"
|
||||
class="action-extra-arg-value"
|
||||
placeholder="Value"
|
||||
<ContextAutocomplete :value="arg.value"
|
||||
:disabled="running"
|
||||
:value="arg.value"
|
||||
@input="onExtraArgValueEdit(i, $event.target.value)">
|
||||
:items="contextAutocompleteItems"
|
||||
:quote="true"
|
||||
:select-on-tab="false"
|
||||
placeholder="Value"
|
||||
@input="onExtraArgValueEdit(i, $event.detail)" />
|
||||
</label>
|
||||
<label class="col-1 buttons">
|
||||
<button type="button" class="action-extra-arg-del" title="Remove argument" @click="$emit('remove', i)">
|
||||
|
@ -68,10 +70,16 @@
|
|||
|
||||
<script>
|
||||
import Argdoc from "./Argdoc"
|
||||
import ContextAutocomplete from "./ContextAutocomplete"
|
||||
import Mixin from "./Mixin"
|
||||
|
||||
export default {
|
||||
name: 'ActionArgs',
|
||||
components: { Argdoc },
|
||||
mixins: [Mixin],
|
||||
components: {
|
||||
Argdoc,
|
||||
ContextAutocomplete,
|
||||
},
|
||||
|
||||
emits: [
|
||||
'add',
|
||||
'arg-edit',
|
||||
|
@ -117,7 +125,7 @@ export default {
|
|||
onArgEdit(name, event) {
|
||||
this.$emit('arg-edit', {
|
||||
name: name,
|
||||
value: event.target.value,
|
||||
value: event.target?.value || event.detail,
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
</h2>
|
||||
|
||||
<ActionArgs :action="action"
|
||||
:context="context"
|
||||
:loading="loading"
|
||||
:running="running"
|
||||
:selected-arg="selectedArg"
|
||||
|
@ -143,6 +144,11 @@ export default {
|
|||
},
|
||||
|
||||
props: {
|
||||
context: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
},
|
||||
|
@ -391,14 +397,14 @@ export default {
|
|||
this.actionDocsCache[this.action.name].html = this.selectedDoc
|
||||
this.setUrlArgs({action: this.action.name})
|
||||
|
||||
const firstArg = this.$el.querySelector('.action-arg-value')
|
||||
this.$nextTick(() => {
|
||||
const firstArg = this.$el.querySelector('.args-body input[type=text]')
|
||||
if (firstArg) {
|
||||
firstArg.focus()
|
||||
} else {
|
||||
this.$nextTick(() => {
|
||||
this.actionInput.focus()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.response = undefined
|
||||
this.error = undefined
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
<div class="action-editor-container">
|
||||
<Modal ref="actionEditor" title="Edit Action">
|
||||
<ActionEditor :value="value"
|
||||
:context="context"
|
||||
:with-save="!readOnly"
|
||||
@input="onInput"
|
||||
v-if="this.$refs.actionEditor?.$data?.isVisible" />
|
||||
|
@ -68,9 +69,11 @@ import ActionEditor from "@/components/Action/ActionEditor"
|
|||
import Draggable from "@/components/elements/Draggable"
|
||||
import Droppable from "@/components/elements/Droppable"
|
||||
import ExtensionIcon from "@/components/elements/ExtensionIcon"
|
||||
import Mixin from "./Mixin"
|
||||
import Modal from "@/components/Modal"
|
||||
|
||||
export default {
|
||||
mixins: [Mixin],
|
||||
emits: [
|
||||
'delete',
|
||||
'drag',
|
||||
|
@ -90,6 +93,11 @@ export default {
|
|||
},
|
||||
|
||||
props: {
|
||||
context: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<div class="actions-list" :class="actionListClasses">
|
||||
<ActionsList :value="value[key]"
|
||||
:context="context"
|
||||
:dragging="dragging"
|
||||
:has-else="hasElse"
|
||||
:indent="indent"
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
:value="newAction"
|
||||
@drop="onDrop(0, $event)">
|
||||
<ActionTile :value="newAction"
|
||||
:context="contexts[Object.keys(contexts).length - 1]"
|
||||
:draggable="false"
|
||||
@input="addAction" />
|
||||
</ListItem>
|
||||
|
@ -244,6 +245,7 @@ export default {
|
|||
props: {
|
||||
value: action,
|
||||
active: this.isDragging,
|
||||
context: this.contexts[index],
|
||||
isInsideLoop: !!(this.isInsideLoop || this.getFor(action) || this.getWhile(action)),
|
||||
readOnly: this.readOnly,
|
||||
ref: `action-tile-${index}`,
|
||||
|
@ -314,6 +316,34 @@ export default {
|
|||
}, {}) || {}
|
||||
},
|
||||
|
||||
contexts() {
|
||||
const commonCtx = {...this.context}
|
||||
const contexts = this.newValue?.reduce?.((acc, action, index) => {
|
||||
acc[index] = this.getContext(action, index, commonCtx)
|
||||
return acc
|
||||
}, {}) || {}
|
||||
|
||||
const nContexts = Object.keys(contexts).length
|
||||
if (nContexts > 0) {
|
||||
contexts[nContexts] = this.getContext(
|
||||
null,
|
||||
nContexts,
|
||||
contexts[nContexts - 1]
|
||||
)
|
||||
|
||||
contexts[nContexts] = {
|
||||
...this.context,
|
||||
...commonCtx,
|
||||
...contexts[nContexts - 1],
|
||||
...contexts[nContexts],
|
||||
}
|
||||
} else {
|
||||
contexts[0] = {...this.context}
|
||||
}
|
||||
|
||||
return contexts
|
||||
},
|
||||
|
||||
dragBlockIndex() {
|
||||
if (this.dragIndex == null) {
|
||||
return
|
||||
|
@ -678,6 +708,33 @@ export default {
|
|||
this.$emit('collapse')
|
||||
},
|
||||
|
||||
getContext(action, index, context) {
|
||||
const ctx = {...(context || this.context || {})}
|
||||
if (index > 0) {
|
||||
ctx.output = {
|
||||
source: 'action',
|
||||
action: this.newValue[index - 1],
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isSet(action)) {
|
||||
Object.keys(action.set).forEach((name) => {
|
||||
if (!name?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
context[name] = { source: 'local' }
|
||||
})
|
||||
}
|
||||
|
||||
const iterator = this.getFor(action)?.iterator
|
||||
if (iterator?.length) {
|
||||
context[iterator] = { source: 'for' }
|
||||
}
|
||||
|
||||
return ctx
|
||||
},
|
||||
|
||||
getParentBlock(indices) {
|
||||
indices = [...indices]
|
||||
let parent = this.newValue
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
:value="value"
|
||||
v-on="componentsData.on">
|
||||
<ActionTile :value="value"
|
||||
:context="context"
|
||||
:draggable="!readOnly"
|
||||
:read-only="readOnly"
|
||||
:with-delete="!readOnly"
|
||||
|
@ -47,6 +48,11 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
|
||||
context: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="condition-block">
|
||||
<ActionsBlock :value="value"
|
||||
:collapsed="collapsed"
|
||||
:context="context"
|
||||
:dragging="isDragging"
|
||||
:has-else="hasElse"
|
||||
:is-inside-loop="isInsideLoop"
|
||||
|
@ -37,7 +38,7 @@
|
|||
<EndBlockTile value="end if"
|
||||
icon="fas fa-question"
|
||||
:active="active"
|
||||
:spacer-bottom="spacerBottom"
|
||||
:spacer-bottom="spacerBottom || dragging_"
|
||||
@drop="onDrop"
|
||||
v-if="isElse || !hasElse" />
|
||||
</template>
|
||||
|
@ -144,6 +145,7 @@ export default {
|
|||
return {
|
||||
props: {
|
||||
active: this.active,
|
||||
context: this.context,
|
||||
readOnly: this.readOnly,
|
||||
spacerBottom: this.spacerBottom,
|
||||
spacerTop: this.spacerTop,
|
||||
|
|
|
@ -46,10 +46,13 @@
|
|||
:visible="true"
|
||||
@close="showConditionEditor = false">
|
||||
<ExpressionEditor :value="value"
|
||||
:context="context"
|
||||
ref="conditionEditor"
|
||||
@input.prevent.stop="onConditionChange"
|
||||
v-if="showConditionEditor">
|
||||
<div class="header">
|
||||
Condition
|
||||
</div>
|
||||
</ExpressionEditor>
|
||||
</Modal>
|
||||
</div>
|
||||
|
@ -59,6 +62,7 @@
|
|||
<script>
|
||||
import ExpressionEditor from "./ExpressionEditor"
|
||||
import ListItem from "./ListItem"
|
||||
import Mixin from "./Mixin"
|
||||
import Modal from "@/components/Modal"
|
||||
import Tile from "@/components/elements/Tile"
|
||||
|
||||
|
@ -76,6 +80,7 @@ export default {
|
|||
'input',
|
||||
],
|
||||
|
||||
mixins: [Mixin],
|
||||
components: {
|
||||
ExpressionEditor,
|
||||
ListItem,
|
||||
|
@ -213,5 +218,11 @@ export default {
|
|||
.drag-spacer {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
:deep(.expression-editor) {
|
||||
.header {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
<template>
|
||||
<div class="autocomplete-with-context">
|
||||
<Autocomplete
|
||||
v-bind="autocompleteProps"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
@input="onInput"
|
||||
@select="onSelect"
|
||||
ref="input" />
|
||||
|
||||
<div class="spacer" ref="spacer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Autocomplete from "@/components/elements/Autocomplete"
|
||||
import AutocompleteProps from "@/mixins/Autocomplete/Props"
|
||||
|
||||
export default {
|
||||
emits: ["blur", "focus", "input"],
|
||||
components: { Autocomplete },
|
||||
mixins: [AutocompleteProps],
|
||||
|
||||
props: {
|
||||
quote: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
autocompleteItemsElement() {
|
||||
return this.$refs.input?.$el?.querySelector(".items")
|
||||
},
|
||||
|
||||
autocompleteItemsHeight() {
|
||||
return this.autocompleteItemsElement?.clientHeight || 0
|
||||
},
|
||||
|
||||
autocompleteProps() {
|
||||
return {
|
||||
...Object.fromEntries(
|
||||
Object.entries(this.$props).filter(([key]) => key !== "quote")
|
||||
),
|
||||
items: this.items,
|
||||
value: this.value,
|
||||
inputOnBlur: false,
|
||||
inputOnSelect: false,
|
||||
showAllItems: true,
|
||||
showResultsWhenBlank: true,
|
||||
}
|
||||
},
|
||||
|
||||
textInput() {
|
||||
return this.$refs.input?.$refs?.input
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
emitInput(value) {
|
||||
this.$emit(
|
||||
"input",
|
||||
new CustomEvent("input", {
|
||||
detail: value,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
isWithinQuote(selection) {
|
||||
let ret = false
|
||||
let value = '' + this.value
|
||||
selection = [...selection]
|
||||
|
||||
while (selection[0] > 0) {
|
||||
if (value[selection[0] - 1] === '$' && value[selection[0]] === '{') {
|
||||
ret = true
|
||||
break
|
||||
}
|
||||
|
||||
selection[0]--
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
return false
|
||||
|
||||
ret = false
|
||||
while (selection[1] < value.length) {
|
||||
if (value[selection[1]] === '}') {
|
||||
ret = true
|
||||
break
|
||||
}
|
||||
|
||||
selection[1]++
|
||||
}
|
||||
|
||||
return ret
|
||||
},
|
||||
|
||||
onBlur(event) {
|
||||
this.$emit("blur", event)
|
||||
this.$refs.spacer.style.height = "0"
|
||||
},
|
||||
|
||||
onFocus(event) {
|
||||
this.$emit("focus", event)
|
||||
setTimeout(() => {
|
||||
this.$refs.spacer.style.height = `${this.autocompleteItemsHeight}px`
|
||||
}, 10)
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
const selection = this.textSelection()
|
||||
this.emitInput(event)
|
||||
this.resetSelection(selection)
|
||||
},
|
||||
|
||||
onSelect(value) {
|
||||
this.$nextTick(() => {
|
||||
const selection = this.textSelection()
|
||||
value = this.quote && !this.isWithinQuote(selection) ? `\${${value}}` : value
|
||||
const newValue = typeof this.value === 'string' ? (
|
||||
this.value.slice(0, selection[0]) +
|
||||
value +
|
||||
this.value.slice(selection[1])
|
||||
) : value
|
||||
|
||||
this.emitInput(newValue)
|
||||
this.resetSelection([selection[0] + value.length, selection[0] + value.length])
|
||||
})
|
||||
},
|
||||
|
||||
resetSelection(selection) {
|
||||
setTimeout(() => {
|
||||
this.textInput?.focus()
|
||||
this.textInput?.setSelectionRange(selection[0], selection[1])
|
||||
}, 10)
|
||||
},
|
||||
|
||||
textSelection() {
|
||||
return [this.textInput?.selectionStart, this.textInput?.selectionEnd]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.autocomplete-with-context {
|
||||
.item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@include from($tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
display: flex;
|
||||
font-size: 0.9em;
|
||||
color: $disabled-fg;
|
||||
|
||||
@include from($tablet) {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -3,14 +3,11 @@
|
|||
<label for="expression">
|
||||
<slot />
|
||||
|
||||
<input type="text"
|
||||
name="expression"
|
||||
autocomplete="off"
|
||||
:autofocus="true"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
ref="text"
|
||||
@input.stop="onInput" />
|
||||
<ContextAutocomplete :value="newValue"
|
||||
:items="contextAutocompleteItems"
|
||||
:quote="quote"
|
||||
@input.stop="onInput"
|
||||
ref="input" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
|
@ -22,10 +19,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ContextAutocomplete from "./ContextAutocomplete"
|
||||
import Mixin from "./Mixin"
|
||||
|
||||
export default {
|
||||
emits: [
|
||||
'input',
|
||||
],
|
||||
emits: ['input'],
|
||||
mixins: [Mixin],
|
||||
components: { ContextAutocomplete },
|
||||
|
||||
props: {
|
||||
value: {
|
||||
|
@ -42,17 +42,23 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
quote: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
hasChanges: false,
|
||||
newValue: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSubmit(event) {
|
||||
const value = this.$refs.text.value.trim()
|
||||
const value = this.newValue?.trim()
|
||||
if (!value.length && !this.allowEmpty) {
|
||||
return
|
||||
}
|
||||
|
@ -62,7 +68,10 @@ export default {
|
|||
},
|
||||
|
||||
onInput(event) {
|
||||
const value = '' + event.target.value
|
||||
if (event?.detail == null)
|
||||
return
|
||||
|
||||
const value = '' + event.detail
|
||||
if (!value?.trim()?.length) {
|
||||
this.hasChanges = this.allowEmpty
|
||||
} else {
|
||||
|
@ -70,7 +79,7 @@ export default {
|
|||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.text.value = value
|
||||
this.newValue = value
|
||||
})
|
||||
},
|
||||
},
|
||||
|
@ -83,12 +92,14 @@ export default {
|
|||
|
||||
mounted() {
|
||||
this.hasChanges = false
|
||||
this.newValue = this.value
|
||||
|
||||
if (!this.value?.trim?.()?.length) {
|
||||
this.hasChanges = this.allowEmpty
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.text.focus()
|
||||
this.textInput?.focus()
|
||||
})
|
||||
},
|
||||
}
|
||||
|
@ -107,8 +118,7 @@ export default {
|
|||
label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="row item list-item" :class="itemClass">
|
||||
<div class="spacer-wrapper" :class="{ hidden: !spacerTop }">
|
||||
<div class="spacer" :class="{ active }" ref="dropTargetTop">
|
||||
<div class="spacer top" :class="{ active }" ref="dropTargetTop">
|
||||
<div class="droppable-wrapper">
|
||||
<div class="droppable-container">
|
||||
<div class="droppable-frame">
|
||||
|
@ -14,14 +14,14 @@
|
|||
<Droppable :element="$refs.dropTargetTop" :disabled="readOnly" v-on="droppableData.top.on" />
|
||||
</div>
|
||||
|
||||
<div class="spacer" v-if="dragging" />
|
||||
<div class="spacer top" v-if="dragging" />
|
||||
|
||||
<slot />
|
||||
|
||||
<div class="spacer" v-if="dragging" />
|
||||
<div class="spacer bottom" v-if="dragging" />
|
||||
|
||||
<div class="spacer-wrapper" :class="{ hidden: !spacerBottom }">
|
||||
<div class="spacer" :class="{ active }" ref="dropTargetBottom">
|
||||
<div class="spacer bottom" :class="{ active }" ref="dropTargetBottom">
|
||||
<div class="droppable-wrapper">
|
||||
<div class="droppable-container">
|
||||
<div class="droppable-frame">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="loop-block">
|
||||
<ActionsBlock :value="value"
|
||||
:collapsed="collapsed"
|
||||
:context="context_"
|
||||
:dragging="isDragging"
|
||||
:indent="indent"
|
||||
:is-inside-loop="true"
|
||||
|
@ -25,7 +26,7 @@
|
|||
<EndBlockTile :value="`end ${type}`"
|
||||
icon="fas fa-arrow-rotate-right"
|
||||
:active="active"
|
||||
:spacer-bottom="spacerBottom"
|
||||
:spacer-bottom="spacerBottom || dragging"
|
||||
@drop="onDrop" />
|
||||
</template>
|
||||
</ActionsBlock>
|
||||
|
@ -134,6 +135,18 @@ export default {
|
|||
return () => {}
|
||||
},
|
||||
|
||||
context_() {
|
||||
const ctx = {...this.context}
|
||||
const iterator = this.loop?.iterator?.trim()
|
||||
if (iterator?.length) {
|
||||
ctx[iterator] = {
|
||||
source: 'for',
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
},
|
||||
|
||||
isDragging() {
|
||||
return this.dragging_ || this.dragging
|
||||
},
|
||||
|
@ -159,6 +172,7 @@ export default {
|
|||
props: {
|
||||
...this.loop,
|
||||
active: this.active,
|
||||
context: this.context_,
|
||||
readOnly: this.readOnly,
|
||||
spacerBottom: this.spacerBottom,
|
||||
spacerTop: this.spacerTop,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
autocomplete="off"
|
||||
:autofocus="true"
|
||||
placeholder="Iterator"
|
||||
:value="iterator"
|
||||
:value="newValue.iterator"
|
||||
ref="iterator"
|
||||
@input.stop="onInput('iterator', $event)" />
|
||||
</label>
|
||||
|
@ -15,14 +15,12 @@
|
|||
in
|
||||
|
||||
<label for="iterable">
|
||||
<input type="text"
|
||||
name="iterable"
|
||||
autocomplete="off"
|
||||
:autofocus="true"
|
||||
<ContextAutocomplete :value="newValue.iterable"
|
||||
:items="contextAutocompleteItems"
|
||||
placeholder="Iterable"
|
||||
:value="iterable"
|
||||
ref="iterable"
|
||||
@input.stop="onInput('iterable', $event)" />
|
||||
@input.stop="onInput('iterable', $event)"
|
||||
ref="iterable" />
|
||||
|
||||
</label>
|
||||
|
||||
<label class="async">
|
||||
|
@ -44,12 +42,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: [
|
||||
'change',
|
||||
'input',
|
||||
],
|
||||
import ContextAutocomplete from "./ContextAutocomplete"
|
||||
import Mixin from "./Mixin"
|
||||
|
||||
export default {
|
||||
emits: ['change', 'input'],
|
||||
mixins: [Mixin],
|
||||
components: { ContextAutocomplete },
|
||||
props: {
|
||||
async: {
|
||||
type: Boolean,
|
||||
|
@ -70,6 +69,11 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
hasChanges: true,
|
||||
newValue: {
|
||||
iterator: null,
|
||||
iterable: null,
|
||||
async: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -86,7 +90,7 @@ export default {
|
|||
},
|
||||
|
||||
onInput(target, event) {
|
||||
const value = '' + event.target.value
|
||||
const value = '' + (event.target?.value || event.detail)
|
||||
if (!value?.trim()?.length) {
|
||||
this.hasChanges = false
|
||||
} else {
|
||||
|
@ -104,7 +108,7 @@ export default {
|
|||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
event.target.value = value
|
||||
this.newValue[target] = value
|
||||
})
|
||||
},
|
||||
},
|
||||
|
@ -112,10 +116,21 @@ export default {
|
|||
watch: {
|
||||
value() {
|
||||
this.hasChanges = false
|
||||
this.newValue = {
|
||||
iterator: this.iterator,
|
||||
iterable: this.iterable,
|
||||
async: this.async,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.newValue = {
|
||||
iterator: this.iterator,
|
||||
iterable: this.iterable,
|
||||
async: this.async,
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.iterator.focus()
|
||||
})
|
||||
|
@ -126,20 +141,12 @@ export default {
|
|||
<style lang="scss" scoped>
|
||||
@import "common";
|
||||
|
||||
.loop-editor {
|
||||
min-width: 40em;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
@mixin label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
|
@ -151,5 +158,26 @@ export default {
|
|||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loop-editor {
|
||||
min-width: 40em;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
@include label;
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(label) {
|
||||
@include label;
|
||||
|
||||
.autocomplete-with-context,
|
||||
.autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -37,12 +37,14 @@
|
|||
<LoopEditor :iterator="iterator"
|
||||
:iterable="iterable"
|
||||
:async="async"
|
||||
:context="context"
|
||||
@change="onLoopChange"
|
||||
v-if="showLoopEditor && type === 'for'">
|
||||
Loop
|
||||
</LoopEditor>
|
||||
|
||||
<ExpressionEditor :value="condition"
|
||||
:context="context"
|
||||
@input.prevent.stop="onConditionChange"
|
||||
v-else-if="showLoopEditor && type === 'while'">
|
||||
Loop Condition
|
||||
|
@ -56,10 +58,12 @@
|
|||
import ExpressionEditor from "./ExpressionEditor"
|
||||
import ListItem from "./ListItem"
|
||||
import LoopEditor from "./LoopEditor"
|
||||
import Mixin from "./Mixin"
|
||||
import Modal from "@/components/Modal"
|
||||
import Tile from "@/components/elements/Tile"
|
||||
|
||||
export default {
|
||||
mixins: [Mixin],
|
||||
emits: [
|
||||
'change',
|
||||
'click',
|
||||
|
|
|
@ -1,5 +1,41 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
context: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
contextAutocompleteItems() {
|
||||
return Object.entries(this.context).reduce((acc, [key, value]) => {
|
||||
let suffix = '<span class="source">from <b>' + (
|
||||
value?.source === 'local'
|
||||
? 'local context'
|
||||
: value?.source
|
||||
) + '</b>'
|
||||
|
||||
if (value?.source === 'action' && value?.action?.action)
|
||||
suffix += ` (<i>${value?.action?.action}</i>)`
|
||||
|
||||
suffix += '</span>'
|
||||
acc.push(
|
||||
{
|
||||
text: key,
|
||||
suffix: suffix,
|
||||
}
|
||||
)
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
},
|
||||
|
||||
textInput() {
|
||||
return this.$refs.input?.$refs?.input?.$refs?.input
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getCondition(value) {
|
||||
value = this.getKey(value) || value
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
@close="showExprEditor = false">
|
||||
<ExpressionEditor :value="value"
|
||||
:allow-empty="true"
|
||||
:context="context"
|
||||
:quote="true"
|
||||
placeholder="Optional return value"
|
||||
ref="exprEditor"
|
||||
@input.prevent.stop="onExprChange"
|
||||
|
@ -39,10 +41,12 @@
|
|||
<script>
|
||||
import ExpressionEditor from "./ExpressionEditor"
|
||||
import ListItem from "./ListItem"
|
||||
import Mixin from "./Mixin"
|
||||
import Modal from "@/components/Modal"
|
||||
import Tile from "@/components/elements/Tile"
|
||||
|
||||
export default {
|
||||
mixins: [Mixin],
|
||||
emits: [
|
||||
'change',
|
||||
'click',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<ListItem class="set-variables-tile"
|
||||
:class="{active}"
|
||||
:dragging="dragging_"
|
||||
:value="value"
|
||||
:active="active"
|
||||
:read-only="readOnly"
|
||||
|
@ -43,10 +44,12 @@
|
|||
v-model="newValue[i][0]"> =
|
||||
</span>
|
||||
<span class="value">
|
||||
<input type="text"
|
||||
<ContextAutocomplete :value="newValue[i][1]"
|
||||
:items="contextAutocompleteItems"
|
||||
:quote="true"
|
||||
:select-on-tab="false"
|
||||
placeholder="Value"
|
||||
@input.prevent.stop
|
||||
v-model="newValue[i][1]">
|
||||
@input.prevent.stop="newValue[i][1] = $event.detail" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -59,12 +62,13 @@
|
|||
v-model="newVariable.name"> =
|
||||
</span>
|
||||
<span class="value">
|
||||
<input type="text"
|
||||
<ContextAutocomplete :value="newVariable.value"
|
||||
:items="contextAutocompleteItems"
|
||||
:quote="true"
|
||||
:select-on-tab="false"
|
||||
placeholder="Value"
|
||||
ref="newVarValue"
|
||||
@blur="onBlur(null)"
|
||||
@input.prevent.stop
|
||||
v-model="newVariable.value">
|
||||
@input.prevent.stop="newVariable.value = $event.detail"
|
||||
@blur="onBlur(null)" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -80,11 +84,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ContextAutocomplete from "./ContextAutocomplete"
|
||||
import ListItem from "./ListItem"
|
||||
import Mixin from "./Mixin"
|
||||
import Modal from "@/components/Modal"
|
||||
import Tile from "@/components/elements/Tile"
|
||||
|
||||
export default {
|
||||
mixins: [Mixin],
|
||||
emits: [
|
||||
'click',
|
||||
'delete',
|
||||
|
@ -98,6 +105,7 @@ export default {
|
|||
],
|
||||
|
||||
components: {
|
||||
ContextAutocomplete,
|
||||
ListItem,
|
||||
Modal,
|
||||
Tile,
|
||||
|
@ -109,6 +117,11 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
|
||||
dragging: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -162,7 +175,7 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
dragging_: false,
|
||||
newValue: [],
|
||||
newVariable: {
|
||||
name: '',
|
||||
|
@ -253,17 +266,17 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.dragging = true
|
||||
this.dragging_ = true
|
||||
this.$emit('drag', event)
|
||||
},
|
||||
|
||||
onDragEnd(event) {
|
||||
this.dragging = false
|
||||
this.dragging_ = false
|
||||
this.$emit('dragend', event)
|
||||
},
|
||||
|
||||
onDrop(event) {
|
||||
this.dragging = false
|
||||
this.dragging_ = false
|
||||
if (this.readOnly) {
|
||||
return
|
||||
}
|
||||
|
@ -315,12 +328,6 @@ export default {
|
|||
@import "common";
|
||||
|
||||
.set-variables-tile {
|
||||
&.active {
|
||||
.spacer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.variables {
|
||||
margin-left: 2.5em;
|
||||
}
|
||||
|
@ -342,6 +349,7 @@ export default {
|
|||
|
||||
.variable {
|
||||
display: flex;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
|
|
|
@ -148,6 +148,7 @@ form {
|
|||
}
|
||||
}
|
||||
|
||||
:deep(.args-body) {
|
||||
.args-list {
|
||||
padding-top: 0.5em;
|
||||
overflow: auto;
|
||||
|
@ -165,6 +166,11 @@ form {
|
|||
width: $params-desktop-width;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.arg {
|
||||
margin-bottom: .25em;
|
||||
@include until($tablet) {
|
||||
|
@ -173,19 +179,22 @@ form {
|
|||
|
||||
.required-flag {
|
||||
width: 1.25em;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-left: 0.25em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
.autocomplete-with-context {
|
||||
width: calc(100% - 1.5em);
|
||||
}
|
||||
}
|
||||
|
||||
.action-arg-value {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.args-body {
|
||||
max-height: calc(50vh - 4.5em);
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
</h3>
|
||||
|
||||
<ActionsList :value="newValue.actions"
|
||||
:context="context"
|
||||
:read-only="readOnly"
|
||||
@input="onActionsEdit" />
|
||||
</div>
|
||||
|
@ -312,6 +313,16 @@ export default {
|
|||
return JSON.stringify(this.newValue)
|
||||
},
|
||||
|
||||
context() {
|
||||
return this.newValue?.args?.reduce((acc, arg) => {
|
||||
acc[arg] = {
|
||||
source: 'args',
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
},
|
||||
|
||||
modal_() {
|
||||
if (this.readOnly)
|
||||
return null
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="autocomplete">
|
||||
<div class="autocomplete" :class="{ 'with-items': showItems }">
|
||||
<label :text="label">
|
||||
<input
|
||||
type="text"
|
||||
|
@ -8,73 +8,43 @@
|
|||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:value="value"
|
||||
@focus="onFocus"
|
||||
@input="onInput"
|
||||
@focus.stop="onFocus"
|
||||
@input.stop="onInput"
|
||||
@blur="onBlur"
|
||||
@keydown="onInputKeyDown"
|
||||
@keyup="onInputKeyUp"
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="items" v-if="showItems">
|
||||
<div class="items" ref="items" v-if="showItems">
|
||||
<div
|
||||
class="item"
|
||||
:class="{ active: i === curIndex }"
|
||||
:key="getItemText(item)"
|
||||
:data-item="getItemText(item)"
|
||||
v-for="(item, i) in visibleItems"
|
||||
@click="onItemSelect(item)">
|
||||
<span v-html="item.prefix" v-if="item.prefix"></span>
|
||||
@click.stop="onItemSelect(item)">
|
||||
<span class="prefix" v-html="item.prefix" v-if="item.prefix"></span>
|
||||
<span class="matching" v-if="value?.length">{{ getItemText(item).substr(0, value.length) }}</span>
|
||||
<span class="normal">{{ getItemText(item).substr(value?.length || 0) }}</span>
|
||||
<span v-html="item.suffix" v-if="item.suffix"></span>
|
||||
<span class="suffix" v-html="item.suffix" v-if="item.suffix"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Props from "@/mixins/Autocomplete/Props"
|
||||
|
||||
export default {
|
||||
emits: ["input"],
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
label: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
placeholder: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
showResultsWhenBlank: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["blur", "focus", "input", "select"],
|
||||
mixins: [Props],
|
||||
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
curIndex: -1,
|
||||
curIndex: null,
|
||||
selectItemTimer: null,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -84,7 +54,7 @@ export default {
|
|||
},
|
||||
|
||||
visibleItems() {
|
||||
if (!this.value?.length)
|
||||
if (!this.value?.length || this.showAllItems)
|
||||
return this.items
|
||||
|
||||
const val = this.value.toUpperCase()
|
||||
|
@ -103,109 +73,140 @@ export default {
|
|||
|
||||
methods: {
|
||||
getItemText(item) {
|
||||
return item.text || item
|
||||
},
|
||||
|
||||
selectNextItem() {
|
||||
this.curIndex++
|
||||
this.normalizeIndex()
|
||||
},
|
||||
|
||||
selectPrevItem() {
|
||||
this.curIndex--
|
||||
this.normalizeIndex()
|
||||
},
|
||||
|
||||
normalizeIndex() {
|
||||
// Go to the beginning after reaching the end
|
||||
if (this.curIndex >= this.visibleItems.length)
|
||||
this.curIndex = 0
|
||||
|
||||
// Go to the end after moving back from the start
|
||||
if (this.curIndex < 0)
|
||||
this.curIndex = this.visibleItems.length - 1
|
||||
|
||||
// Scroll to the element
|
||||
const curText = this.getItemText(this.visibleItems[this.curIndex])
|
||||
const el = this.$el.querySelector(`[data-item='${curText}']`)
|
||||
if (el)
|
||||
el.scrollIntoView({
|
||||
block: "start",
|
||||
inline: "nearest",
|
||||
behavior: "smooth",
|
||||
})
|
||||
return item?.text || item
|
||||
},
|
||||
|
||||
valueIsInItems() {
|
||||
if (this.showAllItems)
|
||||
return true
|
||||
|
||||
if (!this.value)
|
||||
return false
|
||||
|
||||
return this.itemsText.indexOf(this.value) >= 0
|
||||
},
|
||||
|
||||
onFocus() {
|
||||
onFocus(e) {
|
||||
this.$emit("focus", e)
|
||||
if (this.showResultsWhenBlank || this.value?.length)
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
onInput(e) {
|
||||
let val = e.target.value
|
||||
let val = e.target?.value
|
||||
if (val == null) {
|
||||
e.stopPropagation?.()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.valueIsInItems())
|
||||
this.visible = false
|
||||
|
||||
e.stopPropagation()
|
||||
this.$emit("input", val.text || val)
|
||||
this.curIndex = -1
|
||||
this.curIndex = null
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
onBlur(e) {
|
||||
if (this.inputOnBlur) {
|
||||
this.onInput(e)
|
||||
this.$nextTick(() => {
|
||||
if (this.valueIsInItems())
|
||||
this.visible = false
|
||||
})
|
||||
},
|
||||
|
||||
onItemSelect(item) {
|
||||
this.$emit("input", item.text || item)
|
||||
this.$nextTick(() => {
|
||||
if (this.valueIsInItems()) {
|
||||
this.visible = false
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
e.stopPropagation()
|
||||
this.$nextTick(() => this.$refs.input.focus())
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.selectItemTimer) {
|
||||
this.$nextTick(() => this.$refs.input.focus())
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit("blur", e)
|
||||
if (this.valueIsInItems()) {
|
||||
this.visible = false
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
|
||||
onItemSelect(item) {
|
||||
if (this.selectItemTimer) {
|
||||
clearTimeout(this.selectItemTimer)
|
||||
this.selectItemTimer = null
|
||||
}
|
||||
|
||||
this.selectItemTimer = setTimeout(() => {
|
||||
this.selectItemTimer = null
|
||||
}, 250)
|
||||
|
||||
const text = item.text || item
|
||||
this.$emit("select", text)
|
||||
if (this.inputOnSelect)
|
||||
this.$emit("input", text)
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.valueIsInItems()) {
|
||||
if (this.inputOnSelect) {
|
||||
this.visible = false
|
||||
} else {
|
||||
this.visible = true
|
||||
this.curIndex = this.visibleItems.indexOf(item)
|
||||
if (this.curIndex < 0)
|
||||
this.curIndex = null
|
||||
|
||||
this.$refs.input.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onInputKeyUp(e) {
|
||||
if (["ArrowUp", "ArrowDown", "Tab", "Enter", "Escape"].indexOf(e.key) >= 0)
|
||||
if (
|
||||
["ArrowUp", "ArrowDown", "Escape"].indexOf(e.key) >= 0 ||
|
||||
(e.key === "Tab" && this.selectOnTab) ||
|
||||
(e.key === "Enter" && this.curIndex != null)
|
||||
) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
if (e.key === "Enter" && this.valueIsInItems()) {
|
||||
if (e.key === "Enter" && this.valueIsInItems() && this.curIndex != null) {
|
||||
this.$refs.input.blur()
|
||||
this.visible = false
|
||||
}
|
||||
},
|
||||
|
||||
onInputKeyDown(e) {
|
||||
if (!this.showItems)
|
||||
return
|
||||
|
||||
e.stopPropagation()
|
||||
|
||||
if (
|
||||
e.key === 'ArrowDown' ||
|
||||
(e.key === 'Tab' && !e.shiftKey) ||
|
||||
(e.key === 'Tab' && !e.shiftKey && this.selectOnTab) ||
|
||||
(e.key === 'j' && e.ctrlKey)
|
||||
) {
|
||||
this.selectNextItem()
|
||||
this.curIndex = this.curIndex == null ? 0 : this.curIndex + 1
|
||||
e.preventDefault()
|
||||
} else if (
|
||||
e.key === 'ArrowUp' ||
|
||||
(e.key === 'Tab' && e.shiftKey) ||
|
||||
(e.key === 'Tab' && e.shiftKey && this.selectOnTab) ||
|
||||
(e.key === 'k' && e.ctrlKey)
|
||||
) {
|
||||
this.selectPrevItem()
|
||||
this.curIndex = this.curIndex == null ? this.visibleItems.length - 1 : this.curIndex - 1
|
||||
e.preventDefault()
|
||||
} else if (e.key === 'Enter') {
|
||||
if (this.curIndex > -1 && this.visible) {
|
||||
if (this.curIndex != null && this.curIndex >= 0 && this.visible) {
|
||||
e.preventDefault()
|
||||
this.onItemSelect(this.visibleItems[this.curIndex])
|
||||
this.$refs.input.focus()
|
||||
this.$nextTick(() => this.$refs.input.focus())
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
this.visible = false
|
||||
|
@ -220,6 +221,33 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
curIndex() {
|
||||
// Do nothing if the index is not set
|
||||
if (this.curIndex == null)
|
||||
return
|
||||
|
||||
// Go to the beginning after reaching the end
|
||||
if (this.curIndex >= this.visibleItems.length)
|
||||
this.curIndex = 0
|
||||
|
||||
// Go to the end after moving back from the start
|
||||
if (this.curIndex < 0)
|
||||
this.curIndex = this.visibleItems.length - 1
|
||||
|
||||
// Scroll to the element
|
||||
const curText = this.getItemText(this.visibleItems[this.curIndex])
|
||||
const el = this.$el.querySelector(`[data-item='${curText}']`)
|
||||
if (el) {
|
||||
el.scrollIntoView({
|
||||
block: "start",
|
||||
inline: "nearest",
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.addEventListener("click", this.onDocumentClick)
|
||||
if (this.autofocus)
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: [String, Number, Object, Array, Boolean],
|
||||
default: "",
|
||||
},
|
||||
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
label: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
placeholder: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
inputOnBlur: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
inputOnSelect: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
selectOnTab: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
showAllItems: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
showResultsWhenBlank: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
Loading…
Reference in a new issue