Use appsidebar for apps

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2020-04-09 12:58:05 +02:00
parent 228a96508a
commit 8e7c95effb
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
15 changed files with 165 additions and 130 deletions

View File

@ -663,8 +663,6 @@ span.version {
}
.app-level {
margin-top: 8px;
span {
color: var(--color-text-maxcontrast);
background-color: transparent;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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>

View File

@ -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;
}
}
}