diff options
Diffstat (limited to 'apps/user_status/src')
-rw-r--r-- | apps/user_status/src/UserStatus.vue | 81 | ||||
-rw-r--r-- | apps/user_status/src/components/ClearAtSelect.vue | 10 | ||||
-rw-r--r-- | apps/user_status/src/components/CustomMessageInput.vue | 6 | ||||
-rw-r--r-- | apps/user_status/src/components/OnlineStatusSelect.vue | 44 | ||||
-rw-r--r-- | apps/user_status/src/components/PredefinedStatus.vue | 15 | ||||
-rw-r--r-- | apps/user_status/src/components/PredefinedStatusesList.vue | 3 | ||||
-rw-r--r-- | apps/user_status/src/components/PreviousStatus.vue | 8 | ||||
-rw-r--r-- | apps/user_status/src/components/SetStatusModal.vue | 83 | ||||
-rw-r--r-- | apps/user_status/src/menu.js | 7 | ||||
-rw-r--r-- | apps/user_status/src/services/statusOptionsService.js | 3 | ||||
-rw-r--r-- | apps/user_status/src/services/statusService.js | 4 |
11 files changed, 148 insertions, 116 deletions
diff --git a/apps/user_status/src/UserStatus.vue b/apps/user_status/src/UserStatus.vue index d662d69a3d2..07d81aad95c 100644 --- a/apps/user_status/src/UserStatus.vue +++ b/apps/user_status/src/UserStatus.vue @@ -4,39 +4,44 @@ --> <template> - <component :is="inline ? 'div' : 'li'"> - <!-- User Status = Status modal toggle --> - <button v-if="!inline" + <Fragment> + <NcListItem v-if="!inline" class="user-status-menu-item" - @click.stop="openModal"> - <NcUserStatusIcon class="user-status-icon" - :status="statusType" - aria-hidden="true" /> - {{ visibleMessage }} - </button> - - <!-- Dashboard Status --> - <NcButton v-else + compact + :name="visibleMessage" @click.stop="openModal"> <template #icon> <NcUserStatusIcon class="user-status-icon" :status="statusType" aria-hidden="true" /> </template> - {{ visibleMessage }} - </NcButton> - + </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" /> - </component> + </Fragment> </template> <script> +import { getCurrentUser } from '@nextcloud/auth' import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcUserStatusIcon from '@nextcloud/vue/dist/Components/NcUserStatusIcon.js' +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' @@ -46,7 +51,9 @@ export default { name: 'UserStatus', components: { + Fragment, NcButton, + NcListItem, NcUserStatusIcon, SetStatusModal: () => import(/* webpackChunkName: 'user-status-modal' */'./components/SetStatusModal.vue'), }, @@ -153,7 +160,7 @@ export default { } }, handleUserStatusUpdated(state) { - if (OC.getCurrentUser().uid === state.userId) { + if (getCurrentUser()?.uid === state.userId) { this.$store.dispatch('setStatusFromObject', { status: state.status, icon: state.icon, @@ -166,40 +173,12 @@ export default { </script> <style lang="scss" scoped> -.user-status-menu-item { - // Ensure the maxcontrast color is set for the background - --color-text-maxcontrast: var(--color-text-maxcontrast-background-blur, var(--color-main-text)); - - width: auto; - min-width: 44px; - height: 44px; - margin: 0; - border: 0; - border-radius: var(--border-radius-pill); - background-color: var(--color-main-background-blur); - font-size: inherit; - font-weight: normal; - - -webkit-backdrop-filter: var(--background-blur); - backdrop-filter: var(--background-blur); - - &:active, - &:hover, - &:focus-visible { - background-color: var(--color-background-hover); - } - &:focus-visible { - box-shadow: 0 0 0 4px var(--color-main-background) !important; - outline: 2px solid var(--color-main-text) !important; - } -} - .user-status-icon { - width: 16px; - height: 16px; - margin-right: 10px; + width: 20px; + height: 20px; + margin: calc((var(--default-clickable-area) - 20px) / 2); // 20px icon size opacity: 1 !important; - background-size: 16px; + background-size: 20px; vertical-align: middle !important; } </style> diff --git a/apps/user_status/src/components/ClearAtSelect.vue b/apps/user_status/src/components/ClearAtSelect.vue index 550ac404b48..91b816dc04a 100644 --- a/apps/user_status/src/components/ClearAtSelect.vue +++ b/apps/user_status/src/components/ClearAtSelect.vue @@ -14,12 +14,13 @@ :value="option" :clearable="false" placement="top" + label-outside @option:selected="select" /> </div> </template> <script> -import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' +import NcSelect from '@nextcloud/vue/components/NcSelect' import { getAllClearAtOptions } from '../services/clearAtOptionsService.js' import { clearAtFilter } from '../filters/clearAtFilter.js' @@ -72,12 +73,9 @@ export default { <style lang="scss" scoped> .clear-at-select { display: flex; - margin-bottom: 10px; + gap: calc(2 * var(--default-grid-baseline)); align-items: center; - - &__label { - margin-right: 12px; - } + margin-block: 0 calc(2 * var(--default-grid-baseline)); &__select { flex-grow: 1; diff --git a/apps/user_status/src/components/CustomMessageInput.vue b/apps/user_status/src/components/CustomMessageInput.vue index 238a55f5e42..fb129281430 100644 --- a/apps/user_status/src/components/CustomMessageInput.vue +++ b/apps/user_status/src/components/CustomMessageInput.vue @@ -26,9 +26,9 @@ </template> <script> -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcEmojiPicker from '@nextcloud/vue/dist/Components/NcEmojiPicker.js' -import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcEmojiPicker from '@nextcloud/vue/components/NcEmojiPicker' +import NcTextField from '@nextcloud/vue/components/NcTextField' export default { name: 'CustomMessageInput', diff --git a/apps/user_status/src/components/OnlineStatusSelect.vue b/apps/user_status/src/components/OnlineStatusSelect.vue index d0e70093489..0abcc8d68e6 100644 --- a/apps/user_status/src/components/OnlineStatusSelect.vue +++ b/apps/user_status/src/components/OnlineStatusSelect.vue @@ -11,16 +11,17 @@ name="user-status-online" @change="onChange"> <label :for="id" class="user-status-online-select__label"> - {{ label }} <NcUserStatusIcon :status="type" + class="user-status-online-select__icon" aria-hidden="true" /> + {{ label }} <em class="user-status-online-select__subline">{{ subline }}</em> </label> </div> </template> <script> -import NcUserStatusIcon from '@nextcloud/vue/dist/Components/NcUserStatusIcon.js' +import NcUserStatusIcon from '@nextcloud/vue/components/NcUserStatusIcon' export default { name: 'OnlineStatusSelect', @@ -63,45 +64,42 @@ export default { </script> <style lang="scss" scoped> -@use 'sass:math'; -$icon-size: 24px; -$label-padding: 8px; - .user-status-online-select { &__label { - position: relative; - display: block; - margin: $label-padding; - padding: $label-padding; - padding-left: $icon-size + $label-padding * 2; - border: 2px solid var(--color-main-background); + box-sizing: inherit; + display: grid; + grid-template-columns: var(--default-clickable-area) 1fr 2fr; + align-items: center; + gap: var(--default-grid-baseline); + min-height: var(--default-clickable-area); + padding: var(--default-grid-baseline); border-radius: var(--border-radius-large); background-color: var(--color-background-hover); - background-position: $label-padding center; - background-size: $icon-size; - span, - & { + &, & * { cursor: pointer; } - span { - position: absolute; - top: calc(50% - 10px); - left: 10px; - display: block; - width: $icon-size; - height: $icon-size; + &:hover { + background-color: var(--color-background-dark); } } + &__icon { + flex-shrink: 0; + max-width: 34px; + max-height: 100%; + } + &__input:checked + &__label { outline: 2px solid var(--color-main-text); + background-color: var(--color-background-dark); box-shadow: 0 0 0 4px var(--color-main-background); } &__input:focus-visible + &__label { outline: 2px solid var(--color-primary-element) !important; + background-color: var(--color-background-dark); } &__subline { diff --git a/apps/user_status/src/components/PredefinedStatus.vue b/apps/user_status/src/components/PredefinedStatus.vue index 8ce1acd0411..b12892d4add 100644 --- a/apps/user_status/src/components/PredefinedStatus.vue +++ b/apps/user_status/src/components/PredefinedStatus.vue @@ -81,10 +81,19 @@ export default { flex-basis: 100%; border-radius: var(--border-radius); align-items: center; - min-height: 44px; + min-height: var(--default-clickable-area); + padding-inline: var(--default-grid-baseline); + + &, & * { + cursor: pointer; + } + + &:hover { + background-color: var(--color-background-dark); + } &--icon { - flex-basis: 40px; + flex-basis: var(--default-clickable-area); text-align: center; } @@ -106,11 +115,13 @@ export default { &__label:active { outline: 2px solid var(--color-main-text); box-shadow: 0 0 0 4px var(--color-main-background); + background-color: var(--color-background-dark); border-radius: var(--border-radius-large); } &__input:focus-visible + &__label { outline: 2px solid var(--color-primary-element) !important; + background-color: var(--color-background-dark); border-radius: var(--border-radius-large); } } diff --git a/apps/user_status/src/components/PredefinedStatusesList.vue b/apps/user_status/src/components/PredefinedStatusesList.vue index 0632d13d52c..cdf359dce76 100644 --- a/apps/user_status/src/components/PredefinedStatusesList.vue +++ b/apps/user_status/src/components/PredefinedStatusesList.vue @@ -78,6 +78,7 @@ export default { .predefined-statuses-list { display: flex; flex-direction: column; - margin-bottom: 10px; + gap: var(--default-grid-baseline); + margin-block: 0 calc(2 * var(--default-grid-baseline)); } </style> diff --git a/apps/user_status/src/components/PreviousStatus.vue b/apps/user_status/src/components/PreviousStatus.vue index 7c24af8c110..58d6ebd294b 100644 --- a/apps/user_status/src/components/PreviousStatus.vue +++ b/apps/user_status/src/components/PreviousStatus.vue @@ -27,7 +27,7 @@ </template> <script> -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcButton from '@nextcloud/vue/components/NcButton' export default { name: 'PreviousStatus', @@ -65,7 +65,8 @@ export default { flex-basis: 100%; border-radius: var(--border-radius); align-items: center; - min-height: 44px; + min-height: var(--default-clickable-area); + padding-inline: var(--default-grid-baseline); &:hover, &:focus { @@ -77,7 +78,7 @@ export default { } &__icon { - flex-basis: 40px; + flex-basis: var(--default-clickable-area); text-align: center; } @@ -94,6 +95,7 @@ export default { } } } + .backup-status { &__reset-button { justify-content: flex-end; diff --git a/apps/user_status/src/components/SetStatusModal.vue b/apps/user_status/src/components/SetStatusModal.vue index 45e36a803c2..8624ed19e94 100644 --- a/apps/user_status/src/components/SetStatusModal.vue +++ b/apps/user_status/src/components/SetStatusModal.vue @@ -5,14 +5,15 @@ <template> <NcModal size="normal" - :name="$t('user_status', 'Set status')" + label-id="user_status-set-dialog" + dark :set-return-focus="setReturnFocus" @close="closeModal"> <div class="set-status-modal"> <!-- Status selector --> - <div class="set-status-modal__header"> - <h2>{{ $t('user_status', 'Online status') }}</h2> - </div> + <h2 id="user_status-set-dialog" class="set-status-modal__header"> + {{ $t('user_status', 'Online status') }} + </h2> <div class="set-status-modal__online-status" role="radiogroup" :aria-label="$t('user_status', 'Online status')"> @@ -25,15 +26,22 @@ <!-- Status message form --> <form @submit.prevent="saveStatus" @reset="clearStatus"> - <div class="set-status-modal__header"> - <h2>{{ $t('user_status', 'Status message') }}</h2> - </div> + <h3 class="set-status-modal__header"> + {{ $t('user_status', 'Status message') }} + </h3> <div class="set-status-modal__custom-input"> <CustomMessageInput ref="customMessageInput" :icon="icon" :message="editedMessage" @change="setMessage" @select-icon="setIcon" /> + <NcButton v-if="messageId === 'vacationing'" + :href="absencePageUrl" + target="_blank" + type="secondary" + :aria-label="$t('user_status', 'Set absence period')"> + {{ $t('user_status', 'Set absence period and replacement') + ' ↗' }} + </NcButton> </div> <div v-if="hasBackupStatus" class="set-status-modal__automation-hint"> @@ -69,8 +77,9 @@ <script> import { showError } from '@nextcloud/dialogs' -import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import { generateUrl } from '@nextcloud/router' +import NcModal from '@nextcloud/vue/components/NcModal' +import NcButton from '@nextcloud/vue/components/NcButton' import { getAllStatusOptions } from '../services/statusOptionsService.js' import OnlineStatusMixin from '../mixins/OnlineStatusMixin.js' import PredefinedStatusesList from './PredefinedStatusesList.vue' @@ -109,6 +118,7 @@ export default { return { clearAt: null, editedMessage: '', + predefinedMessageId: null, isSavingStatus: false, statuses: getAllStatusOptions(), } @@ -134,6 +144,10 @@ export default { return this.$store.state.userBackupStatus.message || '' }, + absencePageUrl() { + return generateUrl('settings/user/availability#absence') + }, + resetButtonText() { if (this.backupIcon && this.backupMessage) { return this.$t('user_status', 'Reset status to "{icon} {message}"', { @@ -176,6 +190,7 @@ export default { mounted() { this.$store.dispatch('fetchBackupFromServer') + this.predefinedMessageId = this.$store.state.userStatus.messageId if (this.$store.state.userStatus.clearAt !== null) { this.clearAt = { type: '_time', @@ -196,6 +211,7 @@ export default { * @param {string} icon The new icon */ setIcon(icon) { + this.predefinedMessageId = null this.$store.dispatch('setCustomMessage', { message: this.message, icon, @@ -211,6 +227,7 @@ export default { * @param {string} message The new message */ setMessage(message) { + this.predefinedMessageId = null this.editedMessage = message }, /** @@ -227,6 +244,7 @@ export default { * @param {object} status The predefined status object */ selectPredefinedMessage(status) { + this.predefinedMessageId = status.id this.clearAt = status.clearAt this.$store.dispatch('setPredefinedMessage', { messageId: status.id, @@ -246,11 +264,18 @@ export default { try { this.isSavingStatus = true - await this.$store.dispatch('setCustomMessage', { - message: this.editedMessage, - icon: this.icon, - clearAt: this.clearAt, - }) + if (this.predefinedMessageId === null) { + await this.$store.dispatch('setCustomMessage', { + message: this.editedMessage, + icon: this.icon, + clearAt: this.clearAt, + }) + } else { + this.$store.dispatch('setPredefinedMessage', { + messageId: this.predefinedMessageId, + clearAt: this.clearAt, + }) + } } catch (err) { showError(this.$t('user_status', 'There was an error saving the status')) console.debug(err) @@ -278,6 +303,7 @@ export default { } this.isSavingStatus = false + this.predefinedMessageId = null this.closeModal() }, /** @@ -299,6 +325,7 @@ export default { } this.isSavingStatus = false + this.predefinedMessageId = this.$store.state.userStatus?.messageId }, }, } @@ -309,34 +336,48 @@ export default { .set-status-modal { padding: 8px 20px 20px 20px; + &, & * { + box-sizing: border-box; + } + &__header { + font-size: 21px; text-align: center; - font-weight: bold; - margin: 15px 0; + height: fit-content; + min-height: var(--default-clickable-area); + line-height: var(--default-clickable-area); + overflow-wrap: break-word; + margin-block: 0 calc(2 * var(--default-grid-baseline)); } &__online-status { - display: grid; - grid-template-columns: 1fr 1fr; + display: flex; + flex-direction: column; + gap: calc(2 * var(--default-grid-baseline)); + margin-block: 0 calc(2 * var(--default-grid-baseline)); } &__custom-input { display: flex; + flex-direction: column; + align-items: center; + gap: var(--default-grid-baseline); width: 100%; - margin-bottom: 10px; + padding-inline-start: var(--default-grid-baseline); + margin-block: 0 calc(2 * var(--default-grid-baseline)); } &__automation-hint { display: flex; width: 100%; - margin-bottom: 10px; + margin-block: 0 calc(2 * var(--default-grid-baseline)); color: var(--color-text-maxcontrast); } .status-buttons { display: flex; padding: 3px; - padding-left:0; + padding-inline-start:0; gap: 3px; } } diff --git a/apps/user_status/src/menu.js b/apps/user_status/src/menu.js index 2e5e9be7e31..34e5e6eabb1 100644 --- a/apps/user_status/src/menu.js +++ b/apps/user_status/src/menu.js @@ -3,16 +3,15 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import Vue from 'vue' -import { getRequestToken } from '@nextcloud/auth' +import { getCSPNonce } from '@nextcloud/auth' import { subscribe } from '@nextcloud/event-bus' +import Vue from 'vue' import UserStatus from './UserStatus.vue' - import store from './store/index.js' // eslint-disable-next-line camelcase -__webpack_nonce__ = btoa(getRequestToken()) +__webpack_nonce__ = getCSPNonce() Vue.prototype.t = t Vue.prototype.$t = t diff --git a/apps/user_status/src/services/statusOptionsService.js b/apps/user_status/src/services/statusOptionsService.js index 7280e58ec75..6c23645e5be 100644 --- a/apps/user_status/src/services/statusOptionsService.js +++ b/apps/user_status/src/services/statusOptionsService.js @@ -18,6 +18,9 @@ const getAllStatusOptions = () => { type: 'away', label: t('user_status', 'Away'), }, { + type: 'busy', + label: t('user_status', 'Busy'), + }, { type: 'dnd', label: t('user_status', 'Do not disturb'), subline: t('user_status', 'Mute all notifications'), diff --git a/apps/user_status/src/services/statusService.js b/apps/user_status/src/services/statusService.js index fcaf2ef9902..6504411c996 100644 --- a/apps/user_status/src/services/statusService.js +++ b/apps/user_status/src/services/statusService.js @@ -21,7 +21,7 @@ const fetchCurrentStatus = async () => { /** * Fetches the current user-status * - * @param {string} userId + * @param {string} userId Id of the user to fetch the status * @return {Promise<object>} */ const fetchBackupStatus = async (userId) => { @@ -89,7 +89,7 @@ const clearMessage = async () => { /** * Revert the automated status * - * @param {string} messageId + * @param {string} messageId ID of the message to revert * @return {Promise<object>} */ const revertToBackupStatus = async (messageId) => { |