@afeefa/vue-app 0.0.46 → 0.0.50
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/package.json +1 -1
- package/src/api-resources/ApiActions.js +4 -55
- package/src/components/AModal.vue +1 -2
- package/src/components/ASearchSelect.vue +5 -6
- package/src/components/ASelect.vue +10 -2
- package/src/components/list/{RouteFilterSource.js → CurrentRouteFilterSource.js} +2 -2
- package/src/components/list/FilterSourceType.js +7 -0
- package/src/components/list/ListFilterMixin.js +0 -3
- package/src/components/list/ListViewMixin.js +51 -41
- package/src/components/list/NextRouteFilterSource.js +15 -0
- package/src/components/list/filters/ListFilterSelect.vue +29 -24
- package/src/components/search-select/SearchSelectList.vue +4 -1
- package/src/plugins/api-resources/ApiResourcesPlugin.js +13 -0
- package/src/utils/props-helper.js +21 -0
- package/src-admin/bootstrap.js +2 -0
- package/src-admin/components/Splash.vue +8 -1
- package/src-admin/components/detail/DetailProperty.vue +5 -5
- package/src-admin/components/list/ListView.vue +6 -1
- package/src-admin/components/pages/CreatePage.vue +9 -12
- package/src-admin/components/pages/DetailPage.vue +5 -8
- package/src-admin/components/pages/EditPage.vue +10 -13
- package/src-admin/components/pages/ListPage.vue +8 -18
- package/src-admin/components/routes/DetailRoute.vue +7 -2
- package/src-admin/components/routes/EditRoute.vue +7 -2
- package/src-admin/components/routes/ListRoute.vue +20 -6
- package/src-admin/models/Model.js +10 -7
- package/src-admin/styles.scss +4 -2
- package/src/components/list/QuerySourceType.js +0 -4
- package/src/components/list/RouteParamsFilterSource.js +0 -18
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.50
|
package/package.json
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
import { RouteParamsFilterSource } from '@a-vue/components/list/RouteParamsFilterSource'
|
2
1
|
import { AlertEvent, DialogEvent, LoadingEvent, SaveEvent } from '@a-vue/events'
|
3
2
|
import { eventBus } from '@a-vue/plugins/event-bus/EventBus'
|
4
3
|
import { sleep } from '@a-vue/utils/timeout'
|
5
|
-
import { RequestFilters } from '@afeefa/api-resources-client'
|
6
4
|
|
7
5
|
export class GetAction {
|
8
6
|
action = null
|
@@ -35,7 +33,7 @@ export class GetAction {
|
|
35
33
|
eventBus.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
|
36
34
|
}
|
37
35
|
|
38
|
-
const result = await this.action.
|
36
|
+
const result = await this.action.createRequest()
|
39
37
|
.params({
|
40
38
|
id: this.id
|
41
39
|
})
|
@@ -111,7 +109,7 @@ export class SaveAction {
|
|
111
109
|
|
112
110
|
const startTime = Date.now()
|
113
111
|
|
114
|
-
const result = await this.action.
|
112
|
+
const result = await this.action.createRequest()
|
115
113
|
.params({
|
116
114
|
id: this.id || undefined
|
117
115
|
})
|
@@ -195,7 +193,7 @@ export class RemoveAction {
|
|
195
193
|
|
196
194
|
const startTime = Date.now()
|
197
195
|
|
198
|
-
const result = await this.action.
|
196
|
+
const result = await this.action.createRequest()
|
199
197
|
.params({
|
200
198
|
id: this.id
|
201
199
|
})
|
@@ -230,11 +228,6 @@ export class RemoveAction {
|
|
230
228
|
|
231
229
|
export class ListAction {
|
232
230
|
request = null
|
233
|
-
action = null
|
234
|
-
fields = null
|
235
|
-
scopes = {}
|
236
|
-
filters = {}
|
237
|
-
route = null
|
238
231
|
events = true
|
239
232
|
|
240
233
|
setRequest (request) {
|
@@ -242,31 +235,6 @@ export class ListAction {
|
|
242
235
|
return this
|
243
236
|
}
|
244
237
|
|
245
|
-
setAction (action) {
|
246
|
-
this.action = action
|
247
|
-
return this
|
248
|
-
}
|
249
|
-
|
250
|
-
setFiltersForRoute (route) {
|
251
|
-
this.route = route
|
252
|
-
return this
|
253
|
-
}
|
254
|
-
|
255
|
-
setFields (fields) {
|
256
|
-
this.fields = fields
|
257
|
-
return this
|
258
|
-
}
|
259
|
-
|
260
|
-
setScopes (scopes) {
|
261
|
-
this.scopes = scopes
|
262
|
-
return this
|
263
|
-
}
|
264
|
-
|
265
|
-
setFilters (filters) {
|
266
|
-
this.filters = filters
|
267
|
-
return this
|
268
|
-
}
|
269
|
-
|
270
238
|
noEvents (noEvents) {
|
271
239
|
this.events = noEvents === undefined ? false : !noEvents
|
272
240
|
return this
|
@@ -277,26 +245,7 @@ export class ListAction {
|
|
277
245
|
eventBus.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
|
278
246
|
}
|
279
247
|
|
280
|
-
|
281
|
-
|
282
|
-
if (this.route) {
|
283
|
-
const querySource = new RouteParamsFilterSource(this.route)
|
284
|
-
const requestFilters = this.action.createRequestFilters(null, querySource)
|
285
|
-
const storedFilters = RequestFilters.fromHistory(this.route.path)
|
286
|
-
if (storedFilters) {
|
287
|
-
requestFilters.initFromUsed(storedFilters.serialize(), 1)
|
288
|
-
}
|
289
|
-
filters = requestFilters.serialize()
|
290
|
-
}
|
291
|
-
|
292
|
-
const request = this.request ||
|
293
|
-
this.action
|
294
|
-
.request()
|
295
|
-
.scopes(this.scopes)
|
296
|
-
.filters(filters)
|
297
|
-
.fields(this.fields)
|
298
|
-
|
299
|
-
const result = await request.send()
|
248
|
+
const result = await this.request.send()
|
300
249
|
|
301
250
|
if (result.error) {
|
302
251
|
if (this.events) {
|
@@ -64,7 +64,7 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
|
|
64
64
|
}
|
65
65
|
|
66
66
|
mounted () {
|
67
|
-
// monkey patch
|
67
|
+
// monkey patch onFocusin to allow non vuetify-popups to receive focus
|
68
68
|
const dialog = this.$refs.dialog
|
69
69
|
const onFocusin = dialog.onFocusin
|
70
70
|
dialog.onFocusin = e => {
|
@@ -127,7 +127,6 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
|
|
127
127
|
|
128
128
|
createTransientAnchor () {
|
129
129
|
let transientAnchorEl = document.querySelector('.' + this.transientAnchorClass)
|
130
|
-
console.log('anchor', this.anchorPosition)
|
131
130
|
if (!transientAnchorEl) {
|
132
131
|
transientAnchorEl = document.createElement('div')
|
133
132
|
transientAnchorEl.classList.add(this.transientAnchorClass)
|
@@ -46,11 +46,10 @@
|
|
46
46
|
v-if="isOpen"
|
47
47
|
v-bind="listConfig"
|
48
48
|
:q="q"
|
49
|
-
:
|
50
|
-
:
|
49
|
+
:events="false"
|
50
|
+
:history="false"
|
51
51
|
:filterSource="filterSource"
|
52
52
|
:loadOnlyIfKeyword="_loadOnlyIfKeyword"
|
53
|
-
:filters.sync="filters"
|
54
53
|
:count.sync="count"
|
55
54
|
:isLoading.sync="isLoading"
|
56
55
|
>
|
@@ -76,7 +75,7 @@
|
|
76
75
|
<div class="lastColumn" />
|
77
76
|
</template>
|
78
77
|
|
79
|
-
<template #not-found>
|
78
|
+
<template #not-found="{ filters }">
|
80
79
|
<slot
|
81
80
|
name="not-found"
|
82
81
|
:filters="filters"
|
@@ -93,7 +92,7 @@ import { Component, Watch, Mixins } from 'vue-property-decorator'
|
|
93
92
|
import { UsesPositionServiceMixin } from '../services/position/UsesPositionServiceMixin'
|
94
93
|
import { PositionConfig } from '../services/PositionService'
|
95
94
|
import { randomCssClass } from '../utils/random'
|
96
|
-
import {
|
95
|
+
import { FilterSourceType } from '@a-vue/components/list/FilterSourceType'
|
97
96
|
import SearchSelectFilters from './search-select/SearchSelectFilters'
|
98
97
|
import SearchSelectList from './search-select/SearchSelectList'
|
99
98
|
import { CancelOnEscMixin } from '@a-vue/services/escape/CancelOnEscMixin'
|
@@ -109,7 +108,7 @@ export default class ASearchSelect extends Mixins(UsesPositionServiceMixin, Canc
|
|
109
108
|
selectId = randomCssClass(10)
|
110
109
|
isOpen = false
|
111
110
|
items_ = []
|
112
|
-
filterSource =
|
111
|
+
filterSource = FilterSourceType.OBJECT
|
113
112
|
|
114
113
|
isLoading = false
|
115
114
|
filters = []
|
@@ -23,14 +23,22 @@ export default class ASelect extends Mixins(ComponentWidthMixin) {
|
|
23
23
|
items_ = []
|
24
24
|
|
25
25
|
mounted () {
|
26
|
-
// monkey patch v-select
|
26
|
+
// monkey patch v-select to set default value on clear
|
27
|
+
const clearableCallback = this.select.clearableCallback
|
28
|
+
this.select.clearableCallback = () => {
|
29
|
+
this.select.isClear = true
|
30
|
+
clearableCallback()
|
31
|
+
this.select.isClear = false
|
32
|
+
}
|
33
|
+
|
27
34
|
const setValue = this.select.setValue
|
28
35
|
this.select.setValue = value => {
|
29
|
-
if (
|
36
|
+
if (this.select.isClear) {
|
30
37
|
value = this.defaultValue || null
|
31
38
|
}
|
32
39
|
setValue(value)
|
33
40
|
}
|
41
|
+
|
34
42
|
this.init()
|
35
43
|
}
|
36
44
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import {
|
1
|
+
import { ListViewFilterSource } from '@afeefa/api-resources-client'
|
2
2
|
|
3
|
-
export class
|
3
|
+
export class CurrentRouteFilterSource extends ListViewFilterSource {
|
4
4
|
router = null
|
5
5
|
|
6
6
|
constructor (router) {
|
@@ -1,23 +1,30 @@
|
|
1
1
|
import { ListAction } from '@a-vue/api-resources/ApiActions'
|
2
|
+
import { propsWithDefaults } from '@a-vue/utils/props-helper'
|
3
|
+
import { ListViewModel } from '@afeefa/api-resources-client'
|
2
4
|
import { Component, Vue, Watch } from 'vue-property-decorator'
|
3
5
|
|
4
|
-
import {
|
5
|
-
import {
|
6
|
+
import { CurrentRouteFilterSource } from './CurrentRouteFilterSource'
|
7
|
+
import { FilterSourceType } from './FilterSourceType'
|
6
8
|
|
7
9
|
@Component({
|
8
|
-
|
9
|
-
'models', 'meta', // if already loaded
|
10
|
-
'
|
11
|
-
'
|
12
|
-
'loadOnlyIfKeyword'
|
13
|
-
|
10
|
+
...propsWithDefaults([
|
11
|
+
'models', 'meta', // given, if already loaded
|
12
|
+
'listViewConfig',
|
13
|
+
'filterHistoryKey',
|
14
|
+
'loadOnlyIfKeyword',
|
15
|
+
{
|
16
|
+
filterSource: FilterSourceType.QUERY_STRING,
|
17
|
+
events: true,
|
18
|
+
history: true
|
19
|
+
}
|
20
|
+
])
|
14
21
|
})
|
15
22
|
export class ListViewMixin extends Vue {
|
16
23
|
LIST_VIEW = true
|
17
24
|
|
25
|
+
listViewModel = null
|
18
26
|
models_ = []
|
19
27
|
meta_ = {}
|
20
|
-
requestFilters = null
|
21
28
|
isLoading = false
|
22
29
|
|
23
30
|
created () {
|
@@ -25,37 +32,41 @@ export class ListViewMixin extends Vue {
|
|
25
32
|
}
|
26
33
|
|
27
34
|
destroyed () {
|
28
|
-
this.
|
35
|
+
this.listViewModel.off('change', this.filtersChanged)
|
29
36
|
}
|
30
37
|
|
31
38
|
init () {
|
32
|
-
if (this.
|
33
|
-
this
|
34
|
-
this.
|
35
|
-
}
|
36
|
-
|
37
|
-
if (this.requestFilters) {
|
38
|
-
this.requestFilters.off('change', this.filtersChanged)
|
39
|
+
if (this.listViewModel) {
|
40
|
+
// this can happen only on HMR-reload
|
41
|
+
this.listViewModel.off('change', this.filtersChanged)
|
39
42
|
}
|
40
43
|
|
41
|
-
const historyKey = this.
|
42
|
-
?
|
43
|
-
:
|
44
|
-
const
|
45
|
-
|
44
|
+
const historyKey = this.history
|
45
|
+
? [this.$route.path, this.filterHistoryKey].filter(i => i).join('.')
|
46
|
+
: undefined
|
47
|
+
const filterSource = this.filterSource === FilterSourceType.QUERY_STRING
|
48
|
+
? new CurrentRouteFilterSource(this.$router)
|
49
|
+
: undefined
|
46
50
|
|
47
|
-
if (this.
|
48
|
-
|
49
|
-
this.
|
51
|
+
if (this.models) {
|
52
|
+
this.models_ = this.models
|
53
|
+
this.meta_ = this.meta
|
50
54
|
}
|
51
55
|
|
52
|
-
this.
|
56
|
+
this.listViewModel = new ListViewModel(this.listViewConfig)
|
57
|
+
.filterSource(filterSource, !!filterSource)
|
58
|
+
.historyKey(historyKey, this.history)
|
59
|
+
.usedFilters(this.meta_.used_filters || null, this.meta_.count_search || 0)
|
60
|
+
.initFilters({
|
61
|
+
source: !!filterSource,
|
62
|
+
history: !!historyKey,
|
63
|
+
used: !!this.models
|
64
|
+
})
|
65
|
+
.on('change', this.filtersChanged) // listen to change
|
53
66
|
|
54
|
-
this.$emit('update:filters', this.filters)
|
55
67
|
this._filtersInitialized()
|
56
68
|
|
57
69
|
if (this.models) {
|
58
|
-
this.requestFilters.initFromUsed(this.meta_.used_filters, this.meta_.count_search)
|
59
70
|
this.$emit('update:count', this.meta_.count_search)
|
60
71
|
} else {
|
61
72
|
this.load()
|
@@ -64,8 +75,8 @@ export class ListViewMixin extends Vue {
|
|
64
75
|
|
65
76
|
@Watch('$route.query')
|
66
77
|
routeQueryChanged () {
|
67
|
-
if (this.filterSource
|
68
|
-
this.
|
78
|
+
if (this.filterSource === FilterSourceType.QUERY_STRING) {
|
79
|
+
this.listViewModel.filterSourceChanged()
|
69
80
|
}
|
70
81
|
}
|
71
82
|
|
@@ -79,16 +90,15 @@ export class ListViewMixin extends Vue {
|
|
79
90
|
}
|
80
91
|
|
81
92
|
filtersChanged () {
|
82
|
-
console.log('filters changed')
|
83
93
|
this.load()
|
84
94
|
}
|
85
95
|
|
86
96
|
resetFilters () {
|
87
|
-
this.
|
97
|
+
this.listViewModel.resetFilters()
|
88
98
|
}
|
89
99
|
|
90
100
|
get filters () {
|
91
|
-
return this.
|
101
|
+
return this.listViewModel.getFilters().getEntries()
|
92
102
|
}
|
93
103
|
|
94
104
|
get count () {
|
@@ -110,16 +120,16 @@ export class ListViewMixin extends Vue {
|
|
110
120
|
this.isLoading = true
|
111
121
|
this.$emit('update:isLoading', this.isLoading)
|
112
122
|
|
123
|
+
const request = this.listViewModel.getApiRequest()
|
124
|
+
|
113
125
|
const {models, meta} = await new ListAction()
|
114
|
-
.
|
115
|
-
.
|
116
|
-
.setFilters(this.requestFilters.serialize())
|
117
|
-
.setFields(this.fields)
|
118
|
-
.noEvents(this.noEvents)
|
126
|
+
.setRequest(request)
|
127
|
+
.noEvents(!this.events)
|
119
128
|
.load()
|
120
129
|
|
121
|
-
if (!models) { // error
|
122
|
-
this.
|
130
|
+
if (!models) { // error happened
|
131
|
+
this.isLoading = false
|
132
|
+
this.$emit('update:isLoading', this.isLoading)
|
123
133
|
return
|
124
134
|
}
|
125
135
|
|
@@ -127,7 +137,7 @@ export class ListViewMixin extends Vue {
|
|
127
137
|
this.meta_ = meta
|
128
138
|
|
129
139
|
if (this.meta_.used_filters) {
|
130
|
-
this.
|
140
|
+
this.listViewModel.initFromUsedFilters(this.meta_.used_filters, this.meta_.count_search)
|
131
141
|
}
|
132
142
|
|
133
143
|
this.isLoading = false
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { ListViewFilterSource } from '@afeefa/api-resources-client'
|
2
|
+
|
3
|
+
export class NextRouteFilterSource extends ListViewFilterSource {
|
4
|
+
route = null
|
5
|
+
|
6
|
+
constructor (route) {
|
7
|
+
super()
|
8
|
+
|
9
|
+
this.route = route
|
10
|
+
}
|
11
|
+
|
12
|
+
getQuery () {
|
13
|
+
return this.route.query
|
14
|
+
}
|
15
|
+
}
|
@@ -5,7 +5,8 @@
|
|
5
5
|
:items="_items"
|
6
6
|
itemText="itemTitle"
|
7
7
|
itemValue="itemValue"
|
8
|
-
:clearable="filter.value !==
|
8
|
+
:clearable="filter.value !== filter.defaultValue"
|
9
|
+
:defaultValue="filter.defaultValue"
|
9
10
|
v-bind="$attrs"
|
10
11
|
/>
|
11
12
|
</template>
|
@@ -16,16 +17,14 @@ import { Component, Mixins } from 'vue-property-decorator'
|
|
16
17
|
import { ListFilterMixin } from '../ListFilterMixin'
|
17
18
|
import { ListAction } from '@a-vue/api-resources/ApiActions'
|
18
19
|
|
19
|
-
@Component
|
20
|
-
props: ['totalVisible']
|
21
|
-
})
|
20
|
+
@Component
|
22
21
|
export default class ListFilterSelect extends Mixins(ListFilterMixin) {
|
23
22
|
items = null
|
24
23
|
|
25
24
|
created () {
|
26
|
-
if (this.hasOptionsRequest()) {
|
25
|
+
if (this.filter.hasOptionsRequest()) {
|
27
26
|
this.items = this.loadRequestOptions()
|
28
|
-
} else if (this.hasOptions()) {
|
27
|
+
} else if (this.filter.hasOptions()) {
|
29
28
|
this.items = this.getOptions()
|
30
29
|
}
|
31
30
|
}
|
@@ -34,43 +33,49 @@ export default class ListFilterSelect extends Mixins(ListFilterMixin) {
|
|
34
33
|
return this.$attrs.items || this.items || []
|
35
34
|
}
|
36
35
|
|
37
|
-
|
38
|
-
|
36
|
+
get showNullOption () {
|
37
|
+
// either null is a selectable option, than it should be shown in the list
|
38
|
+
// or the default is null, so the list should offer a 'null' option for the unselected state
|
39
|
+
return this.filter.nullIsOption || this.filter.defaultValue === null
|
39
40
|
}
|
40
41
|
|
41
|
-
|
42
|
-
|
42
|
+
createOptions () {
|
43
|
+
const options = []
|
44
|
+
|
45
|
+
if (this.showNullOption) {
|
46
|
+
options.push({
|
47
|
+
itemTitle: 'Alle',
|
48
|
+
itemValue: null
|
49
|
+
})
|
50
|
+
}
|
51
|
+
|
52
|
+
return options
|
43
53
|
}
|
44
54
|
|
45
55
|
async loadRequestOptions () {
|
46
56
|
const {models} = await new ListAction()
|
47
|
-
.setRequest(this.filter.
|
57
|
+
.setRequest(this.filter.createOptionsRequest())
|
48
58
|
.noEvents()
|
49
59
|
.load()
|
50
60
|
|
51
61
|
return [
|
52
|
-
|
53
|
-
itemTitle: 'Alle',
|
54
|
-
itemValue: null
|
55
|
-
},
|
62
|
+
...this.createOptions(),
|
56
63
|
...models.map(model => ({
|
57
64
|
itemTitle: model.name,
|
58
65
|
itemValue: model.id
|
59
66
|
}))
|
60
|
-
|
61
67
|
]
|
62
68
|
}
|
63
69
|
|
64
70
|
getOptions () {
|
65
71
|
return [
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
}))
|
72
|
+
...this.createOptions(),
|
73
|
+
...this.filter.options
|
74
|
+
.filter(o => o !== null) // null is already set in options (if any)
|
75
|
+
.map(o => ({
|
76
|
+
itemTitle: o ? 'Ja' : 'Nein',
|
77
|
+
itemValue: o
|
78
|
+
}))
|
74
79
|
]
|
75
80
|
}
|
76
81
|
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { apiResources } from '@afeefa/api-resources-client'
|
2
|
+
|
3
|
+
class ApiResourcesPlugin {
|
4
|
+
install (Vue) {
|
5
|
+
Object.defineProperty(Vue.prototype, '$apiResources', {
|
6
|
+
get () {
|
7
|
+
return apiResources
|
8
|
+
}
|
9
|
+
})
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
export const apiResourcesPlugin = new ApiResourcesPlugin()
|
@@ -0,0 +1,21 @@
|
|
1
|
+
export function propsWithDefaults (props) {
|
2
|
+
const normalizedProps = {}
|
3
|
+
|
4
|
+
for (const prop of props) {
|
5
|
+
if (typeof prop === 'object') {
|
6
|
+
Object.keys(prop).forEach(subProp => {
|
7
|
+
if (typeof prop[subProp] === 'object') {
|
8
|
+
normalizedProps[subProp] = prop[subProp]
|
9
|
+
} else {
|
10
|
+
normalizedProps[subProp] = { default: prop[subProp] }
|
11
|
+
}
|
12
|
+
})
|
13
|
+
} else {
|
14
|
+
normalizedProps[prop] = null
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
return {
|
19
|
+
props: normalizedProps
|
20
|
+
}
|
21
|
+
}
|
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 { apiResourcesPlugin } from '@a-vue/plugins/api-resources/ApiResourcesPlugin'
|
4
5
|
import { hasOptionsPlugin } from '@a-vue/plugins/has-options/HasOptionsPlugin'
|
5
6
|
import { timeout } from '@a-vue/utils/timeout'
|
6
7
|
import { apiResources } from '@afeefa/api-resources-client'
|
@@ -10,6 +11,7 @@ import { appConfig } from './config/AppConfig'
|
|
10
11
|
import routeConfigPlugin from './config/routing'
|
11
12
|
import vuetify from './config/vuetify'
|
12
13
|
|
14
|
+
Vue.use(apiResourcesPlugin)
|
13
15
|
Vue.use(hasOptionsPlugin)
|
14
16
|
|
15
17
|
Vue.config.productionTip = false
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<template>
|
2
|
-
<v-app>
|
2
|
+
<v-app class="splash">
|
3
3
|
<v-container
|
4
4
|
fill-height
|
5
5
|
fluid
|
@@ -49,6 +49,13 @@ export default class Splash extends Vue {
|
|
49
49
|
|
50
50
|
|
51
51
|
<style lang="scss" scoped>
|
52
|
+
.splash {
|
53
|
+
position: absolute;
|
54
|
+
top: 0;
|
55
|
+
left: 0;
|
56
|
+
width: 100vw;
|
57
|
+
}
|
58
|
+
|
52
59
|
.logo {
|
53
60
|
margin-bottom: 2rem;
|
54
61
|
}
|
@@ -8,10 +8,10 @@
|
|
8
8
|
>
|
9
9
|
{{ _icon.icon }}
|
10
10
|
</v-icon>
|
11
|
-
<label :class="['label', {'label--withIcon':
|
11
|
+
<label :class="['label', {'label--withIcon': _icon != null}]">{{ label }}</label>
|
12
12
|
</div>
|
13
13
|
|
14
|
-
<div :class="['content', {'content--withIcon':
|
14
|
+
<div :class="['content', {'content--withIcon': _icon != null}]">
|
15
15
|
<a-row
|
16
16
|
vertical
|
17
17
|
gap="6"
|
@@ -37,8 +37,8 @@ export default class DetailProperty extends Vue {
|
|
37
37
|
}
|
38
38
|
|
39
39
|
if (this.iconModelType) {
|
40
|
-
const
|
41
|
-
return
|
40
|
+
const ModelClass = apiResources.getModelClass(this.iconModelType)
|
41
|
+
return ModelClass.icon
|
42
42
|
}
|
43
43
|
}
|
44
44
|
}
|
@@ -64,7 +64,7 @@ export default class DetailProperty extends Vue {
|
|
64
64
|
@media (max-width: 900px), (orientation : portrait) {
|
65
65
|
padding-left: 55px;
|
66
66
|
&--withIcon {
|
67
|
-
padding-left: 0;
|
67
|
+
padding-left: 0;
|
68
68
|
}
|
69
69
|
}
|
70
70
|
}
|
@@ -31,6 +31,7 @@
|
|
31
31
|
<slot
|
32
32
|
name="model-table"
|
33
33
|
:model="model"
|
34
|
+
:setFilter="setFilter"
|
34
35
|
/>
|
35
36
|
</a-table-row>
|
36
37
|
</a-table>
|
@@ -72,7 +73,7 @@ export default class ListView extends Mixins(ListViewMixin) {
|
|
72
73
|
|
73
74
|
@Watch('isLoading')
|
74
75
|
isLoadingChanged () {
|
75
|
-
if (this.
|
76
|
+
if (this.events) {
|
76
77
|
if (this.isLoading) {
|
77
78
|
this.$events.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
|
78
79
|
} else {
|
@@ -85,6 +86,10 @@ export default class ListView extends Mixins(ListViewMixin) {
|
|
85
86
|
get _table () {
|
86
87
|
return this.table !== false
|
87
88
|
}
|
89
|
+
|
90
|
+
setFilter (name, value) {
|
91
|
+
this.filters[name].value = value
|
92
|
+
}
|
88
93
|
}
|
89
94
|
</script>
|
90
95
|
|
@@ -56,8 +56,8 @@ import { apiResources } from '@afeefa/api-resources-client'
|
|
56
56
|
})
|
57
57
|
export default class CreatePage extends Mixins(EditPageMixin) {
|
58
58
|
created () {
|
59
|
-
if (!this.$parent.constructor.
|
60
|
-
console.warn('<create-page> owner must provide a static
|
59
|
+
if (!this.$parent.constructor.createRouteConfig) {
|
60
|
+
console.warn('<create-page> owner must provide a static createRouteConfig method.')
|
61
61
|
}
|
62
62
|
|
63
63
|
this.reset()
|
@@ -65,11 +65,15 @@ export default class CreatePage extends Mixins(EditPageMixin) {
|
|
65
65
|
}
|
66
66
|
|
67
67
|
get editConfig () {
|
68
|
-
return this.$parent.constructor.
|
68
|
+
return this.$parent.constructor.createRouteConfig
|
69
69
|
}
|
70
70
|
|
71
71
|
get modelUpateAction () {
|
72
|
-
return this.ModelClass.getAction(
|
72
|
+
return this.editConfig.createAction || this.ModelClass.getAction('create')
|
73
|
+
}
|
74
|
+
|
75
|
+
get _icon () {
|
76
|
+
return this.icon || this.modelToEdit.getIcon()
|
73
77
|
}
|
74
78
|
|
75
79
|
get _title () {
|
@@ -89,7 +93,7 @@ export default class CreatePage extends Mixins(EditPageMixin) {
|
|
89
93
|
get _listLink () {
|
90
94
|
if (this.listLink) {
|
91
95
|
if (typeof this.listLink === 'function') {
|
92
|
-
return this.listLink(
|
96
|
+
return this.listLink()
|
93
97
|
} else {
|
94
98
|
return this.listLink
|
95
99
|
}
|
@@ -97,13 +101,6 @@ export default class CreatePage extends Mixins(EditPageMixin) {
|
|
97
101
|
return this.modelToEdit.getLink('list')
|
98
102
|
}
|
99
103
|
|
100
|
-
get _icon () {
|
101
|
-
if (this.icon) {
|
102
|
-
return this.icon
|
103
|
-
}
|
104
|
-
return this.modelToEdit.getIcon()
|
105
|
-
}
|
106
|
-
|
107
104
|
createModelToEdit () {
|
108
105
|
if (this.createModel) {
|
109
106
|
return this.createModel(this.fields)
|
@@ -55,7 +55,7 @@ import { Component, Vue, Watch } from 'vue-property-decorator'
|
|
55
55
|
import { RemoveAction } from '@a-vue/api-resources/ApiActions'
|
56
56
|
|
57
57
|
@Component({
|
58
|
-
props: ['model', 'title', 'icon', '
|
58
|
+
props: ['model', 'title', 'icon', 'protectRemove', 'listLink']
|
59
59
|
})
|
60
60
|
export default class DetailPage extends Vue {
|
61
61
|
$hasOptions = ['edit', 'remove', 'list']
|
@@ -64,7 +64,7 @@ export default class DetailPage extends Vue {
|
|
64
64
|
removeConfirmed = null
|
65
65
|
|
66
66
|
created () {
|
67
|
-
if (!this.$parent.constructor.
|
67
|
+
if (!this.$parent.constructor.detailRouteConfig) {
|
68
68
|
console.warn('<detail-page> owner must provide a static getDetailConfig method.')
|
69
69
|
}
|
70
70
|
this.$emit('model', this.model)
|
@@ -80,7 +80,7 @@ export default class DetailPage extends Vue {
|
|
80
80
|
}
|
81
81
|
|
82
82
|
get detailConfig () {
|
83
|
-
return this.$parent.constructor.
|
83
|
+
return this.$parent.constructor.detailRouteConfig
|
84
84
|
}
|
85
85
|
|
86
86
|
get document () {
|
@@ -94,7 +94,7 @@ export default class DetailPage extends Vue {
|
|
94
94
|
get _listLink () {
|
95
95
|
if (this.listLink) {
|
96
96
|
if (typeof this.listLink === 'function') {
|
97
|
-
return this.listLink(
|
97
|
+
return this.listLink()
|
98
98
|
} else {
|
99
99
|
return this.listLink
|
100
100
|
}
|
@@ -110,10 +110,7 @@ export default class DetailPage extends Vue {
|
|
110
110
|
}
|
111
111
|
|
112
112
|
get _deleteAction () {
|
113
|
-
|
114
|
-
return this.removeAction
|
115
|
-
}
|
116
|
-
return this.ModelClass.getAction(this.$routeDefinition, 'delete')
|
113
|
+
return this.detailConfig.removeAction || this.ModelClass.getAction('delete')
|
117
114
|
}
|
118
115
|
|
119
116
|
async remove () {
|
@@ -78,8 +78,8 @@ export default class EditPage extends Mixins(EditPageMixin) {
|
|
78
78
|
model_ = null
|
79
79
|
|
80
80
|
created () {
|
81
|
-
if (!this.$parent.constructor.
|
82
|
-
console.warn('<edit-page> owner must provide a static
|
81
|
+
if (!this.$parent.constructor.editRouteConfig) {
|
82
|
+
console.warn('<edit-page> owner must provide a static editRouteConfig method.')
|
83
83
|
}
|
84
84
|
|
85
85
|
this.model_ = this.model
|
@@ -95,18 +95,22 @@ export default class EditPage extends Mixins(EditPageMixin) {
|
|
95
95
|
}
|
96
96
|
|
97
97
|
get editConfig () {
|
98
|
-
return this.$parent.constructor.
|
98
|
+
return this.$parent.constructor.editRouteConfig
|
99
99
|
}
|
100
100
|
|
101
101
|
get modelUpateAction () {
|
102
|
-
return this.ModelClass.getAction(
|
102
|
+
return this.editConfig.updateAction || this.ModelClass.getAction('update')
|
103
103
|
}
|
104
104
|
|
105
105
|
get _getAction () {
|
106
106
|
if (this.getAction) {
|
107
107
|
return this.getAction
|
108
108
|
}
|
109
|
-
return this.ModelClass.getAction(
|
109
|
+
return this.ModelClass.getAction('get')
|
110
|
+
}
|
111
|
+
|
112
|
+
get _icon () {
|
113
|
+
return this.icon || this.model.getIcon()
|
110
114
|
}
|
111
115
|
|
112
116
|
get _title () {
|
@@ -126,7 +130,7 @@ export default class EditPage extends Mixins(EditPageMixin) {
|
|
126
130
|
get _listLink () {
|
127
131
|
if (this.listLink) {
|
128
132
|
if (typeof this.listLink === 'function') {
|
129
|
-
return this.listLink(
|
133
|
+
return this.listLink()
|
130
134
|
} else {
|
131
135
|
return this.listLink
|
132
136
|
}
|
@@ -134,13 +138,6 @@ export default class EditPage extends Mixins(EditPageMixin) {
|
|
134
138
|
return this.model.getLink('list')
|
135
139
|
}
|
136
140
|
|
137
|
-
get _icon () {
|
138
|
-
if (this.icon) {
|
139
|
-
return this.icon
|
140
|
-
}
|
141
|
-
return this.model.getIcon()
|
142
|
-
}
|
143
|
-
|
144
141
|
createModelToEdit () {
|
145
142
|
return this.model_.cloneForEdit(this.fields)
|
146
143
|
}
|
@@ -24,17 +24,21 @@ import { Component, Vue } from 'vue-property-decorator'
|
|
24
24
|
import { apiResources } from '@afeefa/api-resources-client'
|
25
25
|
|
26
26
|
@Component({
|
27
|
-
props: ['title', 'newTitle', 'newLink', '
|
27
|
+
props: ['icon', 'title', 'newTitle', 'newLink', 'Model']
|
28
28
|
})
|
29
29
|
export default class ListPage extends Vue {
|
30
30
|
$hasOptions = ['add']
|
31
31
|
|
32
|
+
get _icon () {
|
33
|
+
return this.icon || this.Model.icon
|
34
|
+
}
|
35
|
+
|
32
36
|
get _title () {
|
33
37
|
if (this.title) {
|
34
38
|
return this.title
|
35
39
|
}
|
36
40
|
|
37
|
-
const type = apiResources.getType(this.
|
41
|
+
const type = apiResources.getType(this.Model.type)
|
38
42
|
return type.t('TITLE_PLURAL')
|
39
43
|
}
|
40
44
|
|
@@ -43,26 +47,12 @@ export default class ListPage extends Vue {
|
|
43
47
|
return this.newTitle
|
44
48
|
}
|
45
49
|
|
46
|
-
const type = apiResources.getType(this.
|
50
|
+
const type = apiResources.getType(this.Model.type)
|
47
51
|
return type.t('TITLE_SINGULAR')
|
48
52
|
}
|
49
53
|
|
50
|
-
get _icon () {
|
51
|
-
if (this.icon) {
|
52
|
-
return this.icon
|
53
|
-
}
|
54
|
-
return this.ModelClass.icon
|
55
|
-
}
|
56
|
-
|
57
54
|
get _newLink () {
|
58
|
-
|
59
|
-
if (typeof this.newLink === 'function') {
|
60
|
-
return this.newLink(this.$route.params)
|
61
|
-
} else {
|
62
|
-
return this.newLink
|
63
|
-
}
|
64
|
-
}
|
65
|
-
return this.ModelClass.getLink('new')
|
55
|
+
return this.newLink || this.Model.getLink('new')
|
66
56
|
}
|
67
57
|
}
|
68
58
|
</script>
|
@@ -18,8 +18,13 @@ Component.registerHooks([
|
|
18
18
|
function load (route) {
|
19
19
|
const routeDefinition = route.meta.routeDefinition
|
20
20
|
const Component = routeDefinition.config.detail
|
21
|
-
|
22
|
-
|
21
|
+
|
22
|
+
if (!Component.detailRouteConfig) {
|
23
|
+
console.warn('A detail route component must implement a static detailRouteConfig property.')
|
24
|
+
}
|
25
|
+
|
26
|
+
const detailConfig = Component.detailRouteConfig
|
27
|
+
const action = detailConfig.action || detailConfig.ModelClass.getAction('get')
|
23
28
|
|
24
29
|
return new GetAction()
|
25
30
|
.setAction(action)
|
@@ -19,8 +19,13 @@ Component.registerHooks([
|
|
19
19
|
function load (route) {
|
20
20
|
const routeDefinition = route.meta.routeDefinition
|
21
21
|
const Component = routeDefinition.config.edit
|
22
|
-
|
23
|
-
|
22
|
+
|
23
|
+
if (!Component.editRouteConfig) {
|
24
|
+
console.warn('An edit route component must implement a static editRouteConfig property.')
|
25
|
+
}
|
26
|
+
|
27
|
+
const editConfig = Component.editRouteConfig
|
28
|
+
const action = editConfig.getAction || editConfig.ModelClass.getAction('get')
|
24
29
|
|
25
30
|
return new GetAction()
|
26
31
|
.setAction(action)
|
@@ -10,6 +10,8 @@
|
|
10
10
|
<script>
|
11
11
|
import { Component, Vue, Watch } from 'vue-property-decorator'
|
12
12
|
import { ListAction } from '@a-vue/api-resources/ApiActions'
|
13
|
+
import { NextRouteFilterSource } from '@a-vue/components/list/NextRouteFilterSource'
|
14
|
+
import { ListViewModel } from '@afeefa/api-resources-client'
|
13
15
|
|
14
16
|
Component.registerHooks([
|
15
17
|
'beforeRouteEnter',
|
@@ -28,14 +30,26 @@ let lastVm = null
|
|
28
30
|
function load (route) {
|
29
31
|
const routeDefinition = route.meta.routeDefinition
|
30
32
|
const Component = routeDefinition.config.list
|
31
|
-
|
32
|
-
|
33
|
+
|
34
|
+
if (!Component.listViewConfig) {
|
35
|
+
console.warn('A list route component must implement a static listViewConfig property.')
|
36
|
+
}
|
37
|
+
|
38
|
+
const request = new ListViewModel(Component.listViewConfig)
|
39
|
+
// read from next route query string, but do not push
|
40
|
+
// list component will be init with used_filters
|
41
|
+
.filterSource(new NextRouteFilterSource(route), false)
|
42
|
+
// read from history, but do not push
|
43
|
+
// list component will be init with used_filters
|
44
|
+
.historyKey(route.path, false)
|
45
|
+
.initFilters({
|
46
|
+
source: true,
|
47
|
+
history: true
|
48
|
+
})
|
49
|
+
.getApiRequest()
|
33
50
|
|
34
51
|
return new ListAction()
|
35
|
-
.
|
36
|
-
.setFields(listConfig.fields)
|
37
|
-
.setScopes(listConfig.scopes)
|
38
|
-
.setFiltersForRoute(route)
|
52
|
+
.setRequest(request)
|
39
53
|
.load()
|
40
54
|
}
|
41
55
|
|
@@ -1,5 +1,6 @@
|
|
1
|
-
import { Model as ApiResourcesModel } from '@afeefa/api-resources-client'
|
1
|
+
import { Model as ApiResourcesModel, apiResources } from '@afeefa/api-resources-client'
|
2
2
|
import { mdiAlphaMCircle } from '@mdi/js'
|
3
|
+
|
3
4
|
export class Model extends ApiResourcesModel {
|
4
5
|
static resourceType = null
|
5
6
|
static routeName = null
|
@@ -9,12 +10,14 @@ export class Model extends ApiResourcesModel {
|
|
9
10
|
return (new this()).getLink(action)
|
10
11
|
}
|
11
12
|
|
12
|
-
static getAction (
|
13
|
+
static getAction (action) {
|
13
14
|
if (this.resourceType) {
|
14
|
-
|
15
|
-
|
15
|
+
return apiResources.getAction({
|
16
|
+
resource: this.resourceType,
|
17
|
+
action
|
18
|
+
})
|
16
19
|
}
|
17
|
-
console.warn('You can\'t get an action of a model without resourceType:', this.type)
|
20
|
+
console.warn('You can\'t get an action out of a model without resourceType:', this.type)
|
18
21
|
return null
|
19
22
|
}
|
20
23
|
|
@@ -32,8 +35,8 @@ export class Model extends ApiResourcesModel {
|
|
32
35
|
}
|
33
36
|
}
|
34
37
|
|
35
|
-
getAction (
|
36
|
-
return this.constructor.getAction(
|
38
|
+
getAction (action) {
|
39
|
+
return this.constructor.getAction(action)
|
37
40
|
}
|
38
41
|
|
39
42
|
getIcon () {
|
package/src-admin/styles.scss
CHANGED
@@ -21,14 +21,16 @@
|
|
21
21
|
}
|
22
22
|
|
23
23
|
.v-application {
|
24
|
-
margin-bottom: 2rem;
|
25
|
-
|
26
24
|
.text-strike {
|
27
25
|
color: inherit;
|
28
26
|
text-decoration: line-through;
|
29
27
|
}
|
30
28
|
}
|
31
29
|
|
30
|
+
.v-main {
|
31
|
+
margin-bottom: 2rem;
|
32
|
+
}
|
33
|
+
|
32
34
|
.theme--light.v-btn.v-btn--has-bg {
|
33
35
|
background-color: #E9E9E9;
|
34
36
|
}
|
@@ -1,18 +0,0 @@
|
|
1
|
-
import { BaseFilterSource } from '@afeefa/api-resources-client'
|
2
|
-
|
3
|
-
export class RouteParamsFilterSource extends BaseFilterSource {
|
4
|
-
route = null
|
5
|
-
|
6
|
-
constructor (route) {
|
7
|
-
super()
|
8
|
-
|
9
|
-
this.route = route
|
10
|
-
}
|
11
|
-
|
12
|
-
getQuery () {
|
13
|
-
return this.route.query
|
14
|
-
}
|
15
|
-
|
16
|
-
push (_query) {
|
17
|
-
}
|
18
|
-
}
|