diff options
Diffstat (limited to 'apps/user_status/src/UserStatus.vue')
-rw-r--r-- | apps/user_status/src/UserStatus.vue | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/apps/user_status/src/UserStatus.vue b/apps/user_status/src/UserStatus.vue new file mode 100644 index 00000000000..07d81aad95c --- /dev/null +++ b/apps/user_status/src/UserStatus.vue @@ -0,0 +1,184 @@ +<!-- + - SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <Fragment> + <NcListItem v-if="!inline" + class="user-status-menu-item" + compact + :name="visibleMessage" + @click.stop="openModal"> + <template #icon> + <NcUserStatusIcon class="user-status-icon" + :status="statusType" + aria-hidden="true" /> + </template> + </NcListItem> + + <div v-else> + <!-- Dashboard Status --> + <NcButton @click.stop="openModal"> + <template #icon> + <NcUserStatusIcon class="user-status-icon" + :status="statusType" + aria-hidden="true" /> + </template> + {{ visibleMessage }} + </NcButton> + </div> + <!-- Status management modal --> + <SetStatusModal v-if="isModalOpen" + :inline="inline" + @close="closeModal" /> + </Fragment> +</template> + +<script> +import { getCurrentUser } from '@nextcloud/auth' +import { subscribe, unsubscribe } from '@nextcloud/event-bus' +import { Fragment } from 'vue-frag' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcListItem from '@nextcloud/vue/components/NcListItem' +import NcUserStatusIcon from '@nextcloud/vue/components/NcUserStatusIcon' +import debounce from 'debounce' + +import { sendHeartbeat } from './services/heartbeatService.js' +import OnlineStatusMixin from './mixins/OnlineStatusMixin.js' + +export default { + name: 'UserStatus', + + components: { + Fragment, + NcButton, + NcListItem, + NcUserStatusIcon, + SetStatusModal: () => import(/* webpackChunkName: 'user-status-modal' */'./components/SetStatusModal.vue'), + }, + mixins: [OnlineStatusMixin], + + props: { + /** + * Whether the component should be rendered as a Dashboard Status or a User Menu Entries + * true = Dashboard Status + * false = User Menu Entries + */ + inline: { + type: Boolean, + default: false, + }, + }, + + data() { + return { + heartbeatInterval: null, + isAway: false, + isModalOpen: false, + mouseMoveListener: null, + setAwayTimeout: null, + } + }, + + /** + * Loads the current user's status from initial state + * and stores it in Vuex + */ + mounted() { + this.$store.dispatch('loadStatusFromInitialState') + + if (OC.config.session_keepalive) { + // Send the latest status to the server every 5 minutes + this.heartbeatInterval = setInterval(this._backgroundHeartbeat.bind(this), 1000 * 60 * 5) + this.setAwayTimeout = () => { + this.isAway = true + } + // Catch mouse movements, but debounce to once every 30 seconds + this.mouseMoveListener = debounce(() => { + const wasAway = this.isAway + this.isAway = false + // Reset the two minute counter + clearTimeout(this.setAwayTimeout) + // If the user did not move the mouse within two minutes, + // mark them as away + setTimeout(this.setAwayTimeout, 1000 * 60 * 2) + + if (wasAway) { + this._backgroundHeartbeat() + } + }, 1000 * 2, true) + window.addEventListener('mousemove', this.mouseMoveListener, { + capture: true, + passive: true, + }) + + this._backgroundHeartbeat() + } + subscribe('user_status:status.updated', this.handleUserStatusUpdated) + }, + + /** + * Some housekeeping before destroying the component + */ + beforeDestroy() { + window.removeEventListener('mouseMove', this.mouseMoveListener) + clearInterval(this.heartbeatInterval) + unsubscribe('user_status:status.updated', this.handleUserStatusUpdated) + }, + + methods: { + /** + * Opens the modal to set a custom status + */ + openModal() { + this.isModalOpen = true + }, + /** + * Closes the modal + */ + closeModal() { + this.isModalOpen = false + }, + + /** + * Sends the status heartbeat to the server + * + * @return {Promise<void>} + * @private + */ + async _backgroundHeartbeat() { + try { + const status = await sendHeartbeat(this.isAway) + if (status?.userId) { + this.$store.dispatch('setStatusFromHeartbeat', status) + } else { + await this.$store.dispatch('reFetchStatusFromServer') + } + } catch (error) { + console.debug('Failed sending heartbeat, got: ' + error.response?.status) + } + }, + handleUserStatusUpdated(state) { + if (getCurrentUser()?.uid === state.userId) { + this.$store.dispatch('setStatusFromObject', { + status: state.status, + icon: state.icon, + message: state.message, + }) + } + }, + }, +} +</script> + +<style lang="scss" scoped> +.user-status-icon { + width: 20px; + height: 20px; + margin: calc((var(--default-clickable-area) - 20px) / 2); // 20px icon size + opacity: 1 !important; + background-size: 20px; + vertical-align: middle !important; +} +</style> |