mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-20 08:02:12 +00:00
fix: better userList caching and sorting
This commit is contained in:
parent
2862b0235c
commit
f22b497e03
5 changed files with 115 additions and 89 deletions
2
common/modules/al.d.ts
vendored
2
common/modules/al.d.ts
vendored
|
|
@ -144,7 +144,7 @@ export type MediaListCollection = {
|
|||
lists: {
|
||||
status: string
|
||||
entries: {
|
||||
media: MediaListMedia
|
||||
media: Media
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,9 +203,9 @@ class AnilistClient {
|
|||
})
|
||||
|
||||
if (this.userID?.viewer?.data?.Viewer) {
|
||||
this.userLists.value = this.getUserLists()
|
||||
this.userLists.value = this.getUserLists({ sort: 'UPDATED_TIME_DESC' })
|
||||
// update userLists every 15 mins
|
||||
setInterval(() => { this.userLists.value = this.getUserLists() }, 1000 * 60 * 15)
|
||||
setInterval(() => this.userLists.value = this.getUserLists({ sort: 'UPDATED_TIME_DESC' }), 1000 * 60 * 15)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,8 +257,8 @@ class AnilistClient {
|
|||
variables: {
|
||||
sort: 'TRENDING_DESC',
|
||||
page: 1,
|
||||
perPage: 30,
|
||||
status_in: '[CURRENT,PLANNING]',
|
||||
perPage: 50,
|
||||
status_in: '[CURRENT,PLANNING,COMPLETED,DROPPED,PAUSED,REPEATING]',
|
||||
...variables
|
||||
}
|
||||
})
|
||||
|
|
@ -369,7 +369,7 @@ class AnilistClient {
|
|||
variables.lists.push('Watched using Miru')
|
||||
}
|
||||
await this.entry(variables)
|
||||
this.userLists.value = this.getUserLists()
|
||||
this.userLists.value = this.getUserLists({ sort: 'UPDATED_TIME_DESC' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -458,40 +458,29 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ MediaListCollection: import('./al.d.ts').MediaListCollection }>>} */
|
||||
async getUserLists (variables = {}) {
|
||||
async getUserLists (variables) {
|
||||
const userId = this.userID?.viewer?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
if (variables.sort) { variables.sort = variables.sort.replace('USER_SCORE_DESC', 'SCORE_DESC')}
|
||||
const query = /* js */`
|
||||
query($id: Int){
|
||||
MediaListCollection(userId: $id, type: ANIME, forceSingleCompletedList: true) {
|
||||
query($id: Int $sort: [MediaListSort]){
|
||||
MediaListCollection(userId: $id, type: ANIME, sort: $sort, forceSingleCompletedList: true) {
|
||||
lists {
|
||||
status,
|
||||
entries {
|
||||
media {
|
||||
id,
|
||||
status,
|
||||
mediaListEntry {
|
||||
progress
|
||||
},
|
||||
nextAiringEpisode {
|
||||
episode
|
||||
},
|
||||
relations {
|
||||
edges {
|
||||
relationType(version:2)
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
${queryObjects}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// this doesn't need to be cached, as SearchIDStatus is already cached, which is the only thing that uses this
|
||||
return await this.alRequest(query, variables)
|
||||
const res = await this.alRequest(query, variables)
|
||||
|
||||
this.updateCache(res.data.MediaListCollection.lists.flatMap(list => list.entries.map(entry => entry.media)));
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ MediaList: { status: string, progress: number, repeat: number }}>>} */
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ export default class SectionsManager {
|
|||
|
||||
static createFallbackLoad (variables, type) {
|
||||
return (page = 1, perPage = 50, search = variables) => {
|
||||
const options = { page, perPage, ...SectionsManager.sanitiseObject(search) }
|
||||
const res = anilistClient.search(options)
|
||||
const res = anilistClient.search({ page, perPage, ...SectionsManager.sanitiseObject(search) })
|
||||
return SectionsManager.wrapResponse(res, perPage, type)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +48,44 @@ export default class SectionsManager {
|
|||
const { data } = await arr
|
||||
return data?.Page.media[i]
|
||||
}
|
||||
|
||||
static isUserSort(variables) {
|
||||
return ['UPDATED_TIME_DESC', 'STARTED_ON_DESC', 'FINISHED_ON_DESC', 'PROGRESS_DESC', 'USER_SCORE_DESC'].includes(variables.sort);
|
||||
}
|
||||
|
||||
static async getPaginatedMediaList(_page, perPage, variables, mediaList) {
|
||||
const ids = mediaList.filter(({ media }) => {
|
||||
if ((!variables.search || (media.title.userPreferred && media.title.userPreferred.toLowerCase().includes(variables.search.toLowerCase())) || (media.title.english && media.title.english.toLowerCase().includes(variables.search.toLowerCase())) || (media.title.romaji && media.title.romaji.toLowerCase().includes(variables.search.toLowerCase())) || (media.title.native && media.title.native.toLowerCase().includes(variables.search.toLowerCase()))) &&
|
||||
(!variables.genre || variables.genre.map(genre => genre.trim().toLowerCase()).every(genre => media.genres.map(genre => genre.trim().toLowerCase()).includes(genre))) &&
|
||||
(!variables.tag || variables.tag.map(tag => tag.trim().toLowerCase()).every(tag => media.tags.map(tag => tag.name.trim().toLowerCase()).includes(tag))) &&
|
||||
(!variables.season || variables.season === media.season) &&
|
||||
(!variables.year || variables.year === media.seasonYear) &&
|
||||
(!variables.format || variables.format === media.format) &&
|
||||
(!variables.status || variables.status === media.status) &&
|
||||
(!variables.continueWatching || (media.status === 'FINISHED' || media.mediaListEntry?.progress < media.nextAiringEpisode?.episode - 1))) {
|
||||
return true;
|
||||
}
|
||||
}).map(({ media }) => (this.isUserSort(variables) ? media : media.id));
|
||||
if (!ids.length) return {}
|
||||
if (this.isUserSort(variables)) {
|
||||
const startIndex = (perPage * (_page - 1));
|
||||
const endIndex = startIndex + perPage;
|
||||
const paginatedIds = ids.slice(startIndex, endIndex);
|
||||
const hasNextPage = ids.length > endIndex;
|
||||
return {
|
||||
data: {
|
||||
Page: {
|
||||
pageInfo: {
|
||||
hasNextPage: hasNextPage
|
||||
},
|
||||
media: paginatedIds
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return anilistClient.searchIDS({ _page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// list of all possible home screen sections
|
||||
|
|
@ -80,24 +117,6 @@ function createSections () {
|
|||
return section
|
||||
}),
|
||||
// user specific sections
|
||||
{
|
||||
title: 'Continue Watching',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.reduce((filtered, { status, entries }) => {
|
||||
return (status === 'CURRENT' || status === 'REPEATING') ? filtered.concat(entries) : filtered
|
||||
}, [])
|
||||
const ids = mediaList.filter(({ media }) => {
|
||||
if (media.status === 'FINISHED') return true
|
||||
return media.mediaListEntry?.progress < media.nextAiringEpisode?.episode - 1
|
||||
}).map(({ media }) => media.id)
|
||||
if (!ids.length) return {}
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
hide: !alToken
|
||||
},
|
||||
{
|
||||
title: 'Sequels You Missed',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
|
|
@ -115,73 +134,92 @@ function createSections () {
|
|||
hide: !alToken
|
||||
},
|
||||
{
|
||||
title: 'Your List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'PLANNING')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
title: 'Continue Watching', variables: { sort: 'UPDATED_TIME_DESC', userList: true, continueWatching: true },
|
||||
load: (_page = 1, perPage = 50, variables = {}) => {
|
||||
const userLists = (!SectionsManager.isUserSort(variables) || variables.sort === 'UPDATED_TIME_DESC') ? anilistClient.userLists.value : anilistClient.getUserLists({ sort: variables.sort })
|
||||
const res = userLists.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.reduce((filtered, { status, entries }) => {
|
||||
return (status === 'CURRENT' || status === 'REPEATING') ? filtered.concat(entries) : filtered
|
||||
}, []);
|
||||
if (!mediaList) return {}
|
||||
return SectionsManager.getPaginatedMediaList(_page, perPage, variables, mediaList)
|
||||
});
|
||||
return SectionsManager.wrapResponse(res, perPage);
|
||||
},
|
||||
hide: !alToken
|
||||
},
|
||||
{
|
||||
title: 'Watching List', variables : { sort: 'UPDATED_TIME_DESC', userList: true },
|
||||
load: (_page = 1, perPage = 50, variables = {}) => {
|
||||
const userLists = (!SectionsManager.isUserSort(variables) || variables.sort === 'UPDATED_TIME_DESC') ? anilistClient.userLists.value : anilistClient.getUserLists({ sort: variables.sort })
|
||||
const res = userLists.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.find(({ status }) => status === 'CURRENT')?.entries
|
||||
if (!mediaList) return {}
|
||||
return SectionsManager.getPaginatedMediaList(_page, perPage, variables, mediaList)
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
hide: !alToken
|
||||
},
|
||||
{
|
||||
title: 'Completed List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'COMPLETED')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
title: 'Completed List', variables : { sort: 'UPDATED_TIME_DESC', userList: true, completedList: true },
|
||||
load: (_page = 1, perPage = 50, variables = {}) => {
|
||||
const userLists = (!SectionsManager.isUserSort(variables) || variables.sort === 'UPDATED_TIME_DESC') ? anilistClient.userLists.value : anilistClient.getUserLists({ sort: variables.sort })
|
||||
const res = userLists.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.find(({ status }) => status === 'COMPLETED')?.entries
|
||||
if (!mediaList) return {}
|
||||
return SectionsManager.getPaginatedMediaList(_page, perPage, variables, mediaList)
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
hide: !alToken
|
||||
},
|
||||
{
|
||||
title: 'Paused List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
title: 'Planning List', variables : { sort: 'POPULARITY_DESC', userList: true },
|
||||
load: (_page = 1, perPage = 50, variables = {}) => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'PAUSED')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
const mediaList = res.data.MediaListCollection.lists.find(({ status }) => status === 'PLANNING')?.entries
|
||||
if (!mediaList) return {}
|
||||
return SectionsManager.getPaginatedMediaList(_page, perPage, variables, mediaList)
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
hide: !alToken
|
||||
},
|
||||
{
|
||||
title: 'Dropped List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'DROPPED')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
title: 'Paused List', variables : { sort: 'UPDATED_TIME_DESC', userList: true },
|
||||
load: (_page = 1, perPage = 50, variables = {}) => {
|
||||
const userLists = (!SectionsManager.isUserSort(variables) || variables.sort === 'UPDATED_TIME_DESC') ? anilistClient.userLists.value : anilistClient.getUserLists({ sort: variables.sort })
|
||||
const res = userLists.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.find(({ status }) => status === 'PAUSED')?.entries
|
||||
if (!mediaList) return {}
|
||||
return SectionsManager.getPaginatedMediaList(_page, perPage, variables, mediaList)
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
hide: !alToken
|
||||
},
|
||||
{
|
||||
title: 'Currently Watching List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'CURRENT')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
title: 'Dropped List', variables : { sort: 'UPDATED_TIME_DESC', userList: true},
|
||||
load: (_page = 1, perPage = 50, variables = {}) => {
|
||||
const userLists = (!SectionsManager.isUserSort(variables) || variables.sort === 'UPDATED_TIME_DESC') ? anilistClient.userLists.value : anilistClient.getUserLists({ sort: variables.sort })
|
||||
const res = userLists.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.find(({ status }) => status === 'DROPPED')?.entries
|
||||
if (!mediaList) return {}
|
||||
return SectionsManager.getPaginatedMediaList(_page, perPage, variables, mediaList)
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
hide: !alToken
|
||||
},
|
||||
// common, non-user specific sections
|
||||
{ title: 'Popular This Season', variables: { sort: 'POPULARITY_DESC', season: currentSeason, year: currentYear } },
|
||||
{ title: 'Trending Now', variables: { sort: 'TRENDING_DESC' } },
|
||||
{ title: 'All Time Popular', variables: { sort: 'POPULARITY_DESC' } },
|
||||
{ title: 'Romance', variables: { sort: 'TRENDING_DESC', genre: 'Romance' } },
|
||||
{ title: 'Action', variables: { sort: 'TRENDING_DESC', genre: 'Action' } },
|
||||
{ title: 'Adventure', variables: { sort: 'TRENDING_DESC', genre: 'Adventure' } },
|
||||
{ title: 'Fantasy', variables: { sort: 'TRENDING_DESC', genre: 'Fantasy' } },
|
||||
{ title: 'Comedy', variables: { sort: 'TRENDING_DESC', genre: 'Comedy' } }
|
||||
{ title: 'Popular This Season', variables: { sort: 'POPULARITY_DESC'} },
|
||||
{ title: 'Trending Now', variables: { sort: 'TRENDING_DESC'} },
|
||||
{ title: 'All Time Popular', variables: { sort: 'POPULARITY_DESC'} },
|
||||
{ title: 'Romance', variables: { sort: 'TRENDING_DESC', genre: ['Romance']} },
|
||||
{ title: 'Action', variables: { sort: 'TRENDING_DESC', genre: ['Action']} },
|
||||
{ title: 'Adventure', variables: { sort: 'TRENDING_DESC', genre: ['Adventure'] } },
|
||||
{ title: 'Fantasy', variables: { sort: 'TRENDING_DESC', genre: ['Fantasy']} },
|
||||
{ title: 'Comedy', variables: { sort: 'TRENDING_DESC', genre: ['Comedy'] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ try {
|
|||
} catch (e) {}
|
||||
try {
|
||||
scopedDefaults = {
|
||||
homeSections: [...(storedSettings.rssFeedsNew || defaults.rssFeedsNew).map(([title]) => title), 'Continue Watching', 'Sequels You Missed', 'Your List', 'Popular This Season', 'Trending Now', 'All Time Popular', 'Romance', 'Action', 'Adventure', 'Fantasy', 'Comedy']
|
||||
homeSections: [...(storedSettings.rssFeedsNew || defaults.rssFeedsNew).map(([title]) => title), 'Continue Watching', 'Sequels You Missed', 'Planning List', 'Popular This Season', 'Trending Now', 'All Time Popular', 'Romance', 'Action', 'Adventure', 'Fantasy', 'Comedy']
|
||||
}
|
||||
} catch (e) {
|
||||
resetSettings()
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@
|
|||
for (const sectionTitle of settings.value.homeSections) manager.add(mappedSections[sectionTitle])
|
||||
|
||||
if (anilistClient.userID?.viewer?.data?.Viewer) {
|
||||
const userSections = ['Continue Watching', 'Sequels You Missed', 'Your List', 'Completed List', 'Paused List', 'Dropped List', 'Currently Watching List']
|
||||
|
||||
const userSections = ['Continue Watching', 'Sequels You Missed', 'Planning List', 'Completed List', 'Paused List', 'Dropped List', 'Watching List']
|
||||
anilistClient.userLists.subscribe(value => {
|
||||
if (!value) return
|
||||
for (const section of manager.sections) {
|
||||
// remove preview value, to force UI to re-request data, which updates it once in viewport
|
||||
if (userSections.includes(section.title)) section.preview.value = section.load(1, 15)
|
||||
if (userSections.includes(section.title)) section.preview.value = section.load(1, 15, section.variables)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue