mirror of
https://github.com/nextcloud/server.git
synced 2025-04-28 02:52:59 +00:00
184 lines
4.4 KiB
Vue
184 lines
4.4 KiB
Vue
<!--
|
|
- 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>
|