aboutsummaryrefslogtreecommitdiffstats
path: root/core/src/components/setup/RecommendedApps.vue
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/components/setup/RecommendedApps.vue')
-rw-r--r--core/src/components/setup/RecommendedApps.vue168
1 files changed, 95 insertions, 73 deletions
diff --git a/core/src/components/setup/RecommendedApps.vue b/core/src/components/setup/RecommendedApps.vue
index cd39929f14b..f2120c28402 100644
--- a/core/src/components/setup/RecommendedApps.vue
+++ b/core/src/components/setup/RecommendedApps.vue
@@ -1,26 +1,10 @@
<!--
- - @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
- -
- - @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
- -
- - @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/>.
- -->
+ - SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
<template>
- <div class="body-login-container">
+ <div class="guest-box" data-cy-setup-recommended-apps>
<h2>{{ t('core', 'Recommended apps') }}</h2>
<p v-if="loadingApps" class="loading text-center">
{{ t('core', 'Loading apps …') }}
@@ -28,51 +12,59 @@
<p v-else-if="loadingAppsError" class="loading-error text-center">
{{ t('core', 'Could not fetch list of apps from the App Store.') }}
</p>
- <p v-else-if="installingApps" class="text-center">
- {{ t('core', 'Installing apps …') }}
- </p>
<div v-for="app in recommendedApps" :key="app.id" class="app">
- <img :src="customIcon(app.id)" alt="">
- <div class="info">
- <h3>
- {{ app.name }}
- <span v-if="app.loading" class="icon icon-loading-small-dark" />
- <span v-else-if="app.active" class="icon icon-checkmark-white" />
- </h3>
- <p v-html="customDescription(app.id)" />
- <p v-if="app.installationError">
- <strong>{{ t('core', 'App download or installation failed') }}</strong>
- </p>
- <p v-else-if="!app.isCompatible">
- <strong>{{ t('core', 'Cannot install this app because it is not compatible') }}</strong>
- </p>
- <p v-else-if="!app.canInstall">
- <strong>{{ t('core', 'Cannot install this app') }}</strong>
- </p>
- </div>
+ <template v-if="!isHidden(app.id)">
+ <img :src="customIcon(app.id)" alt="">
+ <div class="info">
+ <h3>{{ customName(app) }}</h3>
+ <p v-text="customDescription(app.id)" />
+ <p v-if="app.installationError">
+ <strong>{{ t('core', 'App download or installation failed') }}</strong>
+ </p>
+ <p v-else-if="!app.isCompatible">
+ <strong>{{ t('core', 'Cannot install this app because it is not compatible') }}</strong>
+ </p>
+ <p v-else-if="!app.canInstall">
+ <strong>{{ t('core', 'Cannot install this app') }}</strong>
+ </p>
+ </div>
+ <NcCheckboxRadioSwitch :checked="app.isSelected || app.active"
+ :disabled="!app.isCompatible || app.active"
+ :loading="app.loading"
+ @update:checked="toggleSelect(app.id)" />
+ </template>
</div>
- <InstallButton v-if="showInstallButton"
- @click.stop.prevent="installApps" />
+ <div class="dialog-row">
+ <NcButton v-if="showInstallButton && !installingApps"
+ data-cy-setup-recommended-apps-skip
+ :href="defaultPageUrl"
+ variant="tertiary">
+ {{ t('core', 'Skip') }}
+ </NcButton>
- <p class="text-center">
- <a :href="defaultPageUrl">{{ t('core', 'Cancel') }}</a>
- </p>
+ <NcButton v-if="showInstallButton"
+ data-cy-setup-recommended-apps-install
+ :disabled="installingApps || !isAnyAppSelected"
+ variant="primary"
+ @click.stop.prevent="installApps">
+ {{ installingApps ? t('core', 'Installing apps …') : t('core', 'Install recommended apps') }}
+ </NcButton>
+ </div>
</div>
</template>
<script>
-import axios from '@nextcloud/axios'
-import { generateUrl, imagePath } from '@nextcloud/router'
+import { t } from '@nextcloud/l10n'
import { loadState } from '@nextcloud/initial-state'
+import { generateUrl, imagePath } from '@nextcloud/router'
+import axios from '@nextcloud/axios'
import pLimit from 'p-limit'
-import { translate as t } from '@nextcloud/l10n'
-
-// TODO replace with Button component when @nextcloud/vue is upgraded to v5
-import InstallButton from './InstallButton'
+import logger from '../../logger.js'
-import logger from '../../logger'
+import NcButton from '@nextcloud/vue/components/NcButton'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
const recommended = {
calendar: {
@@ -88,25 +80,29 @@ const recommended = {
icon: imagePath('core', 'actions/mail.svg'),
},
spreed: {
- description: t('core', 'Chatting, video calls, screensharing, online meetings and web conferencing – in your browser and with mobile apps.'),
+ description: t('core', 'Chatting, video calls, screen sharing, online meetings and web conferencing – in your browser and with mobile apps.'),
icon: imagePath('core', 'apps/spreed.svg'),
},
richdocuments: {
- description: t('core', 'Collaboratively edit office documents.'),
+ name: 'Nextcloud Office',
+ description: t('core', 'Collaborative documents, spreadsheets and presentations, built on Collabora Online.'),
icon: imagePath('core', 'apps/richdocuments.svg'),
},
+ notes: {
+ description: t('core', 'Distraction free note taking app.'),
+ icon: imagePath('core', 'apps/notes.svg'),
+ },
richdocumentscode: {
- description: t('core', 'Local document editing back-end used by the Collabora Online app.'),
- icon: imagePath('core', 'apps/richdocumentscode.svg'),
+ hidden: true,
},
}
const recommendedIds = Object.keys(recommended)
-const defaultPageUrl = loadState('core', 'defaultPageUrl')
export default {
name: 'RecommendedApps',
components: {
- InstallButton,
+ NcCheckboxRadioSwitch,
+ NcButton,
},
data() {
return {
@@ -115,20 +111,23 @@ export default {
loadingApps: true,
loadingAppsError: false,
apps: [],
- defaultPageUrl,
+ defaultPageUrl: loadState('core', 'defaultPageUrl'),
}
},
computed: {
recommendedApps() {
return this.apps.filter(app => recommendedIds.includes(app.id))
},
+ isAnyAppSelected() {
+ return this.recommendedApps.some(app => app.isSelected)
+ },
},
async mounted() {
try {
const { data } = await axios.get(generateUrl('settings/apps/list'))
logger.info(`${data.apps.length} apps fetched`)
- this.apps = data.apps.map(app => Object.assign(app, { loading: false, installationError: false }))
+ this.apps = data.apps.map(app => Object.assign(app, { loading: false, installationError: false, isSelected: app.isCompatible }))
logger.debug(`${this.recommendedApps.length} recommended apps found`, { apps: this.recommendedApps })
this.showInstallButton = true
@@ -142,23 +141,24 @@ export default {
},
methods: {
installApps() {
- this.showInstallButton = false
this.installingApps = true
const limit = pLimit(1)
const installing = this.recommendedApps
- .filter(app => !app.active && app.isCompatible && app.canInstall)
- .map(app => limit(() => {
+ .filter(app => !app.active && app.isCompatible && app.canInstall && app.isSelected)
+ .map(app => limit(async () => {
logger.info(`installing ${app.id}`)
app.loading = true
return axios.post(generateUrl('settings/apps/enable'), { appIds: [app.id], groups: [] })
.catch(error => {
logger.error(`could not install ${app.id}`, { error })
+ app.isSelected = false
app.installationError = true
})
.then(() => {
logger.info(`installed ${app.id}`)
app.loading = false
+ app.active = true
})
}))
logger.debug(`installing ${installing.length} recommended apps`)
@@ -166,7 +166,7 @@ export default {
.then(() => {
logger.info('all recommended apps installed, redirecting …')
- window.location = defaultPageUrl
+ window.location = this.defaultPageUrl
})
.catch(error => logger.error('could not install recommended apps', { error }))
},
@@ -177,6 +177,12 @@ export default {
}
return recommended[appId].icon
},
+ customName(app) {
+ if (!(app.id in recommended)) {
+ return app.name
+ }
+ return recommended[app.id].name || app.name
+ },
customDescription(appId) {
if (!(appId in recommended)) {
logger.warn(`no app description for recommended app ${appId}`)
@@ -184,13 +190,29 @@ export default {
}
return recommended[appId].description
},
+ isHidden(appId) {
+ if (!(appId in recommended)) {
+ return false
+ }
+ return !!recommended[appId].hidden
+ },
+ toggleSelect(appId) {
+ // disable toggle when installButton is disabled
+ if (!(appId in recommended) || !this.showInstallButton) {
+ return
+ }
+ const index = this.apps.findIndex(app => app.id === appId)
+ this.$set(this.apps[index], 'isSelected', !this.apps[index].isSelected)
+ },
},
}
</script>
<style lang="scss" scoped>
-.body-login-container {
-
+.dialog-row {
+ display: flex;
+ justify-content: end;
+ margin-top: 8px;
}
p {
@@ -215,7 +237,7 @@ p {
img {
height: 50px;
width: 50px;
- filter: invert(1);
+ filter: var(--background-invert-if-dark);
}
img, .info {
@@ -224,17 +246,17 @@ p {
.info {
h3, p {
- text-align: left;
+ text-align: start;
}
h3 {
- color: #fff;
margin-top: 0;
}
+ }
- h3 > span.icon {
- display: inline-block;
- }
+ .checkbox-radio-switch {
+ margin-inline-start: auto;
+ padding: 0 2px;
}
}
</style>