summaryrefslogtreecommitdiffstats
path: root/apps/settings/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-04-09 12:58:05 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-08-14 11:44:16 +0200
commit8e7c95effbae92e7002bfd8253d5f3c0ebc7a7a4 (patch)
tree6ff31de1d0a186cd8e0abe74bcbdf661e8b6f540 /apps/settings/src
parent228a96508a22b3805505ce61e0d36eba1e703460 (diff)
downloadnextcloud-server-8e7c95effbae92e7002bfd8253d5f3c0ebc7a7a4.tar.gz
nextcloud-server-8e7c95effbae92e7002bfd8253d5f3c0ebc7a7a4.zip
Use appsidebar for apps
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/settings/src')
-rw-r--r--apps/settings/src/components/AppDetails.vue147
-rw-r--r--apps/settings/src/views/Apps.vue110
2 files changed, 147 insertions, 110 deletions
diff --git a/apps/settings/src/components/AppDetails.vue b/apps/settings/src/components/AppDetails.vue
index fa586b3d0a1..55519bf9f80 100644
--- a/apps/settings/src/components/AppDetails.vue
+++ b/apps/settings/src/components/AppDetails.vue
@@ -21,76 +21,74 @@
-->
<template>
- <div id="app-details-view" style="padding: 20px;">
- <div class="actions">
- <div class="actions-buttons">
+ <div class="app-details">
+ <div class="app-details__actions">
+ <div v-if="app.active && canLimitToGroups(app)" class="app-details__actions-groups">
+ <input :id="prefix('groups_enable', app.id)"
+ v-model="groupCheckedAppsData"
+ type="checkbox"
+ :value="app.id"
+ class="groups-enable__checkbox checkbox"
+ @change="setGroupLimit">
+ <label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
+ <input type="hidden"
+ class="group_select"
+ :title="t('settings', 'All')"
+ value="">
+ <Multiselect v-if="isLimitedToGroups(app)"
+ :options="groups"
+ :value="appGroups"
+ :options-limit="5"
+ :placeholder="t('settings', 'Limit app usage to groups')"
+ label="name"
+ track-by="id"
+ class="multiselect-vue"
+ :multiple="true"
+ :close-on-select="false"
+ :tag-width="60"
+ @select="addGroupLimitation"
+ @remove="removeGroupLimitation"
+ @search-change="asyncFindGroup">
+ <span slot="noResult">{{ t('settings', 'No results') }}</span>
+ </Multiselect>
+ </div>
+ <div class="app-details__actions-manage">
<input v-if="app.update"
class="update primary"
type="button"
- :value="t('settings', 'Update to {version}', {version: app.update})"
- :disabled="installing || loading(app.id)"
+ :value="t('settings', 'Update to {version}', { version: app.update })"
+ :disabled="installing || isLoading"
@click="update(app.id)">
<input v-if="app.canUnInstall"
class="uninstall"
type="button"
:value="t('settings', 'Remove')"
- :disabled="installing || loading(app.id)"
+ :disabled="installing || isLoading"
@click="remove(app.id)">
<input v-if="app.active"
class="enable"
type="button"
:value="t('settings','Disable')"
- :disabled="installing || loading(app.id)"
+ :disabled="installing || isLoading"
@click="disable(app.id)">
<input v-if="!app.active && (app.canInstall || app.isCompatible)"
v-tooltip.auto="enableButtonTooltip"
class="enable primary"
type="button"
:value="enableButtonText"
- :disabled="!app.canInstall || installing || loading(app.id)"
+ :disabled="!app.canInstall || installing || isLoading"
@click="enable(app.id)">
- <input v-else-if="!app.active"
+ <input v-else-if="!app.active && !app.canInstall"
v-tooltip.auto="forceEnableButtonTooltip"
class="enable force"
type="button"
:value="forceEnableButtonText"
- :disabled="installing || loading(app.id)"
+ :disabled="installing || isLoading"
@click="forceEnable(app.id)">
</div>
- <div class="app-groups">
- <div v-if="app.active && canLimitToGroups(app)" class="groups-enable">
- <input :id="prefix('groups_enable', app.id)"
- v-model="groupCheckedAppsData"
- type="checkbox"
- :value="app.id"
- class="groups-enable__checkbox checkbox"
- @change="setGroupLimit">
- <label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
- <input type="hidden"
- class="group_select"
- :title="t('settings', 'All')"
- value="">
- <Multiselect v-if="isLimitedToGroups(app)"
- :options="groups"
- :value="appGroups"
- :options-limit="5"
- :placeholder="t('settings', 'Limit app usage to groups')"
- label="name"
- track-by="id"
- class="multiselect-vue"
- :multiple="true"
- :close-on-select="false"
- :tag-width="60"
- @select="addGroupLimitation"
- @remove="removeGroupLimitation"
- @search-change="asyncFindGroup">
- <span slot="noResult">{{ t('settings', 'No results') }}</span>
- </Multiselect>
- </div>
- </div>
</div>
- <ul class="app-dependencies">
+ <ul class="app-details__dependencies">
<li v-if="app.missingMinOwnCloudVersion">
{{ t('settings', 'This app has no minimum Nextcloud version assigned. This will be an error in the future.') }}
</li>
@@ -107,7 +105,7 @@
</li>
</ul>
- <p class="documentation">
+ <p class="app-details__documentation">
<a v-if="!app.internal"
class="appslink"
:href="appstoreUrl"
@@ -142,7 +140,7 @@
rel="noreferrer noopener">{{ t('settings', 'Developer documentation') }} ↗</a>
</p>
- <div class="app-description" v-html="renderMarkdown" />
+ <div class="app-details__description" v-html="renderMarkdown" />
</div>
</template>
@@ -151,7 +149,6 @@ import { Multiselect } from '@nextcloud/vue'
import marked from 'marked'
import dompurify from 'dompurify'
-import AppScore from './AppList/AppScore'
import AppManagement from '../mixins/AppManagement'
import PrefixMixin from './PrefixMixin'
@@ -160,11 +157,15 @@ export default {
components: {
Multiselect,
- AppScore,
},
mixins: [AppManagement, PrefixMixin],
- props: ['category', 'app'],
+ props: {
+ app: {
+ type: Object,
+ required: true,
+ },
+ },
data() {
return {
@@ -182,9 +183,6 @@ export default {
}
return null
},
- hasRating() {
- return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
- },
author() {
if (typeof this.app.author === 'string') {
return [
@@ -275,16 +273,45 @@ export default {
}
</script>
-<style scoped>
- .force {
- background: var(--color-main-background);
- border-color: var(--color-error);
- color: var(--color-error);
+<style scoped lang="scss">
+.app-details {
+ padding: 20px;
+
+ &__actions {
+ // app management
+ &-manage {
+ // if too many, shrink them and ellipsis
+ display: flex;
+ input {
+ flex: 0 1 auto;
+ min-width: 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+ }
}
- .force:hover,
- .force:active {
- background: var(--color-error);
- border-color: var(--color-error) !important;
- color: var(--color-main-background);
+ &__dependencies {
+ opacity: .7;
}
+ &__documentation {
+ padding-top: 20px;
+ }
+ &__description {
+ padding-top: 20px;
+ }
+}
+
+.force {
+ color: var(--color-error);
+ border-color: var(--color-error);
+ background: var(--color-main-background);
+}
+.force:hover,
+.force:active {
+ color: var(--color-main-background);
+ border-color: var(--color-error) !important;
+ background: var(--color-error);
+}
+
</style>
diff --git a/apps/settings/src/views/Apps.vue b/apps/settings/src/views/Apps.vue
index 13ddd8bfaf8..313c58afbd9 100644
--- a/apps/settings/src/views/Apps.vue
+++ b/apps/settings/src/views/Apps.vue
@@ -25,6 +25,7 @@
:class="{ 'with-app-sidebar': app}"
:content-class="{ 'icon-loading': loadingList }"
:navigation-class="{ 'icon-loading': loading }">
+ <!-- Categories & filters -->
<AppNavigation>
<template #list>
<AppNavigationItem
@@ -86,9 +87,13 @@
:title="t('settings', 'Developer documentation') + ' ↗'" />
</template>
</AppNavigation>
+
+ <!-- Apps list -->
<AppContent class="app-settings-content" :class="{ 'icon-loading': loadingList }">
<AppList :category="category" :app="app" :search="searchQuery" />
</AppContent>
+
+ <!-- Selected app details -->
<AppSidebar
v-if="id && app"
v-bind="appSidebar"
@@ -97,7 +102,9 @@
<template v-if="!appSidebar.background" #header>
<div class="app-sidebar-header__figure--default-app-icon icon-settings-dark" />
</template>
+
<template #primary-actions>
+ <!-- Featured/Supported badges -->
<div v-if="app.level === 300 || app.level === 200 || hasRating" class="app-level">
<span v-if="app.level === 300"
v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')"
@@ -110,47 +117,14 @@
<AppScore v-if="hasRating" :score="app.appstoreData.ratingOverall" />
</div>
</template>
- <template #secondary-actions>
- <ActionButton v-if="app.update"
- :disabled="installing || isLoading"
- icon="icon-download"
- @click="update(app.id)">
- {{ t('settings', 'Update to {version}', {version: app.update}) }}
- </ActionButton>
- <ActionButton v-if="app.canUnInstall"
- :disabled="installing || isLoading"
- icon="icon-delete"
- @click="remove(app.id)">
- {{ t('settings', 'Remove') }}
- </ActionButton>
- <ActionButton v-if="app.active"
- :disabled="installing || isLoading"
- icon="icon-close"
- @click="disable(app.id)">
- {{ t('settings','Disable') }}
- </ActionButton>
- <ActionButton v-if="!app.active && (app.canInstall || app.isCompatible)"
- v-tooltip.auto="enableButtonTooltip"
- :disabled="!app.canInstall || installing || isLoading"
- icon="icon-checkmark"
- @click="enable(app.id)">
- {{ enableButtonText }}
- </ActionButton>
- <ActionButton v-else-if="!app.active"
- v-tooltip.auto="forceEnableButtonTooltip"
- :disabled="installing || isLoading"
- icon="icon-checkmark"
- @click="forceEnable(app.id)">
- {{ forceEnableButtonText }}
- </ActionButton>
- </template>
- <AppDetails :category="category" :app="app" />
+
+ <!-- Tab content -->
+ <AppDetails :app="app" />
</AppSidebar>
</Content>
</template>
<script>
-import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter'
@@ -164,13 +138,14 @@ import VueLocalStorage from 'vue-localstorage'
import AppList from '../components/AppList'
import AppDetails from '../components/AppDetails'
import AppManagement from '../mixins/AppManagement'
+import AppScore from '../components/AppList/AppScore'
Vue.use(VueLocalStorage)
export default {
name: 'Apps',
+
components: {
- ActionButton,
AppContent,
AppDetails,
AppList,
@@ -178,10 +153,13 @@ export default {
AppNavigationCounter,
AppNavigationItem,
AppNavigationSpacer,
+ AppScore,
AppSidebar,
Content,
},
+
mixins: [AppManagement],
+
props: {
category: {
type: String,
@@ -192,11 +170,14 @@ export default {
default: '',
},
},
+
data() {
return {
searchQuery: '',
+ screenshotLoaded: false,
}
},
+
computed: {
loading() {
return this.$store.getters.loading('categories')
@@ -220,6 +201,10 @@ export default {
return this.$store.getters.getServerData
},
+ hasRating() {
+ return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
+ },
+
// sidebar app binding
appSidebar() {
const author = Array.isArray(this.app.author)
@@ -235,20 +220,33 @@ export default {
return {
subtitle,
- background: this.app.screenshot
+ background: this.app.screenshot && this.screenshotLoaded
? this.app.screenshot
: this.app.preview,
- compact: !this.app.screenshot,
+ compact: !(this.app.screenshot && this.screenshotLoaded),
title: this.app.name,
}
},
},
+
watch: {
category(val, old) {
this.setSearch('')
},
+
+ app() {
+ this.screenshotLoaded = false
+ if (this.app && this.app.screenshot) {
+ const image = new Image()
+ image.onload = (e) => {
+ this.screenshotLoaded = true
+ }
+ image.src = this.app.screenshot
+ }
+ },
},
+
beforeMount() {
this.$store.dispatch('getCategories')
this.$store.dispatch('getAllApps')
@@ -261,6 +259,7 @@ export default {
*/
this.appSearch = new OCA.Search(this.setSearch, this.resetSearch)
},
+
methods: {
setSearch(query) {
this.searchQuery = query
@@ -279,17 +278,17 @@ export default {
</script>
<style lang="scss" scoped>
-
-#app-sidebar::v-deep {
+.app-sidebar::v-deep {
&:not(.app-sidebar--without-background) {
// with full screenshot, let's fill the figure
:not(.app-sidebar-header--compact) .app-sidebar-header__figure {
- background-size: cover
+ background-size: cover;
}
// revert sidebar app icon so it is black
.app-sidebar-header--compact .app-sidebar-header__figure {
- filter: invert(1);
background-size: 32px;
+
+ filter: invert(1);
}
}
@@ -300,19 +299,30 @@ export default {
align-items: center;
justify-content: center;
&--default-app-icon {
- height: 32px;
width: 32px;
+ height: 32px;
background-size: 32px;
}
}
}
- // allow multi line subtitle for the license
- .app-sidebar-header__subtitle {
- white-space: pre-line !important;
- line-height: 16px;
- overflow: visible !important;
- height: 22px;
+ // TODO: migrate to components
+ .app-sidebar-header__desc {
+ // allow multi line subtitle for the license
+ .app-sidebar-header__subtitle {
+ overflow: visible !important;
+ height: auto;
+ white-space: normal !important;
+ line-height: 16px;
+ }
+ }
+
+ .app-sidebar-header__action {
+ // align with tab content
+ margin: 0 20px;
+ input {
+ margin: 3px;
+ }
}
}