Skip to content

Commit 5318dec

Browse files
committed
feat: Inline editing - Selection
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
1 parent 45c8b2d commit 5318dec

3 files changed

Lines changed: 248 additions & 16 deletions

File tree

src/shared/components/ncTable/partials/TableCellMultiSelection.vue

Lines changed: 140 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,48 @@
33
- SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<template>
6-
<div>
7-
<ul>
8-
<li v-for="v in getObjects()" :key="v.id">
9-
{{ v.label }}<span v-if="v.deleted" :title="t('tables', 'This option is outdated.')">&nbsp;⚠️</span>
10-
</li>
11-
</ul>
6+
<div class="cell-multi-selection">
7+
<div v-if="!isEditing" class="non-edit-mode" @click="startEditing">
8+
<ul>
9+
<li v-for="v in getObjects()" :key="v.id">
10+
{{ v.label }}<span v-if="v.deleted" :title="t('tables', 'This option is outdated.')">&nbsp;⚠️</span>
11+
</li>
12+
</ul>
13+
</div>
14+
<div v-else
15+
ref="editingContainer"
16+
class="edit-mode"
17+
tabindex="0"
18+
@keydown.enter="saveChanges"
19+
@keydown.escape="cancelEdit">
20+
<NcSelect v-model="editValues"
21+
:tag-width="80"
22+
:options="getAllNonDeletedOrSelectedOptions"
23+
:multiple="true"
24+
:aria-label-combobox="t('tables', 'Options')"
25+
:disabled="localLoading || !canEditCell()"
26+
style="width: 100%;" />
27+
<div v-if="localLoading" class="loading-indicator">
28+
<div class="icon-loading-small icon-loading-inline" />
29+
</div>
30+
</div>
1231
</div>
1332
</template>
1433

1534
<script>
16-
35+
import { NcSelect } from '@nextcloud/vue'
1736
import { translate as t } from '@nextcloud/l10n'
37+
import cellEditMixin from '../mixins/cellEditMixin.js'
1838
1939
export default {
2040
name: 'TableCellMultiSelection',
2141
42+
components: {
43+
NcSelect,
44+
},
45+
46+
mixins: [cellEditMixin],
47+
2248
props: {
2349
column: {
2450
type: Object,
@@ -35,20 +61,125 @@ export default {
3561
default: null,
3662
},
3763
},
64+
65+
computed: {
66+
getOptions() {
67+
return this.column.selectionOptions || []
68+
},
69+
getAllNonDeletedOrSelectedOptions() {
70+
const options = this.getOptions.filter(item => {
71+
return !item.deleted || this.optionIdIsSelected(item.id)
72+
}) || []
73+
74+
options.forEach(opt => {
75+
if (opt.deleted) {
76+
opt.label += ' ⚠️'
77+
}
78+
})
79+
return options
80+
},
81+
},
82+
83+
watch: {
84+
isEditing(isEditing) {
85+
if (isEditing) {
86+
this.initEditValues()
87+
// Use a small delay to prevent the same click event that triggered editing
88+
// from immediately triggering the click outside handler
89+
this.$nextTick(() => {
90+
setTimeout(() => {
91+
document.addEventListener('click', this.handleClickOutside)
92+
}, 10)
93+
})
94+
} else {
95+
// Remove click outside listener
96+
document.removeEventListener('click', this.handleClickOutside)
97+
}
98+
},
99+
},
100+
38101
methods: {
39102
t,
103+
40104
getObjects() {
41105
return this.column.getObjects(this.value)
42106
},
43-
},
44107
108+
optionIdIsSelected(id) {
109+
// Check if the given id is selected (in the value array)
110+
return this.value && this.value.includes(id)
111+
},
112+
113+
getIdArrayFromObjects(objects) {
114+
const ids = []
115+
objects.forEach(o => {
116+
ids.push(o.id)
117+
})
118+
return ids
119+
},
120+
121+
initEditValues() {
122+
if (this.value !== null) {
123+
this.editValues = this.column.getObjects(this.value)
124+
} else {
125+
this.editValues = []
126+
}
127+
},
128+
async saveChanges() {
129+
if (this.localLoading) {
130+
return
131+
}
132+
133+
const newValue = this.getIdArrayFromObjects(this.editValues)
134+
135+
const success = await this.updateCellValue(newValue)
136+
137+
if (success) {
138+
// Emit the updated value to parent to trigger immediate re-render
139+
this.$emit('input', newValue)
140+
this.$emit('update:value', newValue)
141+
this.isEditing = false
142+
} else {
143+
this.cancelEdit()
144+
}
145+
146+
this.localLoading = false
147+
},
148+
149+
handleClickOutside(event) {
150+
// Check if the click is outside the editing container
151+
if (this.$refs.editingContainer && !this.$refs.editingContainer.contains(event.target)) {
152+
this.saveChanges()
153+
}
154+
},
155+
},
45156
}
46157
</script>
47158
<style lang="scss" scoped>
159+
.cell-multi-selection {
160+
width: 100%;
161+
162+
.non-edit-mode {
163+
cursor: pointer;
164+
min-height: 20px;
165+
}
166+
}
167+
168+
.edit-mode {
169+
.editor-buttons {
170+
display: flex;
171+
gap: 8px;
172+
margin-top: 8px;
173+
align-items: center;
174+
}
175+
176+
.icon-loading-inline {
177+
margin-left: 4px;
178+
}
179+
}
48180
49181
ul {
50182
list-style-type: disc;
51183
padding-left: calc(var(--default-grid-baseline) * 3);
52184
}
53-
54185
</style>

src/shared/components/ncTable/partials/TableCellSelection.vue

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,42 @@
33
- SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<template>
6-
<div>
7-
{{ column.getLabel(value) }}<span v-if="isDeleted()" :title="t('tables', 'This option is outdated.')">&nbsp;⚠️</span>
6+
<div class="cell-selection">
7+
<div v-if="!isEditing" class="non-edit-mode" @click="startEditing">
8+
{{ column.getLabel(value) }}<span v-if="isDeleted()" :title="t('tables', 'This option is outdated.')">&nbsp;⚠️</span>
9+
</div>
10+
<div v-else
11+
ref="editingContainer"
12+
class="edit-mode"
13+
tabindex="0"
14+
@keydown.enter="saveChanges"
15+
@keydown.escape="cancelEdit">
16+
<NcSelect v-model="editValue"
17+
:options="getAllNonDeletedOptions"
18+
:aria-label-combobox="t('tables', 'Options')"
19+
:disabled="localLoading || !canEditCell()"
20+
style="width: 100%;" />
21+
<div v-if="localLoading" class="loading-indicator">
22+
<div class="icon-loading-small icon-loading-inline" />
23+
</div>
24+
</div>
825
</div>
926
</template>
1027

1128
<script>
12-
29+
import { NcSelect } from '@nextcloud/vue'
1330
import { translate as t } from '@nextcloud/l10n'
31+
import cellEditMixin from '../mixins/cellEditMixin.js'
1432
1533
export default {
1634
name: 'TableCellSelection',
1735
36+
components: {
37+
NcSelect,
38+
},
39+
40+
mixins: [cellEditMixin],
41+
1842
props: {
1943
column: {
2044
type: Object,
@@ -31,19 +55,98 @@ export default {
3155
default: null,
3256
},
3357
},
58+
59+
computed: {
60+
getOptions() {
61+
return this.column?.selectionOptions || []
62+
},
63+
getAllNonDeletedOptions() {
64+
return this.getOptions.filter(item => {
65+
return !item.deleted
66+
})
67+
},
68+
},
69+
70+
watch: {
71+
isEditing(isEditing) {
72+
if (isEditing) {
73+
this.initEditValue()
74+
// Use a small delay to prevent the same click event that triggered editing
75+
// from immediately triggering the click outside handler
76+
this.$nextTick(() => {
77+
setTimeout(() => {
78+
document.addEventListener('click', this.handleClickOutside)
79+
}, 10)
80+
})
81+
} else {
82+
// Remove click outside listener
83+
document.removeEventListener('click', this.handleClickOutside)
84+
}
85+
},
86+
},
87+
3488
methods: {
3589
t,
90+
3691
isDeleted() {
37-
this.column.isDeletedLabel(this.value)
92+
return this.column.isDeletedLabel(this.value)
93+
},
94+
95+
getOptionObject(id) {
96+
return this.getOptions.find(e => e.id === id) || null
97+
},
98+
99+
initEditValue() {
100+
if (this.value !== null) {
101+
this.editValue = this.getOptionObject(parseInt(this.value))
102+
} else {
103+
this.editValue = null
104+
}
105+
},
106+
async saveChanges() {
107+
if (this.localLoading) {
108+
return
109+
}
110+
111+
const newValue = this.editValue?.id
112+
113+
const success = await this.updateCellValue(newValue)
114+
115+
if (!success) {
116+
this.cancelEdit()
117+
}
118+
119+
this.localLoading = false
120+
this.isEditing = false
121+
},
122+
123+
handleClickOutside(event) {
124+
// Check if the click is outside the editing container
125+
if (this.$refs.editingContainer && !this.$refs.editingContainer.contains(event.target)) {
126+
this.saveChanges()
127+
}
38128
},
39129
},
40130
}
41131
</script>
42132
43133
<style lang="scss" scoped>
134+
.cell-selection {
135+
width: 100%;
136+
137+
.non-edit-mode {
138+
cursor: pointer;
139+
min-height: 20px;
140+
}
141+
}
142+
143+
.edit-mode {
144+
.icon-loading-inline {
145+
margin-left: 4px;
146+
}
147+
}
44148
45149
span {
46150
cursor: help;
47151
}
48-
49152
</style>

src/shared/components/ncTable/partials/TableRow.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,11 @@ export default {
113113
return [
114114
ColumnTypes.TextRich,
115115
ColumnTypes.Usergroup,
116-
ColumnTypes.SelectionMulti,
117116
ColumnTypes.TextLink,
118117
ColumnTypes.NumberStars,
119118
ColumnTypes.Datetime,
120119
ColumnTypes.DatetimeDate,
121120
ColumnTypes.DatetimeTime,
122-
ColumnTypes.Selection,
123121
]
124122
},
125123
},

0 commit comments

Comments
 (0)