Skip to content

Commit

Permalink
Merge pull request #44259 from nextcloud/feat/app-discover-app-links
Browse files Browse the repository at this point in the history
feat(settings): Allow app discover section links to open app routes or the appstore page
  • Loading branch information
susnux authored Mar 18, 2024
2 parents 92df4af + 3717dd8 commit 133a17a
Show file tree
Hide file tree
Showing 19 changed files with 150 additions and 26 deletions.
2 changes: 1 addition & 1 deletion apps/settings/src/app-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ export interface IAppstoreApp {
isCompatible: boolean

appstoreData: Record<string, never>
releases: IAppstoreAppRelease[]
releases?: IAppstoreAppRelease[]
}
117 changes: 117 additions & 0 deletions apps/settings/src/components/AppStoreDiscover/AppLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!--
- @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
-
- @author Ferdinand Thiessen <[email protected]>
-
- @license AGPL-3.0-or-later
-
- 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>
<a v-if="linkProps" v-bind="linkProps">
<slot />
</a>
<RouterLink v-else-if="routerProps" v-bind="routerProps">
<slot />
</RouterLink>
</template>

<script lang="ts">
import type { RouterLinkProps } from 'vue-router/types/router.js'

import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
import { defineComponent } from 'vue'
import { RouterLink } from 'vue-router'

const knownRoutes = Object.fromEntries(
Object.entries(
loadState<Record<string, { app?: string, href: string }>>('core', 'apps'),
).map(([k, v]) => [v.app ?? k, v.href]),
)

/**
* This component either shows a native link to the installed app or external size - or a router link to the appstore page of the app if not installed
*/
export default defineComponent({
name: 'AppLink',

components: { RouterLink },

props: {
href: {
type: String,
required: true,
},
},

data() {
return {
routerProps: undefined as RouterLinkProps|undefined,
linkProps: undefined as Record<string, string>|undefined,
}
},

watch: {
href: {
immediate: true,
handler() {
const match = this.href.match(/^app:\/\/([^/]+)(\/.+)?$/)
this.routerProps = undefined
this.linkProps = undefined

// not an app url
if (match === null) {
this.linkProps = {
href: this.href,
target: '_blank',
rel: 'noreferrer noopener',
}
return
}

const appId = match[1]
// Check if specific route was requested
if (match[2]) {
// we do no know anything about app internal path so we only allow generic app paths
this.linkProps = {
href: generateUrl(`/apps/${appId}${match[2]}`),
}
return
}

// If we know any route for that app we open it
if (appId in knownRoutes) {
this.linkProps = {
href: knownRoutes[appId],
}
return
}

// Fallback to show the app store entry
this.routerProps = {
to: {
name: 'apps-details',
params: {
category: this.$route.params?.category ?? 'discover',
id: appId,
},
},
}
},
},
},
})
</script>
14 changes: 8 additions & 6 deletions apps/settings/src/components/AppStoreDiscover/PostType.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@
<article :id="domId"
class="app-discover-post"
:class="{ 'app-discover-post--reverse': media && media.alignment === 'start' }">
<component :is="link ? 'a' : 'div'"
<component :is="link ? 'AppLink' : 'div'"
v-if="headline || text"
:href="link"
:target="link ? '_blank' : undefined"
class="app-discover-post__text">
<component :is="inline ? 'h4' : 'h3'">{{ translatedHeadline }}</component>
<component :is="inline ? 'h4' : 'h3'">
{{ translatedHeadline }}
</component>
<p>{{ translatedText }}</p>
</component>
<component :is="mediaLink ? 'a' : 'div'"
<component :is="mediaLink ? 'AppLink' : 'div'"
v-if="mediaSources"
:href="mediaLink"
:target="mediaLink ? '_blank' : undefined"
class="app-discover-post__media"
:class="{
'app-discover-post__media--fullwidth': isFullWidth,
Expand Down Expand Up @@ -79,9 +79,11 @@ import { commonAppDiscoverProps } from './common'
import { useLocalizedValue } from '../../composables/useGetLocalizedValue'

import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
import AppLink from './AppLink.vue'

export default defineComponent({
components: {
AppLink,
NcIconSvgWrapper,
},

Expand Down Expand Up @@ -116,8 +118,8 @@ export default defineComponent({
setup(props) {
const translatedHeadline = useLocalizedValue(computed(() => props.headline))
const translatedText = useLocalizedValue(computed(() => props.text))

const localizedMedia = useLocalizedValue(computed(() => props.media?.content))

const mediaSources = computed(() => localizedMedia.value !== null ? [localizedMedia.value.src].flat() : undefined)
const mediaAlt = computed(() => localizedMedia.value?.alt ?? '')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import Markdown from '../Markdown.vue'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const props = defineProps<{ app: IAppstoreApp }>()

const hasChangelog = computed(() => Object.values(props.app.releases[0]?.translations ?? {}).some(({ changelog }) => !!changelog))
const hasChangelog = computed(() => Object.values(props.app.releases?.[0]?.translations ?? {}).some(({ changelog }) => !!changelog))

const createChangelogFromRelease = (release: IAppstoreAppRelease) => release.translations?.[getLanguage()]?.changelog ?? release.translations?.en?.changelog ?? ''
</script>
Expand Down
7 changes: 7 additions & 0 deletions apps/settings/src/router/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ const routes: RouteConfig[] = [
{
path: '/:index(index.php/)?settings/apps',
name: 'apps',
// redirect to our default route - the app discover section
redirect: {
name: 'apps-category',
params: {
category: 'discover',
},
},
components: {
default: AppStore,
navigation: AppStoreNavigation,
Expand Down
2 changes: 1 addition & 1 deletion apps/settings/src/store/apps-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const useAppsStore = defineStore('settings-apps', {
return this.categories.find(({ id }) => id === categoryId) ?? null
},

getAppById(appId: string) {
getAppById(appId: string): IAppstoreApp|null {
return this.apps.find(({ id }) => id === appId) ?? null
},
},
Expand Down
4 changes: 1 addition & 3 deletions apps/settings/src/views/AppStoreNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
<NcAppNavigation :aria-label="t('settings', 'Apps')">
<template #list>
<NcAppNavigationItem id="app-category-discover"
:to="{ name: 'apps' }"
:exact="true"
:to="{ name: 'apps-category', params: { category: 'discover'} }"
:name="APPS_SECTION_ENUM.discover">
<template #icon>
<NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.discover" />
</template>
</NcAppNavigationItem>
<NcAppNavigationItem id="app-category-installed"
:to="{ name: 'apps-category', params: { category: 'installed'} }"
:exact="true"
:name="APPS_SECTION_ENUM.installed">
<template #icon>
<NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.installed" />
Expand Down
2 changes: 1 addition & 1 deletion apps/settings/src/views/AppStoreSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const hasRating = computed(() => app.value.appstoreData?.ratingNumOverall > 5)
const rating = computed(() => app.value.appstoreData?.ratingNumRecent > 5
? app.value.appstoreData.ratingRecent
: (app.value.appstoreData?.ratingOverall ?? 0.5))
const showSidebar = computed(() => app.value)
const showSidebar = computed(() => app.value !== null)

const { appIcon } = useAppIcon(app)

Expand Down
4 changes: 2 additions & 2 deletions dist/8484-8484.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/8484-8484.js.map

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions dist/8753-8753.js

This file was deleted.

1 change: 0 additions & 1 deletion dist/8753-8753.js.map

This file was deleted.

Loading

0 comments on commit 133a17a

Please sign in to comment.