@afeefa/vue-app 0.0.64 → 0.0.67

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 (36) 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/ARichTextArea.vue +95 -85
  8. package/src/components/ARow.vue +0 -7
  9. package/src/components/form/EditForm.vue +9 -0
  10. package/src/components/form/EditModal.vue +2 -0
  11. package/src/components/form/FormFieldMixin.js +13 -4
  12. package/src/components/form/fields/FormFieldRichTextArea.vue +5 -3
  13. package/src/components/list/ListViewMixin.js +25 -2
  14. package/src/components/mixins/ClickOutsideMixin.js +5 -1
  15. package/src/components/search-select/SearchSelectList.vue +0 -1
  16. package/src/components/vue/Component.js +9 -2
  17. package/src/events.js +1 -0
  18. package/src-admin/components/App.vue +56 -61
  19. package/src-admin/components/Sidebar.vue +66 -0
  20. package/src-admin/components/SidebarItem.vue +59 -0
  21. package/src-admin/components/StickyFooter.vue +42 -0
  22. package/src-admin/components/StickyFooterContainer.vue +66 -0
  23. package/src-admin/components/StickyHeader.vue +73 -0
  24. package/src-admin/components/app/AppBarButtons.vue +0 -7
  25. package/src-admin/components/app/AppBarTitle.vue +55 -11
  26. package/src-admin/components/app/AppBarTitleContainer.vue +2 -3
  27. package/src-admin/components/controls/SearchSelectFormField.vue +1 -0
  28. package/src-admin/components/detail/DetailProperty.vue +20 -16
  29. package/src-admin/components/form/EditFormButtons.vue +21 -4
  30. package/src-admin/components/form/RemoveButton.vue +17 -8
  31. package/src-admin/components/index.js +4 -0
  32. package/src-admin/components/list/ListView.vue +5 -6
  33. package/src-admin/components/pages/EditPage.vue +11 -7
  34. package/src-admin/components/routes/DataRouteMixin.js +1 -1
  35. package/src-admin/config/vuetify.js +22 -2
  36. package/src-admin/styles.scss +21 -4
@@ -1 +1 @@
1
- 0.0.64
1
+ 0.0.67
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.64",
3
+ "version": "0.0.67",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -1,19 +1,38 @@
1
1
  <template>
2
- <div class="d-flex flex-wrap align-center">
3
- <div
4
- v-for="(breadcrumb, index) in breadcrumbs"
5
- :key="index"
6
- class="item mr-2 d-flex align-center"
7
- >
8
- <v-icon>$chevronRightIcon</v-icon>
9
-
10
- <router-link
11
- :to="breadcrumb.to"
12
- :exact="true"
2
+ <div class="a-breadcrumbs d-flex align-start gap-2 mr-4">
3
+ <div :class="['breadcrumbs d-flex align-center', {'flex-wrap': wrapBreadcrumbs_}]">
4
+ <div
5
+ v-for="(breadcrumb, index) in breadcrumbs"
6
+ :key="index"
7
+ class="item mr-2 d-flex align-center"
13
8
  >
14
- {{ getItemTitle(breadcrumb.title) }}
15
- </router-link>
9
+ <v-icon v-if="index > 0">
10
+ $chevronRightIcon
11
+ </v-icon>
12
+
13
+ <router-link
14
+ :to="breadcrumb.to"
15
+ :exact="true"
16
+ >
17
+ {{ breadcrumb.title }}
18
+ </router-link>
19
+ </div>
16
20
  </div>
21
+
22
+ <v-avatar
23
+ v-if="expandVisible"
24
+ class="expand"
25
+ color="#EEE"
26
+ size="1.3rem"
27
+ @click="wrapBreadcrumbs"
28
+ >
29
+ <a-icon>$caret{{ wrapBreadcrumbs_ ? 'Up' : 'Down' }}Icon</a-icon>
30
+ </v-avatar>
31
+
32
+ <div
33
+ v-else
34
+ class="expandDummy"
35
+ />
17
36
  </div>
18
37
  </template>
19
38
 
@@ -28,6 +47,8 @@ export default class ABreadcrumbs extends Vue {
28
47
  breadcrumbs = []
29
48
  titleCache = {}
30
49
  lastRoute = null
50
+ expandVisible = false
51
+ wrapBreadcrumbs_ = false
31
52
 
32
53
  created () {
33
54
  this.$events.on(SaveEvent.STOP_SAVING, this.afterSave)
@@ -101,20 +122,46 @@ export default class ABreadcrumbs extends Vue {
101
122
  }
102
123
 
103
124
  this.breadcrumbs = breadcrumbs
125
+ this.wrapBreadcrumbs_ = false
126
+
127
+ this.scrollBreadcrumbs()
128
+ }
129
+
130
+ scrollBreadcrumbs () {
131
+ this.$nextTick(() => {
132
+ const objDiv = this.$el.querySelector('.breadcrumbs')
133
+ if (objDiv.scrollWidth > objDiv.offsetWidth) {
134
+ objDiv.scrollLeft = objDiv.scrollWidth
135
+ this.expandVisible = true
136
+ } else {
137
+ objDiv.scrollLeft = 0
138
+ this.expandVisible = false
139
+ }
140
+ })
104
141
  }
105
142
 
106
- getItemTitle (title) {
107
- // title = title.concat(title).concat(title)
108
- if (title.length > 20) {
109
- title = title.slice(0, 10).trim() + '...' + title.slice(-10).trim()
143
+ wrapBreadcrumbs () {
144
+ this.wrapBreadcrumbs_ = !this.wrapBreadcrumbs_
145
+ if (this.wrapBreadcrumbs_) {
146
+ const objDiv = this.$el.querySelector('.breadcrumbs')
147
+ objDiv.scrollLeft = 0
148
+ } else {
149
+ this.scrollBreadcrumbs()
110
150
  }
111
- return title.toUpperCase()
112
151
  }
113
152
  }
114
153
  </script>
115
154
 
116
155
 
117
156
  <style lang="scss" scoped>
157
+ .a-breadcrumbs {
158
+ overflow: hidden;
159
+ }
160
+
161
+ .breadcrumbs {
162
+ overflow: hidden;
163
+ }
164
+
118
165
  .item {
119
166
  white-space: nowrap;
120
167
 
@@ -130,4 +177,14 @@ export default class ABreadcrumbs extends Vue {
130
177
  }
131
178
  }
132
179
  }
180
+
181
+ .expand {
182
+ cursor: pointer;
183
+ margin-top: 1px;
184
+ }
185
+
186
+ .expandDummy {
187
+ width: 1.5rem;
188
+ height: 1.5rem;
189
+ }
133
190
  </style>
@@ -13,7 +13,7 @@
13
13
  :label="label"
14
14
  :style="cwm_widthStyle"
15
15
  readonly
16
- v-bind="attrs"
16
+ v-bind="{...$attrs, ...attrs}"
17
17
  :rules="validationRules"
18
18
  v-on="on"
19
19
  @click.native="on.click"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <v-icon
3
- :class="{isButton}"
3
+ :class="{button}"
4
4
  v-bind="$attrs"
5
5
  v-on="$listeners"
6
6
  >
@@ -13,17 +13,14 @@
13
13
  import { Component, Vue } from '@a-vue'
14
14
 
15
15
  @Component({
16
- props: ['button']
16
+ props: [{button: false}]
17
17
  })
18
18
  export default class AIcon extends Vue {
19
- get isButton () {
20
- return this.button !== undefined
21
- }
22
19
  }
23
20
  </script>
24
21
 
25
22
  <style lang="scss" scoped>
26
- .v-icon:not(.isButton)::after {
23
+ .v-icon:not(.button)::after {
27
24
  background: none;
28
25
  }
29
26
  </style>
@@ -1,12 +1,11 @@
1
1
  <template>
2
2
  <v-btn
3
- small
4
3
  v-bind="$attrs"
5
4
  v-on="$listeners"
6
5
  >
7
6
  <v-icon
8
7
  left
9
- class="mr-0"
8
+ class="mr-1"
10
9
  >
11
10
  {{ icon }}
12
11
  </v-icon>
@@ -4,85 +4,84 @@
4
4
  v-if="editor"
5
5
  class="menu-bar"
6
6
  >
7
- <v-btn
8
- small
9
- :class="['menu-button', {'is-active': focus && editor.isActive('bold')}]"
10
- @click="editor.chain().focus().toggleBold().run()"
11
- >
12
- <v-icon>{{ boldIcon }}</v-icon>
13
- </v-btn>
14
-
15
- <v-btn
16
- small
17
- :class="['menu-button', {'is-active': focus && editor.isActive('italic')}]"
18
- @click="editor.chain().focus().toggleItalic().run()"
19
- >
20
- <v-icon>{{ italicIcon }}</v-icon>
21
- </v-btn>
22
-
23
- <v-btn
24
- small
25
- :class="['menu-button', 'strike', {'is-active': focus && editor.isActive('strike')}]"
26
- @click="editor.chain().focus().toggleStrike().run()"
27
- >
28
- <v-icon>{{ strikeIcon }}</v-icon>
29
- </v-btn>
30
-
31
- <v-btn
32
- small
33
- :class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 1})}]"
34
- @click="editor.chain().focus().toggleHeading({level: 1}).run()"
35
- >
36
- <v-icon>{{ h1Icon }}</v-icon>
37
- </v-btn>
38
-
39
- <v-btn
40
- small
41
- :class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 2})}]"
42
- @click="editor.chain().focus().toggleHeading({level: 2}).run()"
43
- >
44
- <v-icon>{{ h2Icon }}</v-icon>
45
- </v-btn>
46
-
47
- <v-btn
48
- small
49
- :class="['menu-button', {'is-active': focus && editor.isActive('bulletList')}]"
50
- @click="editor.chain().focus().toggleBulletList().run()"
51
- >
52
- <v-icon>{{ ulIcon }}</v-icon>
53
- </v-btn>
54
-
55
- <v-btn
56
- small
57
- :class="['menu-button', {'is-active': focus && editor.isActive('orderedList')}]"
58
- @click="editor.chain().focus().toggleOrderedList().run()"
59
- >
60
- <v-icon>{{ olIcon }}</v-icon>
61
- </v-btn>
62
-
63
- <v-btn
64
- small
65
- :class="['menu-button', {'is-active': focus && editor.isActive('blockquote')}]"
66
- @click="editor.chain().focus().toggleBlockquote().run()"
67
- >
68
- <v-icon>{{ commentIcon }}</v-icon>
69
- </v-btn>
70
-
71
- <v-btn
72
- small
73
- class="menu-button"
74
- @click="editor.chain().focus().undo().run()"
75
- >
76
- <v-icon>{{ undoIcon }}</v-icon>
77
- </v-btn>
78
-
79
- <v-btn
80
- small
81
- class="menu-button"
82
- @click="editor.chain().focus().redo().run()"
83
- >
84
- <v-icon>{{ redoIcon }}</v-icon>
85
- </v-btn>
7
+ <div>
8
+ <v-btn
9
+ small
10
+ :class="['menu-button', {'is-active': focus && editor.isActive('bold')}]"
11
+ @click="editor.chain().focus().toggleBold().run()"
12
+ >
13
+ <v-icon>{{ boldIcon }}</v-icon>
14
+ </v-btn>
15
+
16
+ <v-btn
17
+ small
18
+ :class="['menu-button', {'is-active': focus && editor.isActive('italic')}]"
19
+ @click="editor.chain().focus().toggleItalic().run()"
20
+ >
21
+ <v-icon>{{ italicIcon }}</v-icon>
22
+ </v-btn>
23
+
24
+ <v-btn
25
+ small
26
+ :class="['menu-button', 'strike', {'is-active': focus && editor.isActive('strike')}]"
27
+ @click="editor.chain().focus().toggleStrike().run()"
28
+ >
29
+ <v-icon>{{ strikeIcon }}</v-icon>
30
+ </v-btn>
31
+
32
+ <v-btn
33
+ small
34
+ :class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 1})}]"
35
+ @click="editor.chain().focus().toggleHeading({level: 1}).run()"
36
+ >
37
+ <v-icon>{{ h1Icon }}</v-icon>
38
+ </v-btn>
39
+
40
+ <v-btn
41
+ small
42
+ :class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 2})}]"
43
+ @click="editor.chain().focus().toggleHeading({level: 2}).run()"
44
+ >
45
+ <v-icon>{{ h2Icon }}</v-icon>
46
+ </v-btn>
47
+
48
+ <v-btn
49
+ small
50
+ :class="['menu-button', {'is-active': focus && editor.isActive('bulletList')}]"
51
+ @click="editor.chain().focus().toggleBulletList().run()"
52
+ >
53
+ <v-icon>{{ ulIcon }}</v-icon>
54
+ </v-btn>
55
+
56
+ <v-btn
57
+ small
58
+ :class="['menu-button', {'is-active': focus && editor.isActive('orderedList')}]"
59
+ @click="editor.chain().focus().toggleOrderedList().run()"
60
+ >
61
+ <v-icon>{{ olIcon }}</v-icon>
62
+ </v-btn>
63
+
64
+ <v-btn
65
+ small
66
+ :class="['menu-button', {'is-active': focus && editor.isActive('blockquote')}]"
67
+ @click="editor.chain().focus().toggleBlockquote().run()"
68
+ >
69
+ <v-icon>{{ commentIcon }}</v-icon>
70
+ </v-btn>
71
+
72
+ <v-btn
73
+ small
74
+ class="menu-button"
75
+ :disabled="initialValue === editor.getHTML()"
76
+ @click="editor.chain().focus().undo().run()"
77
+ >
78
+ <v-icon>{{ undoIcon }}</v-icon>
79
+ </v-btn>
80
+ </div>
81
+
82
+ <div>
83
+ <slot name="buttons" />
84
+ </div>
86
85
  </div>
87
86
 
88
87
  <editor-content
@@ -119,6 +118,7 @@ import {
119
118
  export default class ARichTextArea extends Vue {
120
119
  editor = null
121
120
  internalValue = null
121
+ initialValue = null
122
122
  focus = false
123
123
 
124
124
  boldIcon = mdiFormatBold
@@ -133,6 +133,7 @@ export default class ARichTextArea extends Vue {
133
133
  redoIcon = mdiRotateRight
134
134
 
135
135
  created () {
136
+ this.initialValue = this.value
136
137
  this.internalValue = this.value
137
138
  }
138
139
 
@@ -158,14 +159,20 @@ export default class ARichTextArea extends Vue {
158
159
  this.$emit('blur')
159
160
  }
160
161
  })
161
-
162
- this.editor.commands.setContent(this.internalValue, false)
163
162
  }
164
163
 
165
164
  beforeDestroy () {
166
165
  this.editor.destroy()
167
166
  }
168
167
 
168
+ /**
169
+ * reset the text area to disable the undo button
170
+ * e.g. after saving the form while keeping it open
171
+ */
172
+ reset () {
173
+ this.initialValue = this.value
174
+ }
175
+
169
176
  @Watch('value')
170
177
  valueChanged () {
171
178
  this.internalValue = this.value
@@ -192,10 +199,6 @@ export default class ARichTextArea extends Vue {
192
199
 
193
200
 
194
201
  <style lang="scss" scoped>
195
- .v-input:not(.v-input--is-focused) ::v-deep .v-counter {
196
- display: none;
197
- }
198
-
199
202
  .a-rich-text-editor {
200
203
  ::v-deep .ProseMirror {
201
204
  &-focused {
@@ -205,9 +208,12 @@ export default class ARichTextArea extends Vue {
205
208
  }
206
209
 
207
210
  .menu-bar {
211
+ display: flex;
212
+ justify-content: space-between;
208
213
  margin: -.2rem 0 .5rem -.2rem;
209
214
  }
210
215
 
216
+ .menu-bar .v-btn,
211
217
  .menu-button {
212
218
  padding: 0 !important;
213
219
  width: 30px !important;
@@ -239,6 +245,10 @@ export default class ARichTextArea extends Vue {
239
245
  &.is-active {
240
246
  background: #ECECEC !important;
241
247
  }
248
+
249
+ &[disabled] {
250
+ background: none !important;
251
+ }
242
252
  }
243
253
 
244
254
  ::v-deep .ProseMirror {
@@ -58,13 +58,6 @@ export default class ARow extends Vue {
58
58
  <style scoped lang="scss">
59
59
  .a-row {
60
60
  display: flex;
61
- overflow: hidden;
62
- @media (max-width: 900px), (orientation : portrait) {
63
- flex-wrap: wrap;
64
- & > * {
65
- flex: 0 0 auto;
66
- }
67
- }
68
61
 
69
62
  &.full {
70
63
  width: 100%;
@@ -28,11 +28,17 @@ export default class EditForm extends Vue {
28
28
  modelToEdit = null
29
29
  valid = false
30
30
  lastJson = null
31
+ forcedUnchange = false
31
32
 
32
33
  created () {
33
34
  this.reset()
34
35
  }
35
36
 
37
+ forceUnchanged () {
38
+ this.forcedUnchange = true
39
+ this.$emit('update:changed', false)
40
+ }
41
+
36
42
  reset () {
37
43
  if (this.createModelToEdit) {
38
44
  this.modelToEdit = this.createModelToEdit(this.model)
@@ -66,6 +72,9 @@ export default class EditForm extends Vue {
66
72
  }
67
73
 
68
74
  get changed () {
75
+ if (this.forcedUnchange) {
76
+ return false
77
+ }
69
78
  // console.log(this.json)
70
79
  // console.log(this.lastJson)
71
80
  return this.json !== this.lastJson
@@ -125,6 +125,8 @@ export default class EditModal extends Vue {
125
125
  * hook to allow to leave a just created (saved) model
126
126
  */
127
127
  ignoreChangesOnClose () {
128
+ // this.$refs.form.forceUnchanged()
129
+ console.info('TODO switch form to forceUnchanged')
128
130
  this.ignoreChangesOnClose_ = true
129
131
  }
130
132
  }
@@ -70,10 +70,19 @@ export class FormFieldMixin extends Vue {
70
70
 
71
71
  if (field.hasOptions()) {
72
72
  const options = field.getOptions()
73
- return Object.keys(options).map(value => ({
74
- itemText: options[value],
75
- itemValue: value
76
- }))
73
+ return options.map((value, index) => {
74
+ if (typeof value === 'object') { // object option
75
+ return {
76
+ itemText: value.title,
77
+ itemValue: value.value
78
+ }
79
+ } else { // scalar option
80
+ return {
81
+ itemText: value,
82
+ itemValue: index
83
+ }
84
+ }
85
+ })
77
86
  }
78
87
  }
79
88
 
@@ -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,3 +1,4 @@
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'