Skip to content

Commit 9f064a1

Browse files
committed
feat: Add inline editing to some cell types
Signed-off-by: Cleopatra Enjeck M. <patrathewhiz@gmail.com>
1 parent df27ea4 commit 9f064a1

File tree

5 files changed

+452
-26
lines changed

5 files changed

+452
-26
lines changed

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

Lines changed: 129 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,166 @@
33
- SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<template>
6-
<div>
7-
{{ column.numberPrefix }}{{ getValue }}{{ column.numberSuffix }}
6+
<div class="cell-number">
7+
<div v-if="!isEditing" @dblclick="startEditing">
8+
{{ column.numberPrefix }}{{ getValue }}{{ column.numberSuffix }}
9+
</div>
10+
<div v-else class="editing-container">
11+
<input
12+
ref="input"
13+
v-model="editValue"
14+
type="number"
15+
:min="getMin"
16+
:max="getMax"
17+
:step="getStep"
18+
:disabled="localLoading"
19+
class="cell-input"
20+
@blur="saveChanges"
21+
@keyup.enter="saveChanges"
22+
@keyup.esc="cancelEdit">
23+
<div v-if="localLoading" class="icon-loading" />
24+
</div>
825
</div>
926
</template>
1027

1128
<script>
29+
import { showError } from '@nextcloud/dialogs'
30+
import { translate as t } from '@nextcloud/l10n'
31+
import { mapActions, mapState } from 'pinia'
32+
import { useDataStore } from '../../../../store/data.js'
1233
1334
export default {
1435
name: 'TableCellNumber',
36+
1537
props: {
1638
column: {
1739
type: Object,
18-
default: () => {},
40+
required: true,
1941
},
2042
rowId: {
2143
type: Number,
22-
default: null,
44+
required: true,
2345
},
2446
value: {
2547
type: Number,
2648
default: null,
2749
},
2850
},
51+
52+
data() {
53+
return {
54+
isEditing: false,
55+
editValue: '',
56+
localLoading: false,
57+
}
58+
},
59+
2960
computed: {
61+
...mapState(useDataStore, {
62+
rowMetadata(state) {
63+
return state.getRowMetadata(this.rowId)
64+
},
65+
}),
66+
3067
getValue() {
3168
if (this.value === null) {
3269
return null
3370
}
3471
return this.value.toFixed(this.column?.numberDecimals)
3572
},
73+
74+
getStep() {
75+
return Math.pow(10, -(this.column?.numberDecimals || 0))
76+
},
77+
getMin() {
78+
if (this.column?.numberMin !== undefined) {
79+
return this.column.numberMin
80+
} else {
81+
return null
82+
}
83+
},
84+
getMax() {
85+
if (this.column?.numberMax !== undefined) {
86+
return this.column.numberMax
87+
} else {
88+
return null
89+
}
90+
},
91+
},
92+
93+
methods: {
94+
...mapActions(useDataStore, ['updateRow']),
95+
96+
startEditing() {
97+
this.editValue = this.value
98+
this.isEditing = true
99+
this.$nextTick(() => {
100+
this.$refs.input.focus()
101+
})
102+
},
103+
104+
async saveChanges() {
105+
if (Number(this.editValue) === this.value) {
106+
this.isEditing = false
107+
return
108+
}
109+
110+
this.localLoading = true
111+
112+
const data = [{
113+
columnId: this.column.id,
114+
value: this.editValue === '' ? null : Number(this.editValue),
115+
}]
116+
117+
const res = await this.updateRow({
118+
id: this.rowId,
119+
isView: this.rowMetadata.isView,
120+
elementId: this.rowMetadata.elementId,
121+
data,
122+
})
123+
124+
if (!res) {
125+
showError(t('tables', 'Could not update cell'))
126+
this.cancelEdit()
127+
} else {
128+
this.$emit('update:value', Number(this.editValue))
129+
}
130+
131+
this.localLoading = false
132+
this.isEditing = false
133+
},
134+
135+
cancelEdit() {
136+
this.isEditing = false
137+
this.editValue = this.value
138+
},
36139
},
37140
}
38141
</script>
39142
40-
<style lang="scss" scoped>
143+
<style scoped>
144+
.cell-number {
145+
width: 100%;
146+
text-align: right;
147+
}
148+
149+
.editing-container {
150+
position: relative;
151+
width: 100%;
152+
}
41153
42-
div {
43-
text-align: right;
154+
.cell-input {
155+
width: 100%;
156+
border: 1px solid var(--color-border);
157+
border-radius: var(--border-radius);
158+
padding: 4px 8px;
159+
text-align: right;
44160
}
45161
162+
.icon-loading {
163+
position: absolute;
164+
right: 8px;
165+
top: 50%;
166+
transform: translateY(-50%);
167+
}
46168
</style>

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

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,156 @@
33
- SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<template>
6-
<div>
7-
<NcProgressBar v-if="getValue !== null" :value="getValue" />
6+
<div class="cell-progress" @dblclick="startEditing">
7+
<div v-if="!isEditing">
8+
<NcProgressBar v-if="getValue !== null" :value="getValue" />
9+
</div>
10+
<div v-else class="editing-container">
11+
<input
12+
ref="input"
13+
v-model.number="editValue"
14+
type="number"
15+
min="0"
16+
max="100"
17+
:disabled="localLoading"
18+
class="cell-input"
19+
@blur="saveChanges"
20+
@keyup.enter="saveChanges"
21+
@keyup.esc="cancelEdit">
22+
<div v-if="localLoading" class="icon-loading" />
23+
</div>
824
</div>
925
</template>
1026

1127
<script>
28+
import { showError } from '@nextcloud/dialogs'
29+
import { translate as t } from '@nextcloud/l10n'
30+
import { mapActions, mapState } from 'pinia'
31+
import { useDataStore } from '../../../../store/data.js'
1232
import { NcProgressBar } from '@nextcloud/vue'
1333
1434
export default {
1535
name: 'TableCellProgress',
16-
components: { NcProgressBar },
36+
37+
components: {
38+
NcProgressBar,
39+
},
40+
1741
props: {
1842
column: {
1943
type: Object,
20-
default: () => {},
44+
required: true,
2145
},
2246
rowId: {
2347
type: Number,
24-
default: null,
48+
required: true,
2549
},
2650
value: {
2751
type: Number,
2852
default: null,
2953
},
3054
},
55+
56+
data() {
57+
return {
58+
isEditing: false,
59+
editValue: '',
60+
localLoading: false,
61+
}
62+
},
63+
3164
computed: {
65+
...mapState(useDataStore, {
66+
rowMetadata(state) {
67+
return state.getRowMetadata(this.rowId)
68+
},
69+
}),
70+
3271
getValue() {
3372
if (this.value !== null && !isNaN(this.value)) {
3473
return this.value
3574
}
3675
return null
3776
},
3877
},
78+
79+
methods: {
80+
...mapActions(useDataStore, ['updateRow']),
81+
82+
startEditing() {
83+
this.editValue = this.value
84+
this.isEditing = true
85+
this.$nextTick(() => {
86+
this.$refs.input.focus()
87+
})
88+
},
89+
90+
async saveChanges() {
91+
const newValue = Number(this.editValue)
92+
if (newValue === this.value || isNaN(newValue)) {
93+
this.isEditing = false
94+
return
95+
}
96+
97+
// Ensure value is between 0 and 100
98+
const clampedValue = Math.min(Math.max(newValue, 0), 100)
99+
100+
this.localLoading = true
101+
102+
const data = [{
103+
columnId: this.column.id,
104+
value: clampedValue,
105+
}]
106+
107+
const res = await this.updateRow({
108+
id: this.rowId,
109+
isView: this.rowMetadata.isView,
110+
elementId: this.rowMetadata.elementId,
111+
data,
112+
})
113+
114+
if (!res) {
115+
showError(t('tables', 'Could not update cell'))
116+
this.cancelEdit()
117+
} else {
118+
this.$emit('update:value', clampedValue)
119+
}
120+
121+
this.localLoading = false
122+
this.isEditing = false
123+
},
124+
125+
cancelEdit() {
126+
this.isEditing = false
127+
this.editValue = this.value
128+
},
129+
},
39130
}
40131
</script>
41132

42133
<style scoped>
134+
.cell-progress {
135+
padding-right: 10px;
136+
min-width: 12vw;
137+
}
43138
44-
div {
45-
padding-right: 10px;
46-
min-width: 12vw;
139+
.editing-container {
140+
position: relative;
141+
width: 100%;
47142
}
48143
144+
.cell-input {
145+
width: 100%;
146+
border: 1px solid var(--color-border);
147+
border-radius: var(--border-radius);
148+
padding: 4px 8px;
149+
text-align: right;
150+
}
151+
152+
.icon-loading {
153+
position: absolute;
154+
right: 8px;
155+
top: 50%;
156+
transform: translateY(-50%);
157+
}
49158
</style>

0 commit comments

Comments
 (0)