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:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-10-01 23:10:10 +0300
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-10-01 23:19:05 +0300
commitf240794f2b9990e6a56b6dd7067c219e7fe1d591 (patch)
tree0074ff88a0e57231d5ec9e22d55018b3f697376c /apps/user_status/src
parent5451dcbec504c3b0a9b57c0794e71868a9b6254e (diff)
Move online status into modal
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/user_status/src')
-rw-r--r--apps/user_status/src/UserStatus.vue (renamed from apps/user_status/src/App.vue)217
-rw-r--r--apps/user_status/src/components/OnlineStatusSelect.vue103
-rw-r--r--apps/user_status/src/components/SetStatusModal.vue54
-rw-r--r--apps/user_status/src/main-user-status-menu.js17
-rw-r--r--apps/user_status/src/mixins/OnlineStatusMixin.js110
5 files changed, 331 insertions, 170 deletions
diff --git a/apps/user_status/src/App.vue b/apps/user_status/src/UserStatus.vue
index ff927505bae..51761582595 100644
--- a/apps/user_status/src/App.vue
+++ b/apps/user_status/src/UserStatus.vue
@@ -20,70 +20,59 @@
-->
<template>
- <li :class="{ inline }">
- <div id="user-status-menu-item">
+ <li>
+ <div class="user-status-menu-item">
+ <!-- Username display -->
<span
v-if="!inline"
- id="user-status-menu-item__header"
+ class="user-status-menu-item__header"
:title="displayName">
{{ displayName }}
</span>
- <Actions
- id="user-status-menu-item__subheader"
- :default-icon="statusIcon"
- container="header"
- :menu-title="visibleMessage"
- :title="visibleMessage">
- <ActionButton
- v-for="status in statuses"
- :key="status.type"
- :icon="status.icon"
- :close-after-click="true"
- :title="status.label"
- @click.prevent.stop="changeStatus(status.type)">
- {{ status.subline }}
- </ActionButton>
- <ActionButton
- icon="icon-rename"
- :close-after-click="true"
- :title="$t('user_status', 'Set status message')"
- @click.prevent.stop="openModal" />
- </Actions>
- <SetStatusModal
- v-if="isModalOpen"
- @close="closeModal" />
+
+ <!-- Status modal toggle -->
+ <toggle :is="inline ? 'button' : 'a'"
+ :class="{'user-status-menu-item__toggle--inline': inline}"
+ class="user-status-menu-item__toggle"
+ href="#"
+ @click.prevent.stop="openModal">
+ <span :class="statusIcon" class="user-status-menu-item__toggle-icon" />
+ {{ visibleMessage }}
+ </toggle>
</div>
+
+ <!-- Status management modal -->
+ <SetStatusModal
+ v-if="isModalOpen"
+ @close="closeModal" />
</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'
+import { sendHeartbeat } from './services/heartbeatService'
+import OnlineStatusMixin from './mixins/OnlineStatusMixin'
+
export default {
- name: 'App',
+ name: 'UserStatus',
+
components: {
- Actions,
- ActionButton,
- SetStatusModal,
+ SetStatusModal: () => import(/* webpackChunkName: 'user-status-modal' */'./components/SetStatusModal'),
},
+ mixins: [OnlineStatusMixin],
+
props: {
inline: {
type: Boolean,
default: false,
},
},
+
data() {
return {
isModalOpen: false,
- statuses: getAllStatusOptions(),
heartbeatInterval: null,
setAwayTimeout: null,
mouseMoveListener: null,
@@ -91,12 +80,6 @@ export default {
}
},
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
*
@@ -105,64 +88,8 @@ export default {
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
@@ -198,6 +125,7 @@ export default {
this._backgroundHeartbeat()
}
},
+
/**
* Some housekeeping before destroying the component
*/
@@ -205,6 +133,7 @@ export default {
window.removeEventListener('mouseMove', this.mouseMoveListener)
clearInterval(this.heartbeatInterval)
},
+
methods: {
/**
* Opens the modal to set a custom status
@@ -218,19 +147,7 @@ export default {
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
*
@@ -248,65 +165,55 @@ export default {
<style lang="scss">
$max-width-user-status: 200px;
-li:not(.inline) #user-status-menu-item {
+.user-status-menu-item {
&__header {
display: block;
+ overflow: hidden;
box-sizing: border-box;
- color: var(--color-text-maxcontrast);
+ max-width: $max-width-user-status;
padding: 10px 12px 5px 38px;
- opacity: 1;
- white-space: nowrap;
text-align: left;
- max-width: $max-width-user-status;
- overflow: hidden;
+ white-space: nowrap;
text-overflow: ellipsis;
+ opacity: 1;
+ color: var(--color-text-maxcontrast);
}
- &__subheader {
- width: 100%;
-
- button.action-item__menutoggle {
- display: block;
- box-sizing: border-box;
- background-color: var(--color-main-background);
- background-position: 12px center;
+ &__toggle {
+ &-icon {
+ width: 16px;
+ height: 16px;
+ margin-right: 10px;
+ opacity: 1 !important;
background-size: 16px;
+ }
+
+ // In dashboard
+ &--inline {
+ width: auto;
+ min-width: 44px;
+ height: 44px;
+ margin: 0;
border: 0;
- border-radius: 0;
+ border-radius: var(--border-radius-pill);
+ background-color: var(--color-background-translucent);
+ font-size: inherit;
font-weight: normal;
- padding-left: 38px;
- opacity: 1;
- max-width: $max-width-user-status;
- overflow: hidden;
- text-overflow: ellipsis;
+ -webkit-backdrop-filter: var(--background-blur);
+ backdrop-filter: var(--background-blur);
+
+ &:active,
&:hover,
&:focus {
- box-shadow: inset 4px 0 var(--color-primary-element);
+ background-color: var(--color-background-hover);
}
}
}
}
-.inline #user-status-menu-item__subheader {
- width: 100%;
-
- button.action-item__menutoggle {
- background-size: 16px;
- border: 0;
- border-radius: var(--border-radius-pill);
- font-weight: normal;
- padding-left: 40px;
-
- &.icon-loading-small {
- &::after {
- left: 21px;
- }
- }
- }
+li {
+ list-style-type: none;
}
- li {
- list-style-type: none;
- }
</style>
diff --git a/apps/user_status/src/components/OnlineStatusSelect.vue b/apps/user_status/src/components/OnlineStatusSelect.vue
new file mode 100644
index 00000000000..b03af75681e
--- /dev/null
+++ b/apps/user_status/src/components/OnlineStatusSelect.vue
@@ -0,0 +1,103 @@
+<!--
+ - @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @author John Molakvoæ <skjnldsv@protonmail.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="user-status-online-select">
+ <input :id="id"
+ :checked="checked"
+ class="user-status-online-select__input"
+ type="radio"
+ name="user-status-online"
+ @change="onChange">
+ <label :for="id" :class="icon" class="user-status-online-select__label">
+ <slot />
+ </label>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'OnlineStatusSelect',
+
+ props: {
+ checked: {
+ type: Boolean,
+ default: false,
+ },
+ icon: {
+ type: String,
+ required: true,
+ },
+ type: {
+ type: String,
+ required: true,
+ },
+ },
+
+ computed: {
+ id() {
+ return `user-status-online-status-${this.type}`
+ },
+ },
+
+ methods: {
+ onChange() {
+ this.$emit('select', this.type)
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+$icon-size: 24px;
+$label-padding: 8px;
+
+.user-status-online-select {
+ // Inputs are here for keyboard navigation, they are not visually visible
+ &__input {
+ position: absolute;
+ top: auto;
+ left: -10000px;
+ overflow: hidden;
+ width: 1px;
+ height: 1px;
+ }
+
+ &__label {
+ display: block;
+ margin: $label-padding;
+ padding: $label-padding;
+ padding-left: $icon-size + $label-padding * 2;
+ border: 2px solid var(--color-main-background);
+ border-radius: var(--border-radius-large);
+ background-color: var(--color-background-hover);
+ background-position: $label-padding center;
+ background-size: $icon-size;
+ }
+
+ &__input:checked + &__label,
+ &__input:focus + &__label,
+ &__label:hover {
+ border-color: var(--color-primary);
+ }
+}
+
+</style>
diff --git a/apps/user_status/src/components/SetStatusModal.vue b/apps/user_status/src/components/SetStatusModal.vue
index c0652ce990b..96223ba7e01 100644
--- a/apps/user_status/src/components/SetStatusModal.vue
+++ b/apps/user_status/src/components/SetStatusModal.vue
@@ -22,11 +22,26 @@
<template>
<Modal
size="normal"
- :title="$t('user_status', 'Set status message')"
+ :title="$t('user_status', 'Set status')"
@close="closeModal">
<div class="set-status-modal">
+ <!-- Status selector -->
<div class="set-status-modal__header">
- <h3>{{ $t('user_status', 'Set status message') }}</h3>
+ <h3>{{ $t('user_status', 'Online status') }}</h3>
+ </div>
+ <div class="set-status-modal__online-status">
+ <OnlineStatusSelect v-for="status in statuses"
+ :key="status.type"
+ v-bind="status"
+ :checked="status.type === statusType"
+ @select="changeStatus">
+ {{ status.label }}
+ </OnlineStatusSelect>
+ </div>
+
+ <!-- Status message -->
+ <div class="set-status-modal__header">
+ <h3>{{ $t('user_status', 'Status message') }}</h3>
</div>
<div class="set-status-modal__custom-input">
<EmojiPicker @select="setIcon">
@@ -57,27 +72,36 @@
</template>
<script>
+import { showError } from '@nextcloud/dialogs'
import EmojiPicker from '@nextcloud/vue/dist/Components/EmojiPicker'
import Modal from '@nextcloud/vue/dist/Components/Modal'
+
+import { getAllStatusOptions } from '../services/statusOptionsService'
+import OnlineStatusMixin from '../mixins/OnlineStatusMixin'
import PredefinedStatusesList from './PredefinedStatusesList'
import CustomMessageInput from './CustomMessageInput'
import ClearAtSelect from './ClearAtSelect'
-import { showError } from '@nextcloud/dialogs'
+import OnlineStatusSelect from './OnlineStatusSelect'
export default {
name: 'SetStatusModal',
+
components: {
+ ClearAtSelect,
+ CustomMessageInput,
EmojiPicker,
Modal,
- CustomMessageInput,
+ OnlineStatusSelect,
PredefinedStatusesList,
- ClearAtSelect,
},
+ mixins: [OnlineStatusMixin],
+
data() {
return {
+ clearAt: null,
icon: null,
message: null,
- clearAt: null,
+ statuses: getAllStatusOptions(),
}
},
computed: {
@@ -90,6 +114,7 @@ export default {
return this.icon || '😀'
},
},
+
/**
* Loads the current status when a user opens dialog
*/
@@ -209,6 +234,18 @@ export default {
min-height: 200px;
padding: 8px 20px 20px 20px;
+ &__header {
+ text-align: center;
+ font-weight: bold;
+ }
+
+ &__online-status {
+ display: grid;
+ // Space between the two sections
+ margin-bottom: 40px;
+ grid-template-columns: 1fr 1fr;
+ }
+
&__custom-input {
display: flex;
width: 100%;
@@ -216,12 +253,12 @@ export default {
.custom-input__emoji-button {
flex-basis: 40px;
- width: 40px;
flex-grow: 0;
- border-radius: var(--border-radius) 0 0 var(--border-radius);
+ width: 40px;
height: 34px;
margin-right: 0;
border-right: none;
+ border-radius: var(--border-radius) 0 0 var(--border-radius);
}
}
@@ -233,4 +270,5 @@ export default {
}
}
}
+
</style>
diff --git a/apps/user_status/src/main-user-status-menu.js b/apps/user_status/src/main-user-status-menu.js
index 322585c3f0c..12fda36e85b 100644
--- a/apps/user_status/src/main-user-status-menu.js
+++ b/apps/user_status/src/main-user-status-menu.js
@@ -21,7 +21,7 @@
*/
import Vue from 'vue'
import { getRequestToken } from '@nextcloud/auth'
-import App from './App'
+import UserStatus from './UserStatus'
import store from './store'
// eslint-disable-next-line camelcase
@@ -36,18 +36,23 @@ __webpack_public_path__ = OC.linkTo('user_status', 'js/')
Vue.prototype.t = t
Vue.prototype.$t = t
-const app = new Vue({
- render: h => h(App),
+// Register settings menu entry
+export default new Vue({
+ el: 'li[data-id="user_status-menuitem"]',
+ // eslint-disable-next-line vue/match-component-file-name
+ name: 'UserStatusRoot',
+ render: h => h(UserStatus),
store,
-}).$mount('li[data-id="user_status-menuitem"]')
+})
+// Register dashboard status
document.addEventListener('DOMContentLoaded', function() {
if (!OCA.Dashboard) {
return
}
OCA.Dashboard.registerStatus('status', (el) => {
- const Dashboard = Vue.extend(App)
+ const Dashboard = Vue.extend(UserStatus)
return new Dashboard({
propsData: {
inline: true,
@@ -56,5 +61,3 @@ document.addEventListener('DOMContentLoaded', function() {
}).$mount(el)
})
})
-
-export { app }
diff --git a/apps/user_status/src/mixins/OnlineStatusMixin.js b/apps/user_status/src/mixins/OnlineStatusMixin.js
new file mode 100644
index 00000000000..ceba40f05e7
--- /dev/null
+++ b/apps/user_status/src/mixins/OnlineStatusMixin.js
@@ -0,0 +1,110 @@
+/**
+ * @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.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 { mapState } from 'vuex'
+import { showError } from '@nextcloud/dialogs'
+
+export default {
+ computed: {
+ ...mapState({
+ statusType: state => state.userStatus.status,
+ statusIsUserDefined: state => state.userStatus.statusIsUserDefined,
+ customIcon: state => state.userStatus.icon,
+ customMessage: state => state.userStatus.message,
+ }),
+
+ /**
+ * 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 ''
+ },
+ },
+
+ methods: {
+ /**
+ * 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)
+ }
+ },
+ },
+}