diff options
Diffstat (limited to 'apps/dashboard/src/App.vue')
-rw-r--r-- | apps/dashboard/src/App.vue | 637 |
1 files changed, 0 insertions, 637 deletions
diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue deleted file mode 100644 index a327bc3467c..00000000000 --- a/apps/dashboard/src/App.vue +++ /dev/null @@ -1,637 +0,0 @@ -<template> - <div id="app-dashboard" :style="backgroundStyle"> - <h2>{{ greeting.text }}</h2> - <ul class="statuses"> - <div v-for="status in sortedRegisteredStatus" - :id="'status-' + status" - :key="status"> - <div :ref="'status-' + status" /> - </div> - </ul> - - <Draggable v-model="layout" - class="panels" - handle=".panel--header" - @end="saveLayout"> - <div v-for="panelId in layout" :key="panels[panelId].id" class="panel"> - <div class="panel--header"> - <h2 :class="panels[panelId].iconClass"> - {{ panels[panelId].title }} - </h2> - </div> - <div class="panel--content" :class="{ loading: !panels[panelId].mounted }"> - <div :ref="panels[panelId].id" :data-id="panels[panelId].id" /> - </div> - </div> - </Draggable> - - <div class="footer"> - <a class="edit-panels icon-rename" - tabindex="0" - @click="showModal" - @keyup.enter="showModal" - @keyup.space="showModal">{{ t('dashboard', 'Customize') }}</a> - </div> - - <Modal v-if="modal" @close="closeModal"> - <div class="modal__content"> - <h3>{{ t('dashboard', 'Edit widgets') }}</h3> - <ol class="panels"> - <li v-for="status in sortedAllStatuses" :key="status"> - <input :id="'status-checkbox-' + status" - type="checkbox" - class="checkbox" - :checked="isStatusActive(status)" - @input="updateStatusCheckbox(status, $event.target.checked)"> - <label :for="'status-checkbox-' + status" :class="statusInfo[status].icon"> - {{ statusInfo[status].text }} - </label> - </li> - </ol> - <Draggable v-model="layout" - class="panels" - tag="ol" - handle=".draggable" - @end="saveLayout"> - <li v-for="panel in sortedPanels" :key="panel.id"> - <input :id="'panel-checkbox-' + panel.id" - type="checkbox" - class="checkbox" - :checked="isActive(panel)" - @input="updateCheckbox(panel, $event.target.checked)"> - <label :for="'panel-checkbox-' + panel.id" :class="isActive(panel) ? 'draggable ' + panel.iconClass : panel.iconClass"> - {{ panel.title }} - </label> - </li> - </Draggable> - - <a v-if="isAdmin" :href="appStoreUrl" class="button">{{ t('dashboard', 'Get more widgets from the app store') }}</a> - - <h3>{{ t('dashboard', 'Change background image') }}</h3> - <BackgroundSettings :background="background" - :theming-default-background="themingDefaultBackground" - @update:background="updateBackground" /> - - <h3>{{ t('dashboard', 'Weather service') }}</h3> - <p> - {{ t('dashboard', 'For your privacy, the weather data is requested by your Nextcloud server on your behalf so the weather service receives no personal information.') }} - </p> - <p class="credits--end"> - <a href="https://api.met.no/doc/TermsOfService" target="_blank" rel="noopener">{{ t('dashboard', 'Weather data from Met.no') }}</a>, - <a href="https://wiki.osmfoundation.org/wiki/Privacy_Policy" target="_blank" rel="noopener">{{ t('dashboard', 'geocoding with Nominatim') }}</a>, - <a href="https://www.opentopodata.org/#public-api" target="_blank" rel="noopener">{{ t('dashboard', 'elevation data from OpenTopoData') }}</a>. - </p> - </div> - </Modal> - </div> -</template> - -<script> -import Vue from 'vue' -import { loadState } from '@nextcloud/initial-state' -import { getCurrentUser } from '@nextcloud/auth' -import Modal from '@nextcloud/vue/dist/Components/Modal' -import Draggable from 'vuedraggable' -import axios from '@nextcloud/axios' -import { generateUrl } from '@nextcloud/router' -import isMobile from './mixins/isMobile' -import BackgroundSettings from './components/BackgroundSettings' -import getBackgroundUrl from './helpers/getBackgroundUrl' - -const panels = loadState('dashboard', 'panels') -const firstRun = loadState('dashboard', 'firstRun') -const background = loadState('dashboard', 'background') -const themingDefaultBackground = loadState('dashboard', 'themingDefaultBackground') -const version = loadState('dashboard', 'version') -const shippedBackgroundList = loadState('dashboard', 'shippedBackgrounds') -const statusInfo = { - weather: { - text: t('dashboard', 'Weather'), - icon: 'icon-weather-status', - }, - status: { - text: t('dashboard', 'Status'), - icon: 'icon-user-status-online', - }, -} - -export default { - name: 'App', - components: { - Modal, - Draggable, - BackgroundSettings, - }, - mixins: [ - isMobile, - ], - data() { - return { - isAdmin: getCurrentUser().isAdmin, - timer: new Date(), - registeredStatus: [], - callbacks: {}, - callbacksStatus: {}, - allCallbacksStatus: {}, - statusInfo, - enabledStatuses: loadState('dashboard', 'statuses'), - panels, - firstRun, - displayName: getCurrentUser()?.displayName, - uid: getCurrentUser()?.uid, - layout: loadState('dashboard', 'layout').filter((panelId) => panels[panelId]), - modal: false, - appStoreUrl: generateUrl('/settings/apps/dashboard'), - statuses: {}, - background, - themingDefaultBackground, - version, - } - }, - computed: { - backgroundImage() { - return getBackgroundUrl(this.background, this.version, this.themingDefaultBackground) - }, - backgroundStyle() { - if ((this.background === 'default' && this.themingDefaultBackground === 'backgroundColor') - || this.background.match(/#[0-9A-Fa-f]{6}/g)) { - return null - } - return { - backgroundImage: `url(${this.backgroundImage})`, - } - }, - greeting() { - const time = this.timer.getHours() - - // Determine part of the day - let partOfDay - if (time >= 22 || time < 5) { - partOfDay = 'night' - } else if (time >= 18) { - partOfDay = 'evening' - } else if (time >= 12) { - partOfDay = 'afternoon' - } else { - partOfDay = 'morning' - } - - // Define the greetings - const good = { - morning: { - generic: t('dashboard', 'Good morning'), - withName: t('dashboard', 'Good morning, {name}', { name: this.displayName }), - }, - afternoon: { - generic: t('dashboard', 'Good afternoon'), - withName: t('dashboard', 'Good afternoon, {name}', { name: this.displayName }), - }, - evening: { - generic: t('dashboard', 'Good evening'), - withName: t('dashboard', 'Good evening, {name}', { name: this.displayName }), - }, - night: { - // Don't use "Good night" as it's not a greeting - generic: t('dashboard', 'Hello'), - withName: t('dashboard', 'Hello, {name}', { name: this.displayName }), - }, - } - - // Figure out which greeting to show - const shouldShowName = this.displayName && this.uid !== this.displayName - return { text: shouldShowName ? good[partOfDay].withName : good[partOfDay].generic } - }, - isActive() { - return (panel) => this.layout.indexOf(panel.id) > -1 - }, - isStatusActive() { - return (status) => !(status in this.enabledStatuses) || this.enabledStatuses[status] - }, - sortedAllStatuses() { - return Object.keys(this.allCallbacksStatus).slice().sort(this.sortStatuses) - }, - sortedPanels() { - return Object.values(this.panels).sort((a, b) => { - const indexA = this.layout.indexOf(a.id) - const indexB = this.layout.indexOf(b.id) - if (indexA === -1 || indexB === -1) { - return indexB - indexA || a.id - b.id - } - return indexA - indexB || a.id - b.id - }) - }, - sortedRegisteredStatus() { - return this.registeredStatus.slice().sort(this.sortStatuses) - }, - }, - watch: { - callbacks() { - this.rerenderPanels() - }, - callbacksStatus() { - for (const app in this.callbacksStatus) { - const element = this.$refs['status-' + app] - if (this.statuses[app] && this.statuses[app].mounted) { - continue - } - if (element) { - this.callbacksStatus[app](element[0]) - Vue.set(this.statuses, app, { mounted: true }) - } else { - console.error('Failed to register panel in the frontend as no backend data was provided for ' + app) - } - } - }, - }, - mounted() { - this.updateGlobalStyles() - this.updateSkipLink() - window.addEventListener('scroll', this.handleScroll) - - setInterval(() => { - this.timer = new Date() - }, 30000) - - if (this.firstRun) { - window.addEventListener('scroll', this.disableFirstrunHint) - } - }, - destroyed() { - window.removeEventListener('scroll', this.handleScroll) - }, - methods: { - /** - * Method to register panels that will be called by the integrating apps - * - * @param {string} app The unique app id for the widget - * @param {function} callback The callback function to register a panel which gets the DOM element passed as parameter - */ - register(app, callback) { - Vue.set(this.callbacks, app, callback) - }, - registerStatus(app, callback) { - // always save callbacks in case user enables the status later - Vue.set(this.allCallbacksStatus, app, callback) - // register only if status is enabled or missing from config - if (this.isStatusActive(app)) { - this.registeredStatus.push(app) - this.$nextTick(() => { - Vue.set(this.callbacksStatus, app, callback) - }) - } - }, - rerenderPanels() { - for (const app in this.callbacks) { - const element = this.$refs[app] - if (this.layout.indexOf(app) === -1) { - continue - } - if (this.panels[app] && this.panels[app].mounted) { - continue - } - if (element) { - this.callbacks[app](element[0], { - widget: this.panels[app], - }) - Vue.set(this.panels[app], 'mounted', true) - } else { - console.error('Failed to register panel in the frontend as no backend data was provided for ' + app) - } - } - }, - saveLayout() { - axios.post(generateUrl('/apps/dashboard/layout'), { - layout: this.layout.join(','), - }) - }, - saveStatuses() { - axios.post(generateUrl('/apps/dashboard/statuses'), { - statuses: JSON.stringify(this.enabledStatuses), - }) - }, - showModal() { - this.modal = true - this.firstRun = false - }, - closeModal() { - this.modal = false - }, - updateCheckbox(panel, currentValue) { - const index = this.layout.indexOf(panel.id) - if (!currentValue && index > -1) { - this.layout.splice(index, 1) - - } else { - this.layout.push(panel.id) - } - Vue.set(this.panels[panel.id], 'mounted', false) - this.saveLayout() - this.$nextTick(() => this.rerenderPanels()) - }, - disableFirstrunHint() { - window.removeEventListener('scroll', this.disableFirstrunHint) - setTimeout(() => { - this.firstRun = false - }, 1000) - }, - updateBackground(data) { - this.background = data.type === 'custom' || data.type === 'default' ? data.type : data.value - this.version = data.version - this.updateGlobalStyles() - }, - updateGlobalStyles() { - document.body.setAttribute('data-dashboard-background', this.background) - if (window.OCA.Theming.inverted) { - document.body.classList.add('dashboard--inverted') - } - - const shippedBackgroundTheme = shippedBackgroundList[this.background] ? shippedBackgroundList[this.background].theming : 'light' - if (shippedBackgroundTheme === 'dark') { - document.body.classList.add('dashboard--dark') - } else { - document.body.classList.remove('dashboard--dark') - } - }, - updateSkipLink() { - // Make sure "Skip to main content" link points to the app content - document.getElementsByClassName('skip-navigation')[0].setAttribute('href', '#app-dashboard') - }, - updateStatusCheckbox(app, checked) { - if (checked) { - this.enableStatus(app) - } else { - this.disableStatus(app) - } - }, - enableStatus(app) { - this.enabledStatuses[app] = true - this.registerStatus(app, this.allCallbacksStatus[app]) - this.saveStatuses() - }, - disableStatus(app) { - this.enabledStatuses[app] = false - const i = this.registeredStatus.findIndex((s) => s === app) - if (i !== -1) { - this.registeredStatus.splice(i, 1) - Vue.set(this.statuses, app, { mounted: false }) - this.$nextTick(() => { - Vue.delete(this.callbacksStatus, app) - }) - } - this.saveStatuses() - }, - sortStatuses(a, b) { - const al = a.toLowerCase() - const bl = b.toLowerCase() - return al > bl - ? 1 - : al < bl - ? -1 - : 0 - }, - handleScroll() { - if (window.scrollY > 70) { - document.body.classList.add('dashboard--scrolled') - } else { - document.body.classList.remove('dashboard--scrolled') - } - }, - }, -} -</script> - -<style lang="scss" scoped> -#app-dashboard { - width: 100%; - background-size: cover; - background-position: center center; - background-repeat: no-repeat; - background-attachment: fixed; - background-color: var(--color-primary); - --color-background-translucent: rgba(255, 255, 255, 0.8); - --background-blur: blur(10px); - - #body-user.theme--dark & { - background-color: var(--color-main-background); - --color-background-translucent: rgba(24, 24, 24, 0.8); - } - - #body-user.theme--highcontrast & { - background-color: var(--color-main-background); - --color-background-translucent: var(--color-main-background); - } - - > h2 { - color: var(--color-primary-text); - text-align: center; - font-size: 32px; - line-height: 130%; - padding: 10vh 16px 0px; - } -} - -.panels { - width: auto; - margin: auto; - max-width: 1500px; - display: flex; - justify-content: center; - flex-direction: row; - align-items: flex-start; - flex-wrap: wrap; -} - -.panel, .panels > div { - width: 320px; - max-width: 100%; - margin: 16px; - background-color: var(--color-background-translucent); - -webkit-backdrop-filter: var(--background-blur); - backdrop-filter: var(--background-blur); - border-radius: var(--border-radius-large); - - #body-user.theme--highcontrast & { - border: 2px solid var(--color-border); - } - - &.sortable-ghost { - opacity: 0.1; - } - - & > .panel--header { - display: flex; - z-index: 1; - top: 50px; - padding: 16px; - cursor: grab; - - &, ::v-deep * { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - } - - &:active { - cursor: grabbing; - } - - a { - flex-grow: 1; - } - - > h2 { - display: block; - flex-grow: 1; - margin: 0; - font-size: 20px; - line-height: 24px; - font-weight: bold; - background-size: 32px; - background-position: 14px 12px; - padding: 16px 8px 16px 60px; - height: 56px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: grab; - } - } - - & > .panel--content { - margin: 0 16px 16px 16px; - height: 420px; - // We specifically do not want scrollbars inside widgets - overflow: hidden; - } - - // No need to extend height of widgets if only one column is shown - @media only screen and (max-width: 709px) { - & > .panel--content { - height: auto; - } - } -} - -.footer { - text-align: center; - transition: bottom var(--animation-slow) ease-in-out; - bottom: 0; - padding: 44px 0; -} - -.edit-panels { - display: inline-block; - margin:auto; - background-position: 16px center; - padding: 12px 16px; - padding-left: 36px; - border-radius: var(--border-radius-pill); - max-width: 200px; - opacity: 1; - text-align: center; -} - -.edit-panels, -.statuses ::v-deep .action-item .action-item__menutoggle, -.statuses ::v-deep .action-item.action-item--open .action-item__menutoggle { - background-color: var(--color-background-translucent); - -webkit-backdrop-filter: var(--background-blur); - backdrop-filter: var(--background-blur); - - &:hover, - &:focus, - &:active { - background-color: var(--color-background-hover); - } -} - -.modal__content { - padding: 32px 16px; - max-height: 70vh; - text-align: center; - overflow: auto; - - ol { - display: flex; - flex-direction: row; - justify-content: center; - list-style-type: none; - padding-bottom: 16px; - } - li { - label { - display: block; - padding: 48px 8px 16px 8px; - margin: 8px; - width: 160px; - background-color: var(--color-background-hover); - border: 2px solid var(--color-main-background); - border-radius: var(--border-radius-large); - background-size: 24px; - background-position: center 16px; - text-align: center; - - &:hover { - border-color: var(--color-primary); - } - } - - input:focus + label { - border-color: var(--color-primary); - } - } - - h3 { - font-weight: bold; - - &:not(:first-of-type) { - margin-top: 64px; - } - } - - // Adjust design of 'Get more widgets' button - .button { - display: inline-block; - padding: 10px 16px; - margin: 0; - } - - p { - max-width: 650px; - margin: 0 auto; - - a:hover, - a:focus { - border-bottom: 2px solid var(--color-border); - } - } - - .credits--end { - padding-bottom: 32px; - color: var(--color-text-maxcontrast); - - a { - color: var(--color-text-maxcontrast); - } - } -} - -.flip-list-move { - transition: transform var(--animation-slow); -} - -.statuses { - display: flex; - flex-direction: row; - justify-content: center; - flex-wrap: wrap; - margin-bottom: 36px; - - & > div { - margin: 8px; - } -} -</style> |