Signed-off-by: Julius Härtl <jus@bitgrid.net>tags/v18.0.0beta1
@@ -6,9 +6,9 @@ | |||
<Multiselect :disabled="!currentOption" v-model="currentOperator" :options="operators" | |||
label="name" track-by="operator" :allow-empty="false" | |||
:placeholder="t('workflowengine', 'Select a comparator')" @input="updateCheck" /> | |||
<component :is="currentOption.component" v-if="currentOperator && currentComponent" v-model="check.value" :disabled="!currentOption" /> | |||
<input v-else v-model="check.value" type="text" | |||
@input="updateCheck" :disabled="!currentOption"> | |||
<component :is="currentOption.component" v-if="currentOperator && currentComponent" v-model="check.value" :disabled="!currentOption" :check="check" @valid="valid=true && validate()" @invalid="valid=false && validate()" /> | |||
<input v-else v-model="check.value" type="text" :class="{ invalid: !valid }" | |||
@input="updateCheck" :disabled="!currentOption" :placeholder="valuePlaceholder"> | |||
<Actions> | |||
<ActionButton v-if="deleteVisible || !currentOption" icon="icon-delete" @click="$emit('remove')" /> | |||
</Actions> | |||
@@ -34,6 +34,10 @@ export default { | |||
check: { | |||
type: Object, | |||
required: true | |||
}, | |||
rule: { | |||
type: Object, | |||
required: true | |||
} | |||
}, | |||
data() { | |||
@@ -41,7 +45,8 @@ export default { | |||
deleteVisible: false, | |||
currentOption: null, | |||
currentOperator: null, | |||
options: [] | |||
options: [], | |||
valid: true, | |||
} | |||
}, | |||
computed: { | |||
@@ -56,6 +61,11 @@ export default { | |||
if (!this.currentOption) { return [] } | |||
const currentComponent = this.Checks[this.currentOption.class].component | |||
return currentComponent | |||
}, | |||
valuePlaceholder() { | |||
if (this.currentOption && this.currentOption.placeholder) { | |||
return this.currentOption.placeholder(this.check) | |||
} | |||
} | |||
}, | |||
mounted() { | |||
@@ -63,6 +73,11 @@ export default { | |||
this.currentOption = this.Checks[this.check.class] | |||
this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator) | |||
}, | |||
watch: { | |||
'check.operator': function () { | |||
this.validate() | |||
} | |||
}, | |||
methods: { | |||
showDelete() { | |||
this.deleteVisible = true | |||
@@ -70,12 +85,27 @@ export default { | |||
hideDelete() { | |||
this.deleteVisible = false | |||
}, | |||
validate() { | |||
if (this.currentOption && this.currentOption.validate) { | |||
if(this.currentOption.validate(this.check)) { | |||
this.valid = true | |||
} else { | |||
this.valid = false | |||
} | |||
} | |||
this.$store.dispatch('setValid', { rule: this.rule, valid: this.rule.valid && this.valid }) | |||
return this.valid | |||
}, | |||
updateCheck() { | |||
if (this.check.class !== this.currentOption.class) { | |||
this.currentOperator = this.operators[0] | |||
} | |||
this.check.class = this.currentOption.class | |||
this.check.operator = this.currentOperator.operator | |||
if (!this.validate()) { | |||
return | |||
} | |||
this.$emit('update', this.check) | |||
} | |||
} | |||
@@ -107,4 +137,7 @@ export default { | |||
margin-top: -5px; | |||
margin-bottom: -5px; | |||
} | |||
.invalid { | |||
border: 1px solid var(--color-error) !important; | |||
} | |||
</style> |
@@ -8,7 +8,7 @@ | |||
</p> | |||
<p v-for="check in rule.checks"> | |||
<span>{{ t('workflowengine', 'and') }}</span> | |||
<Check :check="check" @update="updateRule" @remove="removeCheck(check)" /> | |||
<Check :check="check" :rule="rule" @update="updateRule" @remove="removeCheck(check)" /> | |||
</p> | |||
<p> | |||
<span /> | |||
@@ -74,7 +74,7 @@ export default { | |||
return this.$store.getters.getOperationForRule(this.rule) | |||
}, | |||
ruleStatus() { | |||
if (this.error) { | |||
if (this.error || !this.rule.valid) { | |||
return { | |||
title: t('workflowengine', 'The configuration is invalid'), | |||
class: 'icon-close-white invalid', |
@@ -1,47 +1,104 @@ | |||
<template> | |||
<input type="text" v-model="test"> | |||
<div> | |||
<multiselect | |||
:value="currentValue" | |||
placeholder="Select a file type" | |||
label="label" | |||
track-by="pattern" | |||
:options="options" :multiple="false" :tagging="false" @input="setValue"> | |||
<template slot="singleLabel" slot-scope="props"> | |||
<span class="option__icon" :class="props.option.icon"></span> | |||
<span class="option__title option__title_single">{{ props.option.label }}</span> | |||
</template> | |||
<template slot="option" slot-scope="props"> | |||
<span class="option__icon" :class="props.option.icon"></span> | |||
<span class="option__title">{{ props.option.label }}</span> | |||
</template> | |||
</multiselect> | |||
<input type="text" :value="currentValue.pattern" @input="updateCustom"/> | |||
</div> | |||
</template> | |||
<script> | |||
import { Multiselect } from 'nextcloud-vue' | |||
export default { | |||
name: 'SizeValue', | |||
name: 'FileMimeType', | |||
components: { | |||
Multiselect | |||
}, | |||
data() { | |||
return { | |||
test: 'test', | |||
value: '', | |||
predefinedTypes: [ | |||
{ | |||
icon: 'icon-picture', | |||
label: 'Images', | |||
label: t('workflowengine', 'Images'), | |||
pattern: '/image\\/.*/' | |||
}, | |||
{ | |||
icon: 'icon-category-office', | |||
label: 'Office documents', | |||
label: t('workflowengine', 'Office documents'), | |||
pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/' | |||
}, | |||
{ | |||
icon: 'icon-filetype-file', | |||
label: 'PDF documents', | |||
label: t('workflowengine', 'PDF documents'), | |||
pattern: 'application/pdf' | |||
} | |||
] | |||
} | |||
}, | |||
computed: { | |||
options() { | |||
return [...this.predefinedTypes, this.customValue] | |||
}, | |||
customValue() { | |||
const matchingPredefined = this.predefinedTypes.find((type) => this.value.pattern === type.pattern) | |||
return { | |||
icon: 'icon-settings-dark', | |||
label: t('workflowengine', 'Custom pattern'), | |||
pattern: '', | |||
} | |||
}, | |||
currentValue() { | |||
const matchingPredefined = this.predefinedTypes.find((type) => this.value === type.pattern) | |||
if (matchingPredefined) { | |||
return matchingPredefined | |||
} | |||
return { | |||
icon: 'icon-settings-dark', | |||
label: t('workflowengine', 'Custom pattern'), | |||
pattern: this.value, | |||
} | |||
} | |||
}, | |||
methods: { | |||
validateRegex(string) { | |||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/ | |||
var result = regexRegex.exec(string) | |||
return result !== null | |||
}, | |||
setValue (value) { | |||
// TODO: check if value requires a regex and set the check operator according to that | |||
if (value !== null) { | |||
this.value = value.pattern | |||
} | |||
}, | |||
updateCustom (event) { | |||
console.log(event) | |||
this.value = event.target.value | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.multiselect::v-deep .multiselect__single { | |||
display: flex; | |||
} | |||
input, .multiselect { | |||
width: 100%; | |||
} | |||
</style> |
@@ -40,6 +40,36 @@ const FileChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { | |||
// new way of registering checks | |||
const validateRegex = function(string) { | |||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/ | |||
var result = regexRegex.exec(string) | |||
return result !== null | |||
} | |||
FileChecks.push({ | |||
class: 'OCA\\WorkflowEngine\\Check\\FileName', | |||
name: t('workflowengine', 'File name'), | |||
operators: [ | |||
{ operator: 'is', name: t('workflowengine', 'is') }, | |||
{ operator: '!is', name: t('workflowengine', 'is not') }, | |||
{ operator: 'matches', name: t('workflowengine', 'matches') }, | |||
{ operator: '!matches', name: t('workflowengine', 'does not match') } | |||
], | |||
placeholder: (check) => { | |||
if (check.operator === 'matches' || check.operator === '!matches') { | |||
return '/^dummy-.+$/i' | |||
} | |||
return 'filename.txt' | |||
}, | |||
validate: (check) => { | |||
if (check.operator === 'matches' || check.operator === '!matches') { | |||
return validateRegex(check.value) | |||
} | |||
return true | |||
} | |||
}) | |||
FileChecks.push({ | |||
class: 'OCA\\WorkflowEngine\\Check\\FileMimeType', | |||
name: t('workflowengine', 'File MIME type'), |
@@ -58,7 +58,7 @@ const store = new Vuex.Store({ | |||
}, | |||
mutations: { | |||
addRule(state, rule) { | |||
state.rules.push(rule) | |||
state.rules.push({ ...rule, valid: true }) | |||
}, | |||
updateRule(state, rule) { | |||
const index = state.rules.findIndex((item) => rule.id === item.id) | |||
@@ -129,6 +129,10 @@ const store = new Vuex.Store({ | |||
await confirmPassword() | |||
await axios.delete(getApiUrl(`/${rule.id}`)) | |||
context.commit('removeRule', rule) | |||
}, | |||
setValid (context, { rule, valid }) { | |||
rule.valid = valid | |||
context.commit('updateRule', rule) | |||
} | |||
}, | |||
getters: { |
@@ -16,6 +16,8 @@ import {Operators} from './services/Operation'; | |||
* The component should handle the v-model directive properly, | |||
* so it needs a value property to receive data and emit an input | |||
* event once the data has changed | |||
* @property {callable} placeholder - Return a placeholder of no custom component is used | |||
* @property {callable} validate - validate a check if no custom component is used | |||
**/ | |||
/** |