@afeefa/vue-app 0.0.288 → 0.0.290

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.288
1
+ 0.0.290
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.288",
3
+ "version": "0.0.290",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -0,0 +1,250 @@
1
+ <template>
2
+ <div>
3
+ <v-label v-if="$attrs.label">
4
+ {{ $attrs.label }}
5
+ </v-label>
6
+
7
+ <a-row vertical>
8
+ <div
9
+ v-for="(option, index) in options_"
10
+ :key="option.itemValue"
11
+ class="draggable-checkbox"
12
+ :class="{ 'dragging': draggedIndex === index, 'drag-over': dragOverIndex === index }"
13
+ @dragover="onDragOver(index, $event)"
14
+ @dragleave="onDragLeave"
15
+ @drop="onDrop(index, $event)"
16
+ >
17
+ <div class="checkbox-container">
18
+ <div
19
+ class="drag-handle"
20
+ :draggable="true"
21
+ @dragstart="onDragStart(index, $event)"
22
+ @dragend="onDragEnd"
23
+ >
24
+ <a-icon
25
+ size="1.5rem"
26
+ :icon="{icon: '$dragIcon', color: '#CCCCCC'}"
27
+ />
28
+ </div>
29
+
30
+ <a-checkbox
31
+ :label="option.itemText"
32
+ :value="isChecked(option.itemValue)"
33
+ hide-details
34
+ @input="checked(option.itemValue, $event)"
35
+ >
36
+ <template #label>
37
+ <div v-html="option.itemText" />
38
+ </template>
39
+ </a-checkbox>
40
+ </div>
41
+ </div>
42
+ </a-row>
43
+
44
+ <div
45
+ v-if="errorMessages.length"
46
+ class="mt-1 error--text v-messages"
47
+ >
48
+ {{ errorMessages[0] }}
49
+ </div>
50
+ </div>
51
+ </template>
52
+
53
+
54
+ <script>
55
+ import { Component, Vue, Watch, Inject } from '@a-vue'
56
+
57
+ @Component({
58
+ props: ['value', 'options', 'validator']
59
+ })
60
+ export default class ACheckboxGroup extends Vue {
61
+ value_ = []
62
+ options_ = []
63
+ errorMessages = []
64
+ hasError = false
65
+ draggedIndex = null
66
+ dragOverIndex = null
67
+
68
+ @Inject({ from: 'form', default: null }) form
69
+
70
+ created () {
71
+ this.form && this.form.register(this)
72
+ this.value_ = this.value || []
73
+ this.init()
74
+ }
75
+
76
+ checked (key, value) {
77
+ if (value) {
78
+ if (!this.isChecked(key)) {
79
+ this.value_.push(key)
80
+ }
81
+ } else {
82
+ this.value_ = this.value_.filter(v => v !== key)
83
+ }
84
+ this.$emit('input', this.value_)
85
+ this.validate()
86
+ }
87
+
88
+ isChecked (key) {
89
+ return this.value_.includes(key)
90
+ }
91
+
92
+ @Watch('options')
93
+ optionsChanged () {
94
+ this.init()
95
+ }
96
+
97
+ @Watch('value')
98
+ valueChanged (value) {
99
+ this.value_ = value || []
100
+ }
101
+
102
+ async init () {
103
+ const options = typeof this.options === 'function' ? this.options() : this.options
104
+
105
+ if (options instanceof Promise) {
106
+ this.options_ = await options
107
+ } else {
108
+ this.options_ = options
109
+ }
110
+
111
+ this.$nextTick(() => {
112
+ this.validate()
113
+ })
114
+ }
115
+
116
+ validate () {
117
+ this.errorMessages = []
118
+ if (this.validator) {
119
+ const rules = this.validator.getRules(this.$attrs.label)
120
+ for (const rule of rules) {
121
+ const message = rule(this.value_)
122
+ if (typeof message === 'string') {
123
+ this.errorMessages.push(message)
124
+ break
125
+ }
126
+ }
127
+ }
128
+
129
+ this.hasError = !!this.errorMessages.length // VForm will watch the hasError prop
130
+ return !this.errorMessages.length
131
+ }
132
+
133
+ onDragStart (index, event) {
134
+ this.draggedIndex = index
135
+ event.dataTransfer.effectAllowed = 'move'
136
+ event.dataTransfer.setData('text/html', event.target)
137
+
138
+ // Set the entire checkbox container as the drag image
139
+ const checkboxContainer = event.target.closest('.draggable-checkbox')
140
+ if (checkboxContainer) {
141
+ event.dataTransfer.setDragImage(checkboxContainer, 0, 0)
142
+ }
143
+ }
144
+
145
+ onDragEnd () {
146
+ this.draggedIndex = null
147
+ this.dragOverIndex = null
148
+
149
+ this.$emit('orderChanged', this.options_)
150
+ }
151
+
152
+ onDragOver (index, event) {
153
+ event.preventDefault()
154
+ event.stopPropagation()
155
+ event.dataTransfer.dropEffect = 'move'
156
+ this.dragOverIndex = index
157
+ }
158
+
159
+ onDragLeave (event) {
160
+ // Only clear dragOverIndex if we're actually leaving the draggable-checkbox container
161
+ const rect = event.currentTarget.getBoundingClientRect()
162
+ const x = event.clientX
163
+ const y = event.clientY
164
+
165
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
166
+ this.dragOverIndex = null
167
+ }
168
+ }
169
+
170
+ onDrop (index, event) {
171
+ event.preventDefault()
172
+
173
+ if (this.draggedIndex !== null && this.draggedIndex !== index) {
174
+ // Create a copy of the options array
175
+ const newOptions = [...this.options_]
176
+
177
+ // Remove the dragged item
178
+ const draggedItem = newOptions.splice(this.draggedIndex, 1)[0]
179
+
180
+ // Insert it at the new position
181
+ newOptions.splice(index, 0, draggedItem)
182
+
183
+ // Update the options
184
+ this.options_ = newOptions
185
+
186
+ // Emit an event to notify parent component about the reorder
187
+ this.$emit('reorder', newOptions)
188
+ }
189
+
190
+ this.draggedIndex = null
191
+ this.dragOverIndex = null
192
+ }
193
+ }
194
+ </script>
195
+
196
+ <style scoped>
197
+ .draggable-checkbox {
198
+ transition: all .2s ease;
199
+ border-radius: 4px;
200
+ padding: 2px;
201
+ }
202
+
203
+ .draggable-checkbox:hover {
204
+ background-color: rgba(0, 0, 0, .04);
205
+ }
206
+
207
+ .draggable-checkbox.dragging {
208
+ opacity: .5;
209
+ transform: scale(.95);
210
+ }
211
+
212
+ .draggable-checkbox.drag-over {
213
+ background-color: rgba(25, 118, 210, .1);
214
+ border: 2px dashed rgba(25, 118, 210, .3);
215
+ }
216
+
217
+ .checkbox-container {
218
+ display: flex;
219
+ align-items: center;
220
+ }
221
+
222
+ .drag-handle {
223
+ cursor: grab;
224
+ color: rgba(0, 0, 0, .54);
225
+ transition: all .2s ease;
226
+ display: flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ min-width: 20px;
230
+ height: 20px;
231
+ }
232
+
233
+ .drag-handle:hover {
234
+ background-color: rgba(0, 0, 0, .08);
235
+ color: rgba(0, 0, 0, .87);
236
+ }
237
+
238
+ .drag-handle:active {
239
+ cursor: grabbing;
240
+ }
241
+
242
+ .draggable-checkbox .v-input--checkbox {
243
+ pointer-events: auto;
244
+ flex: 1;
245
+ }
246
+
247
+ .draggable-checkbox .v-input__slot {
248
+ cursor: pointer;
249
+ }
250
+ </style>
@@ -45,7 +45,7 @@ export default class ATableHeader extends Vue {
45
45
 
46
46
  > * {
47
47
  padding: .4rem 0;
48
- padding-right: 1rem;
48
+ padding-right: 1.5rem;
49
49
 
50
50
  &:last-child {
51
51
  padding-right: 0;
@@ -56,7 +56,7 @@ export default class ATableHeader extends Vue {
56
56
  > * {
57
57
  border-bottom: 1px solid #EEEEEE;
58
58
  padding-left: .5rem;
59
- padding-right: 1rem;
59
+ padding-right: 1.5rem;
60
60
  padding-bottom: .6rem;
61
61
 
62
62
  &:last-child {
@@ -3,14 +3,24 @@
3
3
  <a-context-menu>
4
4
  <template #activator>
5
5
  <div class="contextButton">
6
- {{ selectedColumns.length }}/{{ Object.keys(columns).length }}
6
+ {{ selectedColumns.length }}/{{ Object.keys(columns_).length }}
7
7
  </div>
8
8
  </template>
9
9
 
10
- <a-checkbox-group
10
+ <a-draggable-checkbox-group
11
+ v-if="drag"
11
12
  v-model="selectedColumns"
12
13
  :options="options"
14
+ v-on="$listeners"
15
+ @input="columnSelected"
16
+ @orderChanged="columnOrderChanged"
17
+ />
18
+
19
+ <a-checkbox-group
20
+ v-else
21
+ v-model="selectedColumns"
13
22
  vertical
23
+ :options="options"
14
24
  v-on="$listeners"
15
25
  @input="columnSelected"
16
26
  />
@@ -22,29 +32,77 @@
22
32
  import { Component, Vue } from '@a-vue'
23
33
 
24
34
  @Component({
25
- props: ['columns']
35
+ props: ['columns', 'storageKey', {drag: false}]
26
36
  })
27
37
  export default class ListColumnSelector extends Vue {
38
+ columns_ = {}
28
39
  selectedColumns = []
29
40
 
30
41
  created () {
31
- this.selectedColumns = Object.keys(this.columns)
32
- .filter(k => this.columns[k].visible)
42
+ this.loadColumnConfiguration()
43
+ this.saveColumnConfiguration()
33
44
  }
34
45
 
35
46
  get options () {
36
- return Object.keys(this.columns).map(k => {
37
- return {
38
- itemText: this.columns[k].title,
39
- itemValue: k
40
- }
41
- })
47
+ return Object.keys(this.columns)
48
+ .map(k => {
49
+ return {
50
+ itemText: this.columns[k].title,
51
+ itemValue: k
52
+ }
53
+ })
42
54
  }
43
55
 
44
56
  columnSelected () {
45
57
  for (const key in this.columns) {
46
58
  this.columns[key].visible = this.selectedColumns.includes(key)
47
59
  }
60
+ this.saveColumnConfiguration()
61
+ }
62
+
63
+ columnOrderChanged (options) {
64
+ const columns = {}
65
+ for (const option of options) {
66
+ columns[option.itemValue] = this.columns[option.itemValue]
67
+ }
68
+
69
+ this.columns_ = columns
70
+ this.$emit('update:columns', this.columns_)
71
+ this.saveColumnConfiguration()
72
+ }
73
+
74
+ loadColumnConfiguration () {
75
+ if (this.storageKey) {
76
+ try {
77
+ const storageItem = localStorage.getItem(`column-config-${this.storageKey}`)
78
+ if (storageItem) {
79
+ this.columns_ = JSON.parse(storageItem)
80
+ this.selectedColumns = Object.keys(this.columns_)
81
+ .filter(k => this.columns_[k].visible)
82
+
83
+ this.$emit('update:columns', this.columns_)
84
+ return
85
+ }
86
+ } catch (error) {
87
+ console.warn('Failed to load column configuration from localStorage:', error)
88
+ }
89
+ }
90
+
91
+ this.columns_ = this.columns
92
+ this.selectedColumns = Object.keys(this.columns_)
93
+ .filter(k => this.columns[k].visible)
94
+ }
95
+
96
+ saveColumnConfiguration () {
97
+ if (!this.storageKey) {
98
+ return
99
+ }
100
+
101
+ try {
102
+ localStorage.setItem(`column-config-${this.storageKey}`, JSON.stringify(this.columns_))
103
+ } catch (error) {
104
+ console.warn('Failed to save column configuration to localStorage:', error)
105
+ }
48
106
  }
49
107
  }
50
108
  </script>
@@ -15,6 +15,7 @@ import {
15
15
  mdiDotsHorizontal,
16
16
  mdiDotsVertical,
17
17
  mdiDownload,
18
+ mdiDrag,
18
19
  mdiFilter,
19
20
  mdiInformationOutline,
20
21
  mdiLock,
@@ -72,7 +73,8 @@ export default new Vuetify({
72
73
  addIcon: mdiPlusCircle,
73
74
  filterIcon: mdiFilter,
74
75
  infoIcon: mdiInformationOutline,
75
- downloadIcon: mdiDownload
76
+ downloadIcon: mdiDownload,
77
+ dragIcon: mdiDrag
76
78
  }
77
79
  },
78
80
  breakpoint: {