Use appsidebar for apps
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
228a96508a
commit
8e7c95effb
|
@ -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
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue