@afeefa/vue-app 0.0.46 → 0.0.50
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
}
|