aboutsummaryrefslogtreecommitdiffstats
path: root/apps/user_status/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/user_status/src')
-rw-r--r--apps/user_status/src/UserStatus.vue81
-rw-r--r--apps/user_status/src/components/ClearAtSelect.vue10
-rw-r--r--apps/user_status/src/components/CustomMessageInput.vue6
-rw-r--r--apps/user_status/src/components/OnlineStatusSelect.vue44
-rw-r--r--apps/user_status/src/components/PredefinedStatus.vue15
-rw-r--r--apps/user_status/src/components/PredefinedStatusesList.vue3
-rw-r--r--apps/user_status/src/components/PreviousStatus.vue8
-rw-r--r--apps/user_status/src/components/SetStatusModal.vue83
-rw-r--r--apps/user_status/src/menu.js7
-rw-r--r--apps/user_status/src/services/statusOptionsService.js3
-rw-r--r--apps/user_status/src/services/statusService.js4
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) => {