mirror of
https://github.com/NoCrypt/migu.git
synced 2026-03-22 02:27:18 +00:00
feat: list editor
This commit is contained in:
parent
4087a24126
commit
47c8eb0064
5 changed files with 207 additions and 44 deletions
|
|
@ -1,23 +1,13 @@
|
|||
<script>
|
||||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||
import { formatMap, playMedia } from '@/modules/anime.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import Scoring from '@/views/ViewAnime/Scoring.svelte'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
export let mediaList
|
||||
|
||||
let current = mediaList[0]
|
||||
|
||||
async function toggleStatus () {
|
||||
if (!current.mediaListEntry) {
|
||||
// add
|
||||
const res = await setStatus('PLANNING', {}, current)
|
||||
current.mediaListEntry = res.data.SaveMediaListEntry
|
||||
} else {
|
||||
// delete
|
||||
anilistClient.delete({ id: current.mediaListEntry.id })
|
||||
current.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
function toggleFavourite () {
|
||||
anilistClient.favourite({ id: current.id })
|
||||
current.isFavourite = !current.isFavourite
|
||||
|
|
@ -97,9 +87,7 @@
|
|||
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={current.isFavourite} use:click={toggleFavourite}>
|
||||
favorite
|
||||
</button>
|
||||
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={current.mediaListEntry} use:click={toggleStatus}>
|
||||
bookmark
|
||||
</button>
|
||||
<Scoring media={current} />
|
||||
</div>
|
||||
<div class='d-flex'>
|
||||
{#each mediaList as media}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||
import { formatMap, playMedia } from '@/modules/anime.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import Scoring from '@/views/ViewAnime/Scoring.svelte'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
|
|
@ -25,17 +26,6 @@
|
|||
return 'Watch Now'
|
||||
}
|
||||
const playButtonText = getPlayButtonText(media)
|
||||
async function toggleStatus () {
|
||||
if (!media.mediaListEntry) {
|
||||
// add
|
||||
const res = await setStatus('PLANNING', {}, media)
|
||||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
} else {
|
||||
// delete
|
||||
anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
media.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
function toggleFavourite () {
|
||||
anilistClient.favourite({ id: media.id })
|
||||
media.isFavourite = !media.isFavourite
|
||||
|
|
@ -97,9 +87,7 @@
|
|||
<button class='btn btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.isFavourite} use:click={toggleFavourite}>
|
||||
favorite
|
||||
</button>
|
||||
<button class='btn btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.mediaListEntry} use:click={toggleStatus}>
|
||||
bookmark
|
||||
</button>
|
||||
<Scoring {media} previewAnime={true}/>
|
||||
</div>
|
||||
<div class='details text-white text-capitalize pt-15 pb-10 d-flex'>
|
||||
<span class='text-nowrap d-flex align-items-center'>
|
||||
|
|
|
|||
|
|
@ -608,6 +608,7 @@ class AnilistClient {
|
|||
id,
|
||||
status,
|
||||
progress,
|
||||
score,
|
||||
repeat
|
||||
}
|
||||
}`
|
||||
|
|
|
|||
197
common/views/ViewAnime/Scoring.svelte
Normal file
197
common/views/ViewAnime/Scoring.svelte
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<script>
|
||||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import Scoring from '@/views/ViewAnime/Scoring.svelte'
|
||||
import AudioLabel from '@/views/ViewAnime/AudioLabel.svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
export let viewAnime = false
|
||||
export let previewAnime = false
|
||||
|
||||
const showModal = writable(false)
|
||||
|
||||
let score = 0
|
||||
let status = 'NOT IN LIST'
|
||||
let episode = 0
|
||||
let totalEpisodes = (media.episodes ? media.episodes : '?')
|
||||
|
||||
function toggleModal (state) {
|
||||
showModal.set(!$showModal)
|
||||
if (state.save) {
|
||||
saveChanges()
|
||||
} else if (state.delete) {
|
||||
deleteEntry()
|
||||
} else {
|
||||
score = (media.mediaListEntry?.score ? media.mediaListEntry?.score : 0)
|
||||
status = (media.mediaListEntry?.status ? media.mediaListEntry?.status : 'NOT IN LIST')
|
||||
episode = (media.mediaListEntry?.progress ? media.mediaListEntry?.progress : 0)
|
||||
}
|
||||
}
|
||||
|
||||
function deleteEntry() {
|
||||
score = 0
|
||||
episode = 0
|
||||
status = 'NOT IN LIST'
|
||||
if (media.mediaListEntry) {
|
||||
anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
media.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function saveChanges() {
|
||||
if (!status.includes('NOT IN LIST')) {
|
||||
const variables = {
|
||||
repeat: media.mediaListEntry?.repeat || 0,
|
||||
id: media.id,
|
||||
status,
|
||||
episode,
|
||||
score: score * 10,
|
||||
lists: media.mediaListEntry?.customLists?.filter(list => list.enabled).map(list => list.name) || []
|
||||
}
|
||||
const res = await anilistClient.entry(variables)
|
||||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
} else {
|
||||
deleteEntry()
|
||||
}
|
||||
}
|
||||
|
||||
function handleEpisodes(event) {
|
||||
const enteredValue = event.target.value
|
||||
if (/^\d+$/.test(enteredValue)) {
|
||||
if (enteredValue > (media.episodes || media.nextAiringEpisode?.episode - 1)) {
|
||||
episode = media.episodes || media.nextAiringEpisode?.episode - 1
|
||||
} else {
|
||||
episode = parseInt(enteredValue)
|
||||
}
|
||||
} else {
|
||||
episode = 0
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<button class="btn { viewAnime ? 'bg-dark btn-lg font-size-20' : (previewAnime ? 'btn-square' : 'bg-dark-light') + ' font-size-16' } btn-square ml-10 material-symbols-outlined shadow-none border-0" use:click={toggleModal}>
|
||||
{ media.mediaListEntry ? 'border_color' : 'bookmark' }
|
||||
</button>
|
||||
<div class='modal position-absolute bg-dark shadow-lg rounded-3 p-20 z-30 {$showModal ? 'visible' : 'invisible'} {!previewAnime && !viewAnime ? 'banner w-auto h-auto' : (!previewAnime ? 'viewAnime w-auto h-auto' : 'previewAnime')}' use:click={() => {}}>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h5 class="font-weight-bold">List Editor</h5>
|
||||
<button type="button" class="btn btn-square mb-20 text-white font-size-24 font-weight-bold" use:click={toggleModal}>×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group mb-15">
|
||||
<label class="d-block mb-5" for="status">Status</label>
|
||||
<select class="form-control bg-dark-light" id="status" bind:value={status}>
|
||||
<option value selected disabled hidden>Any</option>
|
||||
<option value="CURRENT">Watching</option>
|
||||
<option value="PLANNING">Planned</option>
|
||||
<option value="COMPLETED">Completed</option>
|
||||
<option value="PAUSED">Paused</option>
|
||||
<option value="DROPPED">Dropped</option>
|
||||
<option value="REPEATING">Rewatching</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="d-block mb-5" for="episode">Episode</label>
|
||||
<div class="d-flex">
|
||||
<input class="form-control bg-dark-light w-full" type="number" id="episode" value={episode} on:input={handleEpisodes} />
|
||||
<div>
|
||||
<span class="total-episodes position-absolute text-right pointer-events-none">/ {totalEpisodes}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="d-block mb-5" for="score">Your Score</label>
|
||||
<input class="w-full p-2 bg-dark-light" type="range" id="score" min="0" max="10" bind:value={score} />
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="text-center mt-2 {score != 0 ? 'text-decoration-underline font-weight-bold' : ''}">{score === 0 ? 'Not Rated' : score}</div>
|
||||
{#if score != 0}
|
||||
<span class="ml-5">/ 10</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
{#if !status.includes('NOT IN LIST') && media.mediaListEntry}
|
||||
<button type="button" class="btn btn-delete btn-secondary text-dark mr-20 font-weight-bold shadow-none" use:click={() => toggleModal({ delete: true })}>Delete</button>
|
||||
{/if}
|
||||
<button type="button" class="btn btn-save btn-secondary text-dark font-weight-bold shadow-none" use:click={() => toggleModal({ save: true })}>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.modal :global(.absolute-container) {
|
||||
left: -48% !important;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
color: white !important;
|
||||
background: darkred !important;
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
color: white !important;
|
||||
background: darkgreen !important;
|
||||
}
|
||||
|
||||
.total-episodes {
|
||||
margin-top: 0.65rem;
|
||||
right: 4rem;
|
||||
}
|
||||
|
||||
.previewAnime {
|
||||
top: 65%;
|
||||
margin-top: -26rem;
|
||||
width: 70%;
|
||||
left: 0.5rem;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.viewAnime {
|
||||
top: auto;
|
||||
left: auto;
|
||||
margin-top: -1rem;
|
||||
margin-left: 8rem;
|
||||
}
|
||||
|
||||
.banner {
|
||||
top: auto;
|
||||
left: auto;
|
||||
margin-top: 2rem;
|
||||
margin-left: 19rem;
|
||||
}
|
||||
|
||||
.visible {
|
||||
animation: 0.3s ease 0s 1 load-in;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
animation: load-out 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes load-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { getContext } from 'svelte'
|
||||
import { getMediaMaxEp, formatMap, playMedia, setStatus } from '@/modules/anime.js'
|
||||
import { getMediaMaxEp, formatMap, playMedia } from '@/modules/anime.js'
|
||||
import { playAnime } from '@/views/TorrentSearch/TorrentModal.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
import Details from './Details.svelte'
|
||||
import EpisodeList from './EpisodeList.svelte'
|
||||
import ToggleList from './ToggleList.svelte'
|
||||
import Scoring from './Scoring.svelte'
|
||||
import AudioLabel from './AudioLabel.svelte'
|
||||
import Following from './Following.svelte'
|
||||
import smoothScroll from '@/modules/scroll.js'
|
||||
|
|
@ -43,16 +44,6 @@
|
|||
return 'Watch Now'
|
||||
}
|
||||
$: playButtonText = getPlayButtonText(media)
|
||||
async function toggleStatus () {
|
||||
if (!media.mediaListEntry) {
|
||||
// add
|
||||
const res = await setStatus('PLANNING', {}, media)
|
||||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
} else {
|
||||
anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
media.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
function toggleFavourite () {
|
||||
anilistClient.favourite({ id: media.id })
|
||||
media.isFavourite = !media.isFavourite
|
||||
|
|
@ -132,9 +123,7 @@
|
|||
<button class='btn bg-dark btn-lg btn-square material-symbols-outlined font-size-20 shadow-none border-0' class:filled={media.isFavourite} use:click={toggleFavourite}>
|
||||
favorite
|
||||
</button>
|
||||
<button class='btn bg-dark btn-lg btn-square ml-10 material-symbols-outlined font-size-20 shadow-none border-0' class:filled={media.mediaListEntry} use:click={toggleStatus}>
|
||||
bookmark
|
||||
</button>
|
||||
<Scoring {media} viewAnime={true} />
|
||||
<button class='btn bg-dark btn-lg btn-square ml-10 material-symbols-outlined font-size-20 shadow-none border-0' use:click={() => copyToClipboard(`https://miru.watch/anime/${media.id}`)}>
|
||||
share
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Reference in a new issue