refine the subscription loading time (fix #178) #181
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace OCA\RePod\Controller;
|
||||
|
||||
use OCA\GPodderSync\Core\PodcastData\PodcastDataReader;
|
||||
use OCA\GPodderSync\Core\PodcastData\PodcastMetrics;
|
||||
use OCA\GPodderSync\Core\PodcastData\PodcastMetricsReader;
|
||||
use OCA\RePod\AppInfo\Application;
|
||||
use OCA\RePod\Service\UserService;
|
||||
@ -30,14 +31,19 @@ class MetricsController extends Controller
|
||||
#[NoCSRFRequired]
|
||||
#[FrontpageRoute(verb: 'GET', url: '/metrics')]
|
||||
public function index(): JSONResponse {
|
||||
$subscriptions = $this->podcastMetricsReader->metrics($this->userService->getUserUID());
|
||||
$metrics = $this->podcastMetricsReader->metrics($this->userService->getUserUID());
|
||||
usort($metrics, fn (PodcastMetrics $a, PodcastMetrics $b) => $b->getListenedSeconds() <=> $a->getListenedSeconds());
|
||||
$subscriptions = [];
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$subscription = $metric->toArray();
|
||||
|
||||
foreach ($subscriptions as $id => $subscription) {
|
||||
try {
|
||||
$subscriptions[$id]['data'] = $this->podcastDataReader->getCachedOrFetchPodcastData($subscription->getUrl(), $this->userService->getUserUID());
|
||||
$subscription['data'] = $this->podcastDataReader->getCachedOrFetchPodcastData($metric->getUrl(), $this->userService->getUserUID());
|
||||
} catch (\Exception $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subscriptions[] = $subscription;
|
||||
}
|
||||
|
||||
return new JSONResponse($subscriptions);
|
||||
|
@ -7,8 +7,8 @@
|
||||
"dev": "vite --mode development build",
|
||||
"dev:watch": "vite --mode development build --watch",
|
||||
"watch": "npm run dev:watch",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"lint": "vue-tsc && eslint src",
|
||||
"lint:fix": "vue-tsc && eslint src --fix",
|
||||
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css",
|
||||
"stylelint:fix": "stylelint src/**/*.vue src/**/*.scss src/**/*.css --fix"
|
||||
},
|
||||
|
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<NcGuestContent class="guest">
|
||||
<Loading v-if="!feed.data" />
|
||||
<Loading v-if="!metric.data" />
|
||||
<NcAvatar
|
||||
v-if="feed.data"
|
||||
v-if="metric.data"
|
||||
class="avatar"
|
||||
:display-name="feed.data.author || feed.data.title"
|
||||
:display-name="metric.data.author || metric.data.title"
|
||||
:is-no-user="true"
|
||||
:size="222"
|
||||
:url="feed.data.imageUrl" />
|
||||
<div v-if="feed.data" class="list">
|
||||
<h2 class="title">{{ feed.data.title }}</h2>
|
||||
:url="metric.data.imageUrl" />
|
||||
<div v-if="metric.data" class="list">
|
||||
<h2 class="title">{{ metric.data.title }}</h2>
|
||||
<Loading v-if="loading" />
|
||||
<ul v-if="!loading">
|
||||
<Episode
|
||||
@ -17,14 +17,14 @@
|
||||
:key="episode.guid"
|
||||
:episode="episode"
|
||||
:one-line="true"
|
||||
:url="feed.metrics.url" />
|
||||
:url="metric.url" />
|
||||
</ul>
|
||||
</div>
|
||||
</NcGuestContent>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { EpisodeInterface, SubscriptionInterface } from '../../utils/types.ts'
|
||||
import type { EpisodeInterface, PodcastMetricsInterface } from '../../utils/types.ts'
|
||||
import { NcAvatar, NcGuestContent } from '@nextcloud/vue'
|
||||
import Episode from './Episode.vue'
|
||||
import Loading from '../Atoms/Loading.vue'
|
||||
@ -43,8 +43,8 @@ export default {
|
||||
NcGuestContent,
|
||||
},
|
||||
props: {
|
||||
feed: {
|
||||
type: Object as () => SubscriptionInterface,
|
||||
metric: {
|
||||
type: Object as () => PodcastMetricsInterface,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
@ -57,7 +57,7 @@ export default {
|
||||
this.loading = true
|
||||
const episodes = await axios.get<EpisodeInterface[]>(
|
||||
generateUrl('/apps/repod/episodes/list?url={url}', {
|
||||
url: this.feed.metrics.url,
|
||||
url: this.metric.url,
|
||||
}),
|
||||
)
|
||||
this.episodes = [...episodes.data]
|
||||
|
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<NcAppNavigationItem
|
||||
:loading="loading"
|
||||
:name="feed?.data?.title || url"
|
||||
:name="metric.data?.title || url"
|
||||
:to="toFeedUrl(url)">
|
||||
<template #actions>
|
||||
<NcActionButton
|
||||
:aria-label="t('repod', 'Favorite')"
|
||||
:model-value="feed?.isFavorite"
|
||||
:model-value="metric.isFavorite"
|
||||
:name="t('repod', 'Favorite')"
|
||||
:title="t('repod', 'Favorite')"
|
||||
@update:modelValue="switchFavorite($event)">
|
||||
<template #icon>
|
||||
<StarPlusIcon v-if="!feed?.isFavorite" :size="20" />
|
||||
<StarRemoveIcon v-if="feed?.isFavorite" :size="20" />
|
||||
<StarPlusIcon v-if="!metric.isFavorite" :size="20" />
|
||||
<StarRemoveIcon v-if="metric.isFavorite" :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
@ -27,10 +27,10 @@
|
||||
</template>
|
||||
<template #icon>
|
||||
<NcAvatar
|
||||
:display-name="feed?.data?.author || feed?.data?.title"
|
||||
:display-name="metric.data?.author || metric.data?.title"
|
||||
:is-no-user="true"
|
||||
:url="feed?.data?.imageUrl" />
|
||||
<StarIcon v-if="feed?.isFavorite" class="star" :size="20" />
|
||||
:url="metric.data?.imageUrl" />
|
||||
<StarIcon v-if="metric.isFavorite" class="star" :size="20" />
|
||||
<AlertIcon v-if="failed" />
|
||||
</template>
|
||||
</NcAppNavigationItem>
|
||||
@ -41,7 +41,7 @@ import { NcActionButton, NcAppNavigationItem, NcAvatar } from '@nextcloud/vue'
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import AlertIcon from 'vue-material-design-icons/Alert.vue'
|
||||
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
|
||||
import type { PersonalSettingsPodcastDataInterface } from '../../utils/types.ts'
|
||||
import type { PodcastMetricsInterface } from '../../utils/types.ts'
|
||||
import StarIcon from 'vue-material-design-icons/Star.vue'
|
||||
import StarPlusIcon from 'vue-material-design-icons/StarPlus.vue'
|
||||
import StarRemoveIcon from 'vue-material-design-icons/StarRemove.vue'
|
||||
@ -65,42 +65,21 @@ export default {
|
||||
StarRemoveIcon,
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
metric: {
|
||||
type: Object as () => PodcastMetricsInterface,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
failed: false,
|
||||
loading: true,
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useSubscriptions, ['subs']),
|
||||
feed() {
|
||||
return this.subs.find((sub) => sub.metrics.url === this.url)
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
const podcastData =
|
||||
await axios.get<PersonalSettingsPodcastDataInterface>(
|
||||
generateUrl(
|
||||
'/apps/gpoddersync/personal_settings/podcast_data?url={url}',
|
||||
{
|
||||
url: this.url,
|
||||
},
|
||||
),
|
||||
)
|
||||
this.addMetadatas(this.url, podcastData.data.data)
|
||||
} catch (e) {
|
||||
this.failed = true
|
||||
console.error(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useSubscriptions, ['getSubByUrl', 'subs']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useSubscriptions, ['fetch', 'addMetadatas', 'setFavorite']),
|
||||
...mapActions(useSubscriptions, ['fetch', 'setFavorite']),
|
||||
t,
|
||||
toFeedUrl,
|
||||
async deleteSubscription() {
|
||||
@ -113,14 +92,14 @@ export default {
|
||||
this.loading = true
|
||||
await axios.post(
|
||||
generateUrl('/apps/gpoddersync/subscription_change/create'),
|
||||
{ add: [], remove: [this.url] },
|
||||
{ add: [], remove: [this.metric.url] },
|
||||
)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
showError(t('repod', 'Error while removing the feed'))
|
||||
} finally {
|
||||
this.setFavorite(this.url, false)
|
||||
this.loading = false
|
||||
this.setFavorite(this.metric.url, false)
|
||||
this.loading = true
|
||||
this.fetch()
|
||||
}
|
||||
}
|
||||
@ -132,7 +111,7 @@ export default {
|
||||
return
|
||||
}
|
||||
}
|
||||
this.setFavorite(this.url, value)
|
||||
this.setFavorite(this.metric.url, value)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -13,12 +13,12 @@
|
||||
<NcAppNavigationList v-if="!loading">
|
||||
<Subscription
|
||||
v-for="sub of subs.filter((sub) => sub.isFavorite)"
|
||||
:key="sub.metrics.url"
|
||||
:url="sub.metrics.url" />
|
||||
:key="sub.url"
|
||||
:metric="sub" />
|
||||
<Subscription
|
||||
v-for="sub of subs.filter((sub) => !sub.isFavorite)"
|
||||
:key="sub.metrics.url"
|
||||
:url="sub.metrics.url" />
|
||||
:key="sub.url"
|
||||
:metric="sub" />
|
||||
</NcAppNavigationList>
|
||||
</NcAppContentList>
|
||||
</template>
|
||||
|
@ -1,20 +1,16 @@
|
||||
import type {
|
||||
PersonalSettingsMetricsInterface,
|
||||
PodcastDataInterface,
|
||||
SubscriptionInterface,
|
||||
} from '../utils/types.ts'
|
||||
import { getCookie, setCookie } from '../utils/cookies.ts'
|
||||
import type { PodcastMetricsInterface } from '../utils/types.ts'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { defineStore } from 'pinia'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export const useSubscriptions = defineStore('subscriptions', {
|
||||
state: () => ({
|
||||
subs: [] as SubscriptionInterface[],
|
||||
subs: [] as PodcastMetricsInterface[],
|
||||
}),
|
||||
getters: {
|
||||
getSubByUrl: (state) => (url: string) =>
|
||||
state.subs.find((sub) => sub.metrics.url === url),
|
||||
state.subs.find((sub) => sub.url === url),
|
||||
},
|
||||
actions: {
|
||||
async fetch() {
|
||||
@ -23,34 +19,24 @@ export const useSubscriptions = defineStore('subscriptions', {
|
||||
favorites = JSON.parse(getCookie('repod.favorites') || '[]') || []
|
||||
} catch {}
|
||||
|
||||
const metrics = await axios.get<PersonalSettingsMetricsInterface>(
|
||||
const metrics = await axios.get<PodcastMetricsInterface[]>(
|
||||
generateUrl('/apps/repod/metrics'),
|
||||
)
|
||||
|
||||
this.subs = [...metrics.data.subscriptions]
|
||||
.sort((a, b) => b.listenedSeconds - a.listenedSeconds)
|
||||
.map((sub) => ({
|
||||
metrics: sub,
|
||||
isFavorite: favorites.includes(sub.url),
|
||||
data: this.subs.find((s) => s.metrics.url === sub.url)?.data,
|
||||
}))
|
||||
},
|
||||
addMetadatas(link: string, data: PodcastDataInterface) {
|
||||
this.subs = this.subs.map((sub) =>
|
||||
sub.metrics.url === link ? { ...sub, data } : sub,
|
||||
)
|
||||
this.subs = [...metrics.data].map((sub) => ({
|
||||
...sub,
|
||||
isFavorite: favorites.includes(sub.url),
|
||||
}))
|
||||
},
|
||||
setFavorite(link: string, isFavorite: boolean) {
|
||||
this.subs = this.subs.map((sub) =>
|
||||
sub.metrics.url === link ? { ...sub, isFavorite } : sub,
|
||||
sub.url === link ? { ...sub, isFavorite } : sub,
|
||||
)
|
||||
|
||||
setCookie(
|
||||
'repod.favorites',
|
||||
JSON.stringify(
|
||||
this.subs
|
||||
.filter((sub) => sub.isFavorite)
|
||||
.map((sub) => sub.metrics.url),
|
||||
this.subs.filter((sub) => sub.isFavorite).map((sub) => sub.url),
|
||||
),
|
||||
365,
|
||||
)
|
||||
|
@ -56,16 +56,8 @@ export interface PodcastMetricsInterface {
|
||||
new: number
|
||||
play: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SubscriptionInterface {
|
||||
data?: PodcastDataInterface
|
||||
isFavorite: boolean
|
||||
metrics: PodcastMetricsInterface
|
||||
}
|
||||
|
||||
export interface PersonalSettingsMetricsInterface {
|
||||
subscriptions: PodcastMetricsInterface[]
|
||||
data: PodcastDataInterface
|
||||
isFavorite?: boolean
|
||||
}
|
||||
|
||||
export interface PersonalSettingsPodcastDataInterface {
|
||||
|
@ -11,8 +11,8 @@
|
||||
</template>
|
||||
</EmptyContent>
|
||||
<ul v-if="favorites.length">
|
||||
<li v-for="favorite in favorites" :key="favorite.metrics.url">
|
||||
<Favorite :feed="favorite" />
|
||||
<li v-for="favorite in favorites" :key="favorite.url">
|
||||
<Favorite :metric="favorite" />
|
||||
</li>
|
||||
</ul>
|
||||
</AppContent>
|
||||
|
Loading…
Reference in New Issue
Block a user