@afeefa/vue-app 0.0.61 → 0.0.62

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.
@@ -1 +1 @@
1
- 0.0.61
1
+ 0.0.62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.61",
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
+ }
@@ -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,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('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 () {
@@ -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)
@@ -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)
@@ -1,151 +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: [
65
- 'model',
66
- 'icon',
67
- 'title',
68
- 'listLink',
69
- 'getAction'
70
- ]
29
+ props: ['model', 'createModelToEdit']
71
30
  })
72
- export default class EditPage extends Mixins(EditPageMixin) {
73
- $hasOptions = ['detail', {list: false}]
74
-
75
- model_ = null
31
+ export default class EditPage extends Vue {
32
+ unregisterRouterHook = null
76
33
 
77
34
  created () {
78
- if (!this.$parent.constructor.getEditRouteConfig) {
79
- console.warn('<edit-page> owner must provide a static getEditRouteConfig method.')
80
- }
81
-
82
- this.model_ = this.model
83
- this.reset()
84
- }
85
-
86
- @Watch('model')
87
- modelChanged () {
88
- this.model_ = this.model
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
- return this.$t('Admin.Types', this.ModelClass.type, null, 'TITLE_EMPTY', 'de')
122
- }
123
-
124
- get _listLink () {
125
- if (this.listLink) {
126
- if (typeof this.listLink === 'function') {
127
- return this.listLink()
128
- } else {
129
- 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
130
46
  }
131
- }
132
- return this.model.getLink('list')
133
- }
134
-
135
- createModelToEdit () {
136
- return this.model_.cloneForEdit(this.fields)
137
- }
138
-
139
- get saveParams () {
140
- return {
141
- id: this.model.id
142
- }
47
+ next()
48
+ })
143
49
  }
144
50
 
145
- afterSave (model) {
146
- this.model_ = model
147
- this.reset()
148
- this.$emit('saved', model)
51
+ destroyed () {
52
+ this.unregisterRouterHook()
149
53
  }
150
54
  }
151
55
  </script>
@@ -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
+ }
@@ -17,11 +17,11 @@ export class Model extends ApiResourcesModel {
17
17
  return (new this()).getLink(action)
18
18
  }
19
19
 
20
- static getAction (action) {
20
+ static getAction (actionName) {
21
21
  if (this.resourceType) {
22
22
  return apiResources.getAction({
23
- resource: this.resourceType,
24
- action
23
+ resourceType: this.resourceType,
24
+ actionName
25
25
  })
26
26
  }
27
27
  console.warn('You can\'t get an action out of a model without resourceType:', this.type)