@afeefa/vue-app 0.0.59 → 0.0.62

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1 @@
1
- 0.0.59
1
+ 0.0.62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.59",
3
+ "version": "0.0.62",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -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
+ }
@@ -151,9 +151,11 @@ export default class ARichTextArea extends Vue {
151
151
  },
152
152
  onFocus: ({ editor, event }) => {
153
153
  this.focus = true
154
+ this.$emit('focus')
154
155
  },
155
156
  onBlur: ({ editor, event }) => {
156
157
  this.focus = false
158
+ this.$emit('blur')
157
159
  }
158
160
  })
159
161
 
@@ -45,7 +45,7 @@
45
45
  <div :class="listCssClass">
46
46
  <search-select-list
47
47
  v-if="isOpen"
48
- :listViewConfig="listViewConfig"
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
- 'listViewConfig',
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="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: ['model']
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,17 +48,25 @@ 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('model', {immediate: true})
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.lastJson = this.json
39
- this.$emit('update:changed', this.changed)
60
+ this.reset()
40
61
  }
41
62
 
42
63
  get json () {
43
- return JSON.stringify(this.model)
64
+ return JSON.stringify(this.modelToEdit)
44
65
  }
45
66
 
46
67
  get changed () {
68
+ // console.log(this.json)
69
+ // console.log(this.lastJson)
47
70
  return this.json !== this.lastJson
48
71
  }
49
72
 
@@ -9,7 +9,7 @@ export class FormFieldMixin extends Vue {
9
9
  let parent = this
10
10
  while (parent) {
11
11
  if (parent.EDIT_FORM) {
12
- return parent.model
12
+ return parent.modelToEdit
13
13
  }
14
14
  parent = parent.$parent
15
15
  }
@@ -8,7 +8,7 @@ import { FilterSourceType } from './FilterSourceType'
8
8
  @Component({
9
9
  props: [
10
10
  'models', 'meta', // given, if already loaded
11
- 'listViewConfig',
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.listViewConfig)
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)
@@ -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: ['prepend']
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
- section.appendChild(this.$el)
20
+ if (this.prepend) {
21
+ section.prepend(this.$el)
22
+ } else {
23
+ section.append(this.$el)
24
+ }
17
25
  }
18
26
 
19
27
  destroyed () {
@@ -60,7 +60,7 @@
60
60
  </div>
61
61
 
62
62
  <a-search-select
63
- :listViewConfig="selectableConfig.listViewConfig"
63
+ :listAction="selectableConfig.listAction"
64
64
  :loadOnlyIfKeyword="false"
65
65
  :selectedItems="selectableSelectedItems"
66
66
  :width="selectableWidth"
@@ -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 Start from './Start.vue'
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)
@@ -30,6 +30,7 @@
30
30
  <a-row class="mt-6">
31
31
  <v-btn
32
32
  :disabled="!changed || !valid"
33
+ color="green white--text"
33
34
  @click="save"
34
35
  >
35
36
  Anlegen
@@ -55,16 +56,15 @@ import { EditPageMixin } from './EditPageMixin'
55
56
  })
56
57
  export default class CreatePage extends Mixins(EditPageMixin) {
57
58
  created () {
58
- if (!this.$parent.constructor.createRouteConfig) {
59
+ if (!this.$parent.constructor.getCreateRouteConfig) {
59
60
  console.warn('<create-page> owner must provide a static createRouteConfig method.')
60
61
  }
61
62
 
62
63
  this.reset()
63
- this.$emit('model', this.modelToEdit)
64
64
  }
65
65
 
66
66
  get editConfig () {
67
- return this.$parent.constructor.createRouteConfig
67
+ return this.$parent.constructor.getCreateRouteConfig(this.$route)
68
68
  }
69
69
 
70
70
  get modelUpateAction () {
@@ -85,8 +85,7 @@ export default class CreatePage extends Mixins(EditPageMixin) {
85
85
  return this.title
86
86
  }
87
87
 
88
- const type = this.$apiResources.getType(this.ModelClass.type)
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,146 +1,55 @@
1
1
  <template>
2
- <div class="editPage">
3
- <app-bar-title
4
- :icon="_icon"
5
- :title="_title"
6
- />
7
-
8
- <app-bar-button v-if="$has.list">
9
- <v-btn
10
- :to="_listLink"
11
- small
12
- >
13
- Liste
14
- </v-btn>
15
- </app-bar-button>
16
-
17
- <app-bar-button v-if="$has.detail">
18
- <v-btn
19
- :to="model.getLink()"
20
- small
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)"
18
+ @reset="$refs.form.reset()"
19
+ />
20
+ </template>
21
+ </edit-form>
57
22
  </template>
58
23
 
59
24
  <script>
60
- import { Component, Mixins, Watch } from '@a-vue'
61
- import { EditPageMixin } from './EditPageMixin'
25
+ import { Component, Vue } from '@a-vue'
26
+ import { DialogEvent } from '@a-vue/events'
62
27
 
63
28
  @Component({
64
- props: ['model', 'icon', 'title', 'listLink', 'getAction']
29
+ props: ['model', 'createModelToEdit']
65
30
  })
66
- export default class EditPage extends Mixins(EditPageMixin) {
67
- $hasOptions = ['detail', {list: false}]
68
-
69
- model_ = null
31
+ export default class EditPage extends Vue {
32
+ unregisterRouterHook = null
70
33
 
71
34
  created () {
72
- if (!this.$parent.constructor.getEditRouteConfig) {
73
- console.warn('<edit-page> owner must provide a static getEditRouteConfig method.')
74
- }
75
-
76
- this.model_ = this.model
77
- this.reset()
78
- }
79
-
80
- @Watch('model')
81
- modelChanged () {
82
- this.model_ = this.model
83
- this.reset()
84
- }
85
-
86
- get editConfig () {
87
- return this.$parent.constructor.getEditRouteConfig(this.$route)
88
- }
89
-
90
- get modelUpateAction () {
91
- return this.editConfig.updateAction || this.ModelClass.getAction('save')
92
- }
93
-
94
- get _getAction () {
95
- if (this.getAction) {
96
- return this.getAction
97
- }
98
- return this.ModelClass.getAction('get')
99
- }
100
-
101
- get _icon () {
102
- return this.icon || this.model.getIcon()
103
- }
104
-
105
- get _title () {
106
- const title = this.modelToEdit.getTitle()
107
- if (title) {
108
- return title
109
- }
110
-
111
- if (this.title) {
112
- return this.title
113
- }
114
-
115
- const type = this.$apiResources.getType(this.ModelClass.type)
116
- return type.t('TITLE_EMPTY')
117
- }
118
-
119
- get _listLink () {
120
- if (this.listLink) {
121
- if (typeof this.listLink === 'function') {
122
- return this.listLink()
123
- } else {
124
- return this.listLink
35
+ this.unregisterRouterHook = this.$router.beforeEach(async (to, from, next) => {
36
+ if (this.$refs.form.changed) {
37
+ const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
38
+ title: 'Änderungen verwerfen?',
39
+ message: 'Im Formular sind nicht gespeicherte Änderungen. Sollen diese verworfen werden?',
40
+ yesButton: 'Verwerfen'
41
+ }))
42
+ if (result === DialogEvent.RESULT_YES) {
43
+ next()
44
+ }
45
+ return
125
46
  }
126
- }
127
- return this.model.getLink('list')
128
- }
129
-
130
- createModelToEdit () {
131
- return this.model_.cloneForEdit(this.fields)
132
- }
133
-
134
- get saveParams () {
135
- return {
136
- id: this.model.id
137
- }
47
+ next()
48
+ })
138
49
  }
139
50
 
140
- afterSave (model) {
141
- this.model_ = model
142
- this.reset()
143
- this.$emit('saved', model)
51
+ destroyed () {
52
+ this.unregisterRouterHook()
144
53
  }
145
54
  }
146
55
  </script>
@@ -78,7 +78,11 @@ export class EditPageMixin extends Vue {
78
78
  }
79
79
 
80
80
  reset () {
81
- this.modelToEdit = this.createModelToEdit()
81
+ const modelToEdit = this.createModelToEdit()
82
+ if (this.editConfig.createModelToEdit) {
83
+ this.editConfig.createModelToEdit(modelToEdit)
84
+ }
85
+ this.modelToEdit = modelToEdit // this assignment makes modelToEdit recursively reactive
82
86
  this.$emit('model', this.modelToEdit)
83
87
  }
84
88
 
@@ -37,8 +37,7 @@ export default class ListPage extends Vue {
37
37
  return this.title
38
38
  }
39
39
 
40
- const type = this.$apiResources.getType(this.Model.type)
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
- const type = this.$apiResources.getType(this.Model.type)
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 (action) {
20
+ static getAction (actionName) {
20
21
  if (this.resourceType) {
21
22
  return apiResources.getAction({
22
- resource: this.resourceType,
23
- action
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()