diff options
Diffstat (limited to 'apps/dashboard/src/DashboardApp.vue')
-rw-r--r-- | apps/dashboard/src/DashboardApp.vue | 115 |
1 files changed, 70 insertions, 45 deletions
diff --git a/apps/dashboard/src/DashboardApp.vue b/apps/dashboard/src/DashboardApp.vue index 18dc0a6c467..afc874be2c9 100644 --- a/apps/dashboard/src/DashboardApp.vue +++ b/apps/dashboard/src/DashboardApp.vue @@ -1,5 +1,9 @@ +<!-- + - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later + --> <template> - <div id="app-dashboard"> + <main id="app-dashboard"> <h2>{{ greeting.text }}</h2> <ul class="statuses"> <li v-for="status in sortedRegisteredStatus" @@ -20,15 +24,10 @@ class="panel"> <div class="panel--header"> <h2> - <span :aria-labelledby="`panel-${panels[panelId].id}--header--icon--description`" - aria-hidden="true" - :class="apiWidgets[panels[panelId].id].icon_class" - role="img" /> + <img v-if="apiWidgets[panels[panelId].id].icon_url" :src="apiWidgets[panels[panelId].id].icon_url" alt=""> + <span v-else :class="apiWidgets[panels[panelId].id].icon_class" aria-hidden="true" /> {{ apiWidgets[panels[panelId].id].title }} </h2> - <span :id="`panel-${panels[panelId].id}--header--icon--description`" class="hidden-visually"> - {{ t('dashboard', '"{title} icon"', { title: apiWidgets[panels[panelId].id].title }) }} - </span> </div> <div class="panel--content"> <ApiDashboardWidget :widget="apiWidgets[panels[panelId].id]" @@ -39,13 +38,9 @@ <div v-else :key="panels[panelId].id" class="panel"> <div class="panel--header"> <h2> - <span :aria-labelledby="`panel-${panels[panelId].id}--header--icon--description`" - aria-hidden="true" - :class="panels[panelId].iconClass" - role="img" /> + <span :class="panels[panelId].iconClass" aria-hidden="true" /> {{ panels[panelId].title }} </h2> - <span :id="`panel-${panels[panelId].id}--header--icon--description`" class="hidden-visually"> {{ t('dashboard', '"{title} icon"', { title: panels[panelId].title }) }} </span> </div> <div class="panel--content" :class="{ loading: !panels[panelId].mounted }"> <div :ref="panels[panelId].id" :data-id="panels[panelId].id" /> @@ -93,7 +88,8 @@ :checked="isActive(panel)" @input="updateCheckbox(panel, $event.target.checked)"> <label :for="'panel-checkbox-' + panel.id" :class="{ draggable: isActive(panel) }"> - <span :class="panel.iconClass" aria-hidden="true" /> + <img v-if="panel.iconUrl" alt="" :src="panel.iconUrl"> + <span v-else :class="panel.iconClass" aria-hidden="true" /> {{ panel.title }} </label> </li> @@ -114,7 +110,7 @@ </div> </div> </NcModal> - </div> + </main> </template> <script> @@ -122,10 +118,10 @@ import { generateUrl, generateOcsUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' import { loadState } from '@nextcloud/initial-state' import axios from '@nextcloud/axios' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcButton from '@nextcloud/vue/components/NcButton' import Draggable from 'vuedraggable' -import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' -import NcUserStatusIcon from '@nextcloud/vue/dist/Components/NcUserStatusIcon.js' +import NcModal from '@nextcloud/vue/components/NcModal' +import NcUserStatusIcon from '@nextcloud/vue/components/NcUserStatusIcon' import Pencil from 'vue-material-design-icons/Pencil.vue' import Vue from 'vue' @@ -134,6 +130,7 @@ import ApiDashboardWidget from './components/ApiDashboardWidget.vue' const panels = loadState('dashboard', 'panels') const firstRun = loadState('dashboard', 'firstRun') +const birthdate = new Date(loadState('dashboard', 'birthdate')) const statusInfo = { weather: { @@ -181,15 +178,21 @@ export default { apiWidgets: [], apiWidgetItems: {}, loadingItems: true, + birthdate, } }, computed: { greeting() { const time = this.timer.getHours() + const isBirthday = this.birthdate instanceof Date + && this.birthdate.getMonth() === this.timer.getMonth() + && this.birthdate.getDate() === this.timer.getDate() // Determine part of the day let partOfDay - if (time >= 22 || time < 5) { + if (isBirthday) { + partOfDay = 'birthday' + } else if (time >= 22 || time < 5) { partOfDay = 'night' } else if (time >= 18) { partOfDay = 'evening' @@ -218,6 +221,10 @@ export default { generic: t('dashboard', 'Hello'), withName: t('dashboard', 'Hello, {name}', { name: this.displayName }, undefined, { escape: false }), }, + birthday: { + generic: t('dashboard', 'Happy birthday 🥳🤩🎂🎉'), + withName: t('dashboard', 'Happy birthday, {name} 🥳🤩🎂🎉', { name: this.displayName }, undefined, { escape: false }), + }, } // Figure out which greeting to show @@ -229,7 +236,7 @@ export default { return (panel) => this.layout.indexOf(panel.id) > -1 }, isStatusActive() { - return (status) => !(status in this.enabledStatuses) || this.enabledStatuses[status] + return (status) => this.enabledStatuses.findIndex((s) => s === status) !== -1 }, sortedAllStatuses() { @@ -275,13 +282,17 @@ export default { const apiWidgetIdsToFetch = Object .values(this.apiWidgets) - .filter(widget => this.isApiWidgetV2(widget.id)) + .filter(widget => this.isApiWidgetV2(widget.id) && this.layout.includes(widget.id)) .map(widget => widget.id) await Promise.all(apiWidgetIdsToFetch.map(id => this.fetchApiWidgetItems([id], true))) for (const widget of Object.values(this.apiWidgets)) { if (widget.reload_interval > 0) { setInterval(async () => { + if (!this.layout.includes(widget.id)) { + return + } + await this.fetchApiWidgetItems([widget.id], true) }, widget.reload_interval * 1000) } @@ -349,13 +360,13 @@ export default { } }, saveLayout() { - axios.post(generateUrl('/apps/dashboard/layout'), { - layout: this.layout.join(','), + axios.post(generateOcsUrl('/apps/dashboard/api/v3/layout'), { + layout: this.layout, }) }, saveStatuses() { - axios.post(generateUrl('/apps/dashboard/statuses'), { - statuses: JSON.stringify(this.enabledStatuses), + axios.post(generateOcsUrl('/apps/dashboard/api/v3/statuses'), { + statuses: this.enabledStatuses, }) }, showModal() { @@ -369,9 +380,11 @@ export default { const index = this.layout.indexOf(panel.id) if (!currentValue && index > -1) { this.layout.splice(index, 1) - } else { this.layout.push(panel.id) + if (this.isApiWidgetV2(panel.id)) { + this.fetchApiWidgetItems([panel.id], true) + } } Vue.set(this.panels[panel.id], 'mounted', false) this.saveLayout() @@ -395,15 +408,18 @@ export default { } }, enableStatus(app) { - this.enabledStatuses[app] = true + this.enabledStatuses.push(app) this.registerStatus(app, this.allCallbacksStatus[app]) this.saveStatuses() }, disableStatus(app) { - this.enabledStatuses[app] = false - const i = this.registeredStatus.findIndex((s) => s === app) + const i = this.enabledStatuses.findIndex((s) => s === app) if (i !== -1) { - this.registeredStatus.splice(i, 1) + this.enabledStatuses.splice(i, 1) + } + const j = this.registeredStatus.findIndex((s) => s === app) + if (j !== -1) { + this.registeredStatus.splice(j, 1) Vue.set(this.statuses, app, { mounted: false }) this.$nextTick(() => { Vue.delete(this.callbacksStatus, app) @@ -428,8 +444,8 @@ export default { } }, async fetchApiWidgets() { - const response = await axios.get(generateOcsUrl('/apps/dashboard/api/v1/widgets')) - this.apiWidgets = response.data.ocs.data + const { data } = await axios.get(generateOcsUrl('/apps/dashboard/api/v1/widgets')) + this.apiWidgets = data.ocs.data }, async fetchApiWidgetItems(widgetIds, merge = false) { try { @@ -468,8 +484,8 @@ export default { background-attachment: fixed; > h2 { - // this is shown directly on the background which has `color-primary`, so we need `color-primary-text` - color: var(--color-primary-text); + // this is shown directly on the background image / color + color: var(--color-background-plain-text); text-align: center; font-size: 32px; line-height: 130%; @@ -491,7 +507,6 @@ export default { .panel, .panels > div { // Ensure the maxcontrast color is set for the background --color-text-maxcontrast: var(--color-text-maxcontrast-background-blur, var(--color-main-text)); - width: 320px; max-width: 100%; margin: 16px; @@ -499,7 +514,7 @@ export default { background-color: var(--color-main-background-blur); -webkit-backdrop-filter: var(--filter-background-blur); backdrop-filter: var(--filter-background-blur); - border-radius: var(--border-radius-rounded); + border-radius: var(--border-radius-container-large); #body-user.theme--highcontrast & { border: 2px solid var(--color-border); @@ -516,7 +531,8 @@ export default { padding: 16px; cursor: grab; - &, ::v-deep * { + &, + :deep(*) { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; @@ -547,15 +563,20 @@ export default { overflow: hidden; text-overflow: ellipsis; cursor: grab; + + img, span { background-size: 32px; width: 32px; height: 32px; - margin-right: 16px; background-position: center; float: left; margin-top: -6px; - margin-left: 6px; + margin-inline: 6px 16px; + } + + img { + filter: var(--background-invert-if-dark); } } } @@ -587,7 +608,7 @@ export default { margin:auto; background-position: 16px center; padding: 12px 16px; - padding-left: 36px; + padding-inline-start: 36px; border-radius: var(--border-radius-pill); max-width: 200px; opacity: 1; @@ -597,11 +618,10 @@ export default { .button, .button-vue, .edit-panels, -.statuses ::v-deep .action-item .action-item__menutoggle, -.statuses ::v-deep .action-item.action-item--open .action-item__menutoggle { +.statuses :deep(.action-item .action-item__menutoggle), +.statuses :deep(.action-item.action-item--open .action-item__menutoggle) { // Ensure the maxcontrast color is set for the background --color-text-maxcontrast: var(--color-text-maxcontrast-background-blur, var(--color-main-text)); - background-color: var(--color-main-background-blur); -webkit-backdrop-filter: var(--filter-background-blur); backdrop-filter: var(--filter-background-blur); @@ -639,11 +659,12 @@ export default { background-color: var(--color-background-hover); border: 2px solid var(--color-main-background); border-radius: var(--border-radius-large); - text-align: left; + text-align: start; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + img, span { position: absolute; top: 16px; @@ -652,6 +673,10 @@ export default { background-size: 24px; } + img { + filter: var(--background-invert-if-dark); + } + &:hover { border-color: var(--color-primary-element); } @@ -664,7 +689,7 @@ export default { input[type='checkbox'].checkbox + label:before { position: absolute; - right: 12px; + inset-inline-end: 12px; top: 16px; } |