@afeefa/vue-app 0.0.63 → 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.
@@ -1 +1 @@
1
- 0.0.63
1
+ 0.0.64
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.63",
3
+ "version": "0.0.64",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -7,6 +7,8 @@
7
7
  v-bind="$attrs"
8
8
  :contentClass="modalId"
9
9
  transition="v-fade-transition"
10
+ :persistent="true"
11
+ :no-click-animation="true"
10
12
  @click:outside="cancel"
11
13
  @keydown.esc="cancel"
12
14
  >
@@ -41,7 +43,7 @@ import { getZIndex } from 'vuetify/lib/util/helpers'
41
43
  import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
42
44
 
43
45
  @Component({
44
- props: ['show', 'title', 'triggerClass', 'anchorPosition']
46
+ props: ['show', 'title', 'beforeClose', 'triggerClass', 'anchorPosition']
45
47
  })
46
48
  export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentWidthMixin) {
47
49
  modalId = randomCssClass(10)
@@ -83,7 +85,15 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
83
85
  * and emit a visibility event
84
86
  */
85
87
  @Watch('show')
86
- showChanged () {
88
+ async showChanged () {
89
+ // check if a modal is allowed to be closed
90
+ if (this.modal && !this.show && this.beforeClose) {
91
+ const result = await this.beforeClose()
92
+ if (!result) {
93
+ return
94
+ }
95
+ }
96
+
87
97
  this.modal = this.show
88
98
  }
89
99
 
@@ -121,7 +131,15 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
121
131
  this.urp_registerPositionWatcher(this.position)
122
132
  }
123
133
 
124
- cancel () {
134
+ async cancel () {
135
+ // check if a modal is allowed to be closed
136
+ if (this.modal && this.beforeClose) {
137
+ const result = await this.beforeClose()
138
+ if (!result) {
139
+ return
140
+ }
141
+ }
142
+
125
143
  this.modal = false
126
144
  }
127
145
 
@@ -62,6 +62,10 @@ export default class ATableRow extends Vue {
62
62
  }
63
63
  }
64
64
 
65
+ &.selectable {
66
+ cursor: pointer;
67
+ }
68
+
65
69
  &:hover, &.selected {
66
70
  background: #F4F4F4;
67
71
  }
@@ -0,0 +1,5 @@
1
+ import { BaseEvent } from '@a-vue/plugins/event-bus/BaseEvent'
2
+
3
+ export class FlyingContextEvent extends BaseEvent {
4
+ static HIDE_ALL = 'FlyingContextEvent:hide-all'
5
+ }
@@ -4,9 +4,10 @@
4
4
  autocomplete="off"
5
5
  >
6
6
  <slot
7
+ name="form"
7
8
  :changed="changed"
8
9
  :valid="valid"
9
- :model="modelToEdit"
10
+ :modelToEdit="modelToEdit"
10
11
  />
11
12
  </v-form>
12
13
  </template>
@@ -35,8 +36,8 @@ export default class EditForm extends Vue {
35
36
  reset () {
36
37
  if (this.createModelToEdit) {
37
38
  this.modelToEdit = this.createModelToEdit(this.model)
38
- } else {
39
- this.modelToEdit = this.model
39
+ } else if (this.model) {
40
+ this.modelToEdit = this.model.cloneForEdit()
40
41
  }
41
42
  this.lastJson = this.json
42
43
  }
@@ -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,35 @@ 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.ignoreChangesOnClose_ = true
114
129
  }
115
130
  }
116
131
  </script>
package/src/events.js CHANGED
@@ -2,3 +2,4 @@ export { LoadingEvent } from './components/loading-indicator/LoadingEvent'
2
2
  export { SaveEvent } from './components/save-indicator/SaveEvent'
3
3
  export { AlertEvent } from './components/alert/AlertEvent'
4
4
  export { DialogEvent } from './components/dialog/DialogEvent'
5
+ 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'
@@ -91,6 +91,8 @@
91
91
  </v-list>
92
92
  </v-navigation-drawer>
93
93
 
94
+ <flying-context-container />
95
+
94
96
  <v-app-bar
95
97
  app
96
98
  flat
@@ -142,12 +144,14 @@ import { appConfig } from '@a-admin/config/AppConfig'
142
144
  import { sleep } from '@a-vue/utils/timeout'
143
145
  import AppBarButtons from './app/AppBarButtons'
144
146
  import AppBarTitleContainer from './app/AppBarTitleContainer'
147
+ import FlyingContextContainer from './FlyingContextContainer'
145
148
  import '../styles.scss'
146
149
 
147
150
  @Component({
148
151
  components: {
149
152
  AppBarButtons,
150
- AppBarTitleContainer
153
+ AppBarTitleContainer,
154
+ FlyingContextContainer
151
155
  }
152
156
  })
153
157
  export default class App extends Vue {
@@ -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>
@@ -2,7 +2,7 @@
2
2
  <a-row gap="2">
3
3
  <v-btn
4
4
  :small="small"
5
- :disabled="!changed || !valid"
5
+ :disabled="($has.reset && !changed) || !valid"
6
6
  color="green white--text"
7
7
  @click="$emit('save')"
8
8
  >
@@ -10,7 +10,7 @@
10
10
  </v-btn>
11
11
 
12
12
  <v-icon
13
- v-if="changed"
13
+ v-if="$has.reset && changed"
14
14
  :small="small"
15
15
  text
16
16
  title="Formular zurücksetzen"
@@ -33,6 +33,8 @@ import { mdiRotateLeft} from '@mdi/js'
33
33
  ]
34
34
  })
35
35
  export default class EditFormButtons extends Vue {
36
+ $hasOptions = ['reset']
37
+
36
38
  undoIcon = mdiRotateLeft
37
39
  }
38
40
  </script>
@@ -7,6 +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 FlyingContext from './FlyingContext.vue'
10
11
  import EditFormButtons from './form/EditFormButtons'
11
12
  import RemoveButton from './form/RemoveButton'
12
13
  import ListCard from './list/ListCard'
@@ -17,10 +18,7 @@ import ListTitle from './list/ListTitle'
17
18
  import ListView from './list/ListView'
18
19
  import ModelCount from './model/ModelCount'
19
20
  import ModelIcon from './model/ModelIcon'
20
- import CreatePage from './pages/CreatePage'
21
- import DetailPage from './pages/DetailPage'
22
21
  import EditPage from './pages/EditPage'
23
- import ListPage from './pages/ListPage'
24
22
  import Start from './Start.vue'
25
23
 
26
24
  Vue.component('ListCard', ListCard)
@@ -29,9 +27,6 @@ Vue.component('ListContent', ListContent)
29
27
  Vue.component('ListMeta', ListMeta)
30
28
  Vue.component('ListTitle', ListTitle)
31
29
  Vue.component('ListView', ListView)
32
- Vue.component('ListPage', ListPage)
33
-
34
- Vue.component('CreatePage', CreatePage)
35
30
 
36
31
  Vue.component('EditPage', EditPage)
37
32
  Vue.component('EditFormButtons', EditFormButtons)
@@ -43,7 +38,6 @@ Vue.component('ModelIcon', ModelIcon)
43
38
  Vue.component('DetailContent', DetailContent)
44
39
  Vue.component('DetailMeta', DetailMeta)
45
40
  Vue.component('DetailTitle', DetailTitle)
46
- Vue.component('DetailPage', DetailPage)
47
41
  Vue.component('DetailProperty', DetailProperty)
48
42
  Vue.component('DetailColumn', DetailColumn)
49
43
 
@@ -51,3 +45,4 @@ Vue.component('AppBarButton', AppBarButton)
51
45
  Vue.component('AppBarTitle', AppBarTitle)
52
46
 
53
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) {
@@ -4,9 +4,10 @@
4
4
  :model="model"
5
5
  :createModelToEdit="createModelToEdit"
6
6
  >
7
- <template #default="{model, changed, valid}">
7
+ <template #form="{modelToEdit, changed, valid}">
8
8
  <slot
9
- :model="model"
9
+ name="form"
10
+ :modelToEdit="modelToEdit"
10
11
  :changed="changed"
11
12
  :valid="valid"
12
13
  />
@@ -14,7 +15,8 @@
14
15
  <edit-form-buttons
15
16
  :changed="changed"
16
17
  :valid="valid"
17
- @save="$emit('save', model, ignoreChangesOnRouteChange)"
18
+ :has="{reset: !!modelToEdit.id}"
19
+ @save="$emit('save', modelToEdit, ignoreChangesOnRouteChange)"
18
20
  @reset="$refs.form.reset()"
19
21
  />
20
22
  </template>
@@ -53,6 +55,9 @@ export default class EditPage extends Vue {
53
55
  this.unregisterRouterHook()
54
56
  }
55
57
 
58
+ /**
59
+ * hook to allow to leave a just created (saved) model
60
+ */
56
61
  ignoreChangesOnRouteChange () {
57
62
  this.ignoreChangesOnRouteChange_ = true
58
63
  }
@@ -7,6 +7,7 @@ Component.registerHooks([
7
7
 
8
8
  let onLoadCallback = () => {}
9
9
  let routerHookCalled = false
10
+ let lastResult = null // cache last result because of hmr reload
10
11
 
11
12
  function onLoad (callback) {
12
13
  onLoadCallback = callback
@@ -52,24 +53,32 @@ export class DataRouteMixin extends Vue {
52
53
 
53
54
  /**
54
55
  * triggered both, if route name or route params change
55
- */
56
- @Watch('$route.params')
57
- async routeParamsChanged () {
58
- if (routerHookCalled) {
59
- return
60
- }
56
+ @Watch('$route.params')
57
+ async routeParamsChanged () {
58
+ if (routerHookCalled) {
59
+ return
60
+ }
61
61
 
62
- if (!this.drm_isLoaded) {
63
- const result = await load(this.$route)
62
+ if (!this.drm_isLoaded) {
63
+ const result = await load(this.$route)
64
64
 
65
- if (result !== false) {
66
- this.drm_onLoad(result)
67
- this.drm_isLoaded = true
68
- }
69
- }
70
- }
65
+ if (result !== false) {
66
+ this.drm_onLoad(result)
67
+ this.drm_isLoaded = true
68
+ }
69
+ }
70
+ }
71
+ */
71
72
 
72
73
  drm_onLoad (result) {
73
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
+ }
74
83
  }
75
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'