@afeefa/vue-app 0.0.43 → 0.0.47
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/APagination.vue +0 -4
- package/src/components/ARow.vue +1 -3
- package/src/components/ASearchSelect.vue +2 -2
- package/src/components/ATableHeader.vue +1 -1
- package/src/components/list/FilterSourceType.js +10 -0
- package/src/components/list/ListViewMixin.js +16 -17
- package/src/components/list/ListViewRequest.js +124 -0
- package/src/components/list/{RouteParamsFilterSource.js → RouteQueryFilterSource.js} +1 -1
- package/src/plugins/api-resources/ApiResourcesPlugin.js +13 -0
- package/src-admin/bootstrap.js +3 -1
- package/src-admin/components/{Index.vue → Start.vue} +0 -0
- package/src-admin/components/detail/DetailProperty.vue +5 -5
- package/src-admin/components/index.js +2 -2
- package/src-admin/components/list/ListView.vue +35 -49
- package/src-admin/components/pages/CreatePage.vue +6 -9
- package/src-admin/components/pages/DetailPage.vue +2 -2
- package/src-admin/components/pages/EditPage.vue +7 -10
- package/src-admin/components/pages/ListPage.vue +7 -122
- package/src-admin/components/routes/DetailRoute.vue +1 -7
- package/src-admin/components/routes/EditRoute.vue +1 -7
- package/src-admin/components/routes/ListRoute.vue +9 -10
- package/src-admin/fonts/ibm-plex-sans-v9-latin-700.woff +0 -0
- package/src-admin/fonts/ibm-plex-sans-v9-latin-700.woff2 +0 -0
- package/src-admin/fonts/ibm-plex-sans-v9-latin-700italic.woff +0 -0
- package/src-admin/fonts/ibm-plex-sans-v9-latin-700italic.woff2 +0 -0
- package/src-admin/fonts/ibm-plex-sans-v9-latin-italic.woff +0 -0
- package/src-admin/fonts/ibm-plex-sans-v9-latin-italic.woff2 +0 -0
- package/src-admin/fonts/ibm-plex-sans-v9-latin-regular.woff +0 -0
- package/src-admin/fonts/ibm-plex-sans-v9-latin-regular.woff2 +0 -0
- package/src-admin/models/Model.js +10 -7
- package/src-admin/styles/ibm-plex-sans.scss +43 -0
- package/src/components/list/QuerySourceType.js +0 -4
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.47
|
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) {
|
package/src/components/ARow.vue
CHANGED
@@ -93,7 +93,7 @@ import { Component, Watch, Mixins } from 'vue-property-decorator'
|
|
93
93
|
import { UsesPositionServiceMixin } from '../services/position/UsesPositionServiceMixin'
|
94
94
|
import { PositionConfig } from '../services/PositionService'
|
95
95
|
import { randomCssClass } from '../utils/random'
|
96
|
-
import {
|
96
|
+
import { FilterSourceType } from '@a-vue/components/list/FilterSourceType'
|
97
97
|
import SearchSelectFilters from './search-select/SearchSelectFilters'
|
98
98
|
import SearchSelectList from './search-select/SearchSelectList'
|
99
99
|
import { CancelOnEscMixin } from '@a-vue/services/escape/CancelOnEscMixin'
|
@@ -109,7 +109,7 @@ export default class ASearchSelect extends Mixins(UsesPositionServiceMixin, Canc
|
|
109
109
|
selectId = randomCssClass(10)
|
110
110
|
isOpen = false
|
111
111
|
items_ = []
|
112
|
-
filterSource =
|
112
|
+
filterSource = FilterSourceType.OBJECT
|
113
113
|
|
114
114
|
isLoading = false
|
115
115
|
filters = []
|
@@ -0,0 +1,10 @@
|
|
1
|
+
export class FilterSourceType {
|
2
|
+
// filters are synchronized with url's query string
|
3
|
+
static QUERY_STRING = 'query_string'
|
4
|
+
|
5
|
+
// filters are initialized from given route params (no synchronisation allowed)
|
6
|
+
static ROUTE_PARAMS = 'route_params'
|
7
|
+
|
8
|
+
// filters are synchronized with plain history object
|
9
|
+
static OBJECT = 'object'
|
10
|
+
}
|
@@ -1,14 +1,15 @@
|
|
1
1
|
import { ListAction } from '@a-vue/api-resources/ApiActions'
|
2
2
|
import { Component, Vue, Watch } from 'vue-property-decorator'
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { FilterSourceType } from './FilterSourceType'
|
5
5
|
import { RouteFilterSource } from './RouteFilterSource'
|
6
6
|
|
7
7
|
@Component({
|
8
8
|
props: [
|
9
|
-
'models', 'meta',
|
10
|
-
'
|
11
|
-
'noEvents', 'noHistory',
|
9
|
+
'models', 'meta', // if already loaded
|
10
|
+
'listViewRequest',
|
11
|
+
'noEvents', 'noHistory',
|
12
|
+
'filterHistoryKey', 'filterSource',
|
12
13
|
'loadOnlyIfKeyword'
|
13
14
|
]
|
14
15
|
})
|
@@ -35,19 +36,16 @@ export class ListViewMixin extends Vue {
|
|
35
36
|
}
|
36
37
|
|
37
38
|
if (this.requestFilters) {
|
39
|
+
// this can happen only on HMR-reload
|
38
40
|
this.requestFilters.off('change', this.filtersChanged)
|
39
41
|
}
|
40
42
|
|
41
43
|
const historyKey = this.noHistory
|
42
44
|
? undefined
|
43
45
|
: [this.$route.path, this.filterHistoryKey].filter(i => i).join('.')
|
44
|
-
const
|
45
|
-
|
46
|
-
|
47
|
-
console.log('Initial Filters: ', this.initialFilters)
|
48
|
-
if (this.initialFilters) {
|
49
|
-
this.requestFilters.initFromUsed(this.initialFilters)
|
50
|
-
}
|
46
|
+
const filterSource = this.filterSource === FilterSourceType.OBJECT ? undefined : new RouteFilterSource(this.$router)
|
47
|
+
const action = this.listViewRequest.getAction()
|
48
|
+
this.requestFilters = action.createRequestFilters(historyKey, filterSource)
|
51
49
|
|
52
50
|
this.requestFilters.on('change', this.filtersChanged)
|
53
51
|
|
@@ -64,8 +62,8 @@ export class ListViewMixin extends Vue {
|
|
64
62
|
|
65
63
|
@Watch('$route.query')
|
66
64
|
routeQueryChanged () {
|
67
|
-
if (this.filterSource !==
|
68
|
-
this.requestFilters.
|
65
|
+
if (this.filterSource !== FilterSourceType.QUERY_STRING) {
|
66
|
+
this.requestFilters.filterSourceChanged()
|
69
67
|
}
|
70
68
|
}
|
71
69
|
|
@@ -109,11 +107,12 @@ export class ListViewMixin extends Vue {
|
|
109
107
|
this.isLoading = true
|
110
108
|
this.$emit('update:isLoading', this.isLoading)
|
111
109
|
|
110
|
+
const request = this.listViewRequest
|
111
|
+
.filters(this.requestFilters.serialize())
|
112
|
+
.toApiRequest()
|
113
|
+
|
112
114
|
const {models, meta} = await new ListAction()
|
113
|
-
.
|
114
|
-
.setFields(this.fields)
|
115
|
-
.setScopes(this.scopes)
|
116
|
-
.setFilters(this.requestFilters.serialize())
|
115
|
+
.setRequest(request)
|
117
116
|
.noEvents(this.noEvents)
|
118
117
|
.load()
|
119
118
|
|
@@ -0,0 +1,124 @@
|
|
1
|
+
import { RequestFilters, apiResources } from '@afeefa/api-resources-client'
|
2
|
+
|
3
|
+
export class ListViewRequest {
|
4
|
+
_api = {}
|
5
|
+
_resource = {}
|
6
|
+
_action = {}
|
7
|
+
|
8
|
+
_params = {}
|
9
|
+
_filters = {}
|
10
|
+
_fields = {}
|
11
|
+
|
12
|
+
_filterSource = null
|
13
|
+
_historyKey = null
|
14
|
+
|
15
|
+
action ({api, resource, action}) {
|
16
|
+
this._api = api
|
17
|
+
this._resource = resource
|
18
|
+
this._action = action
|
19
|
+
return this
|
20
|
+
}
|
21
|
+
|
22
|
+
getAction () {
|
23
|
+
return apiResources.getAction({
|
24
|
+
api: this._api,
|
25
|
+
resource: this._resource,
|
26
|
+
action: this._action
|
27
|
+
})
|
28
|
+
}
|
29
|
+
|
30
|
+
params (params) {
|
31
|
+
this._params = params
|
32
|
+
return this
|
33
|
+
}
|
34
|
+
|
35
|
+
getParams () {
|
36
|
+
return this._params
|
37
|
+
}
|
38
|
+
|
39
|
+
filters (filters) {
|
40
|
+
this._filters = filters
|
41
|
+
return this
|
42
|
+
}
|
43
|
+
|
44
|
+
getFilters () {
|
45
|
+
return this._filters
|
46
|
+
}
|
47
|
+
|
48
|
+
fields (fields) {
|
49
|
+
this._fields = fields
|
50
|
+
return this
|
51
|
+
}
|
52
|
+
|
53
|
+
getFields () {
|
54
|
+
return this._fields
|
55
|
+
}
|
56
|
+
|
57
|
+
initFromFilterSource (filterSource) {
|
58
|
+
this._filterSource = filterSource
|
59
|
+
return this
|
60
|
+
}
|
61
|
+
|
62
|
+
recreateFromHistory (historyKey) {
|
63
|
+
this._historyKey = historyKey
|
64
|
+
return this
|
65
|
+
}
|
66
|
+
|
67
|
+
clone () {
|
68
|
+
const request = new ListViewRequest()
|
69
|
+
request._api = this._api
|
70
|
+
request._resource = this._resource
|
71
|
+
request._action = this._action
|
72
|
+
request._params = this._params
|
73
|
+
request._filters = this._filters
|
74
|
+
request._fields = this._fields
|
75
|
+
return request
|
76
|
+
}
|
77
|
+
|
78
|
+
toApiRequest () {
|
79
|
+
const action = this.getAction({
|
80
|
+
api: this._api,
|
81
|
+
resource: this._resource,
|
82
|
+
action: this._action
|
83
|
+
})
|
84
|
+
|
85
|
+
const request = action.createRequest()
|
86
|
+
.params(this._params)
|
87
|
+
.fields(this._fields)
|
88
|
+
|
89
|
+
// filters set on the request will be skipped if filterSource or historyKey are given
|
90
|
+
// and produce results
|
91
|
+
let filtersToUse = this._filters
|
92
|
+
let useHistory = true
|
93
|
+
|
94
|
+
// create and init request filters based on the current filter source state
|
95
|
+
if (this._filterSource) {
|
96
|
+
const requestFilters = action.createRequestFilters(null, this._filterSource)
|
97
|
+
const requestFiltersToUse = requestFilters.serialize()
|
98
|
+
// do not use history if any filter given by the given filter source
|
99
|
+
if (Object.keys(requestFiltersToUse).length) {
|
100
|
+
useHistory = false
|
101
|
+
}
|
102
|
+
|
103
|
+
filtersToUse = {
|
104
|
+
...filtersToUse,
|
105
|
+
...requestFiltersToUse
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
// if any filters is set by current filter source
|
110
|
+
// skip history and give that filter precedence
|
111
|
+
// otherwise: set stored filters
|
112
|
+
if (useHistory && this._historyKey) {
|
113
|
+
// check any already stored filters from a previous request
|
114
|
+
const storedFilters = RequestFilters.fromHistory(this._historyKey)
|
115
|
+
if (storedFilters) {
|
116
|
+
filtersToUse = storedFilters.serialize()
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
request.filters(filtersToUse)
|
121
|
+
|
122
|
+
return request
|
123
|
+
}
|
124
|
+
}
|
@@ -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()
|
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
|
@@ -48,7 +50,7 @@ export async function bootstrap ({ apis, models, routing, authService, app, comp
|
|
48
50
|
vuetify,
|
49
51
|
router,
|
50
52
|
el: '#app',
|
51
|
-
template: '<
|
53
|
+
template: '<start :splash="splash" />',
|
52
54
|
data: {
|
53
55
|
splash
|
54
56
|
}
|
File without changes
|
@@ -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
|
}
|
@@ -7,7 +7,7 @@ import DetailContent from './detail/DetailContent'
|
|
7
7
|
import DetailMeta from './detail/DetailMeta'
|
8
8
|
import DetailProperty from './detail/DetailProperty'
|
9
9
|
import DetailTitle from './detail/DetailTitle'
|
10
|
-
import
|
10
|
+
import Start from './Start.vue'
|
11
11
|
import ListCard from './list/ListCard'
|
12
12
|
import ListColumnHeader from './list/ListColumnHeader'
|
13
13
|
import ListContent from './list/ListContent'
|
@@ -46,4 +46,4 @@ Vue.component('DetailColumn', DetailColumn)
|
|
46
46
|
Vue.component('AppBarButton', AppBarButton)
|
47
47
|
Vue.component('AppBarTitle', AppBarTitle)
|
48
48
|
|
49
|
-
Vue.component('
|
49
|
+
Vue.component('Start', Start)
|
@@ -9,23 +9,32 @@
|
|
9
9
|
</div>
|
10
10
|
|
11
11
|
<template v-if="models_.length">
|
12
|
-
<template v-if="
|
13
|
-
<
|
14
|
-
<
|
12
|
+
<template v-if="_table">
|
13
|
+
<a-table>
|
14
|
+
<a-table-header>
|
15
|
+
<div v-if="$has.icon" />
|
16
|
+
|
15
17
|
<slot name="header-table" />
|
16
|
-
</
|
18
|
+
</a-table-header>
|
17
19
|
|
18
|
-
<
|
20
|
+
<a-table-row
|
19
21
|
v-for="model in models_"
|
20
22
|
:key="model.id"
|
21
|
-
class="row"
|
22
23
|
>
|
24
|
+
<v-icon
|
25
|
+
v-if="$has.icon"
|
26
|
+
:color="model.getIcon().color"
|
27
|
+
size="1.2rem"
|
28
|
+
v-text="model.getIcon().icon"
|
29
|
+
/>
|
30
|
+
|
23
31
|
<slot
|
24
32
|
name="model-table"
|
25
33
|
:model="model"
|
34
|
+
:setFilter="setFilter"
|
26
35
|
/>
|
27
|
-
</
|
28
|
-
</
|
36
|
+
</a-table-row>
|
37
|
+
</a-table>
|
29
38
|
</template>
|
30
39
|
|
31
40
|
<template v-else>
|
@@ -60,6 +69,8 @@ import { LoadingEvent } from '@a-vue/events'
|
|
60
69
|
props: ['table']
|
61
70
|
})
|
62
71
|
export default class ListView extends Mixins(ListViewMixin) {
|
72
|
+
$hasOptions = ['icon']
|
73
|
+
|
63
74
|
@Watch('isLoading')
|
64
75
|
isLoadingChanged () {
|
65
76
|
if (this.noEvents === undefined || !this.noEvents) {
|
@@ -71,56 +82,31 @@ export default class ListView extends Mixins(ListViewMixin) {
|
|
71
82
|
}
|
72
83
|
this.$emit('update:isLoading', this.isLoading)
|
73
84
|
}
|
85
|
+
|
86
|
+
get _table () {
|
87
|
+
return this.table !== false
|
88
|
+
}
|
89
|
+
|
90
|
+
setFilter (name, value) {
|
91
|
+
this.filters[name].value = value
|
92
|
+
}
|
74
93
|
}
|
75
94
|
</script>
|
76
95
|
|
77
96
|
|
78
97
|
<style lang="scss" scoped>
|
98
|
+
.listView {
|
99
|
+
max-width: 100%;
|
100
|
+
overflow-x: auto;
|
101
|
+
overflow-y: hidden;
|
102
|
+
}
|
103
|
+
|
79
104
|
.filters {
|
105
|
+
margin-left: 4px;
|
80
106
|
margin-bottom: 2rem;
|
81
107
|
}
|
82
108
|
|
83
|
-
.table {
|
84
|
-
display: table;
|
85
|
-
border-collapse: collapse;
|
109
|
+
.a-table-row > :last-child {
|
86
110
|
width: 100%;
|
87
|
-
|
88
|
-
.header, .row {
|
89
|
-
display: table-row;
|
90
|
-
> * {
|
91
|
-
display: table-cell;
|
92
|
-
padding: .4rem;
|
93
|
-
padding-right: 1.5rem;
|
94
|
-
white-space: nowrap;
|
95
|
-
vertical-align: middle;
|
96
|
-
|
97
|
-
&.info {
|
98
|
-
background: none !important;
|
99
|
-
color: #888888;
|
100
|
-
}
|
101
|
-
}
|
102
|
-
}
|
103
|
-
|
104
|
-
.header {
|
105
|
-
text-transform: uppercase;
|
106
|
-
letter-spacing: 1px;
|
107
|
-
color: #999999;
|
108
|
-
font-size: .9rem;
|
109
|
-
> * {
|
110
|
-
padding-right: 1rem;
|
111
|
-
}
|
112
|
-
}
|
113
|
-
|
114
|
-
.row {
|
115
|
-
border-bottom: 1px solid #DDDDDD;
|
116
|
-
|
117
|
-
&:hover {
|
118
|
-
background: #F4F4F4;
|
119
|
-
}
|
120
|
-
|
121
|
-
&:last-child {
|
122
|
-
border: none;
|
123
|
-
}
|
124
|
-
}
|
125
111
|
}
|
126
112
|
</style>
|
@@ -69,7 +69,11 @@ export default class CreatePage extends Mixins(EditPageMixin) {
|
|
69
69
|
}
|
70
70
|
|
71
71
|
get modelUpateAction () {
|
72
|
-
return this.ModelClass.getAction(
|
72
|
+
return 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)
|
@@ -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
|
}
|
@@ -113,7 +113,7 @@ export default class DetailPage extends Vue {
|
|
113
113
|
if (this.removeAction) {
|
114
114
|
return this.removeAction
|
115
115
|
}
|
116
|
-
return this.ModelClass.getAction(
|
116
|
+
return this.ModelClass.getAction('delete')
|
117
117
|
}
|
118
118
|
|
119
119
|
async remove () {
|
@@ -99,14 +99,18 @@ export default class EditPage extends Mixins(EditPageMixin) {
|
|
99
99
|
}
|
100
100
|
|
101
101
|
get modelUpateAction () {
|
102
|
-
return this.ModelClass.getAction(
|
102
|
+
return 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
|
}
|
@@ -15,72 +15,7 @@
|
|
15
15
|
/>
|
16
16
|
</app-bar-button>
|
17
17
|
|
18
|
-
<
|
19
|
-
v-bind="$attrs"
|
20
|
-
:models="models"
|
21
|
-
:meta="meta"
|
22
|
-
:action="action"
|
23
|
-
:scopes="scopes"
|
24
|
-
:fields="fields"
|
25
|
-
:table="table"
|
26
|
-
:initialFilters="initialFilters"
|
27
|
-
v-on="$listeners"
|
28
|
-
>
|
29
|
-
<template #filters="{filters, count}">
|
30
|
-
<div class="pl-2">
|
31
|
-
<slot
|
32
|
-
name="filters"
|
33
|
-
:filters="filters"
|
34
|
-
:count="count"
|
35
|
-
/>
|
36
|
-
</div>
|
37
|
-
</template>
|
38
|
-
|
39
|
-
<template
|
40
|
-
v-if="table"
|
41
|
-
#header-table
|
42
|
-
>
|
43
|
-
<div />
|
44
|
-
|
45
|
-
<slot name="header" />
|
46
|
-
</template>
|
47
|
-
|
48
|
-
<template
|
49
|
-
v-if="table"
|
50
|
-
#model-table="{ model }"
|
51
|
-
>
|
52
|
-
<v-icon
|
53
|
-
:color="model.getIcon().color"
|
54
|
-
size="1.5rem"
|
55
|
-
v-text="model.getIcon().icon"
|
56
|
-
/>
|
57
|
-
|
58
|
-
<slot
|
59
|
-
name="model"
|
60
|
-
:model="model"
|
61
|
-
/>
|
62
|
-
|
63
|
-
<div class="lastColumn" />
|
64
|
-
</template>
|
65
|
-
|
66
|
-
<template
|
67
|
-
v-if="!table"
|
68
|
-
#model="{ model }"
|
69
|
-
>
|
70
|
-
<a-row gap="4">
|
71
|
-
<v-icon
|
72
|
-
:color="model.getIcon().color"
|
73
|
-
size="2rem"
|
74
|
-
v-text="model.getIcon().icon"
|
75
|
-
/>
|
76
|
-
|
77
|
-
<slot
|
78
|
-
name="model"
|
79
|
-
:model="model"
|
80
|
-
/>
|
81
|
-
</a-row>
|
82
|
-
</template>
|
83
|
-
</list-view>
|
18
|
+
<slot />
|
84
19
|
</div>
|
85
20
|
</template>
|
86
21
|
|
@@ -89,42 +24,13 @@ import { Component, Vue } from 'vue-property-decorator'
|
|
89
24
|
import { apiResources } from '@afeefa/api-resources-client'
|
90
25
|
|
91
26
|
@Component({
|
92
|
-
props: ['
|
27
|
+
props: ['icon', 'title', 'newTitle', 'newLink', 'Model']
|
93
28
|
})
|
94
29
|
export default class ListPage extends Vue {
|
95
30
|
$hasOptions = ['add']
|
96
31
|
|
97
|
-
|
98
|
-
|
99
|
-
console.warn('<list-view> owner must provide a static getListConfig method.')
|
100
|
-
}
|
101
|
-
}
|
102
|
-
|
103
|
-
get listConfig () {
|
104
|
-
return this.$parent.constructor.getListConfig(this.$route)
|
105
|
-
}
|
106
|
-
|
107
|
-
get ModelClass () {
|
108
|
-
return this.listConfig.ModelClass
|
109
|
-
}
|
110
|
-
|
111
|
-
get action () {
|
112
|
-
if (this.listConfig.action) {
|
113
|
-
return this.listConfig.action
|
114
|
-
}
|
115
|
-
return this.ModelClass.getAction(this.$routeDefinition, 'list')
|
116
|
-
}
|
117
|
-
|
118
|
-
get scopes () {
|
119
|
-
return this.listConfig.scopes
|
120
|
-
}
|
121
|
-
|
122
|
-
get initialFilters () {
|
123
|
-
return this.listConfig.initialFilters
|
124
|
-
}
|
125
|
-
|
126
|
-
get fields () {
|
127
|
-
return this.listConfig.fields
|
32
|
+
get _icon () {
|
33
|
+
return this.icon || this.Model.icon
|
128
34
|
}
|
129
35
|
|
130
36
|
get _title () {
|
@@ -132,7 +38,7 @@ export default class ListPage extends Vue {
|
|
132
38
|
return this.title
|
133
39
|
}
|
134
40
|
|
135
|
-
const type = apiResources.getType(this.
|
41
|
+
const type = apiResources.getType(this.Model.type)
|
136
42
|
return type.t('TITLE_PLURAL')
|
137
43
|
}
|
138
44
|
|
@@ -141,33 +47,12 @@ export default class ListPage extends Vue {
|
|
141
47
|
return this.newTitle
|
142
48
|
}
|
143
49
|
|
144
|
-
const type = apiResources.getType(this.
|
50
|
+
const type = apiResources.getType(this.Model.type)
|
145
51
|
return type.t('TITLE_SINGULAR')
|
146
52
|
}
|
147
53
|
|
148
|
-
get _icon () {
|
149
|
-
if (this.icon) {
|
150
|
-
return this.icon
|
151
|
-
}
|
152
|
-
return this.ModelClass.icon
|
153
|
-
}
|
154
|
-
|
155
54
|
get _newLink () {
|
156
|
-
|
157
|
-
if (typeof this.newLink === 'function') {
|
158
|
-
return this.newLink(this.$route.params)
|
159
|
-
} else {
|
160
|
-
return this.newLink
|
161
|
-
}
|
162
|
-
}
|
163
|
-
return this.ModelClass.getLink('new')
|
55
|
+
return this.newLink || this.Model.getLink('new')
|
164
56
|
}
|
165
57
|
}
|
166
58
|
</script>
|
167
|
-
|
168
|
-
|
169
|
-
<style lang="scss" scoped>
|
170
|
-
.lastColumn {
|
171
|
-
width: 100%;
|
172
|
-
}
|
173
|
-
</style>
|
@@ -19,13 +19,7 @@ function load (route) {
|
|
19
19
|
const routeDefinition = route.meta.routeDefinition
|
20
20
|
const Component = routeDefinition.config.detail
|
21
21
|
const detailConfig = Component.getDetailConfig(route)
|
22
|
-
|
23
|
-
let action = null
|
24
|
-
if (detailConfig.ModelClass) {
|
25
|
-
action = detailConfig.ModelClass.getAction(routeDefinition, 'get')
|
26
|
-
} else {
|
27
|
-
action = detailConfig.action
|
28
|
-
}
|
22
|
+
const action = detailConfig.action || detailConfig.ModelClass.getAction('get')
|
29
23
|
|
30
24
|
return new GetAction()
|
31
25
|
.setAction(action)
|
@@ -20,13 +20,7 @@ function load (route) {
|
|
20
20
|
const routeDefinition = route.meta.routeDefinition
|
21
21
|
const Component = routeDefinition.config.edit
|
22
22
|
const editConfig = Component.getEditConfig(route)
|
23
|
-
|
24
|
-
let action = null
|
25
|
-
if (editConfig.ModelClass) {
|
26
|
-
action = editConfig.ModelClass.getAction(routeDefinition, 'get')
|
27
|
-
} else {
|
28
|
-
action = editConfig.getAction
|
29
|
-
}
|
23
|
+
const action = editConfig.getAction || editConfig.ModelClass.getAction('get')
|
30
24
|
|
31
25
|
return new GetAction()
|
32
26
|
.setAction(action)
|
@@ -10,6 +10,7 @@
|
|
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 { RouteQueryFilterSource } from '@a-vue/components/list/RouteQueryFilterSource'
|
13
14
|
|
14
15
|
Component.registerHooks([
|
15
16
|
'beforeRouteEnter',
|
@@ -28,20 +29,18 @@ let lastVm = null
|
|
28
29
|
function load (route) {
|
29
30
|
const routeDefinition = route.meta.routeDefinition
|
30
31
|
const Component = routeDefinition.config.list
|
31
|
-
const listConfig = Component.getListConfig(route)
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
action = listConfig.ModelClass.getAction(routeDefinition, 'list')
|
36
|
-
} else {
|
37
|
-
action = listConfig.action
|
33
|
+
if (!Component.listViewRequest) {
|
34
|
+
console.warn('A list route component must implement a static listViewRequest property.')
|
38
35
|
}
|
39
36
|
|
37
|
+
const request = Component.listViewRequest.clone()
|
38
|
+
.initFromFilterSource(new RouteQueryFilterSource(route))
|
39
|
+
.recreateFromHistory(route.path)
|
40
|
+
.toApiRequest()
|
41
|
+
|
40
42
|
return new ListAction()
|
41
|
-
.
|
42
|
-
.setFields(listConfig.fields)
|
43
|
-
.setScopes(listConfig.scopes)
|
44
|
-
.setFiltersForRoute(route)
|
43
|
+
.setRequest(request)
|
45
44
|
.load()
|
46
45
|
}
|
47
46
|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -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 () {
|
@@ -0,0 +1,43 @@
|
|
1
|
+
$fontUrl: '';
|
2
|
+
|
3
|
+
@if variable-exists(ibmPlexSansFontUrl) {
|
4
|
+
$fontUrl: $ibmPlexSansFontUrl;
|
5
|
+
}
|
6
|
+
|
7
|
+
/* ibm-plex-sans-regular - latin */
|
8
|
+
@font-face {
|
9
|
+
font-family: 'IBM Plex Sans';
|
10
|
+
font-style: normal;
|
11
|
+
font-weight: 400;
|
12
|
+
src: local(''),
|
13
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
14
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
15
|
+
}
|
16
|
+
|
17
|
+
/* ibm-plex-sans-italic - latin */
|
18
|
+
@font-face {
|
19
|
+
font-family: 'IBM Plex Sans';
|
20
|
+
font-style: italic;
|
21
|
+
font-weight: 400;
|
22
|
+
src: local(''),
|
23
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
24
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
25
|
+
}
|
26
|
+
/* ibm-plex-sans-700 - latin */
|
27
|
+
@font-face {
|
28
|
+
font-family: 'IBM Plex Sans';
|
29
|
+
font-style: normal;
|
30
|
+
font-weight: 700;
|
31
|
+
src: local(''),
|
32
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
33
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
34
|
+
}
|
35
|
+
/* ibm-plex-sans-700italic - latin */
|
36
|
+
@font-face {
|
37
|
+
font-family: 'IBM Plex Sans';
|
38
|
+
font-style: italic;
|
39
|
+
font-weight: 700;
|
40
|
+
src: local(''),
|
41
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
42
|
+
url('#{$fontUrl}/ibm-plex-sans-v9-latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
43
|
+
}
|