@afeefa/vue-app 0.0.61 → 0.0.64

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.
Files changed (42) hide show
  1. package/.afeefa/package/release/version.txt +1 -1
  2. package/README.md +3 -1
  3. package/package.json +1 -1
  4. package/src/api-resources/ApiAction.js +87 -0
  5. package/src/api-resources/ApiActions2.js +13 -0
  6. package/src/api-resources/DeleteAction.js +21 -0
  7. package/src/api-resources/GetAction.js +18 -0
  8. package/src/api-resources/ListAction.js +27 -0
  9. package/src/api-resources/SaveAction.js +15 -0
  10. package/src/components/AModal.vue +21 -3
  11. package/src/components/ASearchSelect.vue +2 -2
  12. package/src/components/ATableRow.vue +4 -0
  13. package/src/components/flying-context/FlyingContextEvent.js +5 -0
  14. package/src/components/form/EditForm.vue +30 -8
  15. package/src/components/form/EditModal.vue +50 -35
  16. package/src/components/form/FormFieldMixin.js +1 -1
  17. package/src/components/form/NestedEditForm.vue +4 -0
  18. package/src/components/list/ListViewMixin.js +2 -2
  19. package/src/events.js +1 -0
  20. package/src-admin/bootstrap.js +1 -0
  21. package/src-admin/components/App.vue +5 -1
  22. package/src-admin/components/FlyingContext.vue +77 -0
  23. package/src-admin/components/FlyingContextContainer.vue +85 -0
  24. package/src-admin/components/app/AppBarButton.vue +10 -2
  25. package/src-admin/components/controls/SearchSelectFormField.vue +1 -1
  26. package/src-admin/components/form/EditFormButtons.vue +40 -0
  27. package/src-admin/components/form/RemoveButton.vue +71 -0
  28. package/src-admin/components/index.js +7 -8
  29. package/src-admin/components/list/ListView.vue +19 -5
  30. package/src-admin/components/pages/EditPage.vue +48 -134
  31. package/src-admin/components/routes/DataRouteMixin.js +84 -0
  32. package/src-admin/config/routing.js +0 -11
  33. package/src-admin/directives/index.js +26 -0
  34. package/src-admin/models/Model.js +3 -3
  35. package/src-admin/components/pages/CreatePage.vue +0 -114
  36. package/src-admin/components/pages/DetailPage.vue +0 -143
  37. package/src-admin/components/pages/EditPageMixin.js +0 -96
  38. package/src-admin/components/pages/ListPage.vue +0 -55
  39. package/src-admin/components/routes/CreateRoute.vue +0 -15
  40. package/src-admin/components/routes/DetailRoute.vue +0 -85
  41. package/src-admin/components/routes/EditRoute.vue +0 -78
  42. package/src-admin/components/routes/ListRoute.vue +0 -110
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <div
3
+ id="flyingContextContainer"
4
+ :class="{visible}"
5
+ >
6
+ <a
7
+ href=""
8
+ @click.prevent="hide"
9
+ >close</a>
10
+
11
+ <br>
12
+ <br>
13
+
14
+ <div id="flyingContextContainer__children" />
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ import { Component, Vue } from '@a-vue'
20
+ import { FlyingContextEvent } from '@a-vue/events'
21
+
22
+ @Component({
23
+ props: []
24
+ })
25
+ export default class FlyingContextContainer extends Vue {
26
+ visible = false
27
+
28
+ mounted () {
29
+ this.mutationWatcher = new MutationObserver(this.domChanged)
30
+ this.mutationWatcher.observe(this.getChildrenContainer(), { childList: true })
31
+
32
+ window.addEventListener('mousedown', this.onClickOutside)
33
+ }
34
+
35
+ domChanged () {
36
+ const container = this.getChildrenContainer()
37
+ this.visible = !!container.children.length
38
+ }
39
+
40
+ hide () {
41
+ if (this.visible) {
42
+ this.$events.dispatch(new FlyingContextEvent(FlyingContextEvent.HIDE_ALL))
43
+ }
44
+ }
45
+
46
+ getChildrenContainer () {
47
+ return this.$el.querySelector('#flyingContextContainer__children')
48
+ }
49
+
50
+ onClickOutside (e) {
51
+ // check if trigger is clicked
52
+ let parent = e.target
53
+ while (parent) {
54
+ if (parent.classList.contains('flyingContextTrigger')) {
55
+ return
56
+ }
57
+ parent = parent.parentElement
58
+ }
59
+
60
+ // check if flying context ist clicked
61
+ if (!this.$el.contains(e.target)) {
62
+ this.hide()
63
+ }
64
+ }
65
+ }
66
+ </script>
67
+
68
+
69
+ <style lang="scss" scoped>
70
+ #flyingContextContainer {
71
+ position: fixed;
72
+ z-index: 200;
73
+ right: 0;
74
+ width: 60vw;
75
+ height: 80vh;
76
+ top: 10vh;
77
+ background: rgba($color: white, $alpha: .6);
78
+ border: 1px solid black;
79
+ transition: right .2s;
80
+
81
+ &:not(.visible) {
82
+ right: -80vw;
83
+ }
84
+ }
85
+ </style>
@@ -8,12 +8,20 @@
8
8
  import { Component, Vue } from '@a-vue'
9
9
 
10
10
  @Component({
11
- props: ['prepend']
11
+ props: {
12
+ prepend: {
13
+ type: Boolean
14
+ }
15
+ }
12
16
  })
13
17
  export default class AppBarButton extends Vue {
14
18
  mounted () {
15
19
  const section = this.getButtonBar()
16
- section.appendChild(this.$el)
20
+ if (this.prepend) {
21
+ section.prepend(this.$el)
22
+ } else {
23
+ section.append(this.$el)
24
+ }
17
25
  }
18
26
 
19
27
  destroyed () {
@@ -60,7 +60,7 @@
60
60
  </div>
61
61
 
62
62
  <a-search-select
63
- :listViewConfig="selectableConfig.listViewConfig"
63
+ :listAction="selectableConfig.listAction"
64
64
  :loadOnlyIfKeyword="false"
65
65
  :selectedItems="selectableSelectedItems"
66
66
  :width="selectableWidth"
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <a-row gap="2">
3
+ <v-btn
4
+ :small="small"
5
+ :disabled="($has.reset && !changed) || !valid"
6
+ color="green white--text"
7
+ @click="$emit('save')"
8
+ >
9
+ Speichern
10
+ </v-btn>
11
+
12
+ <v-icon
13
+ v-if="$has.reset && changed"
14
+ :small="small"
15
+ text
16
+ title="Formular zurücksetzen"
17
+ @click="$emit('reset')"
18
+ >
19
+ {{ undoIcon }}
20
+ </v-icon>
21
+ </a-row>
22
+ </template>
23
+
24
+ <script>
25
+ import { Component, Vue } from '@a-vue'
26
+ import { mdiRotateLeft} from '@mdi/js'
27
+
28
+ @Component({
29
+ props: [
30
+ 'changed',
31
+ 'valid',
32
+ 'small'
33
+ ]
34
+ })
35
+ export default class EditFormButtons extends Vue {
36
+ $hasOptions = ['reset']
37
+
38
+ undoIcon = mdiRotateLeft
39
+ }
40
+ </script>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div>
3
+ <a-icon-button
4
+ small
5
+ color="red white--text"
6
+ :class="'removeButton-' + dialogId"
7
+ icon="$trashCanIcon"
8
+ text="Löschen"
9
+ @click="remove"
10
+ />
11
+
12
+ <a-dialog
13
+ :id="dialogId"
14
+ :anchor="[document, '.removeButton-' + dialogId]"
15
+ :active="protect ? removeKey === removeConfirmed : true"
16
+ >
17
+ <template v-if="protect">
18
+ <div>Bitte folgenden Key eingeben: <strong class="removeKey">{{ removeKey }}</strong></div>
19
+
20
+ <a-text-field
21
+ v-model="removeConfirmed"
22
+ label="Key"
23
+ :focus="true"
24
+ width="100"
25
+ />
26
+ </template>
27
+ </a-dialog>
28
+ </div>
29
+ </template>
30
+
31
+
32
+ <script>
33
+ import { Component, Vue } from '@a-vue'
34
+ import { DialogEvent } from '@a-vue/events'
35
+ import { randomCssClass } from '@a-vue/utils/random'
36
+
37
+ @Component({
38
+ props: ['itemTitle', 'protect']
39
+ })
40
+ export default class EditPage extends Vue {
41
+ dialogId = randomCssClass(10)
42
+ document = document
43
+ removeKey = null
44
+ removeConfirmed = null
45
+
46
+ async remove () {
47
+ if (this.protect) {
48
+ this.removeKey = [...Array(6)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
49
+ }
50
+
51
+ const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
52
+ id: this.dialogId,
53
+ title: this.itemTitle + ' löschen?',
54
+ message: 'Soll ' + this.itemTitle + ' gelöscht werden?',
55
+ yesButton: 'Löschen',
56
+ yesColor: 'red white--text'
57
+ }))
58
+
59
+ if (result === DialogEvent.RESULT_YES) {
60
+ this.$emit('remove')
61
+ }
62
+ }
63
+ }
64
+ </script>
65
+
66
+
67
+ <style lang="scss" scoped>
68
+ .removeKey {
69
+ user-select: none;
70
+ }
71
+ </style>
@@ -7,7 +7,9 @@ import DetailContent from './detail/DetailContent'
7
7
  import DetailMeta from './detail/DetailMeta'
8
8
  import DetailProperty from './detail/DetailProperty'
9
9
  import DetailTitle from './detail/DetailTitle'
10
- import Start from './Start.vue'
10
+ import FlyingContext from './FlyingContext.vue'
11
+ import EditFormButtons from './form/EditFormButtons'
12
+ import RemoveButton from './form/RemoveButton'
11
13
  import ListCard from './list/ListCard'
12
14
  import ListColumnHeader from './list/ListColumnHeader'
13
15
  import ListContent from './list/ListContent'
@@ -16,10 +18,8 @@ import ListTitle from './list/ListTitle'
16
18
  import ListView from './list/ListView'
17
19
  import ModelCount from './model/ModelCount'
18
20
  import ModelIcon from './model/ModelIcon'
19
- import CreatePage from './pages/CreatePage'
20
- import DetailPage from './pages/DetailPage'
21
21
  import EditPage from './pages/EditPage'
22
- import ListPage from './pages/ListPage'
22
+ import Start from './Start.vue'
23
23
 
24
24
  Vue.component('ListCard', ListCard)
25
25
  Vue.component('ListColumnHeader', ListColumnHeader)
@@ -27,11 +27,10 @@ Vue.component('ListContent', ListContent)
27
27
  Vue.component('ListMeta', ListMeta)
28
28
  Vue.component('ListTitle', ListTitle)
29
29
  Vue.component('ListView', ListView)
30
- Vue.component('ListPage', ListPage)
31
-
32
- Vue.component('CreatePage', CreatePage)
33
30
 
34
31
  Vue.component('EditPage', EditPage)
32
+ Vue.component('EditFormButtons', EditFormButtons)
33
+ Vue.component('RemoveButton', RemoveButton)
35
34
 
36
35
  Vue.component('ModelCount', ModelCount)
37
36
  Vue.component('ModelIcon', ModelIcon)
@@ -39,7 +38,6 @@ Vue.component('ModelIcon', ModelIcon)
39
38
  Vue.component('DetailContent', DetailContent)
40
39
  Vue.component('DetailMeta', DetailMeta)
41
40
  Vue.component('DetailTitle', DetailTitle)
42
- Vue.component('DetailPage', DetailPage)
43
41
  Vue.component('DetailProperty', DetailProperty)
44
42
  Vue.component('DetailColumn', DetailColumn)
45
43
 
@@ -47,3 +45,4 @@ Vue.component('AppBarButton', AppBarButton)
47
45
  Vue.component('AppBarTitle', AppBarTitle)
48
46
 
49
47
  Vue.component('Start', Start)
48
+ Vue.component('FlyingContext', FlyingContext)
@@ -28,7 +28,11 @@
28
28
  <a-table-row
29
29
  v-for="model in models_"
30
30
  :key="model.id"
31
- :class="getModelClass(model)"
31
+ v-flying-context-trigger="hasFlyingContext"
32
+ :class="{selectable: hasFlyingContext}"
33
+ v-bind="getRowAttributes(model)"
34
+ v-on="getRowListeners(model)"
35
+ @click="$emit('flyingContext', model)"
32
36
  >
33
37
  <v-icon
34
38
  v-if="$has.icon"
@@ -50,7 +54,6 @@
50
54
  <div
51
55
  v-for="model in models_"
52
56
  :key="model.id"
53
- :class="getModelClass(model)"
54
57
  >
55
58
  <slot
56
59
  name="model"
@@ -76,7 +79,7 @@ import { ListViewMixin } from '@a-vue/components/list/ListViewMixin'
76
79
  import { LoadingEvent } from '@a-vue/events'
77
80
 
78
81
  @Component({
79
- props: ['modelClass']
82
+ props: ['rowAttributes', 'rowListeners']
80
83
  })
81
84
  export default class ListView extends Mixins(ListViewMixin) {
82
85
  $hasOptions = ['icon']
@@ -93,8 +96,19 @@ export default class ListView extends Mixins(ListViewMixin) {
93
96
  this.$emit('update:isLoading', this.isLoading)
94
97
  }
95
98
 
96
- getModelClass (model) {
97
- return this.modelClass && this.modelClass(model)
99
+ getRowAttributes (model) {
100
+ if (typeof this.rowAttributes === 'function') {
101
+ return this.rowAttributes(model)
102
+ }
103
+ return this.rowAttributes
104
+ }
105
+
106
+ getRowListeners (model) {
107
+ return (this.rowListeners && this.rowListeners(model)) || {}
108
+ }
109
+
110
+ get hasFlyingContext () {
111
+ return !!this.$listeners.flyingContext
98
112
  }
99
113
 
100
114
  setFilter (name, value) {
@@ -1,151 +1,65 @@
1
1
  <template>
2
- <div class="editPage">
3
- <app-bar-title
4
- :icon="_icon"
5
- :title="_title"
6
- />
7
-
8
- <app-bar-button v-if="$has.list">
9
- <v-btn
10
- :to="_listLink"
11
- small
12
- >
13
- Liste
14
- </v-btn>
15
- </app-bar-button>
16
-
17
- <app-bar-button v-if="$has.detail">
18
- <v-btn
19
- :to="model.getLink()"
20
- small
21
- >
22
- Ansehen
23
- </v-btn>
24
- </app-bar-button>
25
-
26
- <edit-form
27
- :model="modelToEdit"
28
- :valid.sync="valid"
29
- :changed.sync="changed"
30
- >
31
- <template #fields>
32
- <slot
33
- name="fields"
34
- :model="modelToEdit"
35
- />
36
- </template>
37
- </edit-form>
38
-
39
- <a-row class="mt-8">
40
- <v-btn
41
- :disabled="!changed || !valid"
42
- color="green white--text"
43
- @click="save"
44
- >
45
- Speichern
46
- </v-btn>
47
-
48
- <v-btn
49
- v-if="changed"
50
- text
51
- @click="reset"
52
- >
53
- Zurücksetzen
54
- </v-btn>
55
- </a-row>
56
- </div>
2
+ <edit-form
3
+ ref="form"
4
+ :model="model"
5
+ :createModelToEdit="createModelToEdit"
6
+ >
7
+ <template #form="{modelToEdit, changed, valid}">
8
+ <slot
9
+ name="form"
10
+ :modelToEdit="modelToEdit"
11
+ :changed="changed"
12
+ :valid="valid"
13
+ />
14
+
15
+ <edit-form-buttons
16
+ :changed="changed"
17
+ :valid="valid"
18
+ :has="{reset: !!modelToEdit.id}"
19
+ @save="$emit('save', modelToEdit, ignoreChangesOnRouteChange)"
20
+ @reset="$refs.form.reset()"
21
+ />
22
+ </template>
23
+ </edit-form>
57
24
  </template>
58
25
 
59
26
  <script>
60
- import { Component, Mixins, Watch } from '@a-vue'
61
- import { EditPageMixin } from './EditPageMixin'
27
+ import { Component, Vue } from '@a-vue'
28
+ import { DialogEvent } from '@a-vue/events'
62
29
 
63
30
  @Component({
64
- props: [
65
- 'model',
66
- 'icon',
67
- 'title',
68
- 'listLink',
69
- 'getAction'
70
- ]
31
+ props: ['model', 'createModelToEdit']
71
32
  })
72
- export default class EditPage extends Mixins(EditPageMixin) {
73
- $hasOptions = ['detail', {list: false}]
74
-
75
- model_ = null
33
+ export default class EditPage extends Vue {
34
+ unregisterRouterHook = null
35
+ ignoreChangesOnRouteChange_ = false
76
36
 
77
37
  created () {
78
- if (!this.$parent.constructor.getEditRouteConfig) {
79
- console.warn('<edit-page> owner must provide a static getEditRouteConfig method.')
80
- }
81
-
82
- this.model_ = this.model
83
- this.reset()
84
- }
85
-
86
- @Watch('model')
87
- modelChanged () {
88
- this.model_ = this.model
89
- this.reset()
90
- }
91
-
92
- get editConfig () {
93
- return this.$parent.constructor.getEditRouteConfig(this.$route)
94
- }
95
-
96
- get modelUpateAction () {
97
- return this.editConfig.updateAction || this.ModelClass.getAction('save')
98
- }
99
-
100
- get _getAction () {
101
- if (this.getAction) {
102
- return this.getAction
103
- }
104
- return this.ModelClass.getAction('get')
105
- }
106
-
107
- get _icon () {
108
- return this.icon || this.model.getIcon()
109
- }
110
-
111
- get _title () {
112
- const title = this.modelToEdit.getTitle()
113
- if (title) {
114
- return title
115
- }
116
-
117
- if (this.title) {
118
- return this.title
119
- }
120
-
121
- return this.$t('Admin.Types', this.ModelClass.type, null, 'TITLE_EMPTY', 'de')
122
- }
123
-
124
- get _listLink () {
125
- if (this.listLink) {
126
- if (typeof this.listLink === 'function') {
127
- return this.listLink()
128
- } else {
129
- return this.listLink
38
+ this.unregisterRouterHook = this.$router.beforeEach(async (to, from, next) => {
39
+ if (this.$refs.form.changed && !this.ignoreChangesOnRouteChange_) {
40
+ const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
41
+ title: 'Änderungen verwerfen?',
42
+ message: 'Im Formular sind nicht gespeicherte Änderungen. Sollen diese verworfen werden?',
43
+ yesButton: 'Verwerfen'
44
+ }))
45
+ if (result === DialogEvent.RESULT_YES) {
46
+ next()
47
+ }
48
+ return
130
49
  }
131
- }
132
- return this.model.getLink('list')
133
- }
134
-
135
- createModelToEdit () {
136
- return this.model_.cloneForEdit(this.fields)
50
+ next()
51
+ })
137
52
  }
138
53
 
139
- get saveParams () {
140
- return {
141
- id: this.model.id
142
- }
54
+ destroyed () {
55
+ this.unregisterRouterHook()
143
56
  }
144
57
 
145
- afterSave (model) {
146
- this.model_ = model
147
- this.reset()
148
- this.$emit('saved', model)
58
+ /**
59
+ * hook to allow to leave a just created (saved) model
60
+ */
61
+ ignoreChangesOnRouteChange () {
62
+ this.ignoreChangesOnRouteChange_ = true
149
63
  }
150
64
  }
151
65
  </script>
@@ -0,0 +1,84 @@
1
+ import { Component, Vue, Watch } from '@a-vue'
2
+
3
+ Component.registerHooks([
4
+ 'beforeRouteEnter',
5
+ 'beforeRouteUpdate'
6
+ ])
7
+
8
+ let onLoadCallback = () => {}
9
+ let routerHookCalled = false
10
+ let lastResult = null // cache last result because of hmr reload
11
+
12
+ function onLoad (callback) {
13
+ onLoadCallback = callback
14
+ }
15
+
16
+ function load (route) {
17
+ const Component = [...route.matched].pop().components.default
18
+
19
+ if (!Component.drm_getActions) {
20
+ console.warn('A data route component must implement a static drm_getActions() method.')
21
+ }
22
+
23
+ const request = Component.drm_getActions(route, onLoad)
24
+ if (Array.isArray(request)) {
25
+ return Promise.all(request.map(request => {
26
+ return request
27
+ .dispatchGlobalLoadingEvents()
28
+ .execute()
29
+ }))
30
+ } else {
31
+ return request
32
+ .dispatchGlobalLoadingEvents()
33
+ .execute()
34
+ }
35
+ }
36
+
37
+ @Component
38
+ export class DataRouteMixin extends Vue {
39
+ drm_isLoaded = false
40
+
41
+ async beforeRouteEnter (to, from, next) {
42
+ routerHookCalled = true
43
+ const result = await load(to)
44
+
45
+ next(vm => {
46
+ if (result !== false) {
47
+ vm.drm_onLoad(result)
48
+ vm.drm_isLoaded = true
49
+ }
50
+ routerHookCalled = false
51
+ })
52
+ }
53
+
54
+ /**
55
+ * triggered both, if route name or route params change
56
+ @Watch('$route.params')
57
+ async routeParamsChanged () {
58
+ if (routerHookCalled) {
59
+ return
60
+ }
61
+
62
+ if (!this.drm_isLoaded) {
63
+ const result = await load(this.$route)
64
+
65
+ if (result !== false) {
66
+ this.drm_onLoad(result)
67
+ this.drm_isLoaded = true
68
+ }
69
+ }
70
+ }
71
+ */
72
+
73
+ drm_onLoad (result) {
74
+ onLoadCallback(this, result)
75
+ lastResult = result
76
+ }
77
+
78
+ created () {
79
+ // hmr reload creates vm but not triggers route enter
80
+ if (!routerHookCalled) {
81
+ onLoadCallback(this, lastResult)
82
+ }
83
+ }
84
+ }
@@ -1,7 +1,3 @@
1
- import CreateRoute from '@a-admin/components/routes/CreateRoute'
2
- import DetailRoute from '@a-admin/components/routes/DetailRoute'
3
- import EditRoute from '@a-admin/components/routes/EditRoute'
4
- import ListRoute from '@a-admin/components/routes/ListRoute'
5
1
  import { routeConfigPlugin } from '@a-vue/plugins/route-config/RouteConfigPlugin'
6
2
 
7
3
  export default routeConfigPlugin
@@ -9,13 +5,6 @@ export default routeConfigPlugin
9
5
  linkActiveClass: 'active'
10
6
  })
11
7
 
12
- .defaultComponents({
13
- list: ListRoute,
14
- new: CreateRoute,
15
- detail: DetailRoute,
16
- edit: EditRoute
17
- })
18
-
19
8
  .defaultBreadcrumbTitles({
20
9
  edit: 'Bearbeiten',
21
10
  new: 'Neu'
@@ -0,0 +1,26 @@
1
+ import { Vue } from '@a-vue'
2
+
3
+ Vue.directive('flying-context-trigger', {
4
+ bind: function (el, bindings) {
5
+ if (bindings.value) {
6
+ el.$flyingContextObserver = new MutationObserver(() => {
7
+ if (!el.classList.contains('flyingContextTrigger')) {
8
+ el.classList.add('flyingContextTrigger')
9
+ }
10
+ })
11
+
12
+ el.$flyingContextObserver.observe(el, {
13
+ attributes: true,
14
+ attributeFilter: ['class']
15
+ })
16
+
17
+ el.classList.add('flyingContextTrigger')
18
+ }
19
+ },
20
+
21
+ unbind: function (el) {
22
+ if (el.$flyingContextObserver) {
23
+ el.$flyingContextObserver.disconnect()
24
+ }
25
+ }
26
+ })
@@ -17,11 +17,11 @@ export class Model extends ApiResourcesModel {
17
17
  return (new this()).getLink(action)
18
18
  }
19
19
 
20
- static getAction (action) {
20
+ static getAction (actionName) {
21
21
  if (this.resourceType) {
22
22
  return apiResources.getAction({
23
- resource: this.resourceType,
24
- action
23
+ resourceType: this.resourceType,
24
+ actionName
25
25
  })
26
26
  }
27
27
  console.warn('You can\'t get an action out of a model without resourceType:', this.type)