You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nextcloud/apps/settings/src/components/AppList.vue

244 lines
7.2 KiB
Vue

<!--
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @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 id="app-content-inner">
<div id="apps-list" class="apps-list" :class="{installed: (useBundleView || useListView), store: useAppStoreView}">
<template v-if="useListView">
<div v-if="showUpdateAll" class="toolbar">
{{ n('settings', '%n app has an update available', '%n apps have an update available', counter) }}
<NcButton v-if="showUpdateAll"
id="app-list-update-all"
type="primary"
@click="updateAll">
{{ n('settings', 'Update', 'Update all', counter) }}
</NcButton>
</div>
<div v-if="!showUpdateAll" class="toolbar">
{{ t('settings', 'All apps are up-to-date.') }}
</div>
<transition-group name="app-list" tag="div" class="apps-list-container">
<AppItem v-for="app in apps"
:key="app.id"
:app="app"
:category="category" />
</transition-group>
</template>
<transition-group v-if="useBundleView"
name="app-list"
tag="div"
class="apps-list-container">
<template v-for="bundle in bundles">
<div :key="bundle.id" class="apps-header">
<div class="app-image" />
<h2>{{ bundle.name }} <input type="button" :value="bundleToggleText(bundle.id)" @click="toggleBundle(bundle.id)"></h2>
<div class="app-version" />
<div class="app-level" />
<div class="app-groups" />
<div class="actions">
&nbsp;
</div>
</div>
<AppItem v-for="app in bundleApps(bundle.id)"
:key="bundle.id + app.id"
:app="app"
:category="category" />
</template>
</transition-group>
<template v-if="useAppStoreView">
<AppItem v-for="app in apps"
:key="app.id"
:app="app"
:category="category"
:list-view="false" />
</template>
</div>
<div id="apps-list-search" class="apps-list installed">
<div class="apps-list-container">
<template v-if="search !== '' && searchApps.length > 0">
<div class="section">
<div />
<td colspan="5">
<h2>{{ t('settings', 'Results from other categories') }}</h2>
</td>
</div>
<AppItem v-for="app in searchApps"
:key="app.id"
:app="app"
:category="category"
:list-view="true" />
</template>
</div>
</div>
<div v-if="search !== '' && !loading && searchApps.length === 0 && apps.length === 0" id="apps-list-empty" class="emptycontent emptycontent-search">
<div id="app-list-empty-icon" class="icon-settings-dark" />
<h2>{{ t('settings', 'No apps found for your version') }}</h2>
</div>
<div id="searchresults" />
</div>
</template>
<script>
import AppItem from './AppList/AppItem.vue'
import PrefixMixin from './PrefixMixin.vue'
import pLimit from 'p-limit'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
export default {
name: 'AppList',
components: {
AppItem,
NcButton,
},
mixins: [PrefixMixin],
props: ['category', 'app', 'search'],
computed: {
counter() {
return this.apps.filter(app => app.update).length
},
loading() {
return this.$store.getters.loading('list')
},
hasPendingUpdate() {
return this.apps.filter(app => app.update).length > 0
},
showUpdateAll() {
return this.hasPendingUpdate && this.useListView
},
apps() {
const apps = this.$store.getters.getAllApps
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
.sort(function(a, b) {
const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1) + a.name
const sortStringB = '' + (b.active ? 0 : 1) + (b.update ? 0 : 1) + b.name
return OC.Util.naturalSortCompare(sortStringA, sortStringB)
})
if (this.category === 'installed') {
return apps.filter(app => app.installed)
}
if (this.category === 'enabled') {
return apps.filter(app => app.active && app.installed)
}
if (this.category === 'disabled') {
return apps.filter(app => !app.active && app.installed)
}
if (this.category === 'app-bundles') {
return apps.filter(app => app.bundles)
}
if (this.category === 'updates') {
return apps.filter(app => app.update)
}
if (this.category === 'featured') {
return apps.filter(app => app.level === 200)
}
// filter app store categories
return apps.filter(app => {
return app.appstore && app.category !== undefined
&& (app.category === this.category || app.category.indexOf(this.category) > -1)
})
},
bundles() {
return this.$store.getters.getServerData.bundles.filter(bundle => this.bundleApps(bundle.id).length > 0)
},
bundleApps() {
return function(bundle) {
return this.$store.getters.getAllApps
.filter(app => {
return app.bundleIds !== undefined && app.bundleIds.includes(bundle)
})
}
},
searchApps() {
if (this.search === '') {
return []
}
return this.$store.getters.getAllApps
.filter(app => {
if (app.name.toLowerCase().search(this.search.toLowerCase()) !== -1) {
return (!this.apps.find(_app => _app.id === app.id))
}
return false
})
},
useAppStoreView() {
return !this.useListView && !this.useBundleView
},
useListView() {
return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates' || this.category === 'featured')
},
useBundleView() {
return (this.category === 'app-bundles')
},
allBundlesEnabled() {
return (id) => {
return this.bundleApps(id).filter(app => !app.active).length === 0
}
},
bundleToggleText() {
return (id) => {
if (this.allBundlesEnabled(id)) {
return t('settings', 'Disable all')
}
return t('settings', 'Enable all')
}
},
},
methods: {
toggleBundle(id) {
if (this.allBundlesEnabled(id)) {
return this.disableBundle(id)
}
return this.enableBundle(id)
},
enableBundle(id) {
const apps = this.bundleApps(id).map(app => app.id)
this.$store.dispatch('enableApp', { appId: apps, groups: [] })
.catch((error) => {
console.error(error)
OC.Notification.show(error)
})
},
disableBundle(id) {
const apps = this.bundleApps(id).map(app => app.id)
this.$store.dispatch('disableApp', { appId: apps, groups: [] })
.catch((error) => {
OC.Notification.show(error)
})
},
updateAll() {
const limit = pLimit(1)
this.apps
.filter(app => app.update)
.map(app => limit(() => this.$store.dispatch('updateApp', { appId: app.id }))
)
},
},
}
</script>