3
0

Add cancel buttons to all prompts

This commit is contained in:
Jože Fortun 2024-03-04 17:18:25 +01:00
parent 27e142c1a6
commit 3708ca9f32
8 changed files with 106 additions and 118 deletions

View File

@ -6,6 +6,7 @@
:hide-footer="!current" :hide-footer="!current"
:title="current ? current.title : 'Workflow prompts'" :title="current ? current.title : 'Workflow prompts'"
:busy="isLoading" :busy="isLoading"
footer-class="d-flex"
no-fade no-fade
@hide="deactivate()" @hide="deactivate()"
> >
@ -20,29 +21,22 @@
v-else v-else
> >
<div <div
class="d-flex flex-grow-1 align-items-baseline" class="d-flex flex-grow-1 align-items-baseline mb-2"
v-for="({ key, title, age, prompt }) in list" v-for="({ key, title, age, prompt }) in list"
:key="key" :key="key"
> >
<span <a
class="mr-auto" class="p-0 mr-auto"
@click="activate(prompt)" @click="activate(prompt)"
> >
{{ title }} {{ title }} -
<time <time
class="muted small" class="muted small"
:datetime="prompt.createdAt" :datetime="prompt.createdAt"
> >
{{ age }} {{ age }}
</time> </time>
</span> </a>
<b-button
variant="link"
size="sm"
@click="remove(prompt)"
:disabled="isLoading"
v-if="false"
>Remove</b-button>
</div> </div>
</div> </div>
<template <template
@ -51,6 +45,7 @@
> >
<b-button <b-button
variant="link" variant="link"
class="mr-auto"
@click="activate(true)" @click="activate(true)"
> >
&laquo; Back to list &laquo; Back to list

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div v-if="!hideToasts">
<b-toast <b-toast
v-for="({ prompt, component, passive }) in toasts" v-for="({ prompt, component, passive }) in toasts"
:id="'wfPromptToast-'+prompt.stateID" :id="'wfPromptToast-'+prompt.stateID"
@ -9,21 +9,12 @@
solid solid
:no-auto-hide="!passive" :no-auto-hide="!passive"
:auto-hide-delay="pVal(prompt, 'timeout', defaultTimeout) * 1000" :auto-hide-delay="pVal(prompt, 'timeout', defaultTimeout) * 1000"
:no-close-button="!passive" @hide="onToastHide({ prompt, passive })"
> >
<template #toast-title> <template #toast-title>
<div class="d-flex flex-grow-1 align-items-baseline"> <strong>{{ pVal(prompt, 'title', 'Workflow prompt') }}</strong>
<strong class="mr-auto">{{ pVal(prompt, 'title', 'Workflow prompt') }}</strong>
<b-button
variant="link"
size="sm"
v-if="!passive && active.length > 1"
@click="activate(true)"
>
{{ active.length }} waiting
</b-button>
</div>
</template> </template>
<component <component
v-if="component" v-if="component"
:is="component" :is="component"
@ -52,12 +43,6 @@ export default {
return { return {
passive: new Set(), passive: new Set(),
/**
* Set initial value to NULL
*
* First interval will detect that null is not true|false
* and set it accordingly to the current state
*/
hasFocus: null, hasFocus: null,
hasFocusObserver: 0, hasFocusObserver: 0,
} }
@ -163,6 +148,7 @@ export default {
methods: { methods: {
...mapActions({ ...mapActions({
resume: 'wfPrompts/resume', resume: 'wfPrompts/resume',
cancel: 'wfPrompts/cancel',
activate: 'wfPrompts/activate', activate: 'wfPrompts/activate',
}), }),
@ -175,6 +161,12 @@ export default {
this.resume(values) this.resume(values)
}, },
onToastHide ({ prompt, passive}) {
if (passive) return
this.cancel(prompt)
},
pVal (prompt, k, def = undefined) { pVal (prompt, k, def = undefined) {
return pVal(prompt.payload, k, def) return pVal(prompt.payload, k, def)
}, },
@ -200,7 +192,7 @@ export default {
}, },
setDefaultValues () { setDefaultValues () {
this.passive.clear() this.toasts = []
this.hasFocus = null this.hasFocus = null
this.hasFocusObserver = 0 this.hasFocusObserver = 0
}, },
@ -208,16 +200,58 @@ export default {
} }
</script> </script>
<style scoped lang="scss"> <style lang="scss">
.slide-enter-active { .toast-header {
transition: all .3s ease; align-items: start;
padding: 0.375rem 0.75rem;
strong {
word-break: break-word;
}
.close {
margin-bottom: 0 !important;
}
} }
.slide-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0); // .b-toaster-leave-active {
} // width: 100%;
.slide-enter, .slide-leave-to // }
/* .slide-leave-active below version 2.1.8 */ {
transform: translateX(10px); .b-toaster {
opacity: 0; &.b-toaster-top-right,
&.b-toaster-top-left,
&.b-toaster-bottom-right,
&.b-toaster-bottom-left {
.b-toast {
&.b-toaster-enter-active,
&.b-toaster-leave-active,
&.b-toaster-move {
transition: transform 0.3s ease-in-out; /* Adjust the timing function for smoother transitions */
opacity: 1; /* Ensure opacity is set to avoid flickering during transition */
}
&.b-toaster-enter {
transform: translate(0, -100%); /* Start off-screen when entering */
opacity: 0; /* Start with 0 opacity */
}
&.b-toaster-enter-to,
&.b-toaster-enter-active {
transform: translate(0, 0); /* Move to the visible position */
opacity: 1; /* Fade in during the transition */
}
&.b-toaster-leave-active {
position: absolute;
transform: translate(0, -100%); /* Move off-screen when leaving */
opacity: 0; /* Fade out during the transition */
}
&.b-toaster-leave-to {
opacity: 0; /* Ensure 0 opacity at the end of the transition */
}
}
}
} }
</style> </style>

View File

@ -1,9 +1,8 @@
<template> <template>
<div> <div>
<p v-html="message"></p> <p v-if="!!message" v-html="message" />
<div
class="text-center m-2" <div class="d-flex justify-content-end gap-1">
>
<b-button <b-button
@click="$emit('submit', { confirmed: pRaw('buttonValue', true, 'Boolean') })" @click="$emit('submit', { confirmed: pRaw('buttonValue', true, 'Boolean') })"
:variant="pVal('buttonVariant', 'primary')" :variant="pVal('buttonVariant', 'primary')"

View File

@ -1,9 +1,8 @@
<template> <template>
<div> <div>
<p v-html="message"></p> <p v-if="!!message" v-html="message" />
<div
class="text-center m-2" <div class="d-flex align-items-between justify-content-center gap-2">
>
<b-button <b-button
@click="$emit('submit', { value: pRaw('confirmButtonValue', true, 'Boolean') })" @click="$emit('submit', { value: pRaw('confirmButtonValue', true, 'Boolean') })"
:variant="pVal('confirmButtonVariant', 'primary')" :variant="pVal('confirmButtonVariant', 'primary')"
@ -11,10 +10,11 @@
> >
{{ pVal('confirmButtonLabel', 'Yes') }} {{ pVal('confirmButtonLabel', 'Yes') }}
</b-button> </b-button>
<b-button <b-button
@click="$emit('submit', { value: pRaw('rejectButtonValue', false, 'Boolean') })" @click="$emit('submit', { value: pRaw('rejectButtonValue', false, 'Boolean') })"
:disabled="loading" :disabled="loading"
:variant="pVal('rejectButtonVariant', 'primary')" :variant="pVal('rejectButtonVariant', 'light')"
> >
{{ pVal('rejectButtonLabel', 'No') }} {{ pVal('rejectButtonLabel', 'No') }}
</b-button> </b-button>

View File

@ -1,22 +1,19 @@
<template> <template>
<div> <div>
<p> <p v-if="!!message" v-html="message" />
{{ message }}
</p> <div class="d-flex flex-column gap-1">
<div
class="text-center m-2"
>
<c-input-select <c-input-select
v-model="value" v-model="value"
:options="options" :options="options"
:get-option-key="getOptionKey" :get-option-key="getOptionKey"
:loading="processing" :loading="processing"
append-to-body append-to-body
:calculate-position="calculateDropdownPosition"
option-value="recordID" option-value="recordID"
option-text="label" option-text="label"
placeholder="Select record" placeholder="Select record"
:filterable="false" :filterable="false"
:reduce="r => r.recordID"
class="w-100 mb-3" class="w-100 mb-3"
@search="search" @search="search"
> >
@ -31,8 +28,10 @@
</c-input-select> </c-input-select>
<b-button <b-button
@click="$emit('submit', { value: encodeValue() })"
:disabled="loading" :disabled="loading"
variant="primary"
class="ml-auto"
@click="$emit('submit', { value: encodeValue() })"
> >
{{ pVal('buttonLabel', 'Submit') }} {{ pVal('buttonLabel', 'Submit') }}
</b-button> </b-button>
@ -45,7 +44,6 @@ import CPagination from '../common/CPagination.vue'
import { pVal } from '../utils.ts' import { pVal } from '../utils.ts'
import CInputSelect from '../../input/CInputSelect.vue' import CInputSelect from '../../input/CInputSelect.vue'
import { compose, NoID } from '@cortezaproject/corteza-js' import { compose, NoID } from '@cortezaproject/corteza-js'
import { createPopper } from '@popperjs/core'
import { debounce } from 'lodash' import { debounce } from 'lodash'
export default { export default {
@ -79,12 +77,8 @@ export default {
}, },
computed: { computed: {
// moduleFields returns the available field names labelField () {
moduleFields () { return this.module.fields.find(f => f.name === this.pVal('labelField'))
if (!this.module) {
return []
}
return this.module.fields.map(({ name }) => name)
}, },
showPagination () { showPagination () {
@ -165,7 +159,9 @@ export default {
return { '@type': 'Any', '@value': null } return { '@type': 'Any', '@value': null }
} }
return { '@type': 'ComposeRecord', '@value': this.value.record } const record = this.options.find(({ recordID }) => recordID === this.value)
return { '@type': 'ComposeRecord', '@value': record }
}, },
loadLatest () { loadLatest () {
@ -206,51 +202,6 @@ export default {
} }
}, 300), }, 300),
calculateDropdownPosition (dropdownList, component, { width }) {
/**
* We need to explicitly define the dropdown width since
* it is usually inherited from the parent with CSS.
*/
dropdownList.style.width = width
dropdownList.style['z-index'] = 10000
/**
* Here we position the dropdownList relative to the $refs.toggle Element.
*
* The 'offset' modifier aligns the dropdown so that the $refs.toggle and
* the dropdownList overlap by 1 pixel.
*
* The 'toggleClass' modifier adds a 'drop-up' class to the Vue Select
* wrapper so that we can set some styles for when the dropdown is placed
* above.
*/
const popper = createPopper(component.$refs.toggle, dropdownList, {
placement: 'bottom',
modifiers: [
{
name: 'offset',
options: {
offset: [0, -1],
},
},
{
name: 'toggleClass',
enabled: true,
phase: 'write',
fn ({ state }) {
component.$el.classList.toggle('drop-up', state.placement === 'top')
},
}],
})
/**
* To prevent memory leaks Popper needs to be destroyed.
* If you return function, it will be called just before dropdown is removed from DOM.
*/
return () => popper.destroy()
},
fetchPrefiltered (q) { fetchPrefiltered (q) {
this.processing = true this.processing = true
@ -273,11 +224,15 @@ export default {
this.options = set.map(r => { this.options = set.map(r => {
const record = new compose.Record(this.module, r) const record = new compose.Record(this.module, r)
const label = record.values[this.pVal('labelField')] || record.recordID
let label
if (this.labelField) {
label = this.labelField.isMulti ? record.values[this.pVal('labelField')].join(', ') : record.values[this.pVal('labelField')]
}
return { return {
recordID: record.recordID, recordID: record.recordID,
label, label: label || record.recordID,
record, record,
} }
}) })

View File

@ -1,6 +1,7 @@
<template> <template>
<div> <div>
<p v-html="message"></p> <p v-if="!!message" v-html="message" />
<b-form-group <b-form-group
:label="label" :label="label"
label-class="text-primary" label-class="text-primary"
@ -13,6 +14,7 @@
</b-form-group> </b-form-group>
<b-button <b-button
:disabled="loading" :disabled="loading"
variant="primary"
@click="$emit('submit', { value: { '@value': value, '@type': 'String' }})" @click="$emit('submit', { value: { '@value': value, '@type': 'String' }})"
> >
{{ pVal('buttonLabel', 'Submit') }} {{ pVal('buttonLabel', 'Submit') }}

View File

@ -1,6 +1,7 @@
<template> <template>
<div> <div>
<p v-html="message"></p> <p v-if="!!message" v-html="message" />
<b-form-group <b-form-group
:label="pVal('label', 'Input')" :label="pVal('label', 'Input')"
label-class="text-primary" label-class="text-primary"
@ -28,8 +29,10 @@
:options="options" :options="options"
/> />
</b-form-group> </b-form-group>
<b-button <b-button
:disabled="loading" :disabled="loading"
variant="primary"
@click="$emit('submit', { value: encodeValue() })" @click="$emit('submit', { value: encodeValue() })"
> >
{{ pVal('buttonLabel', 'Submit') }} {{ pVal('buttonLabel', 'Submit') }}

View File

@ -15,7 +15,7 @@ export default {
computed: { computed: {
message () { message () {
return this.pVal('message', '<i>Default prompt message.</i>') return this.pVal('message', '')
}, },
}, },