Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Ehrke <developer@georgehrke.com>2020-06-02 13:48:37 +0300
committerGeorg Ehrke <developer@georgehrke.com>2020-07-31 17:45:27 +0300
commit0fad921840eb801492522af6ef795231163cff20 (patch)
treeddab0d1567d81eeb8d956ec98196180ad296cabd /apps/user_status/src
parentfce6df06e2bd1d68ee5614621ae7f92c6f7fa53d (diff)
Add user-status app
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
Diffstat (limited to 'apps/user_status/src')
-rw-r--r--apps/user_status/src/App.vue271
-rw-r--r--apps/user_status/src/components/ClearAtSelect.vue102
-rw-r--r--apps/user_status/src/components/CustomMessageInput.vue65
-rw-r--r--apps/user_status/src/components/PredefinedStatus.vue111
-rw-r--r--apps/user_status/src/components/PredefinedStatusesList.vue90
-rw-r--r--apps/user_status/src/components/SetStatusModal.vue236
-rw-r--r--apps/user_status/src/filters/clearAtFilter.js68
-rw-r--r--apps/user_status/src/main-user-status-menu.js23
-rw-r--r--apps/user_status/src/services/clearAtOptionsService.js68
-rw-r--r--apps/user_status/src/services/clearAtService.js63
-rw-r--r--apps/user_status/src/services/dateService.js34
-rw-r--r--apps/user_status/src/services/heartbeatService.js40
-rw-r--r--apps/user_status/src/services/predefinedStatusService.js39
-rw-r--r--apps/user_status/src/services/statusOptionsService.js52
-rw-r--r--apps/user_status/src/services/statusService.js98
-rw-r--r--apps/user_status/src/store/index.js35
-rw-r--r--apps/user_status/src/store/predefinedStatuses.js64
-rw-r--r--apps/user_status/src/store/userStatus.js232
18 files changed, 1691 insertions, 0 deletions
diff --git a/apps/user_status/src/App.vue b/apps/user_status/src/App.vue
new file mode 100644
index 00000000000..e8c3021c7ec
--- /dev/null
+++ b/apps/user_status/src/App.vue
@@ -0,0 +1,271 @@
+<!--
+ - @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
+ - @author Georg Ehrke <oc.list@georgehrke.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <li>
+ <div id="user-status-menu-item">
+ <span id="user-status-menu-item__header">{{ displayName }}</span>
+ <Actions
+ id="user-status-menu-item__subheader"
+ :default-icon="statusIcon"
+ :menu-title="visibleMessage">
+ <ActionButton
+ v-for="status in statuses"
+ :key="status.type"
+ :icon="status.icon"
+ :close-after-click="true"
+ @click.prevent.stop="changeStatus(status.type)">
+ {{ status.label }}
+ </ActionButton>
+ <ActionButton
+ icon="icon-rename"
+ :close-after-click="true"
+ @click.prevent.stop="openModal">
+ {{ $t('user_status', 'Set custom status') }}
+ </ActionButton>
+ </Actions>
+ <SetStatusModal
+ v-if="isModalOpen"
+ @close="closeModal" />
+ </div>
+ </li>
+</template>
+
+<script>
+import { getCurrentUser } from '@nextcloud/auth'
+import SetStatusModal from './components/SetStatusModal'
+import Actions from '@nextcloud/vue/dist/Components/Actions'
+import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
+import { mapState } from 'vuex'
+import { showError } from '@nextcloud/dialogs'
+import { getAllStatusOptions } from './services/statusOptionsService'
+import { sendHeartbeat } from './services/heartbeatService'
+import debounce from 'debounce'
+
+export default {
+ name: 'App',
+ components: {
+ Actions,
+ ActionButton,
+ SetStatusModal,
+ },
+ data() {
+ return {
+ isModalOpen: false,
+ statuses: getAllStatusOptions(),
+ heartbeatInterval: null,
+ setAwayTimeout: null,
+ mouseMoveListener: null,
+ isAway: false,
+ }
+ },
+ computed: {
+ ...mapState({
+ statusType: state => state.userStatus.status,
+ statusIsUserDefined: state => state.userStatus.statusIsUserDefined,
+ customIcon: state => state.userStatus.icon,
+ customMessage: state => state.userStatus.message,
+ }),
+ /**
+ * The display-name of the current user
+ *
+ * @returns {String}
+ */
+ displayName() {
+ return getCurrentUser().displayName
+ },
+ /**
+ * The message displayed in the top right corner
+ *
+ * @returns {String}
+ */
+ visibleMessage() {
+ if (this.customIcon && this.customMessage) {
+ return `${this.customIcon} ${this.customMessage}`
+ }
+ if (this.customMessage) {
+ return this.customMessage
+ }
+
+ if (this.statusIsUserDefined) {
+ switch (this.statusType) {
+ case 'online':
+ return this.$t('user_status', 'Online')
+
+ case 'away':
+ return this.$t('user_status', 'Away')
+
+ case 'dnd':
+ return this.$t('user_status', 'Do not disturb')
+
+ case 'invisible':
+ return this.$t('user_status', 'Invisible')
+
+ case 'offline':
+ return this.$t('user_status', 'Offline')
+ }
+ }
+
+ return this.$t('user_status', 'Set status')
+ },
+ /**
+ * The status indicator icon
+ *
+ * @returns {String|null}
+ */
+ statusIcon() {
+ switch (this.statusType) {
+ case 'online':
+ return 'icon-user-status-online'
+
+ case 'away':
+ return 'icon-user-status-away'
+
+ case 'dnd':
+ return 'icon-user-status-dnd'
+
+ case 'invisible':
+ case 'offline':
+ return 'icon-user-status-invisible'
+ }
+
+ return ''
+ },
+ },
+ /**
+ * 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()
+ }
+ },
+ /**
+ * Some housekeeping before destroying the component
+ */
+ beforeDestroy() {
+ window.removeEventListener('mouseMove', this.mouseMoveListener)
+ clearInterval(this.heartbeatInterval)
+ },
+ methods: {
+ /**
+ * Opens the modal to set a custom status
+ */
+ openModal() {
+ this.isModalOpen = true
+ },
+ /**
+ * Closes the modal
+ */
+ closeModal() {
+ this.isModalOpen = false
+ },
+ /**
+ * Changes the user-status
+ *
+ * @param {String} statusType (online / away / dnd / invisible)
+ */
+ async changeStatus(statusType) {
+ try {
+ await this.$store.dispatch('setStatus', { statusType })
+ } catch (err) {
+ showError(this.$t('user_status', 'There was an error saving the new status'))
+ console.debug(err)
+ }
+ },
+ /**
+ * Sends the status heartbeat to the server
+ *
+ * @returns {Promise<void>}
+ * @private
+ */
+ async _backgroundHeartbeat() {
+ await sendHeartbeat(this.isAway)
+ await this.$store.dispatch('reFetchStatusFromServer')
+ },
+ },
+}
+</script>
+
+<style lang="scss">
+#user-status-menu-item {
+ &__header {
+ display: block;
+ align-items: center;
+ color: var(--color-main-text);
+ padding: 10px 12px 5px 12px;
+ box-sizing: border-box;
+ opacity: 1;
+ white-space: nowrap;
+ width: 100%;
+ text-align: center;
+ max-width: 250px;
+ text-overflow: ellipsis;
+ min-width: 175px;
+ }
+
+ &__subheader {
+ width: 100%;
+
+ > button {
+ background-color: var(--color-main-background);
+ background-size: 16px;
+ border: 0;
+ border-radius: 0;
+ font-weight: normal;
+ font-size: 0.875em;
+ padding-left: 40px;
+
+ &:hover,
+ &:focus {
+ box-shadow: inset 4px 0 var(--color-primary-element);
+ }
+ }
+ }
+}
+</style>
diff --git a/apps/user_status/src/components/ClearAtSelect.vue b/apps/user_status/src/components/ClearAtSelect.vue
new file mode 100644
index 00000000000..af0db698ad9
--- /dev/null
+++ b/apps/user_status/src/components/ClearAtSelect.vue
@@ -0,0 +1,102 @@
+<!--
+ - @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
+ - @author Georg Ehrke <oc.list@georgehrke.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <div class="clear-at-select">
+ <span
+ class="clear-at-select__label">
+ {{ $t('user_select', 'Clear status after') }}
+ </span>
+ <Multiselect
+ label="label"
+ :value="option"
+ :options="options"
+ open-direction="top"
+ @select="select" />
+ </div>
+</template>
+
+<script>
+import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
+import { getAllClearAtOptions } from '../services/clearAtOptionsService'
+import { clearAtFilter } from '../filters/clearAtFilter'
+
+export default {
+ name: 'ClearAtSelect',
+ components: {
+ Multiselect,
+ },
+ props: {
+ clearAt: {
+ type: Object,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ options: getAllClearAtOptions(),
+ }
+ },
+ computed: {
+ /**
+ * Returns an object of the currently selected option
+ *
+ * @returns {Object}
+ */
+ option() {
+ return {
+ clearAt: this.clearAt,
+ label: clearAtFilter(this.clearAt),
+ }
+ },
+ },
+ methods: {
+ /**
+ * Triggered when the user selects a new option.
+ *
+ * @param {Object=} option The new selected option
+ */
+ select(option) {
+ if (!option) {
+ return
+ }
+
+ this.$emit('selectClearAt', option.clearAt)
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.clear-at-select {
+ display: flex;
+ margin-bottom: 10px;
+ align-items: center;
+
+ &__label {
+ margin-right: 10px;
+ }
+
+ .multiselect {
+ flex-grow: 1;
+ }
+}
+</style>
diff --git a/apps/user_status/src/components/CustomMessageInput.vue b/apps/user_status/src/components/CustomMessageInput.vue
new file mode 100644
index 00000000000..04bc2f026da
--- /dev/null
+++ b/apps/user_status/src/components/CustomMessageInput.vue
@@ -0,0 +1,65 @@
+<!--
+ - @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
+ - @author Georg Ehrke <oc.list@georgehrke.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+<template>
+ <form
+ class="custom-input__form"
+ @submit.prevent>
+ <input
+ :placeholder="$t('user_status', 'What\'s your status?')"
+ type="text"
+ :value="message"
+ @change="change">
+ </form>
+</template>
+
+<script>
+export default {
+ name: 'CustomMessageInput',
+ props: {
+ message: {
+ type: String,
+ required: true,
+ default: () => '',
+ },
+ },
+ methods: {
+ /**
+ * Notifies the parent component about a changed input
+ *
+ * @param {Event} event The Change Event
+ */
+ change(event) {
+ this.$emit('change', event.target.value)
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.custom-input__form {
+ flex-grow: 1;
+
+ input {
+ width: 100%;
+ border-radius: 0 var(--border-radius) var(--border-radius) 0;
+ }
+}
+</style>
diff --git a/apps/user_status/src/components/PredefinedStatus.vue b/apps/user_status/src/components/PredefinedStatus.vue
new file mode 100644
index 00000000000..c7fd4d63fed
--- /dev/null
+++ b/apps/user_status/src/components/PredefinedStatus.vue
@@ -0,0 +1,111 @@
+<!--
+ - @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
+ - @author Georg Ehrke <oc.list@georgehrke.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+<template>
+ <div
+ class="predefined-status"
+ tabindex="0"
+ @keyup.enter="select"
+ @keyup.space="select"
+ @click="select">
+ <span class="predefined-status__icon">
+ {{ icon }}
+ </span>
+ <span class="predefined-status__message">
+ {{ message }}
+ </span>
+ <span class="predefined-status__clear-at">
+ {{ clearAt | clearAtFilter }}
+ </span>
+ </div>
+</template>
+
+<script>
+import { clearAtFilter } from '../filters/clearAtFilter'
+
+export default {
+ name: 'PredefinedStatus',
+ filters: {
+ clearAtFilter,
+ },
+ props: {
+ messageId: {
+ type: String,
+ required: true,
+ },
+ icon: {
+ type: String,
+ required: true,
+ },
+ message: {
+ type: String,
+ required: true,
+ },
+ clearAt: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ },
+ methods: {
+ /**
+ * Emits an event when the user clicks the row
+ */
+ select() {
+ this.$emit('select')
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.predefined-status {
+ display: flex;
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ flex-basis: 100%;
+ border-radius: var(--border-radius);
+ align-items: center;
+ min-height: 44px;
+
+ &:hover,
+ &:focus {
+ background-color: var(--color-background-hover);
+ }
+
+ &__icon {
+ flex-basis: 40px;
+ text-align: center;
+ }
+
+ &__message {
+ font-weight: bold;
+ padding: 0 6px;
+ }
+
+ &__clear-at {
+ opacity: .7;
+
+ &::before {
+ content: ' - ';
+ }
+ }
+}
+</style>
diff --git a/apps/user_status/src/components/PredefinedStatusesList.vue b/apps/user_status/src/components/PredefinedStatusesList.vue
new file mode 100644
index 00000000000..844fdbbdfe3
--- /dev/null
+++ b/apps/user_status/src/components/PredefinedStatusesList.vue
@@ -0,0 +1,90 @@
+<!--
+ - @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
+ - @author Georg Ehrke <oc.list@georgehrke.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <div
+ v-if="hasLoaded"
+ class="predefined-statuses-list">
+ <PredefinedStatus
+ v-for="status in predefinedStatuses"
+ :key="status.id"
+ :message-id="status.id"
+ :icon="status.icon"
+ :message="status.message"
+ :clear-at="status.clearAt"
+ @select="selectStatus(status)" />
+ </div>
+ <div
+ v-else
+ class="predefined-statuses-list">
+ <div class="icon icon-loading-small" />
+ </div>
+</template>
+
+<script>
+import PredefinedStatus from './PredefinedStatus'
+import { mapState } from 'vuex'
+
+export default {
+ name: 'PredefinedStatusesList',
+ components: {
+ PredefinedStatus,
+ },
+ computed: {
+ ...mapState({
+ predefinedStatuses: state => state.predefinedStatuses.predefinedStatuses,
+ }),
+ /**
+ * Indicator whether the predefined statuses have already been loaded
+ *
+ * @returns {boolean}
+ */
+ hasLoaded() {
+ return this.predefinedStatuses.length > 0
+ },
+ },
+ /**
+ * Loads all predefined statuses from the server
+ * when this component is mounted
+ */
+ mounted() {
+ this.$store.dispatch('loadAllPredefinedStatuses')
+ },
+ methods: {
+ /**
+ * Emits an event when the user selects a status
+ *
+ * @param {Object} status The selected status
+ */
+ selectStatus(status) {
+ this.$emit('selectStatus', status)
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.predefined-statuses-list {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 10px;
+}
+</style>
diff --git a/apps/user_status/src/components/SetStatusModal.vue b/apps/user_status/src/components/SetStatusModal.vue
new file mode 100644
index 00000000000..46c289d9e81
--- /dev/null
+++ b/apps/user_status/src/components/SetStatusModal.vue
@@ -0,0 +1,236 @@
+<!--
+ - @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
+ - @author Georg Ehrke <oc.list@georgehrke.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <Modal
+ size="normal"
+ :title="$t('user_status', 'Set a custom status')"
+ @close="closeModal">
+ <div class="set-status-modal">
+ <div class="set-status-modal__header">
+ <h3>{{ $t('user_status', 'Set a custom status') }}</h3>
+ </div>
+ <div class="set-status-modal__custom-input">
+ <EmojiPicker @select="setIcon">
+ <button
+ class="custom-input__emoji-button">
+ {{ visibleIcon }}
+ </button>
+ </EmojiPicker>
+ <CustomMessageInput
+ :message="message"
+ @change="setMessage" />
+ </div>
+ <PredefinedStatusesList
+ @selectStatus="selectPredefinedMessage" />
+ <ClearAtSelect
+ :clear-at="clearAt"
+ @selectClearAt="setClearAt" />
+ <div class="status-buttons">
+ <button class="status-buttons__select" @click="clearStatus">
+ {{ $t('user_status', 'Clear custom status') }}
+ </button>
+ <button class="status-buttons__primary primary" @click="saveStatus">
+ {{ $t('user_status', 'Set status') }}
+ </button>
+ </div>
+ </div>
+ </Modal>
+</template>
+
+<script>
+import EmojiPicker from '@nextcloud/vue/dist/Components/EmojiPicker'
+import Modal from '@nextcloud/vue/dist/Components/Modal'
+import PredefinedStatusesList from './PredefinedStatusesList'
+import CustomMessageInput from './CustomMessageInput'
+import ClearAtSelect from './ClearAtSelect'
+import { showError } from '@nextcloud/dialogs'
+
+export default {
+ name: 'SetStatusModal',
+ components: {
+ EmojiPicker,
+ Modal,
+ CustomMessageInput,
+ PredefinedStatusesList,
+ ClearAtSelect,
+ },
+ data() {
+ return {
+ icon: null,
+ message: null,
+ clearAt: null,
+ }
+ },
+ computed: {
+ /**
+ * Returns the user-set icon or a smiley in case no icon is set
+ *
+ * @returns {String}
+ */
+ visibleIcon() {
+ return this.icon || '😀'
+ },
+ },
+ /**
+ * Loads the current status when a user opens dialog
+ */
+ mounted() {
+ this.messageId = this.$store.state.userStatus.messageId
+ this.icon = this.$store.state.userStatus.icon
+ this.message = this.$store.state.userStatus.message
+
+ if (this.$store.state.userStatus.clearAt !== null) {
+ this.clearAt = {
+ type: '_time',
+ time: this.$store.state.userStatus.clearAt,
+ }
+ }
+ },
+ methods: {
+ /**
+ * Closes the Set Status modal
+ */
+ closeModal() {
+ this.$emit('close')
+ },
+ /**
+ * Sets a new icon
+ *
+ * @param {String} icon The new icon
+ */
+ setIcon(icon) {
+ this.messageId = null
+ this.icon = icon
+ },
+ /**
+ * Sets a new message
+ *
+ * @param {String} message The new message
+ */
+ setMessage(message) {
+ this.messageId = null
+ this.message = message
+ },
+ /**
+ * Sets a new clearAt value
+ *
+ * @param {Object} clearAt The new clearAt object
+ */
+ setClearAt(clearAt) {
+ this.clearAt = clearAt
+ },
+ /**
+ * Sets new icon/message/clearAt based on a predefined message
+ *
+ * @param {Object} status The predefined status object
+ */
+ selectPredefinedMessage(status) {
+ this.messageId = status.id
+ this.clearAt = status.clearAt
+ this.icon = status.icon
+ this.message = status.message
+ },
+ /**
+ * Saves the status and closes the
+ *
+ * @returns {Promise<void>}
+ */
+ async saveStatus() {
+ try {
+ this.isSavingStatus = true
+
+ if (this.messageId !== null) {
+ await this.$store.dispatch('setPredefinedMessage', {
+ messageId: this.messageId,
+ clearAt: this.clearAt,
+ })
+ } else {
+ await this.$store.dispatch('setCustomMessage', {
+ message: this.message,
+ icon: this.icon,
+ clearAt: this.clearAt,
+ })
+ }
+ } catch (err) {
+ showError(this.$t('user_status', 'There was an error saving the status'))
+ console.debug(err)
+ this.isSavingStatus = false
+ return
+ }
+
+ this.isSavingStatus = false
+ this.closeModal()
+ },
+ /**
+ *
+ * @returns {Promise<void>}
+ */
+ async clearStatus() {
+ try {
+ this.isSavingStatus = true
+
+ await this.$store.dispatch('clearMessage')
+ } catch (err) {
+ showError(this.$t('user_status', 'There was an error clearing the status'))
+ console.debug(err)
+ this.isSavingStatus = false
+ return
+ }
+
+ this.isSavingStatus = false
+ this.closeModal()
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.set-status-modal {
+ min-width: 500px;
+ min-height: 200px;
+ padding: 8px 20px 20px 20px;
+
+ &__custom-input {
+ display: flex;
+ width: 100%;
+ margin-bottom: 10px;
+
+ .custom-input__emoji-button {
+ flex-basis: 40px;
+ width: 40px;
+ flex-grow: 0;
+ border-radius: var(--border-radius) 0 0 var(--border-radius);
+ height: 34px;
+ margin-right: 0;
+ border-right: none;
+ }
+ }
+
+ .status-buttons {
+ display: flex;
+
+ button {
+ flex-basis: 50%;
+ }
+ }
+}
+</style>
diff --git a/apps/user_status/src/filters/clearAtFilter.js b/apps/user_status/src/filters/clearAtFilter.js
new file mode 100644
index 00000000000..22579baa82a
--- /dev/null
+++ b/apps/user_status/src/filters/clearAtFilter.js
@@ -0,0 +1,68 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import { translate as t } from '@nextcloud/l10n'
+import moment from '@nextcloud/moment'
+import { dateFactory } from '../services/dateService'
+
+/**
+ * Formats a clearAt object to be human readable
+ *
+ * @param {Object} clearAt The clearAt object
+ * @returns {string|null}
+ */
+const clearAtFilter = (clearAt) => {
+ if (clearAt === null) {
+ return t('user_status', 'Don\'t clear')
+ }
+
+ if (clearAt.type === 'end-of') {
+ switch (clearAt.time) {
+ case 'day':
+ return t('user_status', 'Today')
+ case 'week':
+ return t('user_status', 'This week')
+
+ default:
+ return null
+ }
+ }
+
+ if (clearAt.type === 'period') {
+ return moment.duration(clearAt.time * 1000).humanize()
+ }
+
+ // This is not an officially supported type
+ // but only used internally to show the remaining time
+ // in the Set Status Modal
+ if (clearAt.type === '_time') {
+ const momentNow = moment(dateFactory())
+ const momentClearAt = moment(clearAt.time, 'X')
+
+ return moment.duration(momentNow.diff(momentClearAt)).humanize()
+ }
+
+ return null
+}
+
+export {
+ clearAtFilter,
+}
diff --git a/apps/user_status/src/main-user-status-menu.js b/apps/user_status/src/main-user-status-menu.js
new file mode 100644
index 00000000000..795f41df4e7
--- /dev/null
+++ b/apps/user_status/src/main-user-status-menu.js
@@ -0,0 +1,23 @@
+import Vue from 'vue'
+import { getRequestToken } from '@nextcloud/auth'
+import App from './App'
+import store from './store'
+
+// eslint-disable-next-line camelcase
+__webpack_nonce__ = btoa(getRequestToken())
+
+// Correct the root of the app for chunk loading
+// OC.linkTo matches the apps folders
+// OC.generateUrl ensure the index.php (or not)
+// eslint-disable-next-line
+__webpack_public_path__ = OC.linkTo('user_status', 'js/')
+
+Vue.prototype.t = t
+Vue.prototype.$t = t
+
+const app = new Vue({
+ render: h => h(App),
+ store,
+}).$mount('li[data-id="user_status-menuitem"]')
+
+export { app }
diff --git a/apps/user_status/src/services/clearAtOptionsService.js b/apps/user_status/src/services/clearAtOptionsService.js
new file mode 100644
index 00000000000..83289f9059f
--- /dev/null
+++ b/apps/user_status/src/services/clearAtOptionsService.js
@@ -0,0 +1,68 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import { translate as t } from '@nextcloud/l10n'
+
+/**
+ * Returns an array
+ *
+ * @returns {Object[]}
+ */
+const getAllClearAtOptions = () => {
+ return [{
+ label: t('user_status', 'Don\'t clear'),
+ clearAt: null,
+ }, {
+ label: t('user_status', '30 minutes'),
+ clearAt: {
+ type: 'period',
+ time: 1800,
+ },
+ }, {
+ label: t('user_status', '1 hour'),
+ clearAt: {
+ type: 'period',
+ time: 3600,
+ },
+ }, {
+ label: t('user_status', '4 hours'),
+ clearAt: {
+ type: 'period',
+ time: 14400,
+ },
+ }, {
+ label: t('user_status', 'Today'),
+ clearAt: {
+ type: 'end-of',
+ time: 'day',
+ },
+ }, {
+ label: t('user_status', 'This week'),
+ clearAt: {
+ type: 'end-of',
+ time: 'week',
+ },
+ }]
+}
+
+export {
+ getAllClearAtOptions,
+}
diff --git a/apps/user_status/src/services/clearAtService.js b/apps/user_status/src/services/clearAtService.js
new file mode 100644
index 00000000000..12328d3b399
--- /dev/null
+++ b/apps/user_status/src/services/clearAtService.js
@@ -0,0 +1,63 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import {
+ dateFactory,
+} from './dateService'
+import moment from '@nextcloud/moment'
+
+/**
+ * Calculates the actual clearAt timestamp
+ *
+ * @param {Object|null} clearAt The clear-at config
+ * @returns {Number|null}
+ */
+const getTimestampForClearAt = (clearAt) => {
+ if (clearAt === null) {
+ return null
+ }
+
+ const date = dateFactory()
+
+ if (clearAt.type === 'period') {
+ date.setSeconds(date.getSeconds() + clearAt.time)
+ return Math.floor(date.getTime() / 1000)
+ }
+ if (clearAt.type === 'end-of') {
+ switch (clearAt.time) {
+ case 'day':
+ case 'week':
+ return Number(moment(date).endOf(clearAt.time).format('X'))
+ }
+ }
+ // This is not an officially supported type
+ // but only used internally to show the remaining time
+ // in the Set Status Modal
+ if (clearAt.type === '_time') {
+ return clearAt.time
+ }
+
+ return null
+}
+
+export {
+ getTimestampForClearAt,
+}
diff --git a/apps/user_status/src/services/dateService.js b/apps/user_status/src/services/dateService.js
new file mode 100644
index 00000000000..641244dada3
--- /dev/null
+++ b/apps/user_status/src/services/dateService.js
@@ -0,0 +1,34 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * Returns a new Date object
+ *
+ * @returns {Date}
+ */
+const dateFactory = () => {
+ return new Date()
+}
+
+export {
+ dateFactory,
+}
diff --git a/apps/user_status/src/services/heartbeatService.js b/apps/user_status/src/services/heartbeatService.js
new file mode 100644
index 00000000000..ca3a7de6d03
--- /dev/null
+++ b/apps/user_status/src/services/heartbeatService.js
@@ -0,0 +1,40 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import HttpClient from '@nextcloud/axios'
+import { generateUrl } from '@nextcloud/router'
+
+/**
+ * Sends a heartbeat
+ *
+ * @param {Boolean} isAway Whether or not the user is active
+ * @returns {Promise<void>}
+ */
+const sendHeartbeat = async(isAway) => {
+ const url = generateUrl('/apps/user_status/heartbeat')
+ await HttpClient.put(url, {
+ status: isAway ? 'away' : 'online',
+ })
+}
+
+export {
+ sendHeartbeat,
+}
diff --git a/apps/user_status/src/services/predefinedStatusService.js b/apps/user_status/src/services/predefinedStatusService.js
new file mode 100644
index 00000000000..116fccb0c56
--- /dev/null
+++ b/apps/user_status/src/services/predefinedStatusService.js
@@ -0,0 +1,39 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import HttpClient from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+/**
+ * Fetches all predefined statuses from the server
+ *
+ * @returns {Promise<void>}
+ */
+const fetchAllPredefinedStatuses = async() => {
+ const url = generateOcsUrl('apps/user_status/api/v1', 2) + '/predefined_statuses?format=json'
+ const response = await HttpClient.get(url)
+
+ return response.data.ocs.data
+}
+
+export {
+ fetchAllPredefinedStatuses,
+}
diff --git a/apps/user_status/src/services/statusOptionsService.js b/apps/user_status/src/services/statusOptionsService.js
new file mode 100644
index 00000000000..f429d6b189f
--- /dev/null
+++ b/apps/user_status/src/services/statusOptionsService.js
@@ -0,0 +1,52 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import { translate as t } from '@nextcloud/l10n'
+
+/**
+ * Returns a list of all user-definable statuses
+ *
+ * @returns {Object[]}
+ */
+const getAllStatusOptions = () => {
+ return [{
+ type: 'online',
+ label: t('user_status', 'Online'),
+ icon: 'icon-user-status-online',
+ }, {
+ type: 'away',
+ label: t('user_status', 'Away'),
+ icon: 'icon-user-status-away',
+ }, {
+ type: 'dnd',
+ label: t('user_status', 'Do not disturb'),
+ icon: 'icon-user-status-dnd',
+
+ }, {
+ type: 'invisible',
+ label: t('user_status', 'Invisible'),
+ icon: 'icon-user-status-invisible',
+ }]
+}
+
+export {
+ getAllStatusOptions,
+}
diff --git a/apps/user_status/src/services/statusService.js b/apps/user_status/src/services/statusService.js
new file mode 100644
index 00000000000..206ff4ee647
--- /dev/null
+++ b/apps/user_status/src/services/statusService.js
@@ -0,0 +1,98 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import HttpClient from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+/**
+ * Fetches the current user-status
+ *
+ * @returns {Promise<Object>}
+ */
+const fetchCurrentStatus = async() => {
+ const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status'
+ const response = await HttpClient.get(url)
+
+ return response.data.ocs.data
+}
+
+/**
+ * Sets the status
+ *
+ * @param {String} statusType The status (online / away / dnd / invisible)
+ * @returns {Promise<void>}
+ */
+const setStatus = async(statusType) => {
+ const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/status'
+ await HttpClient.put(url, {
+ statusType,
+ })
+}
+
+/**
+ * Sets a message based on our predefined statuses
+ *
+ * @param {String} messageId The id of the message, taken from predefined status service
+ * @param {Number|null} clearAt When to automatically clean the status
+ * @returns {Promise<void>}
+ */
+const setPredefinedMessage = async(messageId, clearAt = null) => {
+ const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/message/predefined?format=json'
+ await HttpClient.put(url, {
+ messageId,
+ clearAt,
+ })
+}
+
+/**
+ * Sets a custom message
+ *
+ * @param {String} message The user-defined message
+ * @param {String|null} statusIcon The user-defined icon
+ * @param {Number|null} clearAt When to automatically clean the status
+ * @returns {Promise<void>}
+ */
+const setCustomMessage = async(message, statusIcon = null, clearAt = null) => {
+ const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/message/custom?format=json'
+ await HttpClient.put(url, {
+ message,
+ statusIcon,
+ clearAt,
+ })
+}
+
+/**
+ * Clears the current status of the user
+ *
+ * @returns {Promise<void>}
+ */
+const clearMessage = async() => {
+ const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/message?format=json'
+ await HttpClient.delete(url)
+}
+
+export {
+ fetchCurrentStatus,
+ setStatus,
+ setCustomMessage,
+ setPredefinedMessage,
+ clearMessage,
+}
diff --git a/apps/user_status/src/store/index.js b/apps/user_status/src/store/index.js
new file mode 100644
index 00000000000..d810cae5444
--- /dev/null
+++ b/apps/user_status/src/store/index.js
@@ -0,0 +1,35 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import Vue from 'vue'
+import Vuex from 'vuex'
+import predefinedStatuses from './predefinedStatuses'
+import userStatus from './userStatus'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+ modules: {
+ predefinedStatuses,
+ userStatus,
+ },
+ strict: true,
+})
diff --git a/apps/user_status/src/store/predefinedStatuses.js b/apps/user_status/src/store/predefinedStatuses.js
new file mode 100644
index 00000000000..f7174bf8bfc
--- /dev/null
+++ b/apps/user_status/src/store/predefinedStatuses.js
@@ -0,0 +1,64 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import { fetchAllPredefinedStatuses } from '../services/predefinedStatusService'
+
+const state = {
+ predefinedStatuses: [],
+}
+
+const mutations = {
+
+ /**
+ * Adds a predefined status to the state
+ *
+ * @param {Object} state The Vuex state
+ * @param {Object} status The status to add
+ */
+ addPredefinedStatus(state, status) {
+ state.predefinedStatuses.push(status)
+ },
+}
+
+const getters = {}
+
+const actions = {
+
+ /**
+ * Loads all predefined statuses from the server
+ *
+ * @param {Object} vuex The Vuex components
+ * @param {Function} vuex.commit The Vuex commit function
+ */
+ async loadAllPredefinedStatuses({ state, commit }) {
+ if (state.predefinedStatuses.length > 0) {
+ return
+ }
+
+ const statuses = await fetchAllPredefinedStatuses()
+ for (const status of statuses) {
+ commit('addPredefinedStatus', status)
+ }
+ },
+
+}
+
+export default { state, mutations, getters, actions }
diff --git a/apps/user_status/src/store/userStatus.js b/apps/user_status/src/store/userStatus.js
new file mode 100644
index 00000000000..ebe2aea2047
--- /dev/null
+++ b/apps/user_status/src/store/userStatus.js
@@ -0,0 +1,232 @@
+/**
+ * @copyright Copyright (c) 2020 Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import {
+ fetchCurrentStatus,
+ setStatus,
+ setPredefinedMessage,
+ setCustomMessage,
+ clearMessage,
+} from '../services/statusService'
+import { loadState } from '@nextcloud/initial-state'
+import { getTimestampForClearAt } from '../services/clearAtService'
+
+const state = {
+ // Status (online / away / dnd / invisible / offline)
+ status: null,
+ // Whether or not the status is user-defined
+ statusIsUserDefined: null,
+ // A custom message set by the user
+ message: null,
+ // The icon selected by the user
+ icon: null,
+ // When to automatically clean the status
+ clearAt: null,
+ // Whether or not the message is predefined
+ // (and can automatically be translated by Nextcloud)
+ messageIsPredefined: null,
+ // The id of the message in case it's predefined
+ messageId: null,
+}
+
+const mutations = {
+
+ /**
+ * Sets a new status
+ *
+ * @param {Object} state The Vuex state
+ * @param {Object} data The destructuring object
+ * @param {String} data.statusType The new status type
+ */
+ setStatus(state, { statusType }) {
+ state.status = statusType
+ state.statusIsUserDefined = true
+ },
+
+ /**
+ * Sets a message using a predefined message
+ *
+ * @param {Object} state The Vuex state
+ * @param {Object} data The destructuring object
+ * @param {String} data.messageId The messageId
+ * @param {Number|null} data.clearAt When to automatically clear the status
+ * @param {String} data.message The message
+ * @param {String} data.icon The icon
+ */
+ setPredefinedMessage(state, { messageId, clearAt, message, icon }) {
+ state.messageId = messageId
+ state.messageIsPredefined = true
+
+ state.message = message
+ state.icon = icon
+ state.clearAt = clearAt
+ },
+
+ /**
+ * Sets a custom message
+ *
+ * @param {Object} state The Vuex state
+ * @param {Object} data The destructuring object
+ * @param {String} data.message The message
+ * @param {String} data.icon The icon
+ * @param {Number} data.clearAt When to automatically clear the status
+ */
+ setCustomMessage(state, { message, icon, clearAt }) {
+ state.messageId = null
+ state.messageIsPredefined = false
+
+ state.message = message
+ state.icon = icon
+ state.clearAt = clearAt
+ },
+
+ /**
+ * Clears the status
+ *
+ * @param {Object} state The Vuex state
+ */
+ clearMessage(state) {
+ state.messageId = null
+ state.messageIsPredefined = false
+
+ state.message = null
+ state.icon = null
+ state.clearAt = null
+ },
+
+ /**
+ * Loads the status from initial state
+ *
+ * @param {Object} state The Vuex state
+ * @param {Object} data The destructuring object
+ * @param {String} data.status The status type
+ * @param {Boolean} data.statusIsUserDefined Whether or not this status is user-defined
+ * @param {String} data.message The message
+ * @param {String} data.icon The icon
+ * @param {Number} data.clearAt When to automatically clear the status
+ * @param {Boolean} data.messageIsPredefined Whether or not the message is predefined
+ * @param {string} data.messageId The id of the predefined message
+ */
+ loadStatusFromServer(state, { status, statusIsUserDefined, message, icon, clearAt, messageIsPredefined, messageId }) {
+ state.status = status
+ state.statusIsUserDefined = statusIsUserDefined
+ state.message = message
+ state.icon = icon
+ state.clearAt = clearAt
+ state.messageIsPredefined = messageIsPredefined
+ state.messageId = messageId
+ },
+}
+
+const getters = {}
+
+const actions = {
+
+ /**
+ * Sets a new status
+ *
+ * @param {Object} vuex The Vuex destructuring object
+ * @param {Function} vuex.commit The Vuex commit function
+ * @param {Object} data The data destructuring object
+ * @param {String} data.statusType The new status type
+ * @returns {Promise<void>}
+ */
+ async setStatus({ commit }, { statusType }) {
+ await setStatus(statusType)
+ commit('setStatus', { statusType })
+ },
+
+ /**
+ * Sets a message using a predefined message
+ *
+ * @param {Object} vuex The Vuex destructuring object
+ * @param {Function} vuex.commit The Vuex commit function
+ * @param {Object} vuex.rootState The Vuex root state
+ * @param {Object} data The data destructuring object
+ * @param {String} data.messageId The messageId
+ * @param {Object|null} data.clearAt When to automatically clear the status
+ * @returns {Promise<void>}
+ */
+ async setPredefinedMessage({ commit, rootState }, { messageId, clearAt }) {
+ const resolvedClearAt = getTimestampForClearAt(clearAt)
+
+ await setPredefinedMessage(messageId, resolvedClearAt)
+ const status = rootState.predefinedStatuses.predefinedStatuses.find((status) => status.id === messageId)
+ const { message, icon } = status
+
+ commit('setPredefinedMessage', { messageId, clearAt: resolvedClearAt, message, icon })
+ },
+
+ /**
+ * Sets a custom message
+ *
+ * @param {Object} vuex The Vuex destructuring object
+ * @param {Function} vuex.commit The Vuex commit function
+ * @param {Object} data The data destructuring object
+ * @param {String} data.message The message
+ * @param {String} data.icon The icon
+ * @param {Object|null} data.clearAt When to automatically clear the status
+ * @returns {Promise<void>}
+ */
+ async setCustomMessage({ commit }, { message, icon, clearAt }) {
+ const resolvedClearAt = getTimestampForClearAt(clearAt)
+
+ await setCustomMessage(message, icon, resolvedClearAt)
+ commit('setCustomMessage', { message, icon, clearAt: resolvedClearAt })
+ },
+
+ /**
+ * Clears the status
+ *
+ * @param {Object} vuex The Vuex destructuring object
+ * @param {Function} vuex.commit The Vuex commit function
+ * @returns {Promise<void>}
+ */
+ async clearMessage({ commit }) {
+ await clearMessage()
+ commit('clearMessage')
+ },
+
+ /**
+ * Re-fetches the status from the server
+ *
+ * @param {Object} vuex The Vuex destructuring object
+ * @param {Function} vuex.commit The Vuex commit function
+ * @returns {Promise<void>}
+ */
+ async reFetchStatusFromServer({ commit }) {
+ const status = await fetchCurrentStatus()
+ commit('loadStatusFromServer', status)
+ },
+
+ /**
+ * Loads the server from the initial state
+ *
+ * @param {Object} vuex The Vuex destructuring object
+ * @param {Function} vuex.commit The Vuex commit function
+ */
+ loadStatusFromInitialState({ commit }) {
+ const status = loadState('user_status', 'status')
+ commit('loadStatusFromServer', status)
+ },
+}
+
+export default { state, mutations, getters, actions }