@afeefa/vue-app 0.0.60 → 0.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.afeefa/package/release/version.txt +1 -1
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/api-resources/ApiAction.js +87 -0
- package/src/api-resources/ApiActions2.js +13 -0
- package/src/api-resources/DeleteAction.js +21 -0
- package/src/api-resources/GetAction.js +18 -0
- package/src/api-resources/ListAction.js +27 -0
- package/src/api-resources/SaveAction.js +15 -0
- package/src/components/ASearchSelect.vue +2 -2
- package/src/components/form/EditForm.vue +29 -8
- package/src/components/form/FormFieldMixin.js +1 -1
- package/src/components/form/NestedEditForm.vue +4 -0
- package/src/components/list/ListViewMixin.js +2 -2
- package/src-admin/bootstrap.js +8 -1
- package/src-admin/components/app/AppBarButton.vue +10 -2
- package/src-admin/components/controls/SearchSelectFormField.vue +1 -1
- package/src-admin/components/form/EditFormButtons.vue +38 -0
- package/src-admin/components/form/RemoveButton.vue +71 -0
- package/src-admin/components/index.js +5 -1
- package/src-admin/components/pages/CreatePage.vue +1 -2
- package/src-admin/components/pages/EditPage.vue +43 -135
- package/src-admin/components/pages/ListPage.vue +2 -4
- package/src-admin/components/routes/DataRouteMixin.js +75 -0
- package/src-admin/models/Model.js +8 -3
- package/src-admin/plugins/translation/TranslationPlugin.js +20 -0
- package/src-admin/services/TranslationService.js +35 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.63
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { AlertEvent, LoadingEvent } from '@a-vue/events'
|
|
2
|
+
import { eventBus } from '@a-vue/plugins/event-bus/EventBus'
|
|
3
|
+
import { sleep } from '@a-vue/utils/timeout'
|
|
4
|
+
import { ApiAction as ApiResourcesApiAction } from '@afeefa/api-resources-client'
|
|
5
|
+
|
|
6
|
+
import { SaveEvent } from '../events'
|
|
7
|
+
|
|
8
|
+
export class ApiAction extends ApiResourcesApiAction {
|
|
9
|
+
_dispatchGlobalLoadingEvents = false
|
|
10
|
+
_dispatchGlobalSaveEvents = false
|
|
11
|
+
_minDuration = 100
|
|
12
|
+
_startTime = 0
|
|
13
|
+
|
|
14
|
+
id (id) {
|
|
15
|
+
this.param('id', id)
|
|
16
|
+
return this
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
dispatchGlobalLoadingEvents (dispatch = true) {
|
|
20
|
+
this._dispatchGlobalLoadingEvents = dispatch
|
|
21
|
+
return this
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
dispatchGlobalSaveEvents (dispatch = true) {
|
|
25
|
+
this._dispatchGlobalSaveEvents = dispatch
|
|
26
|
+
return this
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async beforeRequest () {
|
|
30
|
+
this._startTime = Date.now()
|
|
31
|
+
|
|
32
|
+
if (this._dispatchGlobalLoadingEvents) {
|
|
33
|
+
eventBus.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this._dispatchGlobalSaveEvents) {
|
|
37
|
+
eventBus.dispatch(new SaveEvent(SaveEvent.START_SAVING))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async beforeBulkRequest () {
|
|
42
|
+
if (this._dispatchGlobalLoadingEvents) {
|
|
43
|
+
eventBus.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this._dispatchGlobalSaveEvents) {
|
|
47
|
+
eventBus.dispatch(new SaveEvent(SaveEvent.START_SAVING))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async afterRequest () {
|
|
52
|
+
if (this._minDuration) {
|
|
53
|
+
const diffTime = Date.now() - this._startTime
|
|
54
|
+
const restTime = Math.max(0, this._minDuration - diffTime)
|
|
55
|
+
if (restTime) {
|
|
56
|
+
await sleep(restTime / 1000)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (this._dispatchGlobalLoadingEvents) {
|
|
61
|
+
eventBus.dispatch(new LoadingEvent(LoadingEvent.STOP_LOADING))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this._dispatchGlobalSaveEvents) {
|
|
65
|
+
eventBus.dispatch(new SaveEvent(SaveEvent.STOP_SAVING))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async afterBulkRequest () {
|
|
70
|
+
if (this._dispatchGlobalLoadingEvents) {
|
|
71
|
+
eventBus.dispatch(new LoadingEvent(LoadingEvent.STOP_LOADING))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this._dispatchGlobalSaveEvents) {
|
|
75
|
+
eventBus.dispatch(new SaveEvent(SaveEvent.STOP_SAVING))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
alert (message) {
|
|
80
|
+
eventBus.dispatch(new AlertEvent(AlertEvent.MESSAGE, {
|
|
81
|
+
message
|
|
82
|
+
}))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class BulkAction extends ApiAction {
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BulkAction } from './ApiAction'
|
|
2
|
+
import { DeleteAction } from './DeleteAction'
|
|
3
|
+
import { GetAction } from './GetAction'
|
|
4
|
+
import { ListAction } from './ListAction'
|
|
5
|
+
import { SaveAction } from './SaveAction'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
BulkAction,
|
|
9
|
+
ListAction,
|
|
10
|
+
GetAction,
|
|
11
|
+
SaveAction,
|
|
12
|
+
DeleteAction
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiAction } from './ApiAction'
|
|
2
|
+
|
|
3
|
+
export class DeleteAction extends ApiAction {
|
|
4
|
+
_minDuration = 400
|
|
5
|
+
|
|
6
|
+
delete () {
|
|
7
|
+
this.data(null)
|
|
8
|
+
|
|
9
|
+
return this.execute()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async afterRequest () {
|
|
13
|
+
await super.afterRequest()
|
|
14
|
+
|
|
15
|
+
this.alert('Die Daten wurden gelöscht.')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
processResult () {
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AlertEvent } from '@a-vue/events'
|
|
2
|
+
import { eventBus } from '@a-vue/plugins/event-bus/EventBus'
|
|
3
|
+
|
|
4
|
+
import { ApiAction } from './ApiAction'
|
|
5
|
+
|
|
6
|
+
export class GetAction extends ApiAction {
|
|
7
|
+
load () {
|
|
8
|
+
return this.execute()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
processError (result) {
|
|
12
|
+
eventBus.dispatch(new AlertEvent(AlertEvent.ERROR, {
|
|
13
|
+
headline: 'Die Daten konntent nicht geladen werden.',
|
|
14
|
+
message: result.message,
|
|
15
|
+
detail: result.detail
|
|
16
|
+
}))
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NextRouteFilterSource } from '@a-vue/components/list/NextRouteFilterSource'
|
|
2
|
+
import { ListViewModel } from '@afeefa/api-resources-client'
|
|
3
|
+
|
|
4
|
+
import { ApiAction } from './ApiAction'
|
|
5
|
+
|
|
6
|
+
export class ListAction extends ApiAction {
|
|
7
|
+
load () {
|
|
8
|
+
return this.execute()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
initFiltersForRoute (route) {
|
|
12
|
+
const request = new ListViewModel(this)
|
|
13
|
+
// read from next route query string, but do not push
|
|
14
|
+
// list component will be init with used_filters
|
|
15
|
+
.filterSource(new NextRouteFilterSource(route), false)
|
|
16
|
+
// read from history, but do not push
|
|
17
|
+
// list component will be init with used_filters
|
|
18
|
+
.historyKey(route.path, false)
|
|
19
|
+
.initFilters({
|
|
20
|
+
source: true,
|
|
21
|
+
history: true
|
|
22
|
+
})
|
|
23
|
+
.getApiRequest()
|
|
24
|
+
|
|
25
|
+
return ListAction.fromRequest(request)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ApiAction } from './ApiAction'
|
|
2
|
+
|
|
3
|
+
export class SaveAction extends ApiAction {
|
|
4
|
+
_minDuration = 400
|
|
5
|
+
|
|
6
|
+
save () {
|
|
7
|
+
return this.execute()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async afterRequest () {
|
|
11
|
+
await super.afterRequest()
|
|
12
|
+
|
|
13
|
+
this.alert('Die Daten wurden gespeichert.')
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<div :class="listCssClass">
|
|
46
46
|
<search-select-list
|
|
47
47
|
v-if="isOpen"
|
|
48
|
-
:
|
|
48
|
+
:listAction="listAction"
|
|
49
49
|
:q="q"
|
|
50
50
|
:selectedItems="selectedItems"
|
|
51
51
|
:events="false"
|
|
@@ -96,7 +96,7 @@ import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
|
|
|
96
96
|
|
|
97
97
|
@Component({
|
|
98
98
|
props: [
|
|
99
|
-
'
|
|
99
|
+
'listAction',
|
|
100
100
|
'q',
|
|
101
101
|
'width',
|
|
102
102
|
'closeOnSelect',
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
v-model="valid"
|
|
4
4
|
autocomplete="off"
|
|
5
5
|
>
|
|
6
|
-
<slot name="fields" />
|
|
7
|
-
|
|
8
6
|
<slot
|
|
9
7
|
:changed="changed"
|
|
10
8
|
:valid="valid"
|
|
11
|
-
:model="
|
|
9
|
+
:model="modelToEdit"
|
|
12
10
|
/>
|
|
13
11
|
</v-form>
|
|
14
12
|
</template>
|
|
@@ -18,14 +16,31 @@
|
|
|
18
16
|
import { Component, Vue, Watch } from '@a-vue'
|
|
19
17
|
|
|
20
18
|
@Component({
|
|
21
|
-
props: [
|
|
19
|
+
props: [
|
|
20
|
+
'model',
|
|
21
|
+
'createModelToEdit'
|
|
22
|
+
]
|
|
22
23
|
})
|
|
23
24
|
export default class EditForm extends Vue {
|
|
24
25
|
EDIT_FORM = true
|
|
25
26
|
|
|
27
|
+
modelToEdit = null
|
|
26
28
|
valid = false
|
|
27
29
|
lastJson = null
|
|
28
30
|
|
|
31
|
+
created () {
|
|
32
|
+
this.reset()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
reset () {
|
|
36
|
+
if (this.createModelToEdit) {
|
|
37
|
+
this.modelToEdit = this.createModelToEdit(this.model)
|
|
38
|
+
} else {
|
|
39
|
+
this.modelToEdit = this.model
|
|
40
|
+
}
|
|
41
|
+
this.lastJson = this.json
|
|
42
|
+
}
|
|
43
|
+
|
|
29
44
|
/**
|
|
30
45
|
* This will be triggered after the this.model has been set
|
|
31
46
|
* but before sub components may have changed model values
|
|
@@ -33,14 +48,20 @@ export default class EditForm extends Vue {
|
|
|
33
48
|
* Using the created() method would result in already having set
|
|
34
49
|
* the default date, hence not detecting a valid "change" anymore.
|
|
35
50
|
*/
|
|
36
|
-
@Watch('
|
|
51
|
+
// @Watch('modelToEdit', {immediate: true})
|
|
52
|
+
// @Watch('modelToEdit')
|
|
53
|
+
// modelToEditChanged () {
|
|
54
|
+
// this.lastJson = this.json
|
|
55
|
+
// this.$emit('update:changed', this.changed)
|
|
56
|
+
// }
|
|
57
|
+
|
|
58
|
+
@Watch('model')
|
|
37
59
|
modelChanged () {
|
|
38
|
-
this.
|
|
39
|
-
this.$emit('update:changed', this.changed)
|
|
60
|
+
this.reset()
|
|
40
61
|
}
|
|
41
62
|
|
|
42
63
|
get json () {
|
|
43
|
-
return JSON.stringify(this.
|
|
64
|
+
return JSON.stringify(this.modelToEdit)
|
|
44
65
|
}
|
|
45
66
|
|
|
46
67
|
get changed () {
|
|
@@ -8,7 +8,7 @@ import { FilterSourceType } from './FilterSourceType'
|
|
|
8
8
|
@Component({
|
|
9
9
|
props: [
|
|
10
10
|
'models', 'meta', // given, if already loaded
|
|
11
|
-
'
|
|
11
|
+
'listAction',
|
|
12
12
|
'filterHistoryKey',
|
|
13
13
|
'loadOnlyIfKeyword',
|
|
14
14
|
{
|
|
@@ -52,7 +52,7 @@ export class ListViewMixin extends Vue {
|
|
|
52
52
|
this.meta_ = this.meta
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
this.listViewModel = new ListViewModel(this.
|
|
55
|
+
this.listViewModel = new ListViewModel(this.listAction)
|
|
56
56
|
.filterSource(filterSource, !!filterSource)
|
|
57
57
|
.historyKey(historyKey, this.history)
|
|
58
58
|
.usedFilters(this.meta_.used_filters || null, this.meta_.count_search || 0)
|
package/src-admin/bootstrap.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './config/event-bus'
|
|
2
2
|
import './config/components'
|
|
3
3
|
|
|
4
|
+
import { translationPlugin } from '@a-admin/plugins/translation/TranslationPlugin'
|
|
4
5
|
import { apiResourcesPlugin } from '@a-vue/plugins/api-resources/ApiResourcesPlugin'
|
|
5
6
|
import { hasOptionsPlugin } from '@a-vue/plugins/has-options/HasOptionsPlugin'
|
|
6
7
|
import { timeout } from '@a-vue/utils/timeout'
|
|
@@ -12,10 +13,11 @@ import vuetify from './config/vuetify'
|
|
|
12
13
|
|
|
13
14
|
Vue.use(apiResourcesPlugin)
|
|
14
15
|
Vue.use(hasOptionsPlugin)
|
|
16
|
+
Vue.use(translationPlugin)
|
|
15
17
|
|
|
16
18
|
Vue.config.productionTip = false
|
|
17
19
|
|
|
18
|
-
export async function bootstrap ({ apis, models, routing, authService, app, components }) {
|
|
20
|
+
export async function bootstrap ({ apis, models, routing, authService, getTranslations, app, components }) {
|
|
19
21
|
apiResourcesPlugin.register(models, apis)
|
|
20
22
|
|
|
21
23
|
appConfig.authService = authService
|
|
@@ -35,6 +37,11 @@ export async function bootstrap ({ apis, models, routing, authService, app, comp
|
|
|
35
37
|
const router = await routeConfigPlugin.getRouter()
|
|
36
38
|
await apiResourcesPlugin.schemasLoaded()
|
|
37
39
|
|
|
40
|
+
if (getTranslations) {
|
|
41
|
+
const translations = await getTranslations(apiResourcesPlugin.apiResources)
|
|
42
|
+
translationPlugin.setTranslations(translations.models)
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
if (authService) {
|
|
39
46
|
authService.initApp(router)
|
|
40
47
|
}
|
|
@@ -8,12 +8,20 @@
|
|
|
8
8
|
import { Component, Vue } from '@a-vue'
|
|
9
9
|
|
|
10
10
|
@Component({
|
|
11
|
-
props:
|
|
11
|
+
props: {
|
|
12
|
+
prepend: {
|
|
13
|
+
type: Boolean
|
|
14
|
+
}
|
|
15
|
+
}
|
|
12
16
|
})
|
|
13
17
|
export default class AppBarButton extends Vue {
|
|
14
18
|
mounted () {
|
|
15
19
|
const section = this.getButtonBar()
|
|
16
|
-
|
|
20
|
+
if (this.prepend) {
|
|
21
|
+
section.prepend(this.$el)
|
|
22
|
+
} else {
|
|
23
|
+
section.append(this.$el)
|
|
24
|
+
}
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
destroyed () {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<a-row gap="2">
|
|
3
|
+
<v-btn
|
|
4
|
+
:small="small"
|
|
5
|
+
:disabled="!changed || !valid"
|
|
6
|
+
color="green white--text"
|
|
7
|
+
@click="$emit('save')"
|
|
8
|
+
>
|
|
9
|
+
Speichern
|
|
10
|
+
</v-btn>
|
|
11
|
+
|
|
12
|
+
<v-icon
|
|
13
|
+
v-if="changed"
|
|
14
|
+
:small="small"
|
|
15
|
+
text
|
|
16
|
+
title="Formular zurücksetzen"
|
|
17
|
+
@click="$emit('reset')"
|
|
18
|
+
>
|
|
19
|
+
{{ undoIcon }}
|
|
20
|
+
</v-icon>
|
|
21
|
+
</a-row>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
import { Component, Vue } from '@a-vue'
|
|
26
|
+
import { mdiRotateLeft} from '@mdi/js'
|
|
27
|
+
|
|
28
|
+
@Component({
|
|
29
|
+
props: [
|
|
30
|
+
'changed',
|
|
31
|
+
'valid',
|
|
32
|
+
'small'
|
|
33
|
+
]
|
|
34
|
+
})
|
|
35
|
+
export default class EditFormButtons extends Vue {
|
|
36
|
+
undoIcon = mdiRotateLeft
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<a-icon-button
|
|
4
|
+
small
|
|
5
|
+
color="red white--text"
|
|
6
|
+
:class="'removeButton-' + dialogId"
|
|
7
|
+
icon="$trashCanIcon"
|
|
8
|
+
text="Löschen"
|
|
9
|
+
@click="remove"
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
<a-dialog
|
|
13
|
+
:id="dialogId"
|
|
14
|
+
:anchor="[document, '.removeButton-' + dialogId]"
|
|
15
|
+
:active="protect ? removeKey === removeConfirmed : true"
|
|
16
|
+
>
|
|
17
|
+
<template v-if="protect">
|
|
18
|
+
<div>Bitte folgenden Key eingeben: <strong class="removeKey">{{ removeKey }}</strong></div>
|
|
19
|
+
|
|
20
|
+
<a-text-field
|
|
21
|
+
v-model="removeConfirmed"
|
|
22
|
+
label="Key"
|
|
23
|
+
:focus="true"
|
|
24
|
+
width="100"
|
|
25
|
+
/>
|
|
26
|
+
</template>
|
|
27
|
+
</a-dialog>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
import { Component, Vue } from '@a-vue'
|
|
34
|
+
import { DialogEvent } from '@a-vue/events'
|
|
35
|
+
import { randomCssClass } from '@a-vue/utils/random'
|
|
36
|
+
|
|
37
|
+
@Component({
|
|
38
|
+
props: ['itemTitle', 'protect']
|
|
39
|
+
})
|
|
40
|
+
export default class EditPage extends Vue {
|
|
41
|
+
dialogId = randomCssClass(10)
|
|
42
|
+
document = document
|
|
43
|
+
removeKey = null
|
|
44
|
+
removeConfirmed = null
|
|
45
|
+
|
|
46
|
+
async remove () {
|
|
47
|
+
if (this.protect) {
|
|
48
|
+
this.removeKey = [...Array(6)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
|
|
52
|
+
id: this.dialogId,
|
|
53
|
+
title: this.itemTitle + ' löschen?',
|
|
54
|
+
message: 'Soll ' + this.itemTitle + ' gelöscht werden?',
|
|
55
|
+
yesButton: 'Löschen',
|
|
56
|
+
yesColor: 'red white--text'
|
|
57
|
+
}))
|
|
58
|
+
|
|
59
|
+
if (result === DialogEvent.RESULT_YES) {
|
|
60
|
+
this.$emit('remove')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
<style lang="scss" scoped>
|
|
68
|
+
.removeKey {
|
|
69
|
+
user-select: none;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -7,7 +7,8 @@ import DetailContent from './detail/DetailContent'
|
|
|
7
7
|
import DetailMeta from './detail/DetailMeta'
|
|
8
8
|
import DetailProperty from './detail/DetailProperty'
|
|
9
9
|
import DetailTitle from './detail/DetailTitle'
|
|
10
|
-
import
|
|
10
|
+
import EditFormButtons from './form/EditFormButtons'
|
|
11
|
+
import RemoveButton from './form/RemoveButton'
|
|
11
12
|
import ListCard from './list/ListCard'
|
|
12
13
|
import ListColumnHeader from './list/ListColumnHeader'
|
|
13
14
|
import ListContent from './list/ListContent'
|
|
@@ -20,6 +21,7 @@ import CreatePage from './pages/CreatePage'
|
|
|
20
21
|
import DetailPage from './pages/DetailPage'
|
|
21
22
|
import EditPage from './pages/EditPage'
|
|
22
23
|
import ListPage from './pages/ListPage'
|
|
24
|
+
import Start from './Start.vue'
|
|
23
25
|
|
|
24
26
|
Vue.component('ListCard', ListCard)
|
|
25
27
|
Vue.component('ListColumnHeader', ListColumnHeader)
|
|
@@ -32,6 +34,8 @@ Vue.component('ListPage', ListPage)
|
|
|
32
34
|
Vue.component('CreatePage', CreatePage)
|
|
33
35
|
|
|
34
36
|
Vue.component('EditPage', EditPage)
|
|
37
|
+
Vue.component('EditFormButtons', EditFormButtons)
|
|
38
|
+
Vue.component('RemoveButton', RemoveButton)
|
|
35
39
|
|
|
36
40
|
Vue.component('ModelCount', ModelCount)
|
|
37
41
|
Vue.component('ModelIcon', ModelIcon)
|
|
@@ -85,8 +85,7 @@ export default class CreatePage extends Mixins(EditPageMixin) {
|
|
|
85
85
|
return this.title
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
return type.t('TITLE_NEW')
|
|
88
|
+
return this.$t('Admin.Types', this.ModelClass.type, null, 'TITLE_NEW', 'de')
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
get _listLink () {
|
|
@@ -1,152 +1,60 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Ansehen
|
|
23
|
-
</v-btn>
|
|
24
|
-
</app-bar-button>
|
|
25
|
-
|
|
26
|
-
<edit-form
|
|
27
|
-
:model="modelToEdit"
|
|
28
|
-
:valid.sync="valid"
|
|
29
|
-
:changed.sync="changed"
|
|
30
|
-
>
|
|
31
|
-
<template #fields>
|
|
32
|
-
<slot
|
|
33
|
-
name="fields"
|
|
34
|
-
:model="modelToEdit"
|
|
35
|
-
/>
|
|
36
|
-
</template>
|
|
37
|
-
</edit-form>
|
|
38
|
-
|
|
39
|
-
<a-row class="mt-8">
|
|
40
|
-
<v-btn
|
|
41
|
-
:disabled="!changed || !valid"
|
|
42
|
-
color="green white--text"
|
|
43
|
-
@click="save"
|
|
44
|
-
>
|
|
45
|
-
Speichern
|
|
46
|
-
</v-btn>
|
|
47
|
-
|
|
48
|
-
<v-btn
|
|
49
|
-
v-if="changed"
|
|
50
|
-
text
|
|
51
|
-
@click="reset"
|
|
52
|
-
>
|
|
53
|
-
Zurücksetzen
|
|
54
|
-
</v-btn>
|
|
55
|
-
</a-row>
|
|
56
|
-
</div>
|
|
2
|
+
<edit-form
|
|
3
|
+
ref="form"
|
|
4
|
+
:model="model"
|
|
5
|
+
:createModelToEdit="createModelToEdit"
|
|
6
|
+
>
|
|
7
|
+
<template #default="{model, changed, valid}">
|
|
8
|
+
<slot
|
|
9
|
+
:model="model"
|
|
10
|
+
:changed="changed"
|
|
11
|
+
:valid="valid"
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<edit-form-buttons
|
|
15
|
+
:changed="changed"
|
|
16
|
+
:valid="valid"
|
|
17
|
+
@save="$emit('save', model, ignoreChangesOnRouteChange)"
|
|
18
|
+
@reset="$refs.form.reset()"
|
|
19
|
+
/>
|
|
20
|
+
</template>
|
|
21
|
+
</edit-form>
|
|
57
22
|
</template>
|
|
58
23
|
|
|
59
24
|
<script>
|
|
60
|
-
import { Component,
|
|
61
|
-
import {
|
|
25
|
+
import { Component, Vue } from '@a-vue'
|
|
26
|
+
import { DialogEvent } from '@a-vue/events'
|
|
62
27
|
|
|
63
28
|
@Component({
|
|
64
|
-
props: [
|
|
65
|
-
'model',
|
|
66
|
-
'icon',
|
|
67
|
-
'title',
|
|
68
|
-
'listLink',
|
|
69
|
-
'getAction'
|
|
70
|
-
]
|
|
29
|
+
props: ['model', 'createModelToEdit']
|
|
71
30
|
})
|
|
72
|
-
export default class EditPage extends
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
model_ = null
|
|
31
|
+
export default class EditPage extends Vue {
|
|
32
|
+
unregisterRouterHook = null
|
|
33
|
+
ignoreChangesOnRouteChange_ = false
|
|
76
34
|
|
|
77
35
|
created () {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.reset()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get editConfig () {
|
|
93
|
-
return this.$parent.constructor.getEditRouteConfig(this.$route)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
get modelUpateAction () {
|
|
97
|
-
return this.editConfig.updateAction || this.ModelClass.getAction('save')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
get _getAction () {
|
|
101
|
-
if (this.getAction) {
|
|
102
|
-
return this.getAction
|
|
103
|
-
}
|
|
104
|
-
return this.ModelClass.getAction('get')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
get _icon () {
|
|
108
|
-
return this.icon || this.model.getIcon()
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
get _title () {
|
|
112
|
-
const title = this.modelToEdit.getTitle()
|
|
113
|
-
if (title) {
|
|
114
|
-
return title
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (this.title) {
|
|
118
|
-
return this.title
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const type = this.$apiResources.getType(this.ModelClass.type)
|
|
122
|
-
return type.t('TITLE_EMPTY')
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
get _listLink () {
|
|
126
|
-
if (this.listLink) {
|
|
127
|
-
if (typeof this.listLink === 'function') {
|
|
128
|
-
return this.listLink()
|
|
129
|
-
} else {
|
|
130
|
-
return this.listLink
|
|
36
|
+
this.unregisterRouterHook = this.$router.beforeEach(async (to, from, next) => {
|
|
37
|
+
if (this.$refs.form.changed && !this.ignoreChangesOnRouteChange_) {
|
|
38
|
+
const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
|
|
39
|
+
title: 'Änderungen verwerfen?',
|
|
40
|
+
message: 'Im Formular sind nicht gespeicherte Änderungen. Sollen diese verworfen werden?',
|
|
41
|
+
yesButton: 'Verwerfen'
|
|
42
|
+
}))
|
|
43
|
+
if (result === DialogEvent.RESULT_YES) {
|
|
44
|
+
next()
|
|
45
|
+
}
|
|
46
|
+
return
|
|
131
47
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
createModelToEdit () {
|
|
137
|
-
return this.model_.cloneForEdit(this.fields)
|
|
48
|
+
next()
|
|
49
|
+
})
|
|
138
50
|
}
|
|
139
51
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
id: this.model.id
|
|
143
|
-
}
|
|
52
|
+
destroyed () {
|
|
53
|
+
this.unregisterRouterHook()
|
|
144
54
|
}
|
|
145
55
|
|
|
146
|
-
|
|
147
|
-
this.
|
|
148
|
-
this.reset()
|
|
149
|
-
this.$emit('saved', model)
|
|
56
|
+
ignoreChangesOnRouteChange () {
|
|
57
|
+
this.ignoreChangesOnRouteChange_ = true
|
|
150
58
|
}
|
|
151
59
|
}
|
|
152
60
|
</script>
|
|
@@ -37,8 +37,7 @@ export default class ListPage extends Vue {
|
|
|
37
37
|
return this.title
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
return type.t('TITLE_PLURAL')
|
|
40
|
+
return this.$t('Admin.Types', this.Model.type, null, 'TITLE_PLURAL', 'de')
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
get _newTitle () {
|
|
@@ -46,8 +45,7 @@ export default class ListPage extends Vue {
|
|
|
46
45
|
return this.newTitle
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
return type.t('TITLE_SINGULAR')
|
|
48
|
+
return this.$t('Admin.Types', this.Model.type, null, 'TITLE_SINGULAR', 'de')
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
get _newLink () {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Component, Vue, Watch } from '@a-vue'
|
|
2
|
+
|
|
3
|
+
Component.registerHooks([
|
|
4
|
+
'beforeRouteEnter',
|
|
5
|
+
'beforeRouteUpdate'
|
|
6
|
+
])
|
|
7
|
+
|
|
8
|
+
let onLoadCallback = () => {}
|
|
9
|
+
let routerHookCalled = false
|
|
10
|
+
|
|
11
|
+
function onLoad (callback) {
|
|
12
|
+
onLoadCallback = callback
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function load (route) {
|
|
16
|
+
const Component = [...route.matched].pop().components.default
|
|
17
|
+
|
|
18
|
+
if (!Component.drm_getActions) {
|
|
19
|
+
console.warn('A data route component must implement a static drm_getActions() method.')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const request = Component.drm_getActions(route, onLoad)
|
|
23
|
+
if (Array.isArray(request)) {
|
|
24
|
+
return Promise.all(request.map(request => {
|
|
25
|
+
return request
|
|
26
|
+
.dispatchGlobalLoadingEvents()
|
|
27
|
+
.execute()
|
|
28
|
+
}))
|
|
29
|
+
} else {
|
|
30
|
+
return request
|
|
31
|
+
.dispatchGlobalLoadingEvents()
|
|
32
|
+
.execute()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Component
|
|
37
|
+
export class DataRouteMixin extends Vue {
|
|
38
|
+
drm_isLoaded = false
|
|
39
|
+
|
|
40
|
+
async beforeRouteEnter (to, from, next) {
|
|
41
|
+
routerHookCalled = true
|
|
42
|
+
const result = await load(to)
|
|
43
|
+
|
|
44
|
+
next(vm => {
|
|
45
|
+
if (result !== false) {
|
|
46
|
+
vm.drm_onLoad(result)
|
|
47
|
+
vm.drm_isLoaded = true
|
|
48
|
+
}
|
|
49
|
+
routerHookCalled = false
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* triggered both, if route name or route params change
|
|
55
|
+
*/
|
|
56
|
+
@Watch('$route.params')
|
|
57
|
+
async routeParamsChanged () {
|
|
58
|
+
if (routerHookCalled) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!this.drm_isLoaded) {
|
|
63
|
+
const result = await load(this.$route)
|
|
64
|
+
|
|
65
|
+
if (result !== false) {
|
|
66
|
+
this.drm_onLoad(result)
|
|
67
|
+
this.drm_isLoaded = true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
drm_onLoad (result) {
|
|
73
|
+
onLoadCallback(this, result)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { translationService } from '@a-admin/services/TranslationService'
|
|
1
2
|
import { Model as ApiResourcesModel, apiResources } from '@afeefa/api-resources-client'
|
|
2
3
|
import { mdiAlphaMCircle } from '@mdi/js'
|
|
3
4
|
|
|
@@ -16,11 +17,11 @@ export class Model extends ApiResourcesModel {
|
|
|
16
17
|
return (new this()).getLink(action)
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
static getAction (
|
|
20
|
+
static getAction (actionName) {
|
|
20
21
|
if (this.resourceType) {
|
|
21
22
|
return apiResources.getAction({
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
resourceType: this.resourceType,
|
|
24
|
+
actionName
|
|
24
25
|
})
|
|
25
26
|
}
|
|
26
27
|
console.warn('You can\'t get an action out of a model without resourceType:', this.type)
|
|
@@ -39,6 +40,10 @@ export class Model extends ApiResourcesModel {
|
|
|
39
40
|
color: 'blue lighten-2'
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
static translate (realm, objectType, objectId, key, lang) {
|
|
44
|
+
return translationService.translate(realm, objectType, objectId, key, lang)
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
getLink (action = 'detail') {
|
|
43
48
|
return {
|
|
44
49
|
name: `${this.constructor.routeName}.${action}`,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { translationService } from '../../services/TranslationService'
|
|
2
|
+
|
|
3
|
+
class TranslationPlugin {
|
|
4
|
+
setTranslations (translations) {
|
|
5
|
+
translationService
|
|
6
|
+
.setTranslations(translations)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
install (Vue) {
|
|
10
|
+
Vue.mixin({
|
|
11
|
+
created () {
|
|
12
|
+
this.$t = (realm, objectId, objectType, key, lang) => {
|
|
13
|
+
return translationService.translate(realm, objectId, objectType, key, lang)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const translationPlugin = new TranslationPlugin()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class TranslationService {
|
|
2
|
+
translations = {}
|
|
3
|
+
|
|
4
|
+
setTranslations (translations) {
|
|
5
|
+
translations.forEach(t => {
|
|
6
|
+
const key = JSON.stringify([
|
|
7
|
+
t.realm,
|
|
8
|
+
t.object_type,
|
|
9
|
+
t.object_id,
|
|
10
|
+
t.key,
|
|
11
|
+
t.lang
|
|
12
|
+
])
|
|
13
|
+
this.translations[key] = t.value
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
translate (realm, objectType, objectId, key, lang) {
|
|
18
|
+
const tKey = JSON.stringify([
|
|
19
|
+
realm,
|
|
20
|
+
objectType,
|
|
21
|
+
objectId,
|
|
22
|
+
key,
|
|
23
|
+
lang
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
if (!this.translations[tKey]) {
|
|
27
|
+
console.warn(`No translation found for ${tKey}`)
|
|
28
|
+
return `${key}:${lang}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this.translations[tKey]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const translationService = new TranslationService()
|