mirror of
https://github.com/NoCrypt/migu.git
synced 2026-03-11 17:45:32 +00:00
fix: loadTillFull errors
fix: scroll errors feat: better search by image fix: search by image toast in search fix: search by image refreshing search
This commit is contained in:
parent
a3a945bd45
commit
e1d661f623
7 changed files with 107 additions and 37 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Miru",
|
||||
"version": "4.2.12",
|
||||
"version": "4.3.0",
|
||||
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
|
||||
"description": "Stream anime torrents, real-time with no waiting for downloads.",
|
||||
"main": "build/main.js",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script context='module'>
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
|
||||
export const page = writable('home')
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
import { set } from '../views/Settings.svelte'
|
||||
import { click } from '@/modules/click.js'
|
||||
import { page } from '@/App.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
|
||||
export let search
|
||||
let searchTextInput
|
||||
|
|
@ -38,7 +39,12 @@
|
|||
function handleFile ({ target }) {
|
||||
const { files } = target
|
||||
if (files?.[0]) {
|
||||
traceAnime(files[0], 'file')
|
||||
toast.promise(traceAnime(files[0]), {
|
||||
description: 'You can also paste an URL to an image.',
|
||||
loading: 'Looking up anime for image...',
|
||||
success: 'Found anime for image!',
|
||||
error: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.'
|
||||
})
|
||||
target.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +176,7 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input type='file' class='d-none' id='search-image' accept='image/*' on:input={handleFile} />
|
||||
<input type='file' class='d-none' id='search-image' accept='image/*' on:input|preventDefault|stopPropagation={handleFile} />
|
||||
<div class='col-auto p-10 d-flex'>
|
||||
<div class='align-self-end'>
|
||||
<button class='btn btn-square bg-dark-light material-symbols-outlined font-size-18 px-5 align-self-end border-0' type='button'>
|
||||
|
|
@ -182,7 +188,7 @@
|
|||
</div>
|
||||
<div class='col-auto p-10 d-flex'>
|
||||
<div class='align-self-end'>
|
||||
<button class='btn btn-square bg-dark-light material-symbols-outlined font-size-18 px-5 align-self-end border-0' type='button' use:click={searchClear} class:text-primary={!!sanitisedSearch?.length}>
|
||||
<button class='btn btn-square bg-dark-light material-symbols-outlined font-size-18 px-5 align-self-end border-0' type='button' use:click={searchClear} class:text-primary={!!sanitisedSearch?.length || search.disableSearch || search.clearNext}>
|
||||
delete
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,20 @@
|
|||
import EpisodePreviewCard from './EpisodePreviewCard.svelte'
|
||||
import { hoverClick } from '@/modules/click.js'
|
||||
import { since } from '@/modules/util'
|
||||
import { getContext } from 'svelte'
|
||||
export let data
|
||||
|
||||
let preview = false
|
||||
|
||||
const episodeThumbnail = data.episodeData?.image || data.media?.bannerImage || data.media?.coverImage?.extraLarge || ' '
|
||||
|
||||
const view = getContext('view')
|
||||
function viewMedia () {
|
||||
$view = data.media
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='d-flex p-20 pb-10 position-relative' on:pointerenter={() => { preview = true }} on:custom-pointerleave={() => { preview = false }} use:hoverClick={data.onclick}>
|
||||
<div class='d-flex p-20 pb-10 position-relative' on:pointerenter={() => { preview = true }} on:custom-pointerleave={() => { preview = false }} use:hoverClick={data.onclick || viewMedia}>
|
||||
{#if preview}
|
||||
<EpisodePreviewCard {data} />
|
||||
{/if}
|
||||
|
|
@ -33,7 +39,7 @@
|
|||
{data.media?.title.userPreferred || data.parseObject.anime_title}
|
||||
</div>
|
||||
<div class='text-muted font-size-12 title overflow-hidden'>
|
||||
{data.episodeData?.title.en || ''}
|
||||
{data.episodeData?.title?.en || ''}
|
||||
</div>
|
||||
</div>
|
||||
{#if data.episode}
|
||||
|
|
@ -41,9 +47,15 @@
|
|||
<div class='text-white font-weight-bold'>
|
||||
Episode {data.episode}
|
||||
</div>
|
||||
<div class='text-muted font-size-12 title overflow-hidden'>
|
||||
{since(data.date)}
|
||||
</div>
|
||||
{#if data.date}
|
||||
<div class='text-muted font-size-12 title overflow-hidden'>
|
||||
{since(data.date)}
|
||||
</div>
|
||||
{:else if data.similarity}
|
||||
<div class='text-muted font-size-12 title overflow-hidden'>
|
||||
{parseInt(data.similarity * 100)}%
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,23 @@
|
|||
const media = data.media
|
||||
|
||||
const episodeThumbnail = data.episodeData?.image || media?.bannerImage || media?.coverImage.extraLarge || ' '
|
||||
let hide = true
|
||||
</script>
|
||||
|
||||
<div class='position-absolute w-400 mh-400 absolute-container top-0 m-auto bg-dark-light z-30 rounded overflow-hidden pointer d-flex flex-column'>
|
||||
<div class='image h-200 w-full position-relative d-flex justify-content-between align-items-end text-white' class:bg-black={episodeThumbnail === ' '}>
|
||||
<img loading='lazy' src={episodeThumbnail} alt='cover' class='img-cover w-full h-full position-absolute' style:--color={media?.coverImage.color || '#1890ff'} />
|
||||
{#if data.episodeData?.video}
|
||||
<video src={data.episodeData.video}
|
||||
class='w-full position-absolute left-0'
|
||||
class:d-none={hide}
|
||||
playsinline
|
||||
preload='none'
|
||||
loop
|
||||
muted
|
||||
on:loadeddata={() => { hide = false }}
|
||||
autoplay />
|
||||
{/if}
|
||||
<div class='pl-15 pb-10 material-symbols-outlined filled z-10'>play_arrow</div>
|
||||
<div class='pr-20 pb-10 font-size-16 font-weight-medium z-10'>
|
||||
{#if media?.duration}
|
||||
|
|
@ -27,8 +39,8 @@
|
|||
{/if}
|
||||
{data.media?.title.userPreferred || data.parseObject.anime_title}
|
||||
</div>
|
||||
<div class='text-muted font-size-12 title overflow-hidden' title={data.episodeData?.title.en}>
|
||||
{data.episodeData?.title.en || ''}
|
||||
<div class='text-muted font-size-12 title overflow-hidden' title={data.episodeData?.title?.en}>
|
||||
{data.episodeData?.title?.en || ''}
|
||||
</div>
|
||||
</div>
|
||||
{#if data.episode}
|
||||
|
|
@ -36,9 +48,15 @@
|
|||
<div class='text-white font-weight-bold'>
|
||||
Episode {data.episode}
|
||||
</div>
|
||||
<div class='text-muted font-size-12 title overflow-hidden'>
|
||||
{since(data.date)}
|
||||
</div>
|
||||
{#if data.date}
|
||||
<div class='text-muted font-size-12 title overflow-hidden'>
|
||||
{since(data.date)}
|
||||
</div>
|
||||
{:else if data.similarity}
|
||||
<div class='text-muted font-size-12 title overflow-hidden'>
|
||||
{parseInt(data.similarity * 100)}%
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import { alRequest, alSearch } from './anilist.js'
|
|||
import anitomyscript from 'anitomyscript'
|
||||
import { media } from '../views/Player/MediaHandler.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import { view } from '@/App.svelte'
|
||||
import Sections from './sections.js'
|
||||
import { page } from '@/App.svelte'
|
||||
|
||||
import { search, key } from '@/views/Search.svelte'
|
||||
|
||||
import { playAnime } from '../views/RSSView.svelte'
|
||||
|
||||
|
|
@ -16,13 +19,11 @@ window.addEventListener('paste', ({ clipboardData }) => { // WAIT image lookup o
|
|||
if (!item) return
|
||||
const { type } = item
|
||||
if (type.startsWith('image')) {
|
||||
const promise = traceAnime(item.getAsFile())
|
||||
toast.promise(promise, {
|
||||
toast.promise(traceAnime(item.getAsFile()), {
|
||||
description: 'You can also paste an URL to an image.',
|
||||
loading: 'Looking up anime for image...',
|
||||
success: 'Found anime for image!',
|
||||
error: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.'
|
||||
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -39,8 +40,7 @@ window.addEventListener('paste', ({ clipboardData }) => { // WAIT image lookup o
|
|||
src = text
|
||||
}
|
||||
if (src) {
|
||||
const promise = traceAnime(src)
|
||||
toast.promise(promise, {
|
||||
toast.promise(traceAnime(src), {
|
||||
description: 'You can also paste an URL to an image.',
|
||||
loading: 'Looking up anime for image...',
|
||||
success: 'Found anime for image!',
|
||||
|
|
@ -63,9 +63,34 @@ export async function traceAnime (image) { // WAIT lookup logic
|
|||
}
|
||||
const res = await fetch(url, options)
|
||||
const { result } = await res.json()
|
||||
if (result?.[0].similarity >= 0.85) {
|
||||
const res = await alRequest({ method: 'SearchIDSingle', id: result[0].anilist })
|
||||
view.set(res.data.Media)
|
||||
|
||||
if (result?.length) {
|
||||
const ids = result.map(({ anilist }) => anilist)
|
||||
search.value = {
|
||||
clearNext: true,
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...Sections.sanitiseObject(variables) }).then(res => {
|
||||
for (const index in res.data?.Page?.media) {
|
||||
const media = res.data.Page.media[index]
|
||||
const counterpart = result.find(({ anilist }) => anilist === media.id)
|
||||
res.data.Page.media[index] = {
|
||||
media,
|
||||
episode: counterpart.episode,
|
||||
similarity: counterpart.similarity,
|
||||
episodeData: {
|
||||
image: counterpart.image,
|
||||
video: counterpart.video
|
||||
}
|
||||
}
|
||||
}
|
||||
res.data?.Page?.media.sort((a, b) => b.similarity - a.similarity)
|
||||
return res
|
||||
})
|
||||
return Sections.wrapResponse(res, result.length, 'episode')
|
||||
}
|
||||
}
|
||||
key.value = {}
|
||||
page.value = 'search'
|
||||
} else {
|
||||
throw new Error('Search Failed', {
|
||||
message: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
export const search = writable({})
|
||||
|
||||
const items = writable([])
|
||||
export const key = writable({})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
|
@ -13,12 +14,11 @@
|
|||
import { hasNextPage } from '@/modules/sections.js'
|
||||
import smoothScroll from '@/modules/scroll.js'
|
||||
import { debounce } from '@/modules/util.js'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
let page = 0
|
||||
items.value = []
|
||||
hasNextPage.value = true
|
||||
|
||||
let key = {}
|
||||
let container = null
|
||||
|
||||
function loadSearchData () {
|
||||
const load = $search.load || Sections.createFallbackLoad()
|
||||
|
|
@ -27,17 +27,24 @@
|
|||
return nextData[nextData.length - 1].data
|
||||
}
|
||||
const update = debounce(() => {
|
||||
page = 0
|
||||
items.value = []
|
||||
key = {}
|
||||
loadSearchData()
|
||||
$key = {}
|
||||
}, 300)
|
||||
|
||||
$: loadTillFull($key)
|
||||
|
||||
let canScroll = true
|
||||
|
||||
async function loadTillFull (element) {
|
||||
async function loadTillFull () {
|
||||
if (!container) return
|
||||
const cachedKey = $key
|
||||
canScroll = false
|
||||
while (hasNextPage.value && element.scrollHeight <= element.clientHeight) {
|
||||
page = 0
|
||||
items.value = []
|
||||
hasNextPage.value = true
|
||||
await loadSearchData()
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while (hasNextPage.value && container && cachedKey === $key && container.scrollHeight <= container.clientHeight) {
|
||||
canScroll = false
|
||||
await loadSearchData()
|
||||
}
|
||||
canScroll = true
|
||||
|
|
@ -50,13 +57,15 @@
|
|||
canScroll = true
|
||||
}
|
||||
}
|
||||
if ($search.clearNext) $search = {}
|
||||
if ($search.disableSearch) $search.clearNext = true
|
||||
onDestroy(() => {
|
||||
if ($search.clearNext || $search.disableSearch) $search = {}
|
||||
})
|
||||
onMount(loadTillFull)
|
||||
</script>
|
||||
|
||||
<div class='h-full w-full overflow-y-scroll d-flex flex-wrap flex-row root overflow-x-hidden px-50 justify-content-center align-content-start' use:smoothScroll use:loadTillFull on:scroll={infiniteScroll}>
|
||||
<div class='h-full w-full overflow-y-scroll d-flex flex-wrap flex-row root overflow-x-hidden px-50 justify-content-center align-content-start' use:smoothScroll bind:this={container} on:scroll={infiniteScroll}>
|
||||
<Search bind:search={$search} on:input={update} />
|
||||
{#key key}
|
||||
{#key $key}
|
||||
{#each $items as card}
|
||||
<Card {card} />
|
||||
{/each}
|
||||
|
|
|
|||
Loading…
Reference in a new issue