Skip to content

Commit 0782958

Browse files
committed
feat(user_status): Allow to manually revert an automated status
Signed-off-by: Joas Schilling <coding@schilljs.com>
1 parent fbbdc64 commit 0782958

8 files changed

Lines changed: 246 additions & 11 deletions

File tree

apps/user_status/appinfo/routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
['name' => 'UserStatus#setPredefinedMessage', 'url' => '/api/v1/user_status/message/predefined', 'verb' => 'PUT'],
3535
['name' => 'UserStatus#setCustomMessage', 'url' => '/api/v1/user_status/message/custom', 'verb' => 'PUT'],
3636
['name' => 'UserStatus#clearMessage', 'url' => '/api/v1/user_status/message', 'verb' => 'DELETE'],
37+
['name' => 'UserStatus#revertStatus', 'url' => '/api/v1/user_status/revert/{messageId}', 'verb' => 'DELETE'],
3738
// Routes for listing default routes
3839
['name' => 'PredefinedStatus#findAll', 'url' => '/api/v1/predefined_statuses/', 'verb' => 'GET'],
3940
// Route for doing heartbeats

apps/user_status/lib/Controller/UserStatusController.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,19 @@ public function clearMessage(): DataResponse {
184184
return new DataResponse([]);
185185
}
186186

187+
/**
188+
* @NoAdminRequired
189+
*
190+
* @return DataResponse
191+
*/
192+
public function revertStatus(string $messageId): DataResponse {
193+
$backupStatus = $this->service->revertUserStatus($this->userId, $messageId);
194+
if ($backupStatus) {
195+
return new DataResponse($this->formatStatus($backupStatus));
196+
}
197+
return new DataResponse([]);
198+
}
199+
187200
/**
188201
* @param UserStatus $status
189202
* @return array

apps/user_status/lib/Service/StatusService.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,25 +496,27 @@ public function backupCurrentStatus(string $userId): bool {
496496
}
497497
}
498498

499-
public function revertUserStatus(string $userId, string $messageId): void {
499+
public function revertUserStatus(string $userId, string $messageId): ?UserStatus {
500500
try {
501501
/** @var UserStatus $userStatus */
502502
$backupUserStatus = $this->mapper->findByUserId($userId, true);
503503
} catch (DoesNotExistException $ex) {
504504
// No user status to revert, do nothing
505-
return;
505+
return null;
506506
}
507507

508508
$deleted = $this->mapper->deleteCurrentStatusToRestoreBackup($userId, $messageId);
509509
if (!$deleted) {
510510
// Another status is set automatically or no status, do nothing
511-
return;
511+
return null;
512512
}
513513

514514
$backupUserStatus->setIsBackup(false);
515515
// Remove the underscore prefix added when creating the backup
516516
$backupUserStatus->setUserId(substr($backupUserStatus->getUserId(), 1));
517517
$this->mapper->update($backupUserStatus);
518+
519+
return $backupUserStatus;
518520
}
519521

520522
public function revertMultipleUserStatus(array $userIds, string $messageId): void {

apps/user_status/src/components/SetStatusModal.vue

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,25 @@
2424
:title="$t('user_status', 'Set status')"
2525
@close="closeModal">
2626
<div class="set-status-modal">
27+
<!-- Automatic status -->
28+
<template v-if="messageId">
29+
<div class="set-status-modal__header">
30+
<h2>{{ $t('user_status', 'Automated status') }}</h2>
31+
</div>
32+
<div class="set-status-modal__online-status">
33+
{{ $t('user_status', 'Your user status was set automatically') }}
34+
</div>
35+
<div class="status-buttons">
36+
<NcButton :wide="true"
37+
type="secondary"
38+
:text="resetButtonText"
39+
:disabled="isSavingStatus"
40+
@click="revertBackupFromServer">
41+
{{ resetButtonText }}
42+
</NcButton>
43+
</div>
44+
</template>
45+
2746
<!-- Status selector -->
2847
<div class="set-status-modal__header">
2948
<h2>{{ $t('user_status', 'Online status') }}</h2>
@@ -98,21 +117,53 @@ export default {
98117
data() {
99118
return {
100119
clearAt: null,
101-
icon: null,
102-
message: '',
103-
messageId: '',
104120
isSavingStatus: false,
105121
statuses: getAllStatusOptions(),
106122
}
107123
},
108124
125+
computed: {
126+
messageId() {
127+
return this.$store.state.userStatus.messageId
128+
},
129+
icon() {
130+
return this.$store.state.userStatus.icon
131+
},
132+
message() {
133+
return this.$store.state.userStatus.message || ''
134+
},
135+
backupIcon() {
136+
return this.$store.state.userBackupStatus.icon
137+
},
138+
backupMessage() {
139+
return this.$store.state.userBackupStatus.message
140+
},
141+
142+
resetButtonText() {
143+
if (this.backupIcon && this.backupMessage) {
144+
return this.$t('user_status', 'Reset status to "{icon} {message}"', {
145+
icon: this.backupIcon,
146+
message: this.backupMessage,
147+
})
148+
} else if (this.backupMessage) {
149+
return this.$t('user_status', 'Reset status to "{message}"', {
150+
message: this.backupMessage,
151+
})
152+
} else if (this.backupIcon) {
153+
return this.$t('user_status', 'Reset status to "{icon}"', {
154+
icon: this.backupIcon,
155+
})
156+
}
157+
158+
return this.$t('user_status', 'Reset status')
159+
},
160+
},
161+
109162
/**
110163
* Loads the current status when a user opens dialog
111164
*/
112165
mounted() {
113-
this.messageId = this.$store.state.userStatus.messageId
114-
this.icon = this.$store.state.userStatus.icon
115-
this.message = this.$store.state.userStatus.message || ''
166+
this.$store.dispatch('fetchBackupFromServer')
116167
117168
if (this.$store.state.userStatus.clearAt !== null) {
118169
this.clearAt = {
@@ -219,6 +270,27 @@ export default {
219270
return
220271
}
221272
273+
this.isSavingStatus = false
274+
this.closeModal()
275+
},
276+
/**
277+
*
278+
* @return {Promise<void>}
279+
*/
280+
async revertBackupFromServer() {
281+
try {
282+
this.isSavingStatus = true
283+
284+
await this.$store.dispatch('revertBackupFromServer', {
285+
messageId: this.messageId,
286+
})
287+
} catch (err) {
288+
showError(this.$t('user_status', 'There was an error reverting the status'))
289+
console.debug(err)
290+
this.isSavingStatus = false
291+
return
292+
}
293+
222294
this.isSavingStatus = false
223295
this.closeModal()
224296
},

apps/user_status/src/services/statusService.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ const fetchCurrentStatus = async () => {
3535
return response.data.ocs.data
3636
}
3737

38+
/**
39+
* Fetches the current user-status
40+
*
41+
* @param {string} userId
42+
* @return {Promise<object>}
43+
*/
44+
const fetchBackupStatus = async (userId) => {
45+
const url = generateOcsUrl('apps/user_status/api/v1/statuses/{userId}', { userId: '_' + userId })
46+
const response = await HttpClient.get(url)
47+
48+
return response.data.ocs.data
49+
}
50+
3851
/**
3952
* Sets the status
4053
*
@@ -90,10 +103,25 @@ const clearMessage = async () => {
90103
await HttpClient.delete(url)
91104
}
92105

106+
/**
107+
* Revert the automated status
108+
*
109+
* @param {string} messageId
110+
* @return {Promise<object>}
111+
*/
112+
const revertToBackupStatus = async (messageId) => {
113+
const url = generateOcsUrl('apps/user_status/api/v1/user_status/revert/{messageId}', { messageId })
114+
const response = await HttpClient.delete(url)
115+
116+
return response.data.ocs.data
117+
}
118+
93119
export {
94120
fetchCurrentStatus,
121+
fetchBackupStatus,
95122
setStatus,
96123
setCustomMessage,
97124
setPredefinedMessage,
98125
clearMessage,
126+
revertToBackupStatus,
99127
}

apps/user_status/src/store/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import Vue from 'vue'
2424
import Vuex, { Store } from 'vuex'
2525
import predefinedStatuses from './predefinedStatuses'
2626
import userStatus from './userStatus'
27+
import userBackupStatus from './userBackupStatus'
2728

2829
Vue.use(Vuex)
2930

3031
export default new Store({
3132
modules: {
3233
predefinedStatuses,
3334
userStatus,
35+
userBackupStatus,
3436
},
3537
strict: true,
3638
})
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* @copyright Copyright (c) 2020 Georg Ehrke
3+
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
4+
*
5+
* @author Georg Ehrke <oc.list@georgehrke.com>
6+
* @author Joas Schilling <coding@schilljs.com>
7+
*
8+
* @license AGPL-3.0-or-later
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Affero General Public License as
12+
* published by the Free Software Foundation, either version 3 of the
13+
* License, or (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Affero General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Affero General Public License
21+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
*
23+
*/
24+
25+
import {
26+
fetchBackupStatus,
27+
revertToBackupStatus,
28+
} from '../services/statusService'
29+
import { getCurrentUser } from '@nextcloud/auth'
30+
import { emit } from '@nextcloud/event-bus'
31+
32+
const state = {
33+
// Status (online / away / dnd / invisible / offline)
34+
status: null,
35+
// Whether the status is user-defined
36+
statusIsUserDefined: null,
37+
// A custom message set by the user
38+
message: null,
39+
// The icon selected by the user
40+
icon: null,
41+
// When to automatically clean the status
42+
clearAt: null,
43+
// Whether the message is predefined
44+
// (and can automatically be translated by Nextcloud)
45+
messageIsPredefined: null,
46+
// The id of the message in case it's predefined
47+
messageId: null,
48+
}
49+
50+
const mutations = {
51+
/**
52+
* Loads the status from initial state
53+
*
54+
* @param {object} state The Vuex state
55+
* @param {object} data The destructuring object
56+
* @param {string} data.status The status type
57+
* @param {boolean} data.statusIsUserDefined Whether or not this status is user-defined
58+
* @param {string} data.message The message
59+
* @param {string} data.icon The icon
60+
* @param {number} data.clearAt When to automatically clear the status
61+
* @param {boolean} data.messageIsPredefined Whether or not the message is predefined
62+
* @param {string} data.messageId The id of the predefined message
63+
*/
64+
loadBackupStatusFromServer(state, { status, statusIsUserDefined, message, icon, clearAt, messageIsPredefined, messageId }) {
65+
state.status = status
66+
state.message = message
67+
state.icon = icon
68+
69+
// Don't overwrite certain values if the refreshing comes in via short updates
70+
// E.g. from talk participant list which only has the status, message and icon
71+
if (typeof statusIsUserDefined !== 'undefined') {
72+
state.statusIsUserDefined = statusIsUserDefined
73+
}
74+
if (typeof clearAt !== 'undefined') {
75+
state.clearAt = clearAt
76+
}
77+
if (typeof messageIsPredefined !== 'undefined') {
78+
state.messageIsPredefined = messageIsPredefined
79+
}
80+
if (typeof messageId !== 'undefined') {
81+
state.messageId = messageId
82+
}
83+
},
84+
}
85+
86+
const getters = {}
87+
88+
const actions = {
89+
/**
90+
* Re-fetches the status from the server
91+
*
92+
* @param {object} vuex The Vuex destructuring object
93+
* @param {Function} vuex.commit The Vuex commit function
94+
* @return {Promise<void>}
95+
*/
96+
async fetchBackupFromServer({ commit }) {
97+
const status = await fetchBackupStatus(getCurrentUser()?.uid)
98+
commit('loadBackupStatusFromServer', status)
99+
},
100+
101+
async revertBackupFromServer({ commit }, { messageId }) {
102+
const status = await revertToBackupStatus(messageId)
103+
if (status) {
104+
commit('loadBackupStatusFromServer', {})
105+
commit('loadStatusFromServer', status)
106+
emit('user_status:status.updated', {
107+
status: status.status,
108+
message: status.message,
109+
icon: status.icon,
110+
clearAt: status.clearAt,
111+
userId: getCurrentUser()?.uid,
112+
})
113+
}
114+
},
115+
}
116+
117+
export default { state, mutations, getters, actions }

apps/user_status/src/store/userStatus.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ import { emit } from '@nextcloud/event-bus'
3535
const state = {
3636
// Status (online / away / dnd / invisible / offline)
3737
status: null,
38-
// Whether or not the status is user-defined
38+
// Whether the status is user-defined
3939
statusIsUserDefined: null,
4040
// A custom message set by the user
4141
message: null,
4242
// The icon selected by the user
4343
icon: null,
4444
// When to automatically clean the status
4545
clearAt: null,
46-
// Whether or not the message is predefined
46+
// Whether the message is predefined
4747
// (and can automatically be translated by Nextcloud)
4848
messageIsPredefined: null,
4949
// The id of the message in case it's predefined

0 commit comments

Comments
 (0)