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:
ThaUnknown 2023-08-01 00:22:30 +02:00
parent a3a945bd45
commit e1d661f623
7 changed files with 107 additions and 37 deletions

View file

@ -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",

View file

@ -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')

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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.'

View file

@ -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}