mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
rework expression evaluations with dyn getter and more generic evaluation function
This commit is contained in:
parent
fd19dcebf6
commit
82aab9a984
3 changed files with 77 additions and 100 deletions
|
@ -12,18 +12,21 @@
|
||||||
<slot name="tab-before" />
|
<slot name="tab-before" />
|
||||||
|
|
||||||
<template v-for="section in panel.sections">
|
<template v-for="section in panel.sections">
|
||||||
<div v-if="isVisible(section.visible, section)" :key="section.id" class="mb-5">
|
<component
|
||||||
|
v-if="section.visible" :is="section.name ? 'section' : 'div'"
|
||||||
|
:key="section.id" class="mb-5"
|
||||||
|
>
|
||||||
<b-card-title v-if="section.name" title-tag="h3">
|
<b-card-title v-if="section.name" title-tag="h3">
|
||||||
{{ section.name }} <small v-if="section.help">{{ section.help }}</small>
|
{{ section.name }} <small v-if="section.help">{{ section.help }}</small>
|
||||||
</b-card-title>
|
</b-card-title>
|
||||||
|
|
||||||
<template v-for="(field, fname) in section.fields">
|
<template v-for="(field, fname) in section.fields">
|
||||||
<form-field
|
<form-field
|
||||||
v-if="isVisible(field.visible, field)" :key="fname"
|
v-if="field.visible" :key="fname"
|
||||||
v-model="forms[panel.id][fname]" v-bind="field" :validation="validation[fname]"
|
v-model="forms[panel.id][fname]" v-bind="field" :validation="validation[fname]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<slot name="tab-after" />
|
<slot name="tab-after" />
|
||||||
|
@ -31,7 +34,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { configPanelsFieldIsVisible } from '@/helpers/yunohostArguments'
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -55,9 +57,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
isVisible (expression, field) {
|
|
||||||
return configPanelsFieldIsVisible(expression, field, this.forms)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -54,6 +54,49 @@ export function adressToFormValue (address) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate config panel string expression that can contain regular expressions.
|
||||||
|
* Expression are evaluated with the config panel form as context.
|
||||||
|
*
|
||||||
|
* @param {String} expression - A String to evaluate.
|
||||||
|
* @param {Object} forms - A nested form used in config panels.
|
||||||
|
* @return {Boolean} - expression evaluation result.
|
||||||
|
*/
|
||||||
|
export function evaluateExpression (expression, forms) {
|
||||||
|
if (!expression) return true
|
||||||
|
if (expression === '"false"') return false
|
||||||
|
|
||||||
|
const context = Object.values(forms).reduce((ctx, args) => {
|
||||||
|
Object.entries(args).forEach(([name, value]) => {
|
||||||
|
ctx[name] = isObjectLiteral(value) && 'file' in value ? value.content : value
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
// Allow to use match(var,regexp) function
|
||||||
|
const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g')
|
||||||
|
for (const matched of expression.matchAll(matchRe)) {
|
||||||
|
const [fullMatch, varMatch, regExpMatch] = matched
|
||||||
|
const varName = varMatch + '__re' + matched.index
|
||||||
|
context[varName] = new RegExp(regExpMatch, 'm').test(context[varMatch])
|
||||||
|
expression = expression.replace(fullMatch, varName)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return !!evaluate(context, expression)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a property to an Object that will dynamically returns a expression evaluation result.
|
||||||
|
function addEvaluationGetter (prop, obj, expr, ctx) {
|
||||||
|
Object.defineProperty(obj, prop, {
|
||||||
|
get: () => evaluateExpression(expr, ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format app install, actions and config panel argument into a data structure that
|
* Format app install, actions and config panel argument into a data structure that
|
||||||
* will be automaticly transformed into a component on screen.
|
* will be automaticly transformed into a component on screen.
|
||||||
|
@ -243,12 +286,6 @@ export function formatYunoHostArgument (arg) {
|
||||||
field.link = { href: arg.helpLink.href, text: i18n.t(arg.helpLink.text) }
|
field.link = { href: arg.helpLink.href, text: i18n.t(arg.helpLink.text) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg.visible) {
|
|
||||||
field.visible = arg.visible
|
|
||||||
// Temporary value to wait visible expression to be evaluated
|
|
||||||
field.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
field,
|
field,
|
||||||
|
@ -264,10 +301,10 @@ export function formatYunoHostArgument (arg) {
|
||||||
* as v-model values, fields that can be passed to a FormField component and validations.
|
* as v-model values, fields that can be passed to a FormField component and validations.
|
||||||
*
|
*
|
||||||
* @param {Array} args - a yunohost arg array written by a packager.
|
* @param {Array} args - a yunohost arg array written by a packager.
|
||||||
* @param {String} name - (temp) an app name to build a label field in case of manifest install args
|
* @param {Object|null} forms - nested form used as the expression evualuations context.
|
||||||
* @return {Object} an object containing all parsed values to be used in vue views.
|
* @return {Object} an object containing all parsed values to be used in vue views.
|
||||||
*/
|
*/
|
||||||
export function formatYunoHostArguments (args) {
|
export function formatYunoHostArguments (args, forms) {
|
||||||
const form = {}
|
const form = {}
|
||||||
const fields = {}
|
const fields = {}
|
||||||
const validations = {}
|
const validations = {}
|
||||||
|
@ -279,6 +316,12 @@ export function formatYunoHostArguments (args) {
|
||||||
form[arg.name] = value
|
form[arg.name] = value
|
||||||
if (validation) validations[arg.name] = validation
|
if (validation) validations[arg.name] = validation
|
||||||
errors[arg.name] = error
|
errors[arg.name] = error
|
||||||
|
|
||||||
|
if ('visible' in arg) {
|
||||||
|
addEvaluationGetter('visible', field, arg.visible, forms)
|
||||||
|
} else {
|
||||||
|
field.visible = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { form, fields, validations, errors }
|
return { form, fields, validations, errors }
|
||||||
|
@ -302,11 +345,20 @@ export function formatYunoHostConfigPanels (data) {
|
||||||
if (name) panel.name = formatI18nField(name)
|
if (name) panel.name = formatI18nField(name)
|
||||||
if (help) panel.help = formatI18nField(help)
|
if (help) panel.help = formatI18nField(help)
|
||||||
|
|
||||||
for (const { id: sectionId, name, help, visible, options } of sections) {
|
for (const _section of sections) {
|
||||||
const section = { id: sectionId, visible, isVisible: false }
|
const section = { id: _section.id, visible: true }
|
||||||
if (help) section.help = formatI18nField(help)
|
if (_section.help) section.help = formatI18nField(_section.help)
|
||||||
if (name) section.name = formatI18nField(name)
|
if (_section.name) section.name = formatI18nField(_section.name)
|
||||||
const { form, fields, validations, errors } = formatYunoHostArguments(options)
|
if (_section.visible) {
|
||||||
|
addEvaluationGetter('visible', section, _section.visible, result.forms)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
form,
|
||||||
|
fields,
|
||||||
|
validations,
|
||||||
|
errors
|
||||||
|
} = formatYunoHostArguments(_section.options, result.forms)
|
||||||
// Merge all sections forms to the panel to get a unique form
|
// Merge all sections forms to the panel to get a unique form
|
||||||
Object.assign(result.forms[panelId], form)
|
Object.assign(result.forms[panelId], form)
|
||||||
Object.assign(result.validations[panelId], validations)
|
Object.assign(result.validations[panelId], validations)
|
||||||
|
@ -322,47 +374,6 @@ export function formatYunoHostConfigPanels (data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function configPanelsFieldIsVisible (expression, field, forms) {
|
|
||||||
if (!expression || !field) return true
|
|
||||||
const context = {}
|
|
||||||
|
|
||||||
const promises = []
|
|
||||||
for (const args of Object.values(forms)) {
|
|
||||||
for (const shortname in args) {
|
|
||||||
if (args[shortname] instanceof File) {
|
|
||||||
if (expression.includes(shortname)) {
|
|
||||||
promises.push(pFileReader(args[shortname], context, shortname, false))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context[shortname] = args[shortname]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow to use match(var,regexp) function
|
|
||||||
const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g')
|
|
||||||
let i = 0
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
for (const matched of expression.matchAll(matchRe)) {
|
|
||||||
i++
|
|
||||||
const varName = matched[1] + '__re' + i.toString()
|
|
||||||
context[varName] = new RegExp(matched[2], 'm').test(context[matched[1]])
|
|
||||||
expression = expression.replace(matched[0], varName)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
field.isVisible = evaluate(context, expression)
|
|
||||||
} catch {
|
|
||||||
field.isVisible = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return field.isVisible
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format helper for a form value.
|
* Format helper for a form value.
|
||||||
* Convert Boolean to (1|0) and concatenate adresses.
|
* Convert Boolean to (1|0) and concatenate adresses.
|
||||||
|
|
|
@ -25,8 +25,7 @@
|
||||||
>
|
>
|
||||||
<template v-for="(field, fname) in fields">
|
<template v-for="(field, fname) in fields">
|
||||||
<form-field
|
<form-field
|
||||||
v-if="isVisible(field.visible, field)"
|
v-if="field.visible" :key="fname" label-cols="0"
|
||||||
:key="fname" label-cols="0"
|
|
||||||
v-bind="field" v-model="form[fname]" :validation="$v.form[fname]"
|
v-bind="field" v-model="form[fname]" :validation="$v.form[fname]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -47,10 +46,13 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { validationMixin } from 'vuelidate'
|
import { validationMixin } from 'vuelidate'
|
||||||
import evaluate from 'simple-evaluate'
|
|
||||||
|
|
||||||
import api, { objectToParams } from '@/api'
|
import api, { objectToParams } from '@/api'
|
||||||
import { formatYunoHostArguments, formatI18nField, formatFormData, pFileReader } from '@/helpers/yunohostArguments'
|
import {
|
||||||
|
formatYunoHostArguments,
|
||||||
|
formatI18nField,
|
||||||
|
formatFormData
|
||||||
|
} from '@/helpers/yunohostArguments'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppInstall',
|
name: 'AppInstall',
|
||||||
|
@ -112,41 +114,6 @@ export default {
|
||||||
this.errors = errors
|
this.errors = errors
|
||||||
},
|
},
|
||||||
|
|
||||||
isVisible (expression, field) {
|
|
||||||
if (!expression || !field) return true
|
|
||||||
const context = {}
|
|
||||||
|
|
||||||
const promises = []
|
|
||||||
for (const shortname in this.form) {
|
|
||||||
if (this.form[shortname] instanceof File) {
|
|
||||||
if (expression.includes(shortname)) {
|
|
||||||
promises.push(pFileReader(this.form[shortname], context, shortname, false))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context[shortname] = this.form[shortname]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Allow to use match(var,regexp) function
|
|
||||||
const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g')
|
|
||||||
let i = 0
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
for (const matched of expression.matchAll(matchRe)) {
|
|
||||||
i++
|
|
||||||
const varName = matched[1] + '__re' + i.toString()
|
|
||||||
context[varName] = new RegExp(matched[2], 'm').test(context[matched[1]])
|
|
||||||
expression = expression.replace(matched[0], varName)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
field.isVisible = evaluate(context, expression)
|
|
||||||
} catch (error) {
|
|
||||||
field.isVisible = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// This value should be updated magically when vuejs will detect isVisible changed
|
|
||||||
return field.isVisible
|
|
||||||
},
|
|
||||||
|
|
||||||
async performInstall () {
|
async performInstall () {
|
||||||
if ('path' in this.form && this.form.path === '/') {
|
if ('path' in this.form && this.form.path === '/') {
|
||||||
const confirmed = await this.$askConfirmation(
|
const confirmed = await this.$askConfirmation(
|
||||||
|
|
Loading…
Add table
Reference in a new issue