Signed-off-by: Julius Härtl <jus@bitgrid.net>tags/v18.0.0beta1
@@ -382,4 +382,4 @@ import OperationsTemplate from './templates/operations.handlebars'; | |||
this.collection.each(this.renderOperation, this); | |||
} | |||
}); | |||
})(); | |||
})(); |
@@ -0,0 +1,104 @@ | |||
<template> | |||
<div class="check" @click="showDelete" v-click-outside="hideDelete"> | |||
<Multiselect v-model="currentOption" :options="options" label="name" | |||
track-by="class" :allow-empty="false" :placeholder="t('workflowengine', 'Select a filter')" | |||
@input="updateCheck" ref="checkSelector"></Multiselect> | |||
<Multiselect v-if="currentOption" v-model="currentOperator" @input="updateCheck" | |||
:options="operators" label="name" track-by="operator" | |||
:allow-empty="false" :placeholder="t('workflowengine', 'Select a comparator')"></Multiselect> | |||
<component v-if="currentOperator && currentComponent" :is="currentOption.component()" v-model="check.value" /> | |||
<input v-else-if="currentOperator" type="text" v-model="check.value" @input="updateCheck" /> | |||
<Actions> | |||
<ActionButton icon="icon-delete" v-if="deleteVisible || !currentOption" @click="$emit('remove')" /> | |||
</Actions> | |||
</div> | |||
</template> | |||
<script> | |||
import { Multiselect, Actions, ActionButton } from 'nextcloud-vue' | |||
import ClickOutside from 'vue-click-outside'; | |||
export default { | |||
name: 'Check', | |||
components: { | |||
ActionButton, | |||
Actions, | |||
Multiselect | |||
}, | |||
directives: { | |||
ClickOutside | |||
}, | |||
props: { | |||
check: { | |||
type: Object, | |||
required: true | |||
} | |||
}, | |||
data() { | |||
return { | |||
deleteVisible: false, | |||
currentOption: null, | |||
currentOperator: null, | |||
options: [], | |||
} | |||
}, | |||
mounted() { | |||
this.options = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { | |||
if (plugin.component) { | |||
return {...plugin.getCheck(), component: plugin.component} | |||
} | |||
return plugin.getCheck() | |||
}) | |||
this.currentOption = this.options.find((option) => option.class === this.check.class) | |||
this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator) | |||
this.$nextTick(() => { | |||
this.$refs.checkSelector.$el.focus() | |||
}) | |||
}, | |||
computed: { | |||
operators() { | |||
if (!this.currentOption) | |||
return [] | |||
return this.options.find((item) => item.class === this.currentOption.class).operators | |||
}, | |||
currentComponent() { | |||
if (!this.currentOption) | |||
return [] | |||
let currentComponent = this.options.find((item) => item.class === this.currentOption.class).component | |||
return currentComponent && currentComponent() | |||
} | |||
}, | |||
methods: { | |||
showDelete() { | |||
this.deleteVisible = true | |||
}, | |||
hideDelete() { | |||
this.deleteVisible = false | |||
}, | |||
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 | |||
this.$emit('update', this.check) | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.check { | |||
display: flex; | |||
& > .multiselect, | |||
& > input[type=text] { | |||
margin-right: 5px; | |||
} | |||
} | |||
input[type=text] { | |||
margin: 0; | |||
} | |||
::placeholder { | |||
font-size: 10px; | |||
} | |||
</style> |
@@ -0,0 +1,84 @@ | |||
<template> | |||
<div> | |||
<Multiselect :value="currentEvent" :options="allEvents" label="name" track-by="id" :allow-empty="false" :disabled="allEvents.length <= 1" @input="updateEvent"> | |||
<template slot="singleLabel" slot-scope="props"> | |||
<span class="option__icon" :class="props.option.icon"></span> | |||
<span class="option__title option__title_single">{{ props.option.name }}</span> | |||
</template> | |||
<template slot="option" slot-scope="props"> | |||
<span class="option__icon" :class="props.option.icon"></span> | |||
<span class="option__title">{{ props.option.name }}</span> | |||
</template> | |||
</Multiselect> | |||
</div> | |||
</template> | |||
<script> | |||
import { Multiselect } from 'nextcloud-vue' | |||
import { eventService, operationService } from '../services/Operation' | |||
export default { | |||
name: "Event", | |||
components: { | |||
Multiselect | |||
}, | |||
props: { | |||
rule: { | |||
type: Object, | |||
required: true | |||
} | |||
}, | |||
computed: { | |||
currentEvent() { | |||
if (typeof this.rule.event === 'undefined') { | |||
return this.allEvents.length > 0 ? this.allEvents[0] : null | |||
} | |||
return this.allEvents.find(event => event.id === this.rule.event) | |||
}, | |||
allEvents() { | |||
return this.operation.events.map((eventName) => eventService.get(eventName)) | |||
}, | |||
operation() { | |||
return operationService.get(this.rule.class) | |||
} | |||
}, | |||
methods: { | |||
updateEvent(event) { | |||
this.$set(this.rule, 'event', event.id) | |||
this.$emit('update', this.rule) | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.multiselect::v-deep .multiselect__single { | |||
display: flex; | |||
} | |||
.multiselect:not(.multiselect--active)::v-deep .multiselect__tags { | |||
background-color: var(--color-main-background) !important; | |||
border: 1px solid transparent; | |||
} | |||
.multiselect::v-deep .multiselect__tags .multiselect__single { | |||
background-color: var(--color-main-background) !important; | |||
} | |||
.multiselect:not(.multiselect--disabled)::v-deep .multiselect__tags .multiselect__single { | |||
background-image: var(--icon-triangle-s-000); | |||
background-repeat: no-repeat; | |||
background-position: right center; | |||
} | |||
input { | |||
border: 1px solid transparent; | |||
} | |||
.option__title { | |||
margin-left: 5px; | |||
color: var(--color-main-text); | |||
} | |||
.option__title_single { | |||
font-weight: 900; | |||
} | |||
</style> |
@@ -0,0 +1,83 @@ | |||
<template> | |||
<div class="actions__item" :class="{'colored': !!color}" :style="{ backgroundColor: color }"> | |||
<div class="icon" :class="icon"></div> | |||
<h3>{{ title }}</h3> | |||
<small>{{ description }}</small> | |||
<slot /> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: "Operation", | |||
props: { | |||
icon: { | |||
type: String, | |||
required: true | |||
}, | |||
title: { | |||
type: String, | |||
required: true | |||
}, | |||
description: { | |||
type: String, | |||
required: true | |||
}, | |||
color: { | |||
type: String, | |||
required: false | |||
}, | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.actions__item { | |||
display: flex; | |||
flex-direction: column; | |||
flex-grow: 1; | |||
margin-left: -1px; | |||
padding: 10px; | |||
border-radius: var(--border-radius-large); | |||
max-width: 230px; | |||
margin-right: 20px; | |||
} | |||
.icon { | |||
display: block; | |||
width: 100%; | |||
height: 50px; | |||
background-size: 50px 50px; | |||
background-position: center center; | |||
margin-top: 10px; | |||
margin-bottom: 20px; | |||
} | |||
h3, small { | |||
padding: 6px; | |||
text-align: center; | |||
display: block; | |||
} | |||
h3 { | |||
margin: 0; | |||
padding: 0; | |||
font-weight: 500; | |||
} | |||
small { | |||
font-size: 10pt; | |||
} | |||
.icon-block { | |||
background-image: url(/apps-extra/files_accesscontrol/img/app-dark.svg); | |||
} | |||
.icon-convert-pdf { | |||
background-image: url(/apps-extra/workflow_pdf_converter/img/app-dark.svg); | |||
} | |||
.colored { | |||
.icon { | |||
filter: invert(1); | |||
} | |||
* { | |||
color: var(--color-primary-text) | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,45 @@ | |||
<template> | |||
<multiselect :options="options" track-by="id" label="text" v-model="value"></multiselect> | |||
</template> | |||
<script> | |||
const pdfConvertOptions = [ | |||
{ | |||
id: 'keep;preserve', | |||
text: t('workflow_pdf_converter', 'Keep original, preserve existing PDFs'), | |||
}, | |||
{ | |||
id: 'keep;overwrite', | |||
text: t('workflow_pdf_converter', 'Keep original, overwrite existing PDF'), | |||
}, | |||
{ | |||
id: 'delete;preserve', | |||
text: t('workflow_pdf_converter', 'Delete original, preserve existing PDFs'), | |||
}, | |||
{ | |||
id: 'delete;overwrite', | |||
text: t('workflow_pdf_converter', 'Delete original, overwrite existing PDF'), | |||
}, | |||
] | |||
import { Multiselect } from 'nextcloud-vue' | |||
export default { | |||
name: "ConvertToPdf", | |||
components: {Multiselect}, | |||
data() { | |||
return { | |||
options: pdfConvertOptions, | |||
value: pdfConvertOptions[0] | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.multiselect { | |||
width: 100%; | |||
max-width: 300px; | |||
margin: auto; | |||
text-align: center; | |||
} | |||
</style> |
@@ -0,0 +1,29 @@ | |||
<template> | |||
<multiselect :options="options" v-model="value" track-by="id" label="title" :multiple="true" :tagging="true" @input="$emit('input', value.map(item => item.id))"></multiselect> | |||
</template> | |||
<script> | |||
// TODO: fetch tags from endpoint | |||
const tags = [{id: 3, title: 'foo'}, {id: 4, title: 'bar'}] | |||
import { Multiselect } from 'nextcloud-vue' | |||
export default { | |||
name: "Tag", | |||
components: {Multiselect}, | |||
data() { | |||
return { | |||
options: tags, | |||
value: null | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.multiselect { | |||
width: 100%; | |||
max-width: 300px; | |||
margin: auto; | |||
text-align: center; | |||
} | |||
</style> |
@@ -0,0 +1,209 @@ | |||
<template> | |||
<div class="section rule"> | |||
<!-- TODO: icon-confirm --> | |||
<div class="trigger icon-confirm"> | |||
<p> | |||
<span>{{ t('workflowengine', 'When') }}</span> | |||
<Event :rule="rule" @update="updateRule"></Event> | |||
</p> | |||
<p v-for="check in rule.checks"> | |||
<span>{{ t('workflowengine', 'and') }}</span> | |||
<Check :check="check" @update="updateRule" @remove="removeCheck(check)"></Check> | |||
</p> | |||
<p> | |||
<span> </span> | |||
<input v-if="lastCheckComplete" type="button" class="check--add" @click="rule.checks.push({class: null, operator: null, value: null})" value="Add a new filter"/> | |||
</p> | |||
</div> | |||
<div class="action"> | |||
<div class="buttons"> | |||
<button class="status-button icon" :class="ruleStatus.class" v-tooltip="ruleStatus.tooltip" @click="saveRule">{{ ruleStatus.title }}</button> | |||
<Actions> | |||
<ActionButton v-if="rule.id === -1" icon="icon-close" @click="$emit('cancel')">Cancel</ActionButton> | |||
<ActionButton v-else icon="icon-delete" @click="deleteRule">Delete</ActionButton> | |||
</Actions> | |||
</div> | |||
<Operation :icon="operation.icon" :title="operation.title" :description="operation.description"> | |||
<component v-if="operation.options" :is="operation.options" v-model="operation.operation" @input="updateOperation" /> | |||
</Operation> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { Actions, ActionButton, Tooltip } from 'nextcloud-vue' | |||
import Event from './Event' | |||
import Check from './Check' | |||
import Operation from './Operation' | |||
import { operationService } from '../services/Operation' | |||
import axios from 'nextcloud-axios' | |||
import confirmPassword from 'nextcloud-password-confirmation' | |||
export default { | |||
name: 'Rule', | |||
components: { | |||
Operation, Check, Event, Actions, ActionButton | |||
}, | |||
directives: { | |||
Tooltip | |||
}, | |||
props: { | |||
rule: { | |||
type: Object, | |||
required: true, | |||
} | |||
}, | |||
data () { | |||
return { | |||
editing: false, | |||
operationService, | |||
checks: [], | |||
error: null, | |||
dirty: this.rule.id === -1, | |||
checking: false | |||
} | |||
}, | |||
computed: { | |||
operation() { | |||
return this.operationService.get(this.rule.class) | |||
}, | |||
ruleStatus() { | |||
if (this.error) { | |||
return { title: 'Invalid', class: 'icon-close-white invalid', tooltip: { placement: 'bottom', show: true, content: escapeHTML(this.error.data) } } | |||
} | |||
if (!this.dirty || this.checking) { | |||
return { title: 'Active', class: 'icon icon-checkmark' } | |||
} | |||
return { title: 'Save', class: 'icon-confirm-white primary' } | |||
}, | |||
lastCheckComplete() { | |||
const lastCheck = this.rule.checks[this.rule.checks.length-1] | |||
return typeof lastCheck === 'undefined' || lastCheck.class !== null | |||
} | |||
}, | |||
methods: { | |||
updateOperation(operation) { | |||
this.$set(this.rule, 'operation', operation) | |||
}, | |||
async updateRule() { | |||
this.checking = true | |||
if (!this.dirty) { | |||
this.dirty = true | |||
} | |||
try { | |||
let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule) | |||
this.error = null | |||
this.checking = false | |||
} catch (e) { | |||
console.error('Failed to update operation') | |||
this.error = e.response | |||
this.checking = false | |||
} | |||
}, | |||
async saveRule() { | |||
try { | |||
await confirmPassword() | |||
let result | |||
if (this.rule.id === -1) { | |||
result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations`), this.rule) | |||
this.rule.id = result.id | |||
} else { | |||
result = await axios.put(OC.generateUrl(`/apps/workflowengine/operations/${this.rule.id}`), this.rule) | |||
} | |||
this.$emit('update', result.data) | |||
this.dirty = false | |||
this.error = null | |||
} catch (e) { | |||
console.error('Failed to update operation') | |||
this.error = e.response | |||
} | |||
}, | |||
async deleteRule() { | |||
try { | |||
await confirmPassword() | |||
await axios.delete(OC.generateUrl(`/apps/workflowengine/operations/${this.rule.id}`)) | |||
this.$emit('delete') | |||
} catch (e) { | |||
console.error('Failed to delete operation') | |||
this.error = e.response | |||
} | |||
}, | |||
removeCheck(check) { | |||
const index = this.rule.checks.findIndex(item => item === check) | |||
if (index !== -1) { | |||
this.rule.checks.splice(index, 1) | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
button.icon { | |||
padding-left: 32px; | |||
background-position: 10px center; | |||
} | |||
.status-button { | |||
transition: 0.5s ease all; | |||
} | |||
.status-button.primary { | |||
padding-left: 32px; | |||
background-position: 10px center; | |||
} | |||
.status-button:not(.primary) { | |||
background-color: var(--color-main-background); | |||
} | |||
.status-button.invalid { | |||
background-color: var(--color-warning); | |||
color: #fff; | |||
} | |||
.rule { | |||
display: flex; | |||
.trigger, .action { | |||
flex-grow: 1; | |||
min-height: 100px; | |||
width: 50%; | |||
} | |||
.action { | |||
position: relative; | |||
.buttons { | |||
position: absolute; | |||
right: 0; | |||
} | |||
} | |||
.icon-confirm { | |||
background-position: right center; | |||
} | |||
} | |||
.trigger p, .action p { | |||
display: flex; | |||
align-items: center; | |||
margin-bottom: 5px; | |||
& > span { | |||
min-width: 50px; | |||
text-align: right; | |||
color: var(--color-text-light); | |||
padding-right: 5px; | |||
} | |||
.multiselect { | |||
flex-grow: 1; | |||
max-width: 300px; | |||
} | |||
} | |||
.check--add { | |||
background-position: 7px center; | |||
background-color: transparent; | |||
padding-left: 6px; | |||
width: 160px; | |||
border-radius: var(--border-radius); | |||
font-weight: normal; | |||
text-align: left; | |||
font-size: 1em; | |||
} | |||
</style> |
@@ -0,0 +1,39 @@ | |||
<template> | |||
<input type="text" /> | |||
</template> | |||
<script> | |||
import { Multiselect } from 'nextcloud-vue' | |||
export default { | |||
name: 'SizeValue', | |||
components: { | |||
Multiselect | |||
}, | |||
data() { | |||
return { | |||
predefinedTypes: [ | |||
{ | |||
icon: 'icon-picture', | |||
label: 'Images', | |||
pattern: '/image\\/.*/' | |||
}, | |||
{ | |||
icon: 'icon-category-office', | |||
label: 'Office documents', | |||
pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/' | |||
}, | |||
{ | |||
icon: 'icon-filetype-file', | |||
label: 'PDF documents', | |||
pattern: 'application/pdf' | |||
} | |||
] | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
</style> |
@@ -0,0 +1,28 @@ | |||
<template> | |||
<input type="text" placeholder="1 MB" @input="$emit('input', newValue)" v-model="newValue"> | |||
</template> | |||
<script> | |||
export default { | |||
name: "SizeValue", | |||
props: { | |||
value: { | |||
type: String | |||
} | |||
}, | |||
data() { | |||
return { | |||
newValue: this.value | |||
} | |||
}, | |||
watch: { | |||
value() { | |||
this.newValue = this.value | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
</style> |
@@ -0,0 +1,146 @@ | |||
<template> | |||
<div> | |||
<div class="section"> | |||
<h2>{{ t('workflowengine', 'Workflows') }}</h2> | |||
<div class="actions" v-if="!hasUnsavedRule"> | |||
<Operation v-for="operation in getMainOperations" :key="operation.class" | |||
:icon="operation.icon" :title="operation.title" :description="operation.description" :color="operation.color" | |||
@click.native="createNewRule(operation)"></Operation> | |||
</div> | |||
<div class="actions__more" v-if="!hasUnsavedRule && hasMoreOperations"> | |||
<button class="icon" :class="showMoreOperations ? 'icon-triangle-n' : 'icon-triangle-s'" | |||
@click="showMoreOperations=!showMoreOperations"> | |||
{{ showMoreOperations ? t('workflowengine', 'Show less') : t('workflowengine', 'Show more') }} | |||
</button> | |||
</div> | |||
<transition name="slide"> | |||
<div class="actions" v-if="!hasUnsavedRule && showMoreOperations && hasMoreOperations"> | |||
<Operation v-for="operation in getMoreOperations" :key="operation.class" :icon="operation.icon" :title="operation.title" :description="operation.description" | |||
@click.native="createNewRule(operation)"></Operation> | |||
</div> | |||
</transition> | |||
</div> | |||
<transition-group name="slide"> | |||
<Rule v-for="rule in rules" :key="rule.id" :rule="rule" @delete="removeRule(rule)" @cancel="removeRule(rule)" @update="updateRule"></Rule> | |||
</transition-group> | |||
</div> | |||
</template> | |||
<script> | |||
import Rule from './Rule' | |||
import Operation from './Operation' | |||
import { operationService } from '../services/Operation' | |||
import axios from 'nextcloud-axios' | |||
const ACTION_LIMIT = 3 | |||
export default { | |||
name: 'Workflow', | |||
components: { | |||
Operation, | |||
Rule | |||
}, | |||
async mounted() { | |||
const { data } = await axios.get(OC.generateUrl('/apps/workflowengine/operations')) | |||
this.rules = data | |||
}, | |||
data() { | |||
return { | |||
operations: operationService, | |||
showMoreOperations: false, | |||
rules: [] | |||
} | |||
}, | |||
computed: { | |||
hasMoreOperations() { | |||
return Object.keys(this.operations.getAll()).length > ACTION_LIMIT | |||
}, | |||
getMainOperations() { | |||
return Object.values(this.operations.getAll()).slice(0, ACTION_LIMIT) | |||
}, | |||
getMoreOperations() { | |||
return Object.values(this.operations.getAll()).slice(ACTION_LIMIT) | |||
}, | |||
hasUnsavedRule() { | |||
return !!this.rules.find((rule) => rule.id === -1) | |||
} | |||
}, | |||
methods: { | |||
updateRule(rule) { | |||
let index = this.rules.findIndex((item) => rule === item) | |||
this.$set(this.rules, index, rule) | |||
}, | |||
removeRule(rule) { | |||
let index = this.rules.findIndex((item) => rule === item) | |||
this.rules.splice(index, 1) | |||
}, | |||
showAllOperations() { | |||
}, | |||
createNewRule(operation) { | |||
this.rules.unshift({ | |||
id: -1, | |||
class: operation.class, | |||
name: '', // unused in the new ui, there for legacy reasons | |||
checks: [], | |||
operation: operation.operation || '' | |||
}) | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.actions { | |||
display: flex; | |||
.action__item { | |||
border: 1px solid var(--color-border); | |||
} | |||
} | |||
.actions__more { | |||
text-align: center; | |||
} | |||
button.icon { | |||
padding-left: 32px; | |||
background-position: 10px center; | |||
} | |||
.slide-enter-active { | |||
-moz-transition-duration: 0.3s; | |||
-webkit-transition-duration: 0.3s; | |||
-o-transition-duration: 0.3s; | |||
transition-duration: 0.3s; | |||
-moz-transition-timing-function: ease-in; | |||
-webkit-transition-timing-function: ease-in; | |||
-o-transition-timing-function: ease-in; | |||
transition-timing-function: ease-in; | |||
} | |||
.slide-leave-active { | |||
-moz-transition-duration: 0.3s; | |||
-webkit-transition-duration: 0.3s; | |||
-o-transition-duration: 0.3s; | |||
transition-duration: 0.3s; | |||
-moz-transition-timing-function: cubic-bezier(0, 1, 0.5, 1); | |||
-webkit-transition-timing-function: cubic-bezier(0, 1, 0.5, 1); | |||
-o-transition-timing-function: cubic-bezier(0, 1, 0.5, 1); | |||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1); | |||
} | |||
.slide-enter-to, .slide-leave { | |||
max-height: 500px; | |||
overflow: hidden; | |||
} | |||
.slide-enter, .slide-leave-to { | |||
overflow: hidden; | |||
max-height: 0; | |||
} | |||
</style> |
@@ -17,6 +17,7 @@ | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
import FileMimeType from './components/Values/FileMimeType' | |||
(function() { | |||
@@ -65,6 +66,10 @@ | |||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/, | |||
result = regexRegex.exec(string); | |||
return result !== null; | |||
}, | |||
component: function () { | |||
return FileMimeType | |||
} | |||
}; | |||
})(); |
@@ -17,7 +17,7 @@ | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
import SizeValue from './components/Values/SizeValue' | |||
(function() { | |||
OCA.WorkflowEngine = OCA.WorkflowEngine || {}; | |||
@@ -49,6 +49,9 @@ | |||
.tooltip({ | |||
placement: 'bottom' | |||
}); | |||
}, | |||
component: function () { | |||
return SizeValue | |||
} | |||
}; | |||
})(); |
@@ -0,0 +1,141 @@ | |||
import ConvertToPdf from './../components/Operations/ConvertToPdf' | |||
import Tag from './../components/Operations/Tag' | |||
class OperationService { | |||
constructor() { | |||
this.operations = {} | |||
} | |||
registerOperation (operation) { | |||
this.operations[operation.class] = Object.assign({ | |||
color: 'var(--color-primary)' | |||
}, operation) | |||
} | |||
getAll() { | |||
return this.operations | |||
} | |||
get(className) { | |||
return this.operations[className] | |||
} | |||
} | |||
class EventService { | |||
constructor() { | |||
this.events = {} | |||
} | |||
registerEvent(event) { | |||
this.events[event.id] = event | |||
} | |||
getAll() { | |||
return this.events | |||
} | |||
get(id) { | |||
return this.events[id] | |||
} | |||
} | |||
const operationService = new OperationService() | |||
const eventService = new EventService() | |||
const ALL_CHECKS = [ | |||
'OCA\\WorkflowEngine\\Check\\FileMimeType', | |||
'OCA\\WorkflowEngine\\Check\\FileName', | |||
'OCA\\WorkflowEngine\\Check\\FileSize', | |||
'OCA\\WorkflowEngine\\Check\\FileSystemTags', | |||
'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress', | |||
'OCA\\WorkflowEngine\\Check\\RequestTime', | |||
'OCA\\WorkflowEngine\\Check\\RequestURL', | |||
'OCA\\WorkflowEngine\\Check\\RequestUserAgent', | |||
'OCA\\WorkflowEngine\\Check\\UserGroupMembership' | |||
] | |||
/** | |||
* TODO: move to separate apps | |||
* TODO: fetch from initial state api | |||
**/ | |||
const EVENT_FILE_ACCESS = 'EVENT_FILE_ACCESS' | |||
const EVENT_FILE_CHANGED = 'EVENT_FILE_CHANGED' | |||
const EVENT_FILE_TAGGED = 'EVENT_FILE_TAGGED' | |||
eventService.registerEvent({ | |||
id: EVENT_FILE_ACCESS, | |||
name: 'File is accessed', | |||
icon: 'icon-desktop', | |||
checks: ALL_CHECKS, | |||
}) | |||
eventService.registerEvent({ | |||
id: EVENT_FILE_CHANGED, | |||
name: 'File was updated', | |||
icon: 'icon-folder', | |||
checks: ALL_CHECKS, | |||
}) | |||
eventService.registerEvent({ | |||
id: EVENT_FILE_TAGGED, | |||
name: 'File was tagged', | |||
icon: 'icon-tag', | |||
checks: ALL_CHECKS, | |||
}) | |||
operationService.registerOperation({ | |||
class: 'OCA\\FilesAccessControl\\Operation', | |||
title: 'Block access', | |||
description: 'todo', | |||
icon: 'icon-block', | |||
color: 'var(--color-error)', | |||
events: [ | |||
EVENT_FILE_ACCESS | |||
], | |||
operation: 'deny' | |||
}) | |||
operationService.registerOperation({ | |||
class: 'OCA\\FilesAutomatedTagging\\Operation', | |||
title: 'Tag a file', | |||
description: 'todo', | |||
icon: 'icon-tag', | |||
events: [ | |||
EVENT_FILE_CHANGED, | |||
EVENT_FILE_TAGGED | |||
], | |||
options: Tag | |||
}) | |||
operationService.registerOperation({ | |||
class: 'OCA\\WorkflowPDFConverter\\Operation', | |||
title: 'Convert to PDF', | |||
description: 'todo', | |||
color: '#dc5047', | |||
icon: 'icon-convert-pdf', | |||
events: [ | |||
EVENT_FILE_CHANGED, | |||
//EVENT_FILE_TAGGED | |||
], | |||
options: ConvertToPdf | |||
}) | |||
const legacyChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => { | |||
if (plugin.component) { | |||
return {...plugin.getCheck(), component: plugin.component} | |||
} | |||
return plugin.getCheck() | |||
}).reduce((obj, item) => { | |||
obj[item.class] = item | |||
return obj | |||
}, {}) | |||
export { | |||
eventService, | |||
operationService | |||
} |
@@ -1,4 +1,3 @@ | |||
import './admin' | |||
import './filemimetypeplugin' | |||
import './filenameplugin' | |||
import './filesizeplugin' | |||
@@ -10,3 +9,11 @@ import './requestuseragentplugin' | |||
import './usergroupmembershipplugin' | |||
window.OCA.WorkflowEngine = OCA.WorkflowEngine | |||
import Vue from 'vue'; | |||
Vue.prototype.t = t; | |||
import Settings from './components/Workflow'; | |||
const View = Vue.extend(Settings) | |||
new View({}).$mount('#workflowengine') |