@afeefa/vue-app 0.0.63 → 0.0.66

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. package/.afeefa/package/release/version.txt +1 -1
  2. package/package.json +1 -1
  3. package/src/components/ABreadcrumbs.vue +75 -18
  4. package/src/components/ADatePicker.vue +1 -1
  5. package/src/components/AIcon.vue +3 -6
  6. package/src/components/AIconButton.vue +1 -2
  7. package/src/components/AModal.vue +21 -3
  8. package/src/components/ARichTextArea.vue +95 -85
  9. package/src/components/ARow.vue +0 -7
  10. package/src/components/ATableRow.vue +4 -0
  11. package/src/components/flying-context/FlyingContextEvent.js +5 -0
  12. package/src/components/form/EditForm.vue +13 -3
  13. package/src/components/form/EditModal.vue +52 -35
  14. package/src/components/form/fields/FormFieldRichTextArea.vue +5 -3
  15. package/src/components/list/ListViewMixin.js +25 -2
  16. package/src/components/mixins/ClickOutsideMixin.js +5 -1
  17. package/src/components/search-select/SearchSelectList.vue +0 -1
  18. package/src/components/vue/Component.js +9 -2
  19. package/src/events.js +2 -0
  20. package/src-admin/bootstrap.js +1 -0
  21. package/src-admin/components/App.vue +58 -59
  22. package/src-admin/components/FlyingContext.vue +77 -0
  23. package/src-admin/components/FlyingContextContainer.vue +85 -0
  24. package/src-admin/components/Sidebar.vue +66 -0
  25. package/src-admin/components/SidebarItem.vue +59 -0
  26. package/src-admin/components/StickyFooter.vue +42 -0
  27. package/src-admin/components/StickyFooterContainer.vue +66 -0
  28. package/src-admin/components/StickyHeader.vue +73 -0
  29. package/src-admin/components/app/AppBarButtons.vue +0 -7
  30. package/src-admin/components/app/AppBarTitle.vue +55 -11
  31. package/src-admin/components/app/AppBarTitleContainer.vue +2 -3
  32. package/src-admin/components/controls/SearchSelectFormField.vue +1 -0
  33. package/src-admin/components/detail/DetailProperty.vue +20 -16
  34. package/src-admin/components/form/EditFormButtons.vue +25 -6
  35. package/src-admin/components/form/RemoveButton.vue +17 -8
  36. package/src-admin/components/index.js +6 -7
  37. package/src-admin/components/list/ListView.vue +22 -9
  38. package/src-admin/components/pages/EditPage.vue +17 -8
  39. package/src-admin/components/routes/DataRouteMixin.js +24 -15
  40. package/src-admin/config/routing.js +0 -11
  41. package/src-admin/config/vuetify.js +22 -2
  42. package/src-admin/directives/index.js +26 -0
  43. package/src-admin/styles.scss +21 -4
  44. package/src-admin/components/pages/CreatePage.vue +0 -114
  45. package/src-admin/components/pages/DetailPage.vue +0 -143
  46. package/src-admin/components/pages/EditPageMixin.js +0 -96
  47. package/src-admin/components/pages/ListPage.vue +0 -55
  48. package/src-admin/components/routes/CreateRoute.vue +0 -15
  49. package/src-admin/components/routes/DetailRoute.vue +0 -85
  50. package/src-admin/components/routes/EditRoute.vue +0 -78
  51. package/src-admin/components/routes/ListRoute.vue +0 -110
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <a-modal
3
3
  :title="title"
4
+ :beforeClose="beforeClose"
4
5
  :show.sync="show_"
5
6
  v-bind="$attrs"
6
7
  >
@@ -10,16 +11,18 @@
10
11
 
11
12
  <edit-form
12
13
  v-if="show_"
14
+ ref="form"
13
15
  :model="model"
16
+ :createModelToEdit="createModelToEdit"
14
17
  >
15
- <template #fields>
18
+ <template #form="{modelToEdit, changed, valid}">
16
19
  <slot
17
- name="fields"
18
- :model="model"
20
+ name="form"
21
+ :modelToEdit="modelToEdit"
22
+ :changed="changed"
23
+ :valid="valid"
19
24
  />
20
- </template>
21
25
 
22
- <template #default="{changed, valid}">
23
26
  <a-row
24
27
  class="mt-8 mb-1 pb-1 gap-4"
25
28
  right
@@ -31,25 +34,14 @@
31
34
  Schließen
32
35
  </v-btn>
33
36
 
34
- <a-row gap="2">
35
- <v-btn
36
- small
37
- :disabled="!changed || !valid"
38
- color="green white--text"
39
- @click="save"
40
- >
41
- Speichern
42
- </v-btn>
43
-
44
- <v-icon
45
- v-if="changed"
46
- small
47
- text
48
- @click="reset"
49
- >
50
- {{ undoIcon }}
51
- </v-icon>
52
- </a-row>
37
+ <edit-form-buttons
38
+ :changed="changed"
39
+ :valid="valid"
40
+ small
41
+ :has="{reset: !!modelToEdit.id}"
42
+ @save="$emit('save', modelToEdit, ignoreChangesOnClose)"
43
+ @reset="$refs.form.reset()"
44
+ />
53
45
  </a-row>
54
46
  </template>
55
47
  </edit-form>
@@ -59,21 +51,26 @@
59
51
 
60
52
  <script>
61
53
  import { Component, Vue, Watch } from '@a-vue'
62
- import { mdiRotateLeft} from '@mdi/js'
54
+ import { DialogEvent } from '@a-vue/events'
63
55
 
64
56
  @Component({
65
- props: ['model', 'title', 'show']
57
+ props: ['model', 'createModelToEdit', 'title', 'show']
66
58
  })
67
59
  export default class EditModal extends Vue {
68
60
  show_ = false
61
+ ignoreChangesOnClose_ = false
69
62
 
70
- undoIcon = mdiRotateLeft
63
+ created () {
64
+ if (this.show) { // open on create with v-show
65
+ this.open()
66
+ }
67
+ }
71
68
 
72
69
  /**
73
70
  * visiblility changes from outside
74
71
  * this will trigger the show_ watcher,
75
72
  * forward the change to the modal and
76
- * later emit a open/close event
73
+ * later emit an open/close event
77
74
  */
78
75
  @Watch('show')
79
76
  showChanged () {
@@ -90,7 +87,6 @@ export default class EditModal extends Vue {
90
87
  @Watch('show_')
91
88
  show_Changed () {
92
89
  if (this.show_) {
93
- this.reset()
94
90
  this.$emit('open')
95
91
  } else {
96
92
  this.$emit('close')
@@ -101,16 +97,37 @@ export default class EditModal extends Vue {
101
97
  this.show_ = true
102
98
  }
103
99
 
104
- close () {
105
- this.show_ = false
100
+ async beforeClose () {
101
+ // run only if show_ is true to prevent double checks with a-modal
102
+ if (this.show_ && this.$refs.form.changed && !this.ignoreChangesOnClose_) {
103
+ const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
104
+ title: 'Änderungen verwerfen?',
105
+ message: 'Im Formular sind nicht gespeicherte Änderungen. Sollen diese verworfen werden?',
106
+ yesButton: 'Verwerfen'
107
+ }))
108
+ if (result !== DialogEvent.RESULT_YES) {
109
+ return false
110
+ }
111
+ }
112
+ return true
106
113
  }
107
114
 
108
- reset () {
109
- this.$emit('reset')
115
+ async close () {
116
+ const result = await this.beforeClose()
117
+ if (!result) {
118
+ return
119
+ }
120
+
121
+ this.show_ = false
110
122
  }
111
123
 
112
- save () {
113
- this.$emit('save')
124
+ /**
125
+ * hook to allow to leave a just created (saved) model
126
+ */
127
+ ignoreChangesOnClose () {
128
+ // this.$refs.form.forceUnchanged()
129
+ console.info('TODO switch form to forceUnchanged')
130
+ this.ignoreChangesOnClose_ = true
114
131
  }
115
132
  }
116
133
  </script>
@@ -1,7 +1,9 @@
1
1
  <template>
2
- <a-rich-text-area
3
- v-model="model[name]"
4
- />
2
+ <a-rich-text-area v-model="model[name]">
3
+ <template #buttons>
4
+ <slot name="buttons" />
5
+ </template>
6
+ </a-rich-text-area>
5
7
  </template>
6
8
 
7
9
  <script>
@@ -11,6 +11,7 @@ import { FilterSourceType } from './FilterSourceType'
11
11
  'listAction',
12
12
  'filterHistoryKey',
13
13
  'loadOnlyIfKeyword',
14
+ 'checkBeforeLoad',
14
15
  {
15
16
  filterSource: FilterSourceType.QUERY_STRING,
16
17
  events: true,
@@ -64,9 +65,16 @@ export class ListViewMixin extends Vue {
64
65
  .on('change', this.filtersChanged) // listen to change
65
66
 
66
67
  this._filtersInitialized()
68
+ this.$emit('update:filters', this.filters)
69
+ this.$emit('update:listViewModel', this.listViewModel)
67
70
 
68
71
  if (this.models) {
69
72
  this.$emit('update:count', this.meta_.count_search)
73
+
74
+ this.$emit('onLoad', {
75
+ models: this.models_,
76
+ meta: this.meta_
77
+ })
70
78
  } else {
71
79
  this.load()
72
80
  }
@@ -113,6 +121,16 @@ export class ListViewMixin extends Vue {
113
121
  }
114
122
 
115
123
  async load () {
124
+ if (this.checkBeforeLoad) {
125
+ const canLoad = await this.checkBeforeLoad()
126
+ if (!canLoad) {
127
+ if (this.meta_.used_filters) {
128
+ this.listViewModel.initFromUsedFilters(this.meta_.used_filters, this.meta_.count_search)
129
+ }
130
+ return
131
+ }
132
+ }
133
+
116
134
  if (this._loadOnlyIfKeyword && !this.filters.q.value) {
117
135
  this.models_ = []
118
136
  this.meta_ = {}
@@ -132,7 +150,7 @@ export class ListViewMixin extends Vue {
132
150
 
133
151
  if (!models) { // error happened
134
152
  this.isLoading = false
135
- this.$emit('update:isLoading', this.isLoading)
153
+ this.$emit('update:isLoading', false)
136
154
  return
137
155
  }
138
156
 
@@ -144,8 +162,13 @@ export class ListViewMixin extends Vue {
144
162
  }
145
163
 
146
164
  this.isLoading = false
147
- this.$emit('update:isLoading', this.isLoading)
165
+ this.$emit('update:isLoading', false)
148
166
 
149
167
  this.$emit('update:count', this.meta_.count_search)
168
+
169
+ this.$emit('onLoad', {
170
+ models,
171
+ meta
172
+ })
150
173
  }
151
174
  }
@@ -25,10 +25,14 @@ export class ClickOutsideMixin extends Vue {
25
25
  // popup clicked
26
26
  const thisIndex = getZIndex(this.$el)
27
27
  const targetIndex = getZIndex(e.target)
28
- if (targetIndex > thisIndex) {
28
+ if (targetIndex > 10 && targetIndex > thisIndex) { // sidebar === 6
29
29
  return
30
30
  }
31
31
 
32
+ this.com_onClickOutside()
32
33
  this.$emit('click:outside')
33
34
  }
35
+
36
+ com_onClickOutside () {
37
+ }
34
38
  }
@@ -67,7 +67,6 @@ export default class SearchSelectList extends Mixins(ListViewMixin) {
67
67
  if (this.q) {
68
68
  this.filters.q.value = this.q
69
69
  }
70
- this.$emit('update:filters', this.filters)
71
70
  }
72
71
  }
73
72
  </script>
@@ -32,9 +32,16 @@ function propsWithDefaults (props) {
32
32
  // property: { some object }, should be a normal vue props object
33
33
  } else if (value && typeof value === 'object' && value.constructor === Object) {
34
34
  normalizedProps[subProp] = value
35
- // property: true, null, ...
35
+ // property: true, false, null, ...
36
36
  } else {
37
- normalizedProps[subProp] = { default: value }
37
+ if (typeof value === 'boolean') {
38
+ normalizedProps[subProp] = {
39
+ type: Boolean,
40
+ default: value
41
+ }
42
+ } else {
43
+ normalizedProps[subProp] = { default: value }
44
+ }
38
45
  }
39
46
  })
40
47
  } else {
package/src/events.js CHANGED
@@ -1,4 +1,6 @@
1
+ export { BaseEvent } from './plugins/event-bus/BaseEvent'
1
2
  export { LoadingEvent } from './components/loading-indicator/LoadingEvent'
2
3
  export { SaveEvent } from './components/save-indicator/SaveEvent'
3
4
  export { AlertEvent } from './components/alert/AlertEvent'
4
5
  export { DialogEvent } from './components/dialog/DialogEvent'
6
+ export { FlyingContextEvent } from './components/flying-context/FlyingContextEvent'
@@ -1,5 +1,6 @@
1
1
  import './config/event-bus'
2
2
  import './config/components'
3
+ import './directives'
3
4
 
4
5
  import { translationPlugin } from '@a-admin/plugins/translation/TranslationPlugin'
5
6
  import { apiResourcesPlugin } from '@a-vue/plugins/api-resources/ApiResourcesPlugin'
@@ -3,8 +3,9 @@
3
3
  <v-navigation-drawer
4
4
  v-model="drawer"
5
5
  app
6
- fixeds
7
6
  left
7
+ width="280"
8
+ class="menubar"
8
9
  >
9
10
  <v-container
10
11
  flex-column
@@ -72,66 +73,47 @@
72
73
  </v-container>
73
74
  </v-navigation-drawer>
74
75
 
75
- <v-navigation-drawer
76
- v-if="false"
77
- app
78
- clipped
79
- right
80
- >
81
- <v-list>
82
- <v-list-item
83
- v-for="n in 5"
84
- :key="n"
85
- link
86
- >
87
- <v-list-item-content>
88
- <v-list-item-title>Item {{ n }}</v-list-item-title>
89
- </v-list-item-content>
90
- </v-list-item>
91
- </v-list>
92
- </v-navigation-drawer>
93
-
94
- <v-app-bar
95
- app
96
- flat
97
- dense
98
- color="#FAFAFA"
99
- >
100
- <div class="d-flex align-start mt-n2">
76
+ <a-loading-indicator
77
+ fixed
78
+ top
79
+ left
80
+ class="loadingIndicator"
81
+ :isLoading="isLoading"
82
+ :color="loaderColor"
83
+ />
84
+
85
+ <v-main id="v-main">
86
+ <a-row
87
+ start
88
+ class="topbar"
89
+ >
101
90
  <v-app-bar-nav-icon
102
- class="sidebarToggleButton mr-2 ml-n1"
91
+ class="sidebarToggleButton mr-2 ml-4"
103
92
  @click="toggleDrawer"
104
93
  />
105
- <a-breadcrumbs class="mt-2" />
106
- </div>
107
-
108
- <a-loading-indicator
109
- fixed
110
- top
111
- left
112
- class="loadingIndicator"
113
- :isLoading="isLoading"
114
- :color="loaderColor"
115
- />
116
- </v-app-bar>
117
-
118
- <v-main>
94
+
95
+ <a-breadcrumbs />
96
+ </a-row>
97
+
119
98
  <v-container
120
99
  fluid
121
- class="pa-4"
100
+ class="pa-8 pt-4"
122
101
  >
123
- <div class="d-flex align-center">
124
- <app-bar-title-container class="flex-grow-1 mb-4" />
125
- <app-bar-buttons class="mr-2 mb-4" />
126
- </div>
102
+ <sticky-header />
127
103
 
128
104
  <router-view :class="{isLoading}" />
129
105
  </v-container>
106
+
107
+ <sticky-footer-container />
130
108
  </v-main>
131
109
 
132
110
  <a-dialog id="app" />
133
111
 
134
112
  <a-save-indicator />
113
+
114
+ <sidebar />
115
+
116
+ <flying-context-container />
135
117
  </div>
136
118
  </template>
137
119
 
@@ -142,17 +124,24 @@ import { appConfig } from '@a-admin/config/AppConfig'
142
124
  import { sleep } from '@a-vue/utils/timeout'
143
125
  import AppBarButtons from './app/AppBarButtons'
144
126
  import AppBarTitleContainer from './app/AppBarTitleContainer'
127
+ import FlyingContextContainer from './FlyingContextContainer'
128
+ import StickyFooterContainer from './StickyFooterContainer'
129
+ import Sidebar from './Sidebar'
130
+ import StickyHeader from './StickyHeader'
145
131
  import '../styles.scss'
146
132
 
147
133
  @Component({
148
134
  components: {
149
135
  AppBarButtons,
150
- AppBarTitleContainer
136
+ AppBarTitleContainer,
137
+ FlyingContextContainer,
138
+ StickyFooterContainer,
139
+ Sidebar,
140
+ StickyHeader
151
141
  }
152
142
  })
153
143
  export default class App extends Vue {
154
144
  drawer = true
155
- mainDrawer = false
156
145
  isLoading = false
157
146
  account = null
158
147
 
@@ -199,16 +188,6 @@ export default class App extends Vue {
199
188
  this.drawer = !this.drawer
200
189
  }
201
190
 
202
- @Watch('drawer')
203
- async drawerChanged () {
204
- if (this.drawer) {
205
- this.mainDrawer = false
206
- } else {
207
- await sleep(0.1)
208
- this.mainDrawer = true
209
- }
210
- }
211
-
212
191
  get hasAuthService () {
213
192
  return !!appConfig.authService
214
193
  }
@@ -244,4 +223,24 @@ export default class App extends Vue {
244
223
  height: 36px !important;
245
224
  margin-top: 1px;
246
225
  }
226
+
227
+ .topbar {
228
+ position: relative;
229
+ width: 100%;
230
+ left: 0;
231
+ top: 0;
232
+ padding: .2rem 1rem;
233
+ }
234
+
235
+ .a-breadcrumbs {
236
+ margin-top: 7px;
237
+ }
238
+
239
+ .menubar {
240
+ // background: #666666 !important;
241
+ }
242
+
243
+ #sidebar {
244
+ // background: #F4F4F4 !important;
245
+ }
247
246
  </style>
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <div class="flyingContext">
3
+ <div :class="contextId">
4
+ <slot v-if="isVisible" />
5
+ </div>
6
+ </div>
7
+ </template>
8
+
9
+ <script>
10
+ import { Component, Watch, Vue } from '@a-vue'
11
+ import { FlyingContextEvent } from '@a-vue/events'
12
+ import { randomCssClass } from '@a-vue/utils/random'
13
+
14
+ @Component({
15
+ props: [{show: false}]
16
+ })
17
+ export default class FlyingContext extends Vue {
18
+ isVisible = false
19
+ contextId = randomCssClass(10)
20
+
21
+ created () {
22
+ this.$events.on(FlyingContextEvent.HIDE_ALL, this.onHide)
23
+ }
24
+
25
+ mounted () {
26
+ if (this.show) {
27
+ this.showChanged()
28
+ }
29
+ }
30
+
31
+ @Watch('show')
32
+ showChanged () {
33
+ if (this.isVisible === this.show) {
34
+ return
35
+ }
36
+
37
+ this.isVisible = this.show
38
+
39
+ if (this.isVisible) {
40
+ const container = this.getSidebarContainer()
41
+ container.appendChild(this.getContent())
42
+ } else {
43
+ this.$el.appendChild(this.getContent())
44
+ }
45
+ }
46
+
47
+ destroyed () {
48
+ const container = this.getSidebarContainer()
49
+ const el = this.getContent()
50
+ if (container.contains(el)) {
51
+ container.removeChild(el)
52
+ }
53
+
54
+ this.$events.off(FlyingContextEvent.HIDE_ALL, this.onHide)
55
+ }
56
+
57
+ onHide () {
58
+ if (this.isVisible) {
59
+ this.$el.appendChild(this.getContent())
60
+ this.isVisible = false
61
+ this.$emit('hide')
62
+ }
63
+ }
64
+
65
+ getContent () {
66
+ return document.querySelector('.' + this.contextId)
67
+ }
68
+
69
+ getSidebarContainer () {
70
+ return document.getElementById('flyingContextContainer__children')
71
+ }
72
+ }
73
+ </script>
74
+
75
+
76
+ <style lang="scss" scoped>
77
+ </style>
@@ -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>
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <v-navigation-drawer
3
+ id="sidebar"
4
+ v-model="visible"
5
+ app
6
+ right
7
+ disable-resize-watcher
8
+ width="220"
9
+ >
10
+ <div id="sidebar__children">
11
+ <div class="top" />
12
+ <div class="bottom" />
13
+ </div>
14
+ </v-navigation-drawer>
15
+ </template>
16
+
17
+ <script>
18
+ import { Component, Vue } from '@a-vue'
19
+
20
+ @Component({
21
+ props: []
22
+ })
23
+ export default class Sidebar extends Vue {
24
+ visible = false
25
+
26
+ mounted () {
27
+ this.mutationWatcher = new MutationObserver(this.domChanged)
28
+ this.mutationWatcher.observe(this.$el.querySelector('#sidebar__children > .top'), { childList: true })
29
+ this.mutationWatcher.observe(this.$el.querySelector('#sidebar__children > .bottom'), { childList: true })
30
+
31
+ this.domChanged()
32
+ }
33
+
34
+ domChanged () {
35
+ this.visible = this.hasSidebarItems()
36
+ }
37
+
38
+ getChildrenContainer () {
39
+ return this.$el.querySelector('#sidebar__children')
40
+ }
41
+
42
+ hasSidebarItems () {
43
+ return !!(this.$el.querySelector('#sidebar__children .top').children.length +
44
+ this.$el.querySelector('#sidebar__children .bottom').children.length)
45
+ }
46
+ }
47
+ </script>
48
+
49
+
50
+ <style lang="scss" scoped>
51
+ #sidebar {
52
+ &__children {
53
+ width: 100%;
54
+ }
55
+ }
56
+
57
+ #sidebar__children {
58
+ height: 100%;
59
+ padding: 2rem;
60
+ // padding-left: 4rem;
61
+
62
+ display: flex;
63
+ flex-direction: column;
64
+ justify-content: space-between;
65
+ }
66
+ </style>