@afeefa/vue-app 0.0.63 → 0.0.64

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'