refactor(user_status): migrate to Vue 3

Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
pull/56544/head
Grigorii K. Shartsev 2025-11-20 01:36:00 +07:00 committed by Maksim Sukharev
parent b10d5d3ca0
commit 8ca4a7a036
13 changed files with 104 additions and 79 deletions

@ -1,4 +0,0 @@
/*!
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/.icon-user-status{background-image:url("../img/app.svg")}.icon-user-status-dark{background-image:url("../img/app-dark.svg");filter:var(--background-invert-if-dark)}/*# sourceMappingURL=user-status-menu.css.map */

@ -1 +0,0 @@
{"version":3,"sourceRoot":"","sources":["user-status-menu.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA,GAIA,kBACC,uCAGD,uBACC,4CACA","file":"user-status-menu.css"}

@ -1,2 +0,0 @@
SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later

@ -70,6 +70,6 @@ class BeforeTemplateRenderedListener implements IEventListener {
});
Util::addScript('user_status', 'menu');
Util::addStyle('user_status', 'user-status-menu');
Util::addStyle('user_status', 'menu');
}
}

@ -4,46 +4,44 @@
-->
<template>
<Fragment>
<NcListItem
v-if="!inline"
class="user-status-menu-item"
compact
:name="visibleMessage"
@click.stop="openModal">
<NcListItem
v-if="!inline"
:class="$style.userStatusMenuItem"
compact
:name="visibleMessage"
@click.stop="openModal">
<template #icon>
<NcUserStatusIcon
:class="$style.userStatusIcon"
:status="statusType"
aria-hidden="true" />
</template>
</NcListItem>
<div v-else>
<!-- Dashboard Status -->
<NcButton @click.stop="openModal">
<template #icon>
<NcUserStatusIcon
class="user-status-icon"
:class="$style.userStatusIcon"
: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>
{{ visibleMessage }}
</NcButton>
</div>
<!-- Status management modal -->
<SetStatusModal
v-if="isModalOpen"
:inline="inline"
@close="closeModal" />
</template>
<script>
import { getCurrentUser } from '@nextcloud/auth'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import debounce from 'debounce'
import { Fragment } from 'vue-frag'
import { defineAsyncComponent } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcListItem from '@nextcloud/vue/components/NcListItem'
import NcUserStatusIcon from '@nextcloud/vue/components/NcUserStatusIcon'
@ -55,11 +53,10 @@ export default {
name: 'UserStatus',
components: {
Fragment,
NcButton,
NcListItem,
NcUserStatusIcon,
SetStatusModal: () => import(/* webpackChunkName: 'user-status-modal' */'./components/SetStatusModal.vue'),
SetStatusModal: defineAsyncComponent(() => import('./components/SetStatusModal.vue')),
},
mixins: [OnlineStatusMixin],
@ -126,7 +123,7 @@ export default {
/**
* Some housekeeping before destroying the component
*/
beforeDestroy() {
beforeUnmount() {
window.removeEventListener('mouseMove', this.mouseMoveListener)
clearInterval(this.heartbeatInterval)
unsubscribe('user_status:status.updated', this.handleUserStatusUpdated)
@ -179,8 +176,15 @@ export default {
}
</script>
<style lang="scss" scoped>
.user-status-icon {
<style lang="scss" module>
// Note: As for v9.3.0 NcListItem does not support <style scoped>
.userStatusMenuItem,
.userStatusMenuItem * {
// TODO: Vue 3 migration - add box-sizing to core menu component
box-sizing: border-box;
}
.userStatusIcon {
width: 20px;
height: 20px;
margin: calc((var(--default-clickable-area) - 20px) / 2); // 20px icon size

@ -39,7 +39,7 @@ export default {
},
},
emits: ['select-clear-at'],
emits: ['selectClearAt'],
data() {
return {
@ -74,7 +74,7 @@ export default {
return
}
this.$emit('select-clear-at', option.clearAt)
this.$emit('selectClearAt', option.clearAt)
},
},
}

@ -61,7 +61,7 @@ export default {
emits: [
'change',
'select-icon',
'selectIcon',
],
computed: {
@ -85,14 +85,14 @@ export default {
/**
* Notifies the parent component about a changed input
*
* @param {Event} event The Change Event
* @param {string} value The new input value
*/
onChange(event) {
this.$emit('change', event.target.value)
onChange(value) {
this.$emit('change', value)
},
setIcon(icon) {
this.$emit('select-icon', icon)
this.$emit('selectIcon', icon)
},
},
}

@ -12,10 +12,12 @@
name="user-status-online"
@change="onChange">
<label :for="id" class="user-status-online-select__label">
<NcUserStatusIcon
:status="type"
class="user-status-online-select__icon"
aria-hidden="true" />
<span class="user-status-online-select__icon-wrapper">
<NcUserStatusIcon
:status="type"
class="user-status-online-select__icon"
aria-hidden="true" />
</span>
{{ label }}
<em class="user-status-online-select__subline">{{ subline }}</em>
</label>
@ -92,10 +94,17 @@ export default {
}
}
&__icon-wrapper {
height: var(--default-clickable-area);
width: var(--default-clickable-area);
display: flex;
align-items: center;
justify-content: center;
}
&__icon {
height: 20px;
width: 20px;
padding: calc((var(--default-clickable-area) - 20px) / 2);
}
&__input:checked + &__label {

@ -36,7 +36,7 @@ export default {
PredefinedStatus,
},
emits: ['select-status'],
emits: ['selectStatus'],
data() {
return {
@ -80,7 +80,7 @@ export default {
*/
selectStatus(status) {
this.lastSelected = status.id
this.$emit('select-status', status)
this.$emit('selectStatus', status)
},
},
}

@ -3,13 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCSPNonce } from '@nextcloud/auth'
import { subscribe } from '@nextcloud/event-bus'
import Vue from 'vue'
import { createApp } from 'vue'
import UserStatus from './UserStatus.vue'
import store from './store/index.js'
__webpack_nonce__ = getCSPNonce()
import './user-status-icons.css'
const mountPoint = document.getElementById('user_status-menu-entry')
@ -18,12 +17,17 @@ const mountPoint = document.getElementById('user_status-menu-entry')
*/
function mountMenuEntry() {
const mountPoint = document.getElementById('user_status-menu-entry')
new Vue({
el: mountPoint,
render: (h) => h(UserStatus),
store,
})
// TODO: fix me after Core migration to Vue 3
// In Vue 2 menu items were mounted in place to the menu items
// In Vue 3 they are mounted inside the menu item
// A workaround - replace the menu item with "display: contents" div
const transparentMountPoint = document.createElement('div')
transparentMountPoint.style.display = 'contents'
mountPoint.replaceWith(transparentMountPoint)
createApp(UserStatus)
.use(store)
.mount(transparentMountPoint)
}
if (mountPoint) {
@ -39,12 +43,10 @@ document.addEventListener('DOMContentLoaded', function() {
}
OCA.Dashboard.registerStatus('status', (el) => {
const Dashboard = Vue.extend(UserStatus)
return new Dashboard({
propsData: {
inline: true,
},
store,
}).$mount(el)
createApp(UserStatus, {
inline: true,
})
.use(store)
.mount(el)
})
})

@ -3,15 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Vue from 'vue'
import Vuex, { Store } from 'vuex'
import { createStore } from 'vuex'
import predefinedStatuses from './predefinedStatuses.js'
import userBackupStatus from './userBackupStatus.js'
import userStatus from './userStatus.js'
Vue.use(Vuex)
export default new Store({
export default createStore({
modules: {
predefinedStatuses,
userStatus,

@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
.icon-user-status {
background-image: url("../img/app.svg");
}

@ -51,7 +51,6 @@ import NcAvatar from '@nextcloud/vue/components/NcAvatar'
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu'
import AccountMenuEntry from '../components/AccountMenu/AccountMenuEntry.vue'
import AccountMenuProfileEntry from '../components/AccountMenu/AccountMenuProfileEntry.vue'
import { getAllStatusOptions } from '../../../apps/user_status/src/services/statusOptionsService.js'
import logger from '../logger.js'
interface ISettingsNavigationEntry {
@ -93,7 +92,27 @@ interface ISettingsNavigationEntry {
classes: string
}
const USER_DEFINABLE_STATUSES = getAllStatusOptions()
// See: apps/user_status/src/services/statusOptionsService.js
// TODO: either import this again from the user_status app when core is migrated to Vue 3
// Or get rid of the forbidden import
const USER_DEFINABLE_STATUSES = [{
type: 'online',
label: t('user_status', 'Online'),
}, {
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'),
}, {
type: 'invisible',
label: t('user_status', 'Invisible'),
subline: t('user_status', 'Appear offline'),
}]
export default defineComponent({
name: 'AccountMenu',