@afeefa/vue-app 0.0.64 → 0.0.67

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