mirror of
https://github.com/NoCrypt/migu.git
synced 2026-03-11 17:45:32 +00:00
feat: show filler episodes
perf: optimise fonts perf: use svg icons fix: exit fullscreen on navigation fix: exit rss on navigation
This commit is contained in:
parent
9c24904021
commit
9acf663f33
34 changed files with 452 additions and 782 deletions
|
|
@ -3,6 +3,7 @@
|
|||
import { writable } from 'simple-store-svelte'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { rss } from './views/TorrentSearch/TorrentModal.svelte'
|
||||
|
||||
export const page = writable('home')
|
||||
export const view = writable(null)
|
||||
|
|
@ -37,6 +38,8 @@
|
|||
if (!state) return
|
||||
ignoreNext = true
|
||||
view.set(null)
|
||||
rss.set(null)
|
||||
if (document.fullscreenElement) document.exitFullscreen()
|
||||
if (state.type === 'page') {
|
||||
page.set(state.value)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import NavbarLink from './NavbarLink.svelte'
|
||||
import { MagnifyingGlass } from 'svelte-radix'
|
||||
import { Users, Clock, ListMusic, Settings, Heart } from 'lucide-svelte'
|
||||
const view = getContext('view')
|
||||
export let page
|
||||
function close () {
|
||||
|
|
@ -15,13 +17,47 @@
|
|||
<nav class='navbar navbar-fixed-bottom d-block d-md-none border-0 bg-dark'>
|
||||
<div class='navbar-menu h-full d-flex flex-row justify-content-center align-items-center m-0 pb-5' class:animate={page !== 'player'}>
|
||||
<img src='./logo_filled.png' class='w-50 h-50 m-10 pointer p-5' alt='ico' use:click={close} />
|
||||
<NavbarLink click={() => { page = 'search' }} _page='search' css='ml-auto' icon='search' {page} />
|
||||
<NavbarLink click={() => { page = 'schedule' }} _page='schedule' icon='schedule' {page} />
|
||||
<NavbarLink click={() => { page = 'search' }} _page='search' css='ml-auto' icon='search' {page} let:active>
|
||||
<MagnifyingGlass size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' stroke-width={active ? '2' : '0'} stroke='currentColor' />
|
||||
</NavbarLink>
|
||||
<NavbarLink click={() => { page = 'schedule' }} _page='schedule' icon='schedule' {page} let:active>
|
||||
<Clock size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</NavbarLink>
|
||||
{#if $media?.media}
|
||||
<NavbarLink click={() => { $view = $media.media }} icon='queue_music' {page} />
|
||||
<NavbarLink click={() => { $view = $media.media }} icon='queue_music' {page} let:active>
|
||||
<ListMusic size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</NavbarLink>
|
||||
{/if}
|
||||
<NavbarLink click={() => { page = 'watchtogether' }} _page='watchtogether' icon='groups' {page} />
|
||||
<NavbarLink click={() => { IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/') }} icon='favorite' css='ml-auto donate' {page} />
|
||||
<NavbarLink click={() => { page = 'settings' }} _page='settings' icon='settings' {page} />
|
||||
<NavbarLink click={() => { page = 'watchtogether' }} _page='watchtogether' icon='groups' {page} let:active>
|
||||
<Users size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</NavbarLink>
|
||||
<NavbarLink click={() => { IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/') }} icon='favorite' css='ml-auto donate' {page} let:active>
|
||||
<Heart size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded donate' strokeWidth={active ? '3.5' : '2'} fill='currentColor' />
|
||||
</NavbarLink>
|
||||
<NavbarLink click={() => { page = 'settings' }} _page='settings' icon='settings' {page} let:active>
|
||||
<Settings size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</NavbarLink>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.navbar .animate :global(.donate) {
|
||||
animation: glow 1s ease-in-out infinite alternate;
|
||||
}
|
||||
.navbar :global(.donate):active {
|
||||
color: #fa68b6 !important;
|
||||
}
|
||||
.navbar :global(.donate) {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
color: #fa68b6;
|
||||
text-shadow: 0 0 1rem #fa68b6;
|
||||
}
|
||||
@keyframes glow {
|
||||
from {
|
||||
filter: drop-shadow(0 0 1rem #fa68b6);
|
||||
}
|
||||
to {
|
||||
filter: drop-shadow(0 0 0.5rem #fa68b6);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
let _click = () => {}
|
||||
export { _click as click }
|
||||
export let image = ''
|
||||
export let page
|
||||
export let _page = ''
|
||||
export let css = ''
|
||||
|
|
@ -13,53 +12,20 @@
|
|||
<div
|
||||
class='navbar-link navbar-link-with-icon pointer overflow-hidden {css}'
|
||||
use:click={_click}>
|
||||
{#if image}
|
||||
<span class='material-symbols-outlined rounded' class:filled={page === _page}>
|
||||
<img src={image} class='h-30 rounded' alt='logo' />
|
||||
</span>
|
||||
{:else}
|
||||
<span class='material-symbols-outlined rounded' class:filled={page === _page}>{icon}</span>
|
||||
{/if}
|
||||
|
||||
<span class='rounded d-flex'>
|
||||
<slot active={page === _page}>{icon}</slot>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes glow {
|
||||
from {
|
||||
text-shadow: 0 0 2rem #fa68b6;
|
||||
}
|
||||
to {
|
||||
text-shadow: 0 0 1rem #fa68b6;
|
||||
}
|
||||
}
|
||||
.animate .donate .material-symbols-outlined {
|
||||
animation: glow 1s ease-in-out infinite alternate;
|
||||
}
|
||||
.donate:active .material-symbols-outlined {
|
||||
background: #fff;
|
||||
color: #fa68b6 !important;
|
||||
}
|
||||
.donate .material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
color: #fa68b6;
|
||||
text-shadow: 0 0 1rem #fa68b6;
|
||||
}
|
||||
/* .sidebar-menu {
|
||||
padding-top: 10rem;
|
||||
} */
|
||||
.text {
|
||||
opacity: 1;
|
||||
transition: opacity 0.8s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-link > span {
|
||||
color: #fff;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
.navbar-link > span {
|
||||
color: #fff;
|
||||
transition: background .8s cubic-bezier(0.25, 0.8, 0.25, 1), color .8s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
|
@ -68,34 +34,9 @@
|
|||
background: #fff;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
font-size: 1.4rem;
|
||||
padding: 0.75rem;
|
||||
height: 5.5rem;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-size: 2.2rem;
|
||||
min-width: 4rem;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-link img {
|
||||
font-size: 2.2rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin: 0.5rem;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: var(--sidebar-brand-image-margin-right);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
import { click } from '@/modules/click.js'
|
||||
import { page } from '@/App.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import { MagnifyingGlass, Image } from 'svelte-radix'
|
||||
import { Type, Drama, Leaf, MonitorPlay, Tv, ArrowDownWideNarrow, Trash2, Tags, Grid3X3, Grid2X2 } from 'lucide-svelte'
|
||||
|
||||
export let search
|
||||
let searchTextInput
|
||||
|
|
@ -58,12 +60,12 @@
|
|||
<div class='row'>
|
||||
<div class='col-lg col-4 p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-symbols-outlined mr-10 font-size-30'>title</div>
|
||||
<Type class='mr-10' size='3rem' />
|
||||
Title
|
||||
</div>
|
||||
<div class='input-group'>
|
||||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text d-flex material-symbols-outlined bg-dark-light pr-0 font-size-18'>search</span>
|
||||
<MagnifyingGlass size='2.75rem' class='input-group-text bg-dark-light pr-0' />
|
||||
</div>
|
||||
<input
|
||||
bind:this={searchTextInput}
|
||||
|
|
@ -78,7 +80,7 @@
|
|||
</div>
|
||||
<div class='col-lg col-4 p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-symbols-outlined mr-10 font-size-30'>theater_comedy</div>
|
||||
<Drama class='mr-10' size='3rem' />
|
||||
Genre
|
||||
</div>
|
||||
<div class='input-group'>
|
||||
|
|
@ -107,7 +109,7 @@
|
|||
</div>
|
||||
<div class='col-lg col-4 p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-symbols-outlined mr-10 font-size-30'>spa</div>
|
||||
<Leaf class='mr-10' size='3rem' />
|
||||
Season
|
||||
</div>
|
||||
<div class='input-group'>
|
||||
|
|
@ -129,7 +131,7 @@
|
|||
</div>
|
||||
<div class='col p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-symbols-outlined mr-10 font-size-30'>monitor</div>
|
||||
<Tv class='mr-10' size='3rem' />
|
||||
Format
|
||||
</div>
|
||||
<div class='input-group'>
|
||||
|
|
@ -145,7 +147,7 @@
|
|||
</div>
|
||||
<div class='col p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-symbols-outlined mr-10 font-size-30'>live_tv</div>
|
||||
<MonitorPlay class='mr-10' size='3rem' />
|
||||
Status
|
||||
</div>
|
||||
<div class='input-group'>
|
||||
|
|
@ -160,7 +162,7 @@
|
|||
</div>
|
||||
<div class='col p-10 d-flex flex-column justify-content-end'>
|
||||
<div class='pb-10 font-size-24 font-weight-semi-bold d-flex'>
|
||||
<div class='material-symbols-outlined mr-10 font-size-30'>sort</div>
|
||||
<ArrowDownWideNarrow class='mr-10' size='3rem' />
|
||||
Sort
|
||||
</div>
|
||||
<div class='input-group'>
|
||||
|
|
@ -177,37 +179,34 @@
|
|||
<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'>
|
||||
<label for='search-image' class='pointer mb-0'>
|
||||
image
|
||||
<button class='btn btn-square bg-dark-light px-5 align-self-end border-0' type='button'>
|
||||
<label for='search-image' class='pointer mb-0 d-flex align-items-center justify-content-center'>
|
||||
<Image size='1.625rem' />
|
||||
</label>
|
||||
</button>
|
||||
</div>
|
||||
</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 || search.disableSearch || search.clearNext}>
|
||||
delete
|
||||
<button class='btn btn-square bg-dark-light d-flex align-items-center justify-content-center px-5 align-self-end border-0' type='button' use:click={searchClear} class:text-primary={!!sanitisedSearch?.length || search.disableSearch || search.clearNext}>
|
||||
<Trash2 size='1.625rem' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='w-full px-10 pt-10 h-50 d-flex flex-colum align-items-center'>
|
||||
{#if sanitisedSearch?.length}
|
||||
<span class='material-symbols-outlined font-size-24 mr-20 filled text-dark-light'>sell</span>
|
||||
<Tags class='text-dark-light mr-20' size='3rem' />
|
||||
{#each sanitisedSearch as badge}
|
||||
<span class='badge bg-light border-0 py-5 px-10 text-capitalize mr-20 text-white text-nowrap'>{('' + badge).replace(/_/g, ' ').toLowerCase()}</span>
|
||||
{/each}
|
||||
{/if}
|
||||
<span class='material-symbols-outlined font-size-24 mr-10 filled ml-auto text-dark-light pointer' class:text-muted={$settings.cards === 'small'} use:click={() => changeCardMode('small')}>grid_on</span>
|
||||
<span class='material-symbols-outlined font-size-24 filled text-dark-light pointer' class:text-muted={$settings.cards === 'full'} use:click={() => changeCardMode('full')}>grid_view</span>
|
||||
<span class='mr-10 filled ml-auto text-dark-light pointer' class:text-muted={$settings.cards === 'small'} use:click={() => changeCardMode('small')}><Grid3X3 size='2.25rem' /></span>
|
||||
<span class='text-dark-light pointer' class:text-muted={$settings.cards === 'full'} use:click={() => changeCardMode('full')}><Grid2X2 size='2.25rem' /></span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.text-dark-light {
|
||||
color: var(--gray-color-light);
|
||||
}
|
||||
.input-group,
|
||||
.container-fluid button, .pointer {
|
||||
transition: scale 0.2s ease;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
import { logout } from './Logout.svelte'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import SidebarLink from './SidebarLink.svelte'
|
||||
import { Clock, Download, Heart, Home, ListMusic, LogIn, Settings, Users } from 'lucide-svelte'
|
||||
import { MagnifyingGlass } from 'svelte-radix'
|
||||
|
||||
let updateState = ''
|
||||
|
||||
|
|
@ -40,105 +42,71 @@
|
|||
<div class='sidebar z-30 d-md-block' class:animated={$settings.expandingSidebar}>
|
||||
<div class='sidebar-overlay pointer-events-none h-full position-absolute' />
|
||||
<div class='sidebar-menu h-full d-flex flex-column justify-content-center align-items-center m-0 pb-5' class:animate={page !== 'player'}>
|
||||
<SidebarLink click={handleAlLogin} icon='login' text={anilistClient.userID?.viewer?.data?.Viewer ? 'Logout' : 'Login With AniList'} css='mt-auto' {page} image={anilistClient.userID?.viewer?.data?.Viewer?.avatar.medium} />
|
||||
<SidebarLink click={() => { page = 'home' }} _page='home' icon='home' text='Home' {page} />
|
||||
<SidebarLink click={() => { page = 'search' }} _page='search' icon='search' text='Search' {page} />
|
||||
<SidebarLink click={() => { page = 'schedule' }} _page='schedule' icon='schedule' text='Schedule' {page} />
|
||||
<SidebarLink click={handleAlLogin} icon='login' text={anilistClient.userID?.viewer?.data?.Viewer ? 'Logout' : 'Login With AniList'} css='mt-auto' {page} image={anilistClient.userID?.viewer?.data?.Viewer?.avatar.medium}>
|
||||
<LogIn size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' />
|
||||
</SidebarLink>
|
||||
<SidebarLink click={() => { page = 'home' }} _page='home' text='Home' {page} let:active>
|
||||
<Home size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</SidebarLink>
|
||||
<SidebarLink click={() => { page = 'search' }} _page='search' text='Search' {page} let:active>
|
||||
<MagnifyingGlass size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' stroke-width={active ? '2' : '0'} stroke='currentColor' />
|
||||
</SidebarLink>
|
||||
<SidebarLink click={() => { page = 'schedule' }} _page='schedule' icon='schedule' text='Schedule' {page} let:active>
|
||||
<Clock size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</SidebarLink>
|
||||
{#if $media?.media}
|
||||
<SidebarLink click={() => { $view = $media.media }} icon='queue_music' text='Now Playing' {page} />
|
||||
<SidebarLink click={() => { $view = $media.media }} icon='queue_music' text='Now Playing' {page} let:active>
|
||||
<ListMusic size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</SidebarLink>
|
||||
{/if}
|
||||
<SidebarLink click={() => { page = 'watchtogether' }} _page='watchtogether' icon='groups' text='Watch Together' {page} />
|
||||
<SidebarLink click={() => { IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/') }} icon='favorite' text='Support This App' css='mt-auto donate' {page} />
|
||||
<SidebarLink click={() => { page = 'watchtogether' }} _page='watchtogether' icon='groups' text='Watch Together' {page} let:active>
|
||||
<Users size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</SidebarLink>
|
||||
<SidebarLink click={() => { IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/') }} icon='favorite' text='Support This App' css='mt-auto' {page} let:active>
|
||||
<Heart size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded donate' strokeWidth={active ? '3.5' : '2'} fill='currentColor' />
|
||||
</SidebarLink>
|
||||
{#if updateState === 'downloading'}
|
||||
<SidebarLink click={() => { toast('Update is downloading...') }} icon='download' text='Update Downloading...' {page} />
|
||||
<SidebarLink click={() => { toast('Update is downloading...') }} icon='download' text='Update Downloading...' {page} let:active>
|
||||
<Download size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</SidebarLink>
|
||||
{:else if updateState === 'ready'}
|
||||
<SidebarLink click={() => { IPC.emit('quit-and-install') }} css='update' icon='download' text='Update Ready!' {page} />
|
||||
<SidebarLink click={() => { IPC.emit('quit-and-install') }} icon='download' text='Update Ready!' {page} let:active>
|
||||
<Download size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded update' strokeWidth={active ? '3.5' : '2'} />
|
||||
</SidebarLink>
|
||||
{/if}
|
||||
<SidebarLink click={() => { page = 'settings' }} _page='settings' icon='settings' text='Settings' {page} />
|
||||
<SidebarLink click={() => { page = 'settings' }} _page='settings' icon='settings' text='Settings' {page} let:active>
|
||||
<Settings size='2rem' class='flex-shrink-0 p-5 w-30 h-30 m-5 rounded' strokeWidth={active ? '3.5' : '2'} />
|
||||
</SidebarLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes glow {
|
||||
from {
|
||||
text-shadow: 0 0 2rem #fa68b6;
|
||||
}
|
||||
to {
|
||||
text-shadow: 0 0 1rem #fa68b6;
|
||||
}
|
||||
}
|
||||
.animate .donate .material-symbols-outlined {
|
||||
.sidebar .animate :global(.donate) {
|
||||
animation: glow 1s ease-in-out infinite alternate;
|
||||
}
|
||||
.donate:hover .material-symbols-outlined {
|
||||
background: #fff;
|
||||
.sidebar :global(.donate):hover {
|
||||
color: #fa68b6 !important;
|
||||
}
|
||||
.donate .material-symbols-outlined {
|
||||
.sidebar :global(.donate) {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
color: #fa68b6;
|
||||
text-shadow: 0 0 1rem #fa68b6;
|
||||
}
|
||||
.update .material-symbols-outlined {
|
||||
:global(.update) {
|
||||
color: #47cb6a;
|
||||
font-variation-settings: 'FILL' 1;
|
||||
}
|
||||
@keyframes glow {
|
||||
from {
|
||||
filter: drop-shadow(0 0 1rem #fa68b6);
|
||||
}
|
||||
to {
|
||||
filter: drop-shadow(0 0 0.5rem #fa68b6);
|
||||
}
|
||||
}
|
||||
.sidebar-menu {
|
||||
padding-top: 10rem;
|
||||
}
|
||||
.text {
|
||||
opacity: 1;
|
||||
transition: opacity 0.8s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar-link > span {
|
||||
color: #fff;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
color: #fff;
|
||||
transition: background .8s cubic-bezier(0.25, 0.8, 0.25, 1), color .8s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
.sidebar-link:hover > span > *:nth-child(1) {
|
||||
background: #fff;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.sidebar-link {
|
||||
width: 100%;
|
||||
font-size: 1.4rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
height: 5.5rem;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-size: 2.2rem;
|
||||
min-width: 4rem;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar-link img {
|
||||
font-size: 2.2rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin: 0.5rem;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: var(--sidebar-brand-image-margin-right);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
transition: width .8s cubic-bezier(0.25, 0.8, 0.25, 1), left .8s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
|
||||
|
|
|
|||
|
|
@ -11,50 +11,24 @@
|
|||
export let icon = ''
|
||||
</script>
|
||||
|
||||
<div
|
||||
class='sidebar-link sidebar-link-with-icon pointer overflow-hidden {css}'
|
||||
<div class='sidebar-link sidebar-link-with-icon pointer overflow-hidden {css}'
|
||||
use:click={_click}>
|
||||
<span class='text-nowrap d-flex align-items-center w-full h-full'>
|
||||
{#if image}
|
||||
<span class='material-symbols-outlined rounded' class:filled={page === _page}>
|
||||
<span class='rounded d-flex'>
|
||||
<img src={image} class='h-30 rounded' alt='logo' />
|
||||
</span>
|
||||
<span class='text ml-20'>{text}</span>
|
||||
{:else}
|
||||
<span class='material-symbols-outlined rounded' class:filled={page === _page}>{icon}</span>
|
||||
<span class='rounded d-flex'>
|
||||
<slot active={page === _page}>{icon}</slot>
|
||||
</span>
|
||||
<span class='text ml-20'>{text}</span>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes glow {
|
||||
from {
|
||||
text-shadow: 0 0 2rem #fa68b6;
|
||||
}
|
||||
to {
|
||||
text-shadow: 0 0 1rem #fa68b6;
|
||||
}
|
||||
}
|
||||
.animate .donate .material-symbols-outlined {
|
||||
animation: glow 1s ease-in-out infinite alternate;
|
||||
}
|
||||
.donate:hover .material-symbols-outlined {
|
||||
background: #fff;
|
||||
color: #fa68b6 !important;
|
||||
}
|
||||
.donate .material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
color: #fa68b6;
|
||||
text-shadow: 0 0 1rem #fa68b6;
|
||||
}
|
||||
.update .material-symbols-outlined {
|
||||
color: #47cb6a;
|
||||
font-variation-settings: 'FILL' 1;
|
||||
}
|
||||
.sidebar-menu {
|
||||
padding-top: 10rem;
|
||||
}
|
||||
.text {
|
||||
opacity: 1;
|
||||
transition: opacity 0.8s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
|
|
@ -68,12 +42,12 @@
|
|||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
.sidebar-link > span > span:nth-child(1) {
|
||||
color: #fff;
|
||||
transition: background .8s cubic-bezier(0.25, 0.8, 0.25, 1), color .8s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
.sidebar-link:hover > span > *:nth-child(1) {
|
||||
.sidebar-link:hover > span > span:nth-child(1) {
|
||||
background: #fff;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
|
@ -85,16 +59,6 @@
|
|||
height: 5.5rem;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-size: 2.2rem;
|
||||
min-width: 4rem;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar-link img {
|
||||
font-size: 2.2rem;
|
||||
width: 3rem;
|
||||
|
|
@ -108,22 +72,4 @@
|
|||
img {
|
||||
margin-right: var(--sidebar-brand-image-margin-right);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
transition: width .8s cubic-bezier(0.25, 0.8, 0.25, 1), left .8s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
|
||||
background: none !important;
|
||||
overflow-y: unset;
|
||||
overflow-x: visible;
|
||||
left: unset;
|
||||
}
|
||||
.sidebar.animated:hover {
|
||||
width: 22rem
|
||||
}
|
||||
.sidebar-overlay {
|
||||
width: var(--sidebar-width);
|
||||
transition: width .8s cubic-bezier(0.25, 0.8, 0.25, 1), left .8s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
|
||||
background: var(--sidebar-gradient);
|
||||
backdrop-filter: blur(2px);
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import { alToken } from '@/modules/settings.js'
|
||||
import { Bookmark, Heart } from 'lucide-svelte'
|
||||
export let mediaList
|
||||
|
||||
let current = mediaList[0]
|
||||
|
|
@ -93,11 +94,11 @@
|
|||
use:click={() => playMedia(current)}>
|
||||
Watch Now
|
||||
</button>
|
||||
<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} disabled={!alToken}>
|
||||
favorite
|
||||
<button class='btn bg-dark-light btn-square ml-10 d-flex align-items-center justify-content-center shadow-none border-0' use:click={toggleFavourite} disabled={!alToken}>
|
||||
<Heart fill={current.isFavourite ? 'currentColor' : 'transparent'} size='1.5rem' />
|
||||
</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} disabled={!alToken}>
|
||||
bookmark
|
||||
<button class='btn bg-dark-light btn-square ml-10 d-flex align-items-center justify-content-center shadow-none border-0' use:click={toggleStatus} disabled={!alToken}>
|
||||
<Bookmark fill={current.mediaListEntry ? 'currentColor' : 'transparent'} size='1.5rem' />
|
||||
</button>
|
||||
</div>
|
||||
<div class='d-flex'>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { getContext } from 'svelte'
|
||||
import { liveAnimeEpisodeProgress } from '@/modules/animeprogress.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { Play } from 'lucide-svelte'
|
||||
export let data
|
||||
|
||||
let preview = false
|
||||
|
|
@ -32,12 +33,7 @@
|
|||
<div class='item d-flex flex-column h-full pointer content-visibility-auto'>
|
||||
<div class='image h-200 w-full position-relative rounded overflow-hidden d-flex justify-content-between align-items-end text-white' class:bg-black={episodeThumbnail === ' '}>
|
||||
<img loading='lazy' src={episodeThumbnail} alt='cover' class='cover-img w-full h-full position-absolute' style:--color={media?.coverImage?.color || '#1890ff'} />
|
||||
{#if data.failed}
|
||||
<div class='material-symbols-outlined pl-10 pt-10 position-absolute top-0 left-0 text-danger filled font-weight-medium' title='Failed to resolve media z-10'>
|
||||
sync_problem
|
||||
</div>
|
||||
{/if}
|
||||
<div class='pl-10 pb-10 material-symbols-outlined filled z-10'>play_arrow</div>
|
||||
<Play class='mb-5 ml-5 pl-10 pb-10 z-10' fill='currentColor' size='3rem' />
|
||||
<div class='pr-15 pb-10 font-size-16 font-weight-medium z-10'>
|
||||
{#if media?.duration}
|
||||
{media.duration}m
|
||||
|
|
@ -86,9 +82,6 @@
|
|||
z-index: 30;
|
||||
/* fixes transform scaling on click causing z-index issues */
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.title {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { statusColorMap, formatMap } from '@/modules/anime.js'
|
||||
import { since } from '@/modules/util'
|
||||
import { liveAnimeEpisodeProgress } from '@/modules/animeprogress.js'
|
||||
import { CalendarDays, Play, Tv } from 'lucide-svelte'
|
||||
export let data
|
||||
/** @type {import('@/modules/al.d.ts').Media | null} */
|
||||
const media = data.media
|
||||
|
|
@ -26,12 +27,7 @@
|
|||
on:loadeddata={() => { hide = false }}
|
||||
autoplay />
|
||||
{/if}
|
||||
{#if data.failed}
|
||||
<div class='material-symbols-outlined pl-10 pt-10 position-absolute top-0 left-0 text-danger filled font-weight-medium z-10' title='Failed to resolve media'>
|
||||
sync_problem
|
||||
</div>
|
||||
{/if}
|
||||
<div class='pl-15 pb-10 material-symbols-outlined filled z-10'>play_arrow</div>
|
||||
<Play class='mb-5 ml-5 pl-10 pb-10 z-10' fill='currentColor' size='3rem' />
|
||||
<div class='pr-20 pb-10 font-size-16 font-weight-medium z-10'>
|
||||
{#if media?.duration}
|
||||
{media.duration}m
|
||||
|
|
@ -78,13 +74,13 @@
|
|||
</div>
|
||||
{#if media}
|
||||
<div class='d-flex flex-row pt-15 font-weight-medium justify-content-between w-full text-muted'>
|
||||
<div class='d-flex align-items-center' style='margin-left: -3px'>
|
||||
<span class='material-symbols-outlined font-size-24 pr-5'>calendar_month</span>
|
||||
{media.seasonYear || 'N/A'}
|
||||
<div class='d-flex align-items-center' style='margin-left: -2px'>
|
||||
<CalendarDays class='pr-5' size='2.6rem' />
|
||||
<span class='line-height-1'>{media.seasonYear || 'N/A'}</span>
|
||||
</div>
|
||||
<div class='d-flex align-items-center'>
|
||||
{formatMap[media.format]}
|
||||
<span class='material-symbols-outlined font-size-24 pl-5'>monitor</span>
|
||||
<span class='line-height-1'>{formatMap[media.format]}</span>
|
||||
<Tv class='pl-5' size='2.6rem' />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -92,9 +88,6 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.description {
|
||||
display: -webkit-box !important;
|
||||
-webkit-line-clamp: 3;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import { alToken } from '@/modules/settings.js'
|
||||
import { Bookmark, Heart, Play, VolumeX, Volume2 } from 'lucide-svelte'
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
|
||||
|
|
@ -55,7 +56,14 @@
|
|||
<div class='banner position-relative bg-black overflow-hidden'>
|
||||
<img src={media.bannerImage || `https://i.ytimg.com/vi/${media.trailer?.id}/hqdefault.jpg` || ' '} alt='banner' class='img-cover w-full h-full' />
|
||||
{#if media.trailer?.id}
|
||||
<div class='material-symbols-outlined filled position-absolute z-10 top-0 right-0 p-15 font-size-22' class:d-none={hide} use:click={toggleMute}>{muted ? 'volume_off' : 'volume_up'}</div>
|
||||
<div class='position-absolute z-10 top-0 right-0 p-15' use:click={toggleMute}>
|
||||
{#if muted}
|
||||
<VolumeX size='2.2rem' fill='currentColor' />
|
||||
{:else}
|
||||
<Volume2 size='2.2rem' fill='currentColor' />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- indivious is nice because its faster, but not reliable -->
|
||||
<!-- <video src={`https://inv.tux.pizza/latest_version?id=${media.trailer.id}&itag=18`}
|
||||
class='w-full h-full position-absolute left-0'
|
||||
|
|
@ -85,16 +93,14 @@
|
|||
<button class='btn btn-secondary flex-grow-1 text-dark font-weight-bold shadow-none border-0 d-flex align-items-center justify-content-center'
|
||||
use:click={play}
|
||||
disabled={media.status === 'NOT_YET_RELEASED'}>
|
||||
<span class='material-symbols-outlined font-size-20 filled pr-10'>
|
||||
play_arrow
|
||||
</span>
|
||||
<Play class='pr-10 z-10' fill='currentColor' size='2.2rem' />
|
||||
{playButtonText}
|
||||
</button>
|
||||
<button class='btn btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.isFavourite} use:click={toggleFavourite} disabled={!alToken}>
|
||||
favorite
|
||||
<button class='btn btn-square ml-10 d-flex align-items-center justify-content-center shadow-none border-0' use:click={toggleFavourite} disabled={!alToken}>
|
||||
<Heart fill={media.isFavourite ? 'currentColor' : 'transparent'} size='1.5rem' />
|
||||
</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} disabled={!alToken}>
|
||||
bookmark
|
||||
<button class='btn btn-square ml-10 d-flex align-items-center justify-content-center shadow-none border-0' use:click={toggleStatus} disabled={!alToken}>
|
||||
<Bookmark fill={media.mediaListEntry ? 'currentColor' : 'transparent'} size='1.5rem' />
|
||||
</button>
|
||||
</div>
|
||||
<div class='details text-white text-capitalize pt-15 pb-10 d-flex'>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { countdown } from '@/modules/util.js'
|
||||
|
||||
import { page } from '@/App.svelte'
|
||||
import { CalendarDays, Tv } from 'lucide-svelte'
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
let preview = false
|
||||
|
|
@ -45,13 +46,13 @@
|
|||
{media.title.userPreferred}
|
||||
</div>
|
||||
<div class='d-flex flex-row mt-auto pt-10 font-weight-medium justify-content-between w-full text-muted'>
|
||||
<div class='d-flex align-items-center pr-5' style='margin-left: -2px'>
|
||||
<span class='material-symbols-outlined font-size-24 pr-5'>calendar_month</span>
|
||||
{media.seasonYear || 'N/A'}
|
||||
<div class='d-flex align-items-center pr-5' style='margin-left: -1px'>
|
||||
<CalendarDays class='pr-5' size='2.6rem' />
|
||||
<span class='line-height-1'>{media.seasonYear || 'N/A'}</span>
|
||||
</div>
|
||||
<div class='d-flex align-items-center text-nowrap text-right'>
|
||||
{formatMap[media.format]}
|
||||
<span class='material-symbols-outlined font-size-24 pl-5'>monitor</span>
|
||||
<span class='line-height-1'>{formatMap[media.format]}</span>
|
||||
<Tv class='pl-5' size='2.6rem' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,10 +33,37 @@
|
|||
--section-end-gradient: linear-gradient(270deg, #17191cff 0%, #17191c00 100%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto";
|
||||
src: /* webpackIgnore: true */ url(Roboto.ttf) format("truetype");
|
||||
unicode-range: U+0000, U+0002, U+0009, U+000D, U+0020-007E, U+00A0-0377,
|
||||
U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03E1,
|
||||
U+03F0-052F, U+1AB0-1ABE, U+1D00-1DF5, U+1DFC-1F15, U+1F18-1F1D,
|
||||
U+1F20-1F45, U+1F48-1F4D, U+1F50-1F57, U+1F59, U+1F5B, U+1F5D,
|
||||
U+1F5F-1F7D, U+1F80-1FB4, U+1FB6-1FC4, U+1FC6-1FD3, U+1FD6-1FDB,
|
||||
U+1FDD-1FEF, U+1FF2-1FF4, U+1FF6-1FFE, U+2000-2027, U+202F-205F,
|
||||
U+2070-2071, U+2074-208E, U+2090-209C, U+20A0-20BE, U+20DB-20DC,
|
||||
U+20E3, U+20E8, U+20F0, U+2100-2101, U+2103, U+2105-2106, U+2109,
|
||||
U+2113, U+2116-2117, U+211E-2123, U+2125-2126, U+212A-212B, U+212E,
|
||||
U+2132, U+213B, U+214D, U+214F-2189, U+2191, U+2193, U+2202, U+2206,
|
||||
U+220F, U+2211-2212, U+221A, U+221E, U+222B, U+2248, U+2260,
|
||||
U+2264-2265, U+2423, U+25CA, U+2669-266F, U+27E6-27EF, U+2B4E-2B4F,
|
||||
U+2B5A-2B5F, U+2C60-2C7F, U+2DE0-2E42, U+A640-A69D, U+A69F,
|
||||
U+A700-A7AD, U+A7B0-A7B1, U+A7F7-A7FF, U+A92E, U+AB30-AB5F,
|
||||
U+AB64-AB65, U+EE01-EE02, U+F6C3, U+FB00-FB06, U+FE20-FE2D, U+FEFF,
|
||||
U+FFFC-FFFD, U+1F16A-1F16B;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
background-color: var(--dark-color) !important;
|
||||
}
|
||||
|
||||
.h-30 {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
:root {
|
||||
--sidebar-minimised: 7rem;
|
||||
|
|
@ -45,6 +72,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.line-height-1 {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.line-height-normal {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
|
@ -82,27 +117,8 @@ a[href]:active, button:not([disabled]):active, fieldset:not([disabled]):active,
|
|||
z-index: 100;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-family: "Material Symbols Outlined Variable";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-feature-settings: "liga";
|
||||
font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 64;
|
||||
}
|
||||
|
||||
.filled {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
.text-dark-light {
|
||||
color: var(--gray-color-light);
|
||||
}
|
||||
|
||||
.pointer {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import App from './App.svelte'
|
||||
import 'quartermoon/css/quartermoon-variables.css'
|
||||
import '@fontsource-variable/material-symbols-outlined/full.css'
|
||||
import '@fontsource-variable/nunito'
|
||||
import '@fontsource/roboto'
|
||||
import './css.css'
|
||||
|
||||
export default new App({ target: document.body })
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ export default new class AnimeResolver {
|
|||
|
||||
debug(`Finding ${titleObjects.length} titles: ${titleObjects.map(obj => obj.title).join(', ')}`)
|
||||
|
||||
for (const chunk of chunks(titleObjects, 62)) { // single title has a complexity of 8.1, al limits complexity to 500
|
||||
for (const chunk of chunks(titleObjects, 60)) {
|
||||
// single title has a complexity of 8.1, al limits complexity to 500, so this can be at most 62, undercut it to 60, al pagination is 50, but at most we'll do 30 titles since isAduld duplicates each title
|
||||
for (const [key, media] of await anilistClient.alSearchCompound(chunk)) {
|
||||
debug(`Found ${key} as ${media.id}: ${media.title.userPreferred}`)
|
||||
this.animeNameCache[key] = media
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
"name": "common",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fontsource-variable/material-symbols-outlined": "^5.0.24",
|
||||
"@fontsource-variable/nunito": "^5.0.18",
|
||||
"@fontsource/roboto": "^5.0.12",
|
||||
"@thaunknown/ani-resourced": "^1.0.3",
|
||||
"anitomyscript": "github:ThaUnknown/anitomyscript#42290c4b3f256893be08a4e89051f448ff5e9d00",
|
||||
"bottleneck": "^2.19.5",
|
||||
|
|
@ -12,16 +10,18 @@
|
|||
"comlink": "^4.4.1",
|
||||
"jassub": "^1.7.17",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"lucide-svelte": "^0.429.0",
|
||||
"p2pt": "github:ThaUnknown/p2pt#modernise",
|
||||
"perfect-seekbar": "^1.1.0",
|
||||
"quartermoon": "^1.2.3",
|
||||
"simple-font-select": "^1.0.1",
|
||||
"simple-store-svelte": "^1.0.6",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-keybinds": "^1.0.8",
|
||||
"svelte-keybinds": "^1.0.9",
|
||||
"svelte-loader": "^3.1.9",
|
||||
"svelte-miniplayer": "^1.0.5",
|
||||
"svelte-persisted-store": "^0.11.0",
|
||||
"svelte-radix": "^1.1.0",
|
||||
"svelte-sonner": "^0.3.19",
|
||||
"video-deband": "^1.0.5",
|
||||
"webpack-merge": "^5.10.0"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
import { SUPPORTS } from '@/modules/support.js'
|
||||
import 'rvfc-polyfill'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { ArrowDown, ArrowUp, Captions, Cast, CircleHelp, Contrast, FastForward, Keyboard, List, ListMusic, ListVideo, Maximize, Minimize, Pause, PictureInPicture, PictureInPicture2, Play, Proportions, RefreshCcw, Rewind, RotateCcw, RotateCw, ScreenShare, SkipBack, SkipForward, Users, Volume1, Volume2, VolumeX } from 'lucide-svelte'
|
||||
|
||||
const emit = createEventDispatcher()
|
||||
|
||||
|
|
@ -465,11 +466,13 @@
|
|||
KeyX: {
|
||||
fn: () => screenshot(),
|
||||
id: 'screenshot_monitor',
|
||||
icon: ScreenShare,
|
||||
type: 'icon',
|
||||
desc: 'Save Screenshot to Clipboard'
|
||||
},
|
||||
KeyI: {
|
||||
fn: () => toggleStats(),
|
||||
icon: List,
|
||||
id: 'list',
|
||||
type: 'icon',
|
||||
desc: 'Toggle Stats'
|
||||
|
|
@ -477,24 +480,28 @@
|
|||
Backquote: {
|
||||
fn: () => (showKeybinds = !showKeybinds),
|
||||
id: 'help_outline',
|
||||
icon: CircleHelp,
|
||||
type: 'icon',
|
||||
desc: 'Toggle Keybinds'
|
||||
},
|
||||
Space: {
|
||||
fn: () => playPause(),
|
||||
id: 'play_arrow',
|
||||
play: Play,
|
||||
type: 'icon',
|
||||
desc: 'Play/Pause'
|
||||
},
|
||||
KeyN: {
|
||||
fn: () => playNext(),
|
||||
id: 'skip_next',
|
||||
icon: SkipForward,
|
||||
type: 'icon',
|
||||
desc: 'Next Episode'
|
||||
},
|
||||
KeyB: {
|
||||
fn: () => playLast(),
|
||||
id: 'skip_previous',
|
||||
icon: SkipBack,
|
||||
type: 'icon',
|
||||
desc: 'Previous Episode'
|
||||
},
|
||||
|
|
@ -503,24 +510,28 @@
|
|||
$settings.playerDeband = !$settings.playerDeband
|
||||
},
|
||||
id: 'deblur',
|
||||
icon: Contrast,
|
||||
type: 'icon',
|
||||
desc: 'Toggle Video Debanding'
|
||||
},
|
||||
KeyM: {
|
||||
fn: () => (muted = !muted),
|
||||
id: 'volume_off',
|
||||
icon: VolumeX,
|
||||
type: 'icon',
|
||||
desc: 'Toggle Mute'
|
||||
},
|
||||
KeyP: {
|
||||
fn: () => togglePopout(),
|
||||
id: 'picture_in_picture',
|
||||
icon: PictureInPicture2,
|
||||
type: 'icon',
|
||||
desc: 'Toggle Picture in Picture'
|
||||
},
|
||||
KeyF: {
|
||||
fn: () => toggleFullscreen(),
|
||||
id: 'fullscreen',
|
||||
icon: Maximize,
|
||||
type: 'icon',
|
||||
desc: 'Toggle Fullscreen'
|
||||
},
|
||||
|
|
@ -532,18 +543,21 @@
|
|||
KeyW: {
|
||||
fn: () => { fitWidth = !fitWidth },
|
||||
id: 'fit_width',
|
||||
icon: Proportions,
|
||||
type: 'icon',
|
||||
desc: 'Toggle Video Cover'
|
||||
},
|
||||
KeyD: {
|
||||
fn: () => toggleCast(),
|
||||
id: 'cast',
|
||||
icon: Cast,
|
||||
type: 'icon',
|
||||
desc: 'Toggle Cast [broken]'
|
||||
},
|
||||
KeyC: {
|
||||
fn: () => cycleSubtitles(),
|
||||
id: 'subtitles',
|
||||
icon: Captions,
|
||||
type: 'icon',
|
||||
desc: 'Cycle Subtitles'
|
||||
},
|
||||
|
|
@ -554,6 +568,7 @@
|
|||
rewind()
|
||||
},
|
||||
id: 'fast_rewind',
|
||||
icon: Rewind,
|
||||
type: 'icon',
|
||||
desc: 'Rewind'
|
||||
},
|
||||
|
|
@ -564,6 +579,7 @@
|
|||
forward()
|
||||
},
|
||||
id: 'fast_forward',
|
||||
icon: FastForward,
|
||||
type: 'icon',
|
||||
desc: 'Seek'
|
||||
},
|
||||
|
|
@ -574,6 +590,7 @@
|
|||
volume = Math.min(1, volume + 0.05)
|
||||
},
|
||||
id: 'volume_up',
|
||||
icon: Volume2,
|
||||
type: 'icon',
|
||||
desc: 'Volume Up'
|
||||
},
|
||||
|
|
@ -584,23 +601,27 @@
|
|||
volume = Math.max(0, volume - 0.05)
|
||||
},
|
||||
id: 'volume_down',
|
||||
icon: Volume1,
|
||||
type: 'icon',
|
||||
desc: 'Volume Down'
|
||||
},
|
||||
BracketLeft: {
|
||||
fn: () => { playbackRate = video.defaultPlaybackRate -= 0.1 },
|
||||
id: 'history',
|
||||
icon: RotateCcw,
|
||||
type: 'icon',
|
||||
desc: 'Decrease Playback Rate'
|
||||
},
|
||||
BracketRight: {
|
||||
fn: () => { playbackRate = video.defaultPlaybackRate += 0.1 },
|
||||
id: 'update',
|
||||
icon: RotateCw,
|
||||
type: 'icon',
|
||||
desc: 'Increase Playback Rate'
|
||||
},
|
||||
Backslash: {
|
||||
fn: () => { playbackRate = video.defaultPlaybackRate = 1 },
|
||||
icon: RefreshCcw,
|
||||
id: 'schedule',
|
||||
type: 'icon',
|
||||
desc: 'Reset Playback Rate'
|
||||
|
|
@ -1029,7 +1050,15 @@
|
|||
{#if showKeybinds && !miniplayer}
|
||||
<div class='position-absolute bg-tp w-full h-full z-50 font-size-12 p-20 d-flex align-items-center justify-content-center pointer' on:pointerup|self={() => (showKeybinds = false)} tabindex='-1' role='button'>
|
||||
<Keybinds let:prop={item} autosave={true} clickable={true}>
|
||||
<div class:material-symbols-outlined={item?.type} class='bind' title={item?.desc} style='pointer-events: all !important;'>{item?.id || ''}</div>
|
||||
{#if item?.type}
|
||||
<div class='bind icon' title={item?.desc} style='pointer-events: all !important;'>
|
||||
{#if item?.icon}
|
||||
<svelte:component this={item.icon} size='2rem' />
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class='bind font-weight-normal' title={item?.desc} style='pointer-events: all !important;'>{item?.id || ''}</div>
|
||||
{/if}
|
||||
</Keybinds>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -1097,11 +1126,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class='d-flex col-4 justify-content-center'>
|
||||
<span class='material-symbols-outlined'> people </span>
|
||||
<span class='icon'><Users size='3rem' class='pt-5' /> </span>
|
||||
<span class='stats'>{torrent.peers || 0}</span>
|
||||
<span class='material-symbols-outlined'> arrow_downward </span>
|
||||
<span class='icon'><ArrowDown size='3rem' /></span>
|
||||
<span class='stats'>{fastPrettyBytes(torrent.down)}/s</span>
|
||||
<span class='material-symbols-outlined'> arrow_upward </span>
|
||||
<span class='icon'><ArrowUp size='3rem' /></span>
|
||||
<span class='stats'>{fastPrettyBytes(torrent.up)}/s</span>
|
||||
</div>
|
||||
<div class='col-4' />
|
||||
|
|
@ -1113,10 +1142,24 @@
|
|||
<div class='w-full h-full position-absolute toggle-immerse d-none' on:dblclick={toggleFullscreen} on:click|self={toggleImmerse} />
|
||||
<div class='w-full h-full position-absolute mobile-focus-target d-none' use:click={() => { page = 'player' }} />
|
||||
<!-- eslint-disable-next-line svelte/valid-compile -->
|
||||
<span class='material-symbols-outlined ctrl h-full align-items-center justify-content-end w-150 mw-full mr-auto' on:click={rewind}> fast_rewind </span>
|
||||
<span class='material-symbols-outlined ctrl' data-name='playPause' use:click={playPause}> {ended ? 'replay' : paused ? 'play_arrow' : 'pause'} </span>
|
||||
<span class='icon ctrl h-full align-items-center justify-content-end w-150 mw-full mr-auto' on:click={rewind}>
|
||||
<Rewind size='3rem' />
|
||||
</span>
|
||||
<span class='icon ctrl' data-name='playPause' use:click={playPause}>
|
||||
{#if ended}
|
||||
<RotateCw size='3rem' />
|
||||
{:else}
|
||||
{#if paused}
|
||||
<Play size='3rem' fill='white' />
|
||||
{:else}
|
||||
<Pause size='3rem' fill='white' />
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
<!-- eslint-disable-next-line svelte/valid-compile -->
|
||||
<span class='material-symbols-outlined ctrl h-full align-items-center w-150 mw-full ml-auto' on:click={forward}> fast_forward </span>
|
||||
<span class='icon ctrl h-full align-items-center w-150 mw-full ml-auto' on:click={forward}>
|
||||
<FastForward size='3rem' />
|
||||
</span>
|
||||
<div class='position-absolute bufferingDisplay' />
|
||||
{#if currentSkippable}
|
||||
<button class='skip btn text-dark position-absolute bottom-0 right-0 mr-20 mb-5 font-weight-bold z-30' use:click={skip}>
|
||||
|
|
@ -1139,26 +1182,47 @@
|
|||
/>
|
||||
</div>
|
||||
<div class='d-flex'>
|
||||
<span class='material-symbols-outlined ctrl' title='Play/Pause [Space]' data-name='playPause' use:click={playPause}> {ended ? 'replay' : paused ? 'play_arrow' : 'pause'} </span>
|
||||
<span class='icon ctrl m-5' title='Play/Pause [Space]' data-name='playPause' use:click={playPause}>
|
||||
{#if ended}
|
||||
<RotateCw size='2rem' />
|
||||
{:else}
|
||||
{#if paused}
|
||||
<Play size='2rem' fill='white' />
|
||||
{:else}
|
||||
<Pause size='2rem' fill='white' />
|
||||
{/if}
|
||||
{/if}</span>
|
||||
{#if hasLast}
|
||||
<span class='material-symbols-outlined ctrl' title='Last [B]' use:click={playLast}> skip_previous </span>
|
||||
<span class='icon ctrl m-5' title='Last [B]' use:click={playLast}>
|
||||
<SkipBack size='2rem' fill='white' />
|
||||
</span>
|
||||
{/if}
|
||||
{#if hasNext}
|
||||
<span class='material-symbols-outlined ctrl' title='Next [N]' use:click={playNext}> skip_next </span>
|
||||
<span class='icon ctrl m-5' title='Next [N]' use:click={playNext}>
|
||||
<SkipForward size='2rem' fill='white' />
|
||||
</span>
|
||||
{/if}
|
||||
<div class='d-flex w-auto volume'>
|
||||
<span class='material-symbols-outlined ctrl' title='Mute [M]' data-name='toggleMute' use:click={toggleMute}> {muted ? 'volume_off' : 'volume_up'} </span>
|
||||
<span class='icon ctrl m-5' title='Mute [M]' data-name='toggleMute' use:click={toggleMute}>
|
||||
{#if muted}
|
||||
<VolumeX size='2rem' fill='white' />
|
||||
{:else}
|
||||
<Volume2 size='2rem' fill='white' />
|
||||
{/if}
|
||||
</span>
|
||||
<input class='ctrl h-full custom-range' type='range' min='0' max='1' step='any' data-name='setVolume' bind:value={volume} />
|
||||
</div>
|
||||
<div class='ts' class:mr-auto={playbackRate === 1}>{toTS(targetTime, safeduration > 3600 ? 2 : 3)} / {toTS(safeduration - targetTime, safeduration > 3600 ? 2 : 3)}</div>
|
||||
{#if playbackRate !== 1}
|
||||
<div class='ts mr-auto'>x{playbackRate.toFixed(1)}</div>
|
||||
{/if}
|
||||
<span class='material-symbols-outlined ctrl keybinds' title='Keybinds [`]' use:click={() => (showKeybinds = true)}> keyboard </span>
|
||||
<span class='icon ctrl m-5 keybinds' title='Keybinds [`]' use:click={() => (showKeybinds = true)}>
|
||||
<Keyboard size='2rem' />
|
||||
</span>
|
||||
{#if 'audioTracks' in HTMLVideoElement.prototype && video?.audioTracks?.length > 1}
|
||||
<div class='dropdown dropup with-arrow' use:click={toggleDropdown}>
|
||||
<span class='material-symbols-outlined ctrl' title='Audio Tracks'>
|
||||
queue_music
|
||||
<span class='icon ctrl m-5' title='Audio Tracks'>
|
||||
<ListMusic size='2rem' />
|
||||
</span>
|
||||
<div class='dropdown-menu dropdown-menu-left ctrl custom-radio p-10 pb-5 text-capitalize'>
|
||||
{#each video.audioTracks as track}
|
||||
|
|
@ -1172,8 +1236,8 @@
|
|||
{/if}
|
||||
{#if 'videoTracks' in HTMLVideoElement.prototype && video?.videoTracks?.length > 1}
|
||||
<div class='dropdown dropup with-arrow'>
|
||||
<span class='material-symbols-outlined ctrl' title='Video Tracks'>
|
||||
playlist_play
|
||||
<span class='icon ctrl m-5' title='Video Tracks'>
|
||||
<ListVideo size='2rem' />
|
||||
</span>
|
||||
<div class='dropdown-menu dropdown-menu-left ctrl custom-radio p-10 pb-5 text-capitalize'>
|
||||
{#each video.videoTracks as track}
|
||||
|
|
@ -1187,8 +1251,8 @@
|
|||
{/if}
|
||||
{#if subHeaders?.length}
|
||||
<div class='subtitles dropdown dropup with-arrow' use:click={toggleDropdown}>
|
||||
<span class='material-symbols-outlined ctrl' title='Subtitles [C]'>
|
||||
subtitles
|
||||
<span class='icon ctrl m-5' title='Subtitles [C]'>
|
||||
<Captions size='2rem' />
|
||||
</span>
|
||||
<div class='dropdown-menu dropdown-menu-right ctrl custom-radio p-10 pb-5 text-capitalize'>
|
||||
<input name='subtitle-radio-set' type='radio' id='subtitle-off-radio' value='off' checked={subHeaders && subs?.current === -1} />
|
||||
|
|
@ -1206,17 +1270,29 @@
|
|||
</div>
|
||||
{/if}
|
||||
{#if 'PresentationRequest' in window && canCast && current}
|
||||
<span class='material-symbols-outlined ctrl' title='Cast Video [D]' data-name='toggleCast' use:click={toggleCast}>
|
||||
{presentationConnection ? 'cast_connected' : 'cast'}
|
||||
<span class='icon ctrl m-5' title='Cast Video [D]' data-name='toggleCast' use:click={toggleCast}>
|
||||
{#if presentationConnection}
|
||||
<Cast size='2rem' fill='white' strokeWidth={0} />
|
||||
{:else}
|
||||
<Cast size='2rem' />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if 'pictureInPictureEnabled' in document}
|
||||
<span class='material-symbols-outlined ctrl' title='Popout Window [P]' data-name='togglePopout' use:click={togglePopout}>
|
||||
{pip ? 'featured_video' : 'picture_in_picture'}
|
||||
<span class='icon ctrl m-5' title='Popout Window [P]' data-name='togglePopout' use:click={togglePopout}>
|
||||
{#if pip}
|
||||
<PictureInPicture size='2rem' />
|
||||
{:else}
|
||||
<PictureInPicture2 size='2rem' />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
<span class='material-symbols-outlined ctrl' title='Fullscreen [F]' data-name='toggleFullscreen' use:click={toggleFullscreen}>
|
||||
{isFullscreen ? 'fullscreen_exit' : 'fullscreen'}
|
||||
<span class='icon ctrl m-5' title='Fullscreen [F]' data-name='toggleFullscreen' use:click={toggleFullscreen}>
|
||||
{#if isFullscreen}
|
||||
<Minimize size='2rem' />
|
||||
{:else}
|
||||
<Maximize size='2rem' />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1379,11 +1455,10 @@
|
|||
height: 1px !important;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
.icon {
|
||||
font-size: 2.8rem;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
font-variation-settings: 'FILL' 1, 'wght' 300, 'GRAD' 100, 'opsz' 64;
|
||||
}
|
||||
|
||||
.immersed {
|
||||
|
|
@ -1415,9 +1490,6 @@
|
|||
transition: 0.5s opacity ease 0.2s;
|
||||
filter: drop-shadow(0 0 8px #000);
|
||||
}
|
||||
.disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.buffering .middle .bufferingDisplay {
|
||||
opacity: 1 !important;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { click } from '@/modules/click.js'
|
||||
import { sections } from '@/modules/sections.js'
|
||||
import { SUPPORTS } from '@/modules/support.js'
|
||||
import { ArrowDown, ArrowDownUp, ArrowUp, Trash2 } from 'lucide-svelte'
|
||||
|
||||
const allowedHomeSections = sections.map(({ title }) => title)
|
||||
export let homeSections
|
||||
|
|
@ -16,19 +17,15 @@
|
|||
|
||||
$: {
|
||||
if (draggingItemIndex != null && hoveredItemIndex != null && draggingItemIndex !== hoveredItemIndex) {
|
||||
[homeSections[draggingItemIndex], homeSections[hoveredItemIndex]] = [homeSections[hoveredItemIndex], homeSections[draggingItemIndex]]
|
||||
swapItem(draggingItemIndex, hoveredItemIndex)
|
||||
|
||||
draggingItemIndex = hoveredItemIndex
|
||||
}
|
||||
}
|
||||
|
||||
function moveItem (index, direction) {
|
||||
if (direction === 'up' && index > 0) {
|
||||
[homeSections[index], homeSections[index - 1]] = [homeSections[index - 1], homeSections[index]]
|
||||
} else if (direction === 'down' && index < homeSections.length - 1) {
|
||||
[homeSections[index], homeSections[index + 1]] = [homeSections[index + 1], homeSections[index]]
|
||||
}
|
||||
homeSections = homeSections
|
||||
function swapItem (a, b) {
|
||||
b = Math.min(homeSections.length - 1, Math.max(0, b))
|
||||
;[homeSections[a], homeSections[b]] = [homeSections[b], homeSections[a]]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -37,7 +34,7 @@
|
|||
class='input-group mb-10 ghost w-full'
|
||||
style='top: {mouseYCoordinate + distanceTopGrabbedVsPointer}px;'>
|
||||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text d-flex justify-content-center px-5 material-symbols-outlined font-size-20'>swap_vert</span>
|
||||
<span class='input-group-text d-flex align-items-center px-5'><ArrowDownUp size='1.8rem' /></span>
|
||||
</div>
|
||||
<select class='form-control' value={draggingItem}>
|
||||
{#each allowedHomeSections as section}
|
||||
|
|
@ -45,7 +42,7 @@
|
|||
{/each}
|
||||
</select>
|
||||
<div class='input-group-append'>
|
||||
<button type='button' class='btn btn-danger input-group-append px-5 material-symbols-outlined font-size-20'>delete</button>
|
||||
<button type='button' class='btn btn-danger btn-square input-group-append px-5 d-flex align-items-center'><Trash2 size='1.8rem' /></button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -71,14 +68,14 @@
|
|||
}}>
|
||||
{#if !SUPPORTS.isAndroid}
|
||||
<div class='input-group-prepend grab'>
|
||||
<span class='input-group-text d-flex justify-content-center px-5 material-symbols-outlined font-size-20'>swap_vert</span>
|
||||
<span class='input-group-text d-flex align-items-center px-5'><ArrowDownUp size='1.8rem' /></span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class='input-group-prepend'>
|
||||
<button use:click={() => moveItem(index, 'up')} class='input-group-text d-flex justify-content-center px-5 material-symbols-outlined font-size-20 pointer'>arrow_upward</button>
|
||||
<button use:click={() => swapItem(index, index - 1)} class='input-group-text d-flex align-items-center px-5 pointer'><ArrowUp size='1.8rem' /></button>
|
||||
</div>
|
||||
<div class='input-group-prepend'>
|
||||
<button use:click={() => moveItem(index, 'down')} class='input-group-text d-flex justify-content-center px-5 material-symbols-outlined font-size-20 pointer'>arrow_downward</button>
|
||||
<button use:click={() => swapItem(index, index + 1)} class='input-group-text d-flex align-items-center px-5 pointer'><ArrowDown size='1.8rem' /></button>
|
||||
</div>
|
||||
{/if}
|
||||
<select class='form-control bg-dark w-300 mw-full' bind:value={homeSections[index]}>
|
||||
|
|
@ -87,7 +84,7 @@
|
|||
{/each}
|
||||
</select>
|
||||
<div class='input-group-append'>
|
||||
<button type='button' use:click={() => { homeSections.splice(index, 1); homeSections = homeSections }} class='btn btn-danger input-group-append px-5 material-symbols-outlined font-size-20'>delete</button>
|
||||
<button type='button' use:click={() => { homeSections.splice(index, 1); homeSections = homeSections }} class='btn btn-danger btn-square input-group-append px-5 d-flex align-items-center'><Trash2 size='1.8rem' /></button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import IPC from '@/modules/ipc.js'
|
||||
import SettingCard from './SettingCard.svelte'
|
||||
import { SUPPORTS } from '@/modules/support.js'
|
||||
import { Trash2 } from 'lucide-svelte'
|
||||
function updateAngle () {
|
||||
IPC.emit('angle', settings.value.angle)
|
||||
}
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
<option value='Judas [Small Size]'>{settings.toshoURL + 'rss2?qx=1&q="[Judas] "'}</option>
|
||||
</datalist>
|
||||
<div class='input-group-append'>
|
||||
<button type='button' use:click={() => { settings.rssFeedsNew.splice(i, 1); settings.rssFeedsNew = settings.rssFeedsNew }} class='btn btn-danger input-group-append px-5 material-symbols-outlined font-size-20'>delete</button>
|
||||
<button type='button' use:click={() => { settings.rssFeedsNew.splice(i, 1); settings.rssFeedsNew = settings.rssFeedsNew }} class='btn btn-danger btn-square input-group-append px-5 d-flex align-items-center'><Trash2 size='1.8rem' /></button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import { SUPPORTS } from '@/modules/support.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { Trash2 } from 'lucide-svelte'
|
||||
export let settings
|
||||
|
||||
async function changeFont ({ detail }) {
|
||||
|
|
@ -39,7 +40,7 @@
|
|||
<div class='input-group w-400 mw-full'>
|
||||
<FontSelect class='form-control bg-dark w-300 mw-full' on:change={changeFont} value={settings.font?.name} />
|
||||
<div class='input-group-append'>
|
||||
<button type='button' class='btn btn-danger btn-square px-5 material-symbols-outlined font-size-20' use:click={() => removeFont()}>delete</button>
|
||||
<button type='button' use:click={() => removeFont()} class='btn btn-danger btn-square input-group-append px-5 d-flex align-items-center'><Trash2 size='1.8rem' /></button>
|
||||
</div>
|
||||
</div>
|
||||
</SettingCard>
|
||||
|
|
|
|||
|
|
@ -38,27 +38,28 @@
|
|||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { logout } from '@/components/Logout.svelte'
|
||||
import smoothScroll from '@/modules/scroll.js'
|
||||
import { AppWindow, Heart, LogIn, Logs, Play, Rss, Settings } from 'lucide-svelte'
|
||||
|
||||
const groups = {
|
||||
player: {
|
||||
name: 'Player',
|
||||
icon: 'play_arrow'
|
||||
icon: Play
|
||||
},
|
||||
torrent: {
|
||||
name: 'Torrent',
|
||||
icon: 'rss_feed'
|
||||
icon: Rss
|
||||
},
|
||||
interface: {
|
||||
name: 'Interface',
|
||||
icon: 'settings'
|
||||
icon: AppWindow
|
||||
},
|
||||
app: {
|
||||
name: 'App',
|
||||
icon: 'info'
|
||||
icon: Settings
|
||||
},
|
||||
changelog: {
|
||||
name: 'Changelog',
|
||||
icon: 'description'
|
||||
icon: Logs
|
||||
}
|
||||
}
|
||||
function pathListener (data) {
|
||||
|
|
@ -97,28 +98,28 @@
|
|||
<div class='px-20 py-15 font-size-24 font-weight-semi-bold'>Settings</div>
|
||||
{#each Object.values(groups) as group}
|
||||
<TabLabel>
|
||||
<div class='px-20 py-10 d-flex'>
|
||||
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>{group.icon}</span>
|
||||
<div class='font-size-16'>{group.name}</div>
|
||||
<div class='px-20 py-10 d-flex align-items-center'>
|
||||
<svelte:component this={group.icon} class='pr-10 d-inline-flex' size='3.1rem' fill={group.icon === Play ? 'currentColor' : 'transparent'} />
|
||||
<div class='font-size-16 line-height-normal'>{group.name}</div>
|
||||
</div>
|
||||
</TabLabel>
|
||||
{/each}
|
||||
<div class='pointer my-5 rounded' tabindex='0' role='button' use:click={() => IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/')}>
|
||||
<div class='px-20 py-10 d-flex'>
|
||||
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>favorite</span>
|
||||
<div class='font-size-16'>Donate</div>
|
||||
<div class='px-20 py-10 d-flex align-items-center'>
|
||||
<Heart class='pr-10 d-inline-flex' size='3.1rem' />
|
||||
<div class='font-size-16 line-height-normal'>Donate</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='pointer my-5 rounded' use:click={loginButton}>
|
||||
<div class='px-20 py-10 d-flex'>
|
||||
<div class='px-20 py-10 d-flex align-items-center'>
|
||||
{#if anilistClient.userID?.viewer?.data?.Viewer}
|
||||
<span class='material-symbols-outlined rounded mr-10'>
|
||||
<span class='rounded mr-10'>
|
||||
<img src={anilistClient.userID.viewer.data.Viewer.avatar.medium} class='h-30 rounded' alt='logo' />
|
||||
</span>
|
||||
<div class='font-size-16 login-image-text'>Logout</div>
|
||||
{:else}
|
||||
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>login</span>
|
||||
<div class='font-size-16'>Login With AniList</div>
|
||||
<LogIn class='pr-10 d-inline-flex' size='3.1rem' />
|
||||
<div class='font-size-16 line-height-normal'>Login With AniList</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -233,10 +234,6 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.h-30 {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
.settings :global(input:not(:focus):invalid) {
|
||||
box-shadow: 0 0 0 0.2rem var(--danger-color) !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
import IPC from '@/modules/ipc.js'
|
||||
import SettingCard from './SettingCard.svelte'
|
||||
import { SUPPORTS } from '@/modules/support.js'
|
||||
import { Trash2 } from 'lucide-svelte'
|
||||
export let settings
|
||||
|
||||
function handleFolder () {
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
<div class='input-group-prepend overflow-hidden w-full'>
|
||||
<span class='input-group-text bg-dark w-full'>{extension}</span>
|
||||
</div>
|
||||
<button type='button' class='btn btn-danger btn-square px-5 material-symbols-outlined font-size-20' use:click={() => removeExtension(i)}>delete</button>
|
||||
<button type='button' use:click={() => removeExtension(i)} class='btn btn-danger btn-square input-group-append px-5 d-flex align-items-center'><Trash2 size='1.8rem' /></button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script context='module'>
|
||||
import { click } from '@/modules/click.js'
|
||||
import { fastPrettyBytes, since } from '@/modules/util.js'
|
||||
import { Database, BadgeCheck } from 'lucide-svelte'
|
||||
|
||||
/** @typedef {import('@thaunknown/ani-resourced/sources/types.d.ts').Result} Result */
|
||||
/** @typedef {import('anitomyscript').AnitomyResult} AnitomyResult */
|
||||
|
|
@ -86,13 +87,9 @@
|
|||
<div class='d-flex w-full'>
|
||||
<div class='font-size-22 font-weight-bold text-nowrap'>{result.parseObject?.release_group && result.parseObject.release_group.length < 20 ? result.parseObject.release_group : 'No Group'}</div>
|
||||
{#if result.type === 'batch'}
|
||||
<div class='material-symbols-outlined card-title symbol-bold ml-auto' title='Batch'>
|
||||
database
|
||||
</div>
|
||||
<Database size='2.6rem' class='ml-auto' />
|
||||
{:else if result.verified}
|
||||
<div class='material-symbols-outlined card-title symbol-bold ml-auto' style='color: #53da33' title='Verified'>
|
||||
verified
|
||||
</div>
|
||||
<BadgeCheck size='2.8rem' class='ml-auto' style='color: #53da33' />
|
||||
{/if}
|
||||
</div>
|
||||
<div class='font-size-14 text-muted text-truncate overflow-hidden'>{simplifyFilename(result.parseObject)}</div>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
import { add } from '@/modules/torrent.js'
|
||||
import TorrentSkeletonCard from './TorrentSkeletonCard.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { MagnifyingGlass } from 'svelte-radix'
|
||||
|
||||
/** @type {{ media: Media, episode?: number }} */
|
||||
export let search
|
||||
|
|
@ -130,7 +131,7 @@
|
|||
{/await}
|
||||
<div class='input-group mt-20'>
|
||||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text d-flex material-symbols-outlined bg-dark pr-0 font-size-18'>search</span>
|
||||
<MagnifyingGlass size='2.75rem' class='input-group-text bg-dark pr-0' />
|
||||
</div>
|
||||
<input
|
||||
type='search'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { findInCurrent } from '../Player/MediaHandler.svelte'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
|
||||
const rss = writable(null)
|
||||
export const rss = writable(null)
|
||||
|
||||
export function playAnime (media, episode = 1, force) {
|
||||
episode = Number(episode)
|
||||
|
|
|
|||
|
|
@ -1,161 +0,0 @@
|
|||
<script>
|
||||
import { alToken } from '../../views/Settings.svelte'
|
||||
import { addToast } from '../../components/Toasts.svelte'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { getContext } from 'svelte'
|
||||
import { getMediaMaxEp } from '@/modules/anime.js'
|
||||
import { playAnime } from '../RSSView.svelte'
|
||||
import { click } from '@/modules/click.js'
|
||||
export let media = null
|
||||
|
||||
const toggleStatusMap = {
|
||||
CURRENT: 'DROPPED',
|
||||
COMPLETED: 'REPEATING',
|
||||
PAUSED: 'CURRENT',
|
||||
REPEATING: 'CURRENT',
|
||||
DROPPED: 'PLANNING',
|
||||
PLANNING: 'remove'
|
||||
}
|
||||
async function toggleStatus () {
|
||||
if (media.mediaListEntry?.status !== 'PLANNING') {
|
||||
// add
|
||||
await setStatus(toggleStatusMap[media.mediaListEntry?.status] || 'PLANNING')
|
||||
} else {
|
||||
await anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
}
|
||||
update()
|
||||
}
|
||||
function getStatusText (media) {
|
||||
if (media.mediaListEntry) {
|
||||
const { status } = media.mediaListEntry
|
||||
if (status === 'PLANNING') return 'Remove From List'
|
||||
if (media.mediaListEntry?.status in toggleStatusMap) return 'Drop From Watching'
|
||||
}
|
||||
return 'Add To List'
|
||||
}
|
||||
function setStatus (status, other = {}) {
|
||||
const variables = {
|
||||
id: media.id,
|
||||
status,
|
||||
...other
|
||||
}
|
||||
return anilistClient.entry(variables)
|
||||
}
|
||||
async function update () {
|
||||
media = (await anilistClient.searchIDSingle({ id: media.id })).data.Media
|
||||
}
|
||||
async function score (media, score) {
|
||||
const variables = {
|
||||
id: media.id,
|
||||
score: score * 10
|
||||
}
|
||||
await anilistClient.entry(variables)
|
||||
media = (await anilistClient.searchIDSingle({ id: media.id })).data.Media
|
||||
}
|
||||
const trailer = getContext('trailer')
|
||||
function viewTrailer (media) {
|
||||
$trailer = media.trailer.id
|
||||
}
|
||||
function copyToClipboard (text) {
|
||||
navigator.clipboard.writeText(text)
|
||||
addToast({
|
||||
title: 'Copied to clipboard',
|
||||
text: 'Copied share URL to clipboard',
|
||||
type: 'primary',
|
||||
duration: '5000'
|
||||
})
|
||||
}
|
||||
function openInBrowser (url) {
|
||||
IPC.emit('open', url)
|
||||
}
|
||||
function getPlayText (media) {
|
||||
if (media.mediaListEntry) {
|
||||
const { status, progress } = media.mediaListEntry
|
||||
if (progress) {
|
||||
if (status === 'COMPLETED') return 'Rewatch'
|
||||
return 'Continue ' + Math.min(getMediaMaxEp(media, true), progress + 1)
|
||||
}
|
||||
}
|
||||
return 'Play'
|
||||
}
|
||||
async function play (media) {
|
||||
let ep = 1
|
||||
if (media.mediaListEntry) {
|
||||
const { status, progress } = media.mediaListEntry
|
||||
if (progress) {
|
||||
if (status === 'COMPLETED') {
|
||||
await setStatus('REPEATING', { episode: 0 })
|
||||
} else {
|
||||
ep = Math.min(getMediaMaxEp(media, true), progress + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
playAnime(media, ep, true)
|
||||
media = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='col-md-4 d-flex justify-content-end flex-column'>
|
||||
<div class='d-flex flex-column flex-wrap'>
|
||||
<button
|
||||
class='btn btn-primary d-flex align-items-center font-weight-bold font-size-24 h-50 mb-5'
|
||||
type='button'
|
||||
use:click={() => play(media)}>
|
||||
<span class='material-symbols-outlined mr-10 font-size-24 w-30'> play_arrow </span>
|
||||
<span>{getPlayText(media)}</span>
|
||||
</button>
|
||||
{#if alToken}
|
||||
<button class='btn d-flex align-items-center mb-5 font-weight-bold font-size-16 btn-primary' use:click={toggleStatus}>
|
||||
<span class='material-symbols-outlined mr-10 font-size-18 w-30'> {(media.mediaListEntry?.status in toggleStatusMap) ? 'remove' : 'add'} </span>
|
||||
{getStatusText(media)}
|
||||
</button>
|
||||
<div class='input-group shadow-lg mb-5 font-size-16'>
|
||||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text bg-tp pl-15 d-flex material-symbols-outlined font-size-18'>hotel_class</span>
|
||||
</div>
|
||||
<select class='form-control' required value={(media.mediaListEntry?.score || '').toString()} on:change={({ target }) => { score(media, Number(target.value)) }}>
|
||||
<option value selected disabled hidden>Score</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
<option>4</option>
|
||||
<option>5</option>
|
||||
<option>6</option>
|
||||
<option>7</option>
|
||||
<option>8</option>
|
||||
<option>9</option>
|
||||
<option>10</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
{#if media.trailer}
|
||||
<button class='btn d-flex align-items-center mb-5 font-weight-bold font-size-16' use:click={() => viewTrailer(media)}>
|
||||
<span class='material-symbols-outlined mr-15 font-size-18 w-30'> live_tv </span>
|
||||
Trailer
|
||||
</button>
|
||||
{/if}
|
||||
<div class='d-flex mb-5 w-full'>
|
||||
<button class='btn flex-fill font-weight-bold font-size-16 d-flex align-items-center' use:click={() => { openInBrowser(`https://anilist.co/anime/${media.id}`) }}>
|
||||
<span class='material-symbols-outlined mr-15 font-size-18 w-30'> open_in_new </span>
|
||||
Open
|
||||
</button>
|
||||
<button class='btn flex-fill font-weight-bold font-size-16 ml-5 d-flex align-items-center' use:click={() => { copyToClipboard(`https://miru.watch/anime/${media.id}`) }}>
|
||||
<span class='material-symbols-outlined mr-15 font-size-18 w-30'> share </span>
|
||||
Share
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
select.form-control:invalid {
|
||||
color: var(--dm-input-placeholder-text-color);
|
||||
}
|
||||
.bg-tp {
|
||||
background-color: var(--dm-button-bg-color) !important;
|
||||
}
|
||||
.w-30 {
|
||||
width: 3rem
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
<script>
|
||||
import { Building2, FolderKanban, Languages, Leaf, MonitorPlay, Type } from 'lucide-svelte'
|
||||
|
||||
export let media = null
|
||||
|
||||
const detailsMap = [
|
||||
{ property: 'season', label: 'Season', icon: 'spa', custom: 'property' },
|
||||
{ property: 'status', label: 'Status', icon: 'live_tv' },
|
||||
{ property: 'nodes', label: 'Studio', icon: 'business' },
|
||||
{ property: 'source', label: 'Source', icon: 'source' },
|
||||
{ property: 'english', label: 'English', icon: 'title' },
|
||||
{ property: 'romaji', label: 'Romaji', icon: 'translate' },
|
||||
{ property: 'season', label: 'Season', icon: Leaf, custom: 'property' },
|
||||
{ property: 'status', label: 'Status', icon: MonitorPlay },
|
||||
{ property: 'nodes', label: 'Studio', icon: Building2 },
|
||||
{ property: 'source', label: 'Source', icon: FolderKanban },
|
||||
{ property: 'english', label: 'English', icon: Type },
|
||||
{ property: 'romaji', label: 'Romaji', icon: Languages },
|
||||
{ property: 'native', label: 'Native', icon: '語', custom: 'icon' }
|
||||
]
|
||||
function getCustomProperty (detail, media) {
|
||||
|
|
@ -33,12 +35,16 @@
|
|||
{#each detailsMap as detail}
|
||||
{@const property = getProperty(detail.property, media)}
|
||||
{#if property}
|
||||
<div class='d-flex flex-row mx-10 py-5'>
|
||||
<div class={'mr-10 ' + (detail.custom === 'icon' ? 'd-flex align-items-center text-nowrap font-size-20 font-weight-bold' : 'material-symbols-outlined font-size-24')}>
|
||||
{detail.icon}
|
||||
</div>
|
||||
<div class='d-flex flex-row mx-10 py-5 justify-content-center'>
|
||||
{#if detail.custom !== 'icon'}
|
||||
<svelte:component size='2rem' this={detail.icon} class='mr-10' />
|
||||
{:else}
|
||||
<div class='mr-10 d-flex align-items-center text-nowrap font-size-12 font-weight-bold line-height-normal'>
|
||||
{detail.icon}
|
||||
</div>
|
||||
{/if}
|
||||
<div class='d-flex flex-column justify-content-center text-nowrap'>
|
||||
<div class='font-weight-bold select-all'>
|
||||
<div class='font-weight-bold select-all line-height-normal'>
|
||||
{#if detail.custom === 'property'}
|
||||
{getCustomProperty(detail, media)}
|
||||
{:else if property.constructor === Array}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
<script context='module'>
|
||||
let fillerEpisodes = {}
|
||||
|
||||
fetch('https://raw.githubusercontent.com/ThaUnknown/filler-scrape/master/filler.json').then(async res => {
|
||||
fillerEpisodes = await res.json()
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { since } from '@/modules/util'
|
||||
import { click } from '@/modules/click.js'
|
||||
|
|
@ -22,12 +30,12 @@
|
|||
export let play
|
||||
|
||||
const episodeList = Array.from({ length: episodeCount }, (_, i) => ({
|
||||
episode: i + 1, image: null, summary: null, rating: null, title: null, length: null, airdate: null, airingAt: null
|
||||
episode: i + 1, image: null, summary: null, rating: null, title: null, length: null, airdate: null, airingAt: null, filler: fillerEpisodes[id]?.includes(i)
|
||||
}))
|
||||
async function load () {
|
||||
const res = await fetch('https://api.ani.zip/mappings?anilist_id=' + id)
|
||||
const { episodes, specialCount, episodeCount } = await res.json()
|
||||
/** @type {{ airingAt: number; episode: number; }[]} */
|
||||
/** @type {{ airingAt: number; episode: number; filler?: boolean }[]} */
|
||||
let alEpisodes = episodeList
|
||||
|
||||
// fallback: pull episodes from airing schedule if anime doesn't have expected episode count
|
||||
|
|
@ -35,14 +43,14 @@
|
|||
const settled = (await anilistClient.episodes({ id })).data.Page?.airingSchedules
|
||||
if (settled?.length) alEpisodes = settled
|
||||
}
|
||||
for (const { episode, airingAt } of alEpisodes) {
|
||||
for (const { episode, airingAt, filler } of alEpisodes) {
|
||||
const alDate = new Date((airingAt || 0) * 1000)
|
||||
|
||||
// validate by air date if the anime has specials AND doesn't have matching episode count
|
||||
const needsValidation = !(!specialCount || (media.episodes && media.episodes === episodeCount && episodes[Number(episode)]))
|
||||
const { image, summary, rating, title, length, airdate } = needsValidation ? episodeByAirDate(alDate, episodes, episode) : (episodes[Number(episode)] || {})
|
||||
|
||||
episodeList[episode - 1] = { episode, image, summary, rating, title, length: length || duration, airdate: +alDate || airdate, airingAt: +alDate || airdate }
|
||||
episodeList[episode - 1] = { episode, image, summary, rating, title, length: length || duration, airdate: +alDate || airdate, airingAt: +alDate || airdate, filler }
|
||||
}
|
||||
}
|
||||
load()
|
||||
|
|
@ -50,17 +58,22 @@
|
|||
const animeProgress = liveAnimeProgress(id)
|
||||
</script>
|
||||
|
||||
{#each episodeOrder ? episodeList : [...episodeList].reverse() as { episode, image, summary, rating, title, length, airdate }}
|
||||
{#each episodeOrder ? episodeList : [...episodeList].reverse() as { episode, image, summary, rating, title, length, airdate, filler }}
|
||||
{@const completed = !watched && userProgress >= episode}
|
||||
{@const target = userProgress + 1 === episode}
|
||||
{@const progress = !watched && ($animeProgress?.[episode] ?? 0)}
|
||||
<div class='w-full my-20 content-visibility-auto scale' class:opacity-half={completed} class:px-20={!target} class:h-150={image || summary}>
|
||||
<div class='rounded w-full h-full overflow-hidden d-flex flex-xsm-column flex-row pointer' class:border={target} class:bg-black={completed} class:bg-dark={!completed} use:click={() => play(episode)}>
|
||||
<div class='rounded w-full h-full overflow-hidden d-flex flex-xsm-column flex-row pointer position-relative' class:border={target || filler} class:bg-black={completed} class:border-secondary={filler} class:bg-dark={!completed} use:click={() => play(episode)}>
|
||||
{#if image}
|
||||
<div class='h-full'>
|
||||
<img alt='thumbnail' src={image} class='img-cover h-full' />
|
||||
</div>
|
||||
{/if}
|
||||
{#if filler}
|
||||
<div class='position-absolute bottom-0 right-0 bg-secondary py-5 px-10 text-dark rounded-top rounded-left font-weight-bold'>
|
||||
Filler
|
||||
</div>
|
||||
{/if}
|
||||
<div class='h-full w-full px-20 py-15 d-flex flex-column'>
|
||||
<div class='w-full d-flex flex-row mb-15'>
|
||||
<div class='text-white font-weight-bold font-size-16 overflow-hidden title'>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { ExternalLink } from 'lucide-svelte'
|
||||
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
|
|
@ -22,7 +23,9 @@
|
|||
<img src={friend.user.avatar.medium} alt='avatar' class='w-50 h-50 img-fluid rounded cover-img' />
|
||||
<span class='my-0 pl-20 mr-auto text-truncate'>{friend.user.name}</span>
|
||||
<span class='my-0 px-10 text-capitalize'>{friend.status.toLowerCase()}</span>
|
||||
<span class='material-symbols-outlined pointer text-primary font-size-18' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
|
||||
<span class='pointer text-primary d-flex align-items-center' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}>
|
||||
<ExternalLink size='1.8rem' />
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
<script>
|
||||
import { playAnime } from '../RSSView.svelte'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { getMediaMaxEp } from '@/modules/anime.js'
|
||||
import { getContext } from 'svelte'
|
||||
import Details from './Details.svelte'
|
||||
import Following from './Following.svelte'
|
||||
import Controls from './Controls.svelte'
|
||||
import ToggleList from './ToggleList.svelte'
|
||||
import { click } from '@/modules/click.js'
|
||||
|
||||
const view = getContext('view')
|
||||
const trailer = getContext('trailer')
|
||||
function close () {
|
||||
$view = null
|
||||
}
|
||||
$: media = $view
|
||||
let modal
|
||||
$: media && modal?.focus()
|
||||
$: !$trailer && modal?.focus()
|
||||
$: maxPlayEp = getMediaMaxEp($view || {}, true)
|
||||
function checkClose ({ keyCode }) {
|
||||
if (keyCode === 27) close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='modal modal-full z-40' class:show={media} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal}>
|
||||
{#if media}
|
||||
<div class='h-full modal-content bg-very-dark p-0 overflow-y-auto'>
|
||||
<button class='close pointer z-30 bg-dark top-20 right-0 position-absolute' type='button' use:click={close}> × </button>
|
||||
<div class='h-md-half w-full position-relative z-20'>
|
||||
<div class='h-full w-full position-absolute bg-dark-light banner' style:--bannerurl={`url('${media.bannerImage || ''}')`} />
|
||||
<div class='d-flex h-full top w-full'>
|
||||
<div class='container-xl w-full'>
|
||||
<div class='row d-flex justify-content-end flex-row h-full px-20 pt-20 px-xl-0'>
|
||||
<div class='col-md-3 col-4 d-flex h-full justify-content-end flex-column pb-15 align-items-center'>
|
||||
<img class='contain-img rounded mw-full mh-full shadow' alt='cover' src={media.coverImage?.extraLarge || media.coverImage?.medium} />
|
||||
</div>
|
||||
<div class='col-md-9 col-8 row align-content-end'>
|
||||
<div class='col-md-8 col-12 d-flex justify-content-end flex-column pl-20'>
|
||||
<div class='px-md-20 d-flex flex-column font-size-12'>
|
||||
<span class='title font-weight-bold pb-sm-15 text-white select-all'>
|
||||
{media.title.userPreferred}
|
||||
</span>
|
||||
<div class='d-flex flex-row font-size-18 pb-sm-15'>
|
||||
{#if media.averageScore}
|
||||
<span class='material-symbols-outlined mr-10 font-size-24'> trending_up </span>
|
||||
<span class='mr-20'>
|
||||
Rating: {media.averageScore + '%'}
|
||||
</span>
|
||||
{/if}
|
||||
{#if media.format}
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'> monitor </span>
|
||||
<span class='mr-20 text-capitalize'>
|
||||
Format: {media.format === 'TV' ? media.format : media.format?.replace(/_/g, ' ').toLowerCase()}
|
||||
</span>
|
||||
{/if}
|
||||
{#if media.episodes !== 1 && getMediaMaxEp(media)}
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'> theaters </span>
|
||||
<span class='mr-20'>
|
||||
Episodes: {getMediaMaxEp(media)}
|
||||
</span>
|
||||
{:else if media.duration}
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'> timer </span>
|
||||
<span class='mr-20'>
|
||||
Length: {media.duration + ' min'}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class='pb-15 pt-5 px-5 overflow-x-auto text-nowrap font-weight-bold'>
|
||||
{#each media.genres as genre}
|
||||
<div class='badge badge-pill shadow'>
|
||||
{genre}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Controls bind:media={$view} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='container-xl bg-very-dark z-10'>
|
||||
<div class='row p-20 px-xl-0 flex-column-reverse flex-md-row'>
|
||||
<div class='col-md-9 pr-50'>
|
||||
<h1 class='title font-weight-bold text-white'>Synopsis</h1>
|
||||
<div class='font-size-16 pre-wrap select-all card m-0'>
|
||||
{media.description?.replace(/<[^>]*>/g, '') || ''}
|
||||
</div>
|
||||
<ToggleList list={media.relations?.edges?.filter(({ node }) => node.type === 'ANIME')} let:item title='Relations'>
|
||||
<div class='w-150 mx-15 my-10 rel pointer'
|
||||
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.id })).data.Media }}>
|
||||
<img loading='lazy' src={item.node.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
|
||||
<div class='pt-5'>{item.relationType.replace(/_/g, ' ').toLowerCase()}</div>
|
||||
<h5 class='font-weight-bold text-white mb-5'>{item.node.title.userPreferred}</h5>
|
||||
</div>
|
||||
</ToggleList>
|
||||
{#if maxPlayEp}
|
||||
<h1 class='title font-weight-bold text-white pt-20'>Episodes</h1>
|
||||
<div class='card m-0 d-inline-block'>
|
||||
<table class='table table-hover w-500 table-auto '>
|
||||
<tbody>
|
||||
{#each Array(maxPlayEp) as _, i}
|
||||
{@const ep = maxPlayEp - i}
|
||||
<tr class="font-size-20 py-10 pointer {ep <= media.mediaListEntry?.progress ? 'text-muted' : 'text-white'}"
|
||||
use:click={() => {
|
||||
playAnime(media, ep)
|
||||
close()
|
||||
}}>
|
||||
<td class='w-full font-weight-semi-bold'>Episode {ep}</td>
|
||||
<td class='material-symbols-outlined text-right h-full d-table-cell'>play_arrow</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
<ToggleList list={media.recommendations.edges.filter(edge => edge.node.mediaRecommendation)} let:item title='Recommendations'>
|
||||
<div class='w-150 mx-15 my-10 rel pointer'
|
||||
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.mediaRecommendation.id })).data.Media }}>
|
||||
<img loading='lazy' src={item.node.mediaRecommendation.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
|
||||
<h5 class='font-weight-bold text-white mb-5'>{item.node.mediaRecommendation.title.userPreferred}</h5>
|
||||
</div>
|
||||
</ToggleList>
|
||||
</div>
|
||||
<div class='col-md-3 px-sm-0 px-20'>
|
||||
{#if media.mediaListEntry?.progress}
|
||||
<h1 class='title font-weight-bold text-white'>Progress</h1>
|
||||
<div class='card m-0 pt-20 pb-15 d-flex flex-md-column flex-row text-capitalize align-items-start'>
|
||||
<div class='progress w-full'>
|
||||
<div class='progress-bar' role='progressbar' style='width: {media.mediaListEntry?.progress / getMediaMaxEp(media) * 100}%;' />
|
||||
</div>
|
||||
<div class='font-weight-bold pt-10'>
|
||||
{media.mediaListEntry?.progress} / {getMediaMaxEp(media)} Available Episodes
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<Details {media} />
|
||||
<Following {media} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pre-wrap {
|
||||
white-space: pre-wrap
|
||||
}
|
||||
.banner {
|
||||
background: no-repeat center center;
|
||||
background-size: cover;
|
||||
background-image: linear-gradient(0deg, rgba(17, 20, 23, 1) 0%, rgba(17, 20, 23, 0.8) 25%, rgba(17, 20, 23, 0.4) 50%, rgba(37, 40, 44, 0) 100%), var(--bannerurl) !important;
|
||||
}
|
||||
|
||||
.d-table-cell {
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
.top {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.title {
|
||||
font-size: 4rem;
|
||||
}
|
||||
.pr-50 {
|
||||
padding-right: 5rem;
|
||||
}
|
||||
.close {
|
||||
top: 4rem !important;
|
||||
left: unset !important;
|
||||
right: 2.5rem !important;
|
||||
}
|
||||
.badge {
|
||||
background-color: var(--dm-button-bg-color) !important;
|
||||
padding: 0.6rem 2rem;
|
||||
font-size: 1.4rem;
|
||||
border: none;
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
.rel-img{
|
||||
height: 27rem;
|
||||
width: 17rem
|
||||
}
|
||||
|
||||
.cover-img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.rel {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.rel:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
import smoothScroll from '@/modules/scroll.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { alToken } from '@/modules/settings.js'
|
||||
import { Bookmark, Clapperboard, ExternalLink, Heart, Play, Share2, Timer, TrendingUp, Tv } from 'lucide-svelte'
|
||||
|
||||
const view = getContext('view')
|
||||
function close () {
|
||||
|
|
@ -68,6 +69,15 @@
|
|||
IPC.emit('open', url)
|
||||
}
|
||||
let episodeOrder = true
|
||||
|
||||
// async function score (media, score) {
|
||||
// const variables = {
|
||||
// id: media.id,
|
||||
// score: score * 10
|
||||
// }
|
||||
// await anilistClient.entry(variables)
|
||||
// media = (await anilistClient.searchIDSingle({ id: media.id })).data.Media
|
||||
// }
|
||||
</script>
|
||||
|
||||
<div class='modal modal-full z-100' class:show={media} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal}>
|
||||
|
|
@ -86,7 +96,7 @@
|
|||
<div class='d-flex flex-row font-size-18 flex-wrap mt-5'>
|
||||
{#if media.averageScore}
|
||||
<div class='d-flex flex-row mt-10'>
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'> trending_up </span>
|
||||
<TrendingUp class='mx-10' size='2.2rem' />
|
||||
<span class='mr-20'>
|
||||
Rating: {media.averageScore + '%'}
|
||||
</span>
|
||||
|
|
@ -94,7 +104,7 @@
|
|||
{/if}
|
||||
{#if media.format}
|
||||
<div class='d-flex flex-row mt-10'>
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'> monitor </span>
|
||||
<Tv class='mx-10' size='2.2rem' />
|
||||
<span class='mr-20 text-capitalize'>
|
||||
Format: {formatMap[media.format]}
|
||||
</span>
|
||||
|
|
@ -102,14 +112,14 @@
|
|||
{/if}
|
||||
{#if media.episodes !== 1 && getMediaMaxEp(media)}
|
||||
<div class='d-flex flex-row mt-10'>
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'> theaters </span>
|
||||
<Clapperboard class='mx-10' size='2.2rem' />
|
||||
<span class='mr-20'>
|
||||
Episodes: {getMediaMaxEp(media)}
|
||||
</span>
|
||||
</div>
|
||||
{:else if media.duration}
|
||||
<div class='d-flex flex-row mt-10'>
|
||||
<span class='material-symbols-outlined mx-10 font-size-24'> timer </span>
|
||||
<Timer class='mx-10' size='2.2rem' />
|
||||
<span class='mr-20'>
|
||||
Length: {media.duration + ' min'}
|
||||
</span>
|
||||
|
|
@ -120,24 +130,40 @@
|
|||
<button class='btn btn-lg btn-secondary w-250 text-dark font-weight-bold shadow-none border-0 d-flex align-items-center justify-content-center mr-10 mt-20'
|
||||
use:click={() => play()}
|
||||
disabled={media.status === 'NOT_YET_RELEASED'}>
|
||||
<span class='material-symbols-outlined font-size-24 filled pr-10'>
|
||||
play_arrow
|
||||
</span>
|
||||
<Play class='mr-10' fill='currentColor' size='1.6rem' />
|
||||
{playButtonText}
|
||||
</button>
|
||||
<div class='mt-20'>
|
||||
<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} disabled={!alToken}>
|
||||
favorite
|
||||
<div class='mt-20 d-flex'>
|
||||
<button class='btn bg-dark btn-lg btn-square d-flex align-items-center justify-content-center shadow-none border-0' use:click={toggleFavourite} disabled={!alToken}>
|
||||
<Heart fill={media.isFavourite ? 'currentColor' : 'transparent'} size='1.7rem' />
|
||||
</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} disabled={!alToken}>
|
||||
bookmark
|
||||
<button class='btn bg-dark btn-lg btn-square d-flex align-items-center justify-content-center shadow-none border-0 ml-10' use:click={toggleStatus} disabled={!alToken}>
|
||||
<Bookmark fill={media.mediaListEntry ? 'currentColor' : 'transparent'} size='1.7rem' />
|
||||
</button>
|
||||
<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 class='btn bg-dark btn-lg btn-square d-flex align-items-center justify-content-center shadow-none border-0 ml-10' use:click={() => copyToClipboard(`https://miru.watch/anime/${media.id}`)}>
|
||||
<Share2 size='1.7rem' />
|
||||
</button>
|
||||
<button class='btn bg-dark btn-lg btn-square ml-10 material-symbols-outlined font-size-20 shadow-none border-0' use:click={() => openInBrowser(`https://anilist.co/anime/${media.id}`)}>
|
||||
open_in_new
|
||||
<button class='btn bg-dark btn-lg btn-square d-flex align-items-center justify-content-center shadow-none border-0 ml-10' use:click={() => openInBrowser(`https://anilist.co/anime/${media.id}`)}>
|
||||
<ExternalLink size='1.7rem' />
|
||||
</button>
|
||||
<!-- <div class='input-group shadow-lg mb-5 font-size-16'>
|
||||
<div class='input-group-prepend'>
|
||||
<span class='input-group-text bg-tp pl-15 d-flex font-size-18'>hotel_class</span> stars
|
||||
</div>
|
||||
<select class='form-control' required value={(media.mediaListEntry?.score || '').toString()} on:change={({ target }) => { score(media, Number(target.value)) }}>
|
||||
<option value selected disabled hidden>Score</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
<option>4</option>
|
||||
<option>5</option>
|
||||
<option>6</option>
|
||||
<option>7</option>
|
||||
<option>8</option>
|
||||
<option>9</option>
|
||||
<option>10</option>
|
||||
</select>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { ExternalLink, User } from 'lucide-svelte'
|
||||
export let peers
|
||||
export let invite
|
||||
export let cleanup
|
||||
|
|
@ -17,15 +18,12 @@
|
|||
{#if peer.user?.avatar?.medium}
|
||||
<img src={peer.user?.avatar?.medium} alt='avatar' class='w-50 h-50 img-fluid rounded' />
|
||||
{:else}
|
||||
<span class='material-symbols-outlined w-50 h-50 anon'> person </span>
|
||||
<span class='w-50 h-50 anon d-flex align-items-center'><User size='4rem' /></span>
|
||||
{/if}
|
||||
<h4 class='my-0 pl-20 mr-auto'>{peer.user?.name || 'Anonymous'}</h4>
|
||||
<h4 class='my-0 pl-20 mr-auto line-height-normal'>{peer.user?.name || 'Anonymous'}</h4>
|
||||
{#if peer.user?.name}
|
||||
<span class='material-symbols-outlined pointer text-primary' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + peer.user?.name)}> open_in_new </span>
|
||||
<span class='pointer text-primary d-flex align-items-center' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + peer.user?.name)}><ExternalLink size='2.5rem' /></span>
|
||||
{/if}
|
||||
<!-- {#if state === 'host'}
|
||||
<span class='material-symbols-outlined ml-15 pointer text-danger' use:click={() => peer.peer.pc.close()}> logout </span>
|
||||
{/if} -->
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
|
||||
<script>
|
||||
import Lobby from './Lobby.svelte'
|
||||
import { Plus, UserPlus } from 'lucide-svelte'
|
||||
|
||||
let joinText
|
||||
|
||||
|
|
@ -179,11 +180,11 @@
|
|||
{#if !$state}
|
||||
<div class='d-flex flex-row flex-wrap justify-content-center align-items-center h-full mb-20 pb-20 root'>
|
||||
<div class='card d-flex flex-column align-items-center w-300 h-300 justify-content-end'>
|
||||
<span class='font-size-80 material-symbols-outlined d-flex align-items-center h-full'>add</span>
|
||||
<Plus size='6rem' class='d-flex align-items-center h-full' />
|
||||
<button class='btn btn-primary btn-lg mt-10 btn-block' type='button' use:click={() => joinLobby()}>Create Lobby</button>
|
||||
</div>
|
||||
<div class='card d-flex flex-column align-items-center w-300 h-300 justify-content-end'>
|
||||
<span class='font-size-80 material-symbols-outlined d-flex align-items-center h-full'>group_add</span>
|
||||
<UserPlus size='6rem' class='d-flex align-items-center h-full' />
|
||||
<h2 class='font-weight-bold'>Join Lobby</h2>
|
||||
<input
|
||||
type='text'
|
||||
|
|
@ -203,7 +204,4 @@
|
|||
.font-size-50 {
|
||||
font-size: 5rem;
|
||||
}
|
||||
.font-size-80 {
|
||||
font-size: 8rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ module.exports = (parentDir, alias = {}, aliasFields = 'browser', filename = 'ap
|
|||
module: false,
|
||||
url: false,
|
||||
debug: resolve(__dirname, './modules/debug.js'),
|
||||
'svelte-radix': resolve(__dirname, '../node_modules/svelte-radix/dist/index.js'),
|
||||
'bittorrent-tracker/lib/client/websocket-tracker.js': resolve('../node_modules/bittorrent-tracker/lib/client/websocket-tracker.js')
|
||||
},
|
||||
extensions: ['.mjs', '.js', '.svelte']
|
||||
|
|
|
|||
|
|
@ -142,15 +142,9 @@ importers:
|
|||
|
||||
common:
|
||||
dependencies:
|
||||
'@fontsource-variable/material-symbols-outlined':
|
||||
specifier: ^5.0.24
|
||||
version: 5.0.24
|
||||
'@fontsource-variable/nunito':
|
||||
specifier: ^5.0.18
|
||||
version: 5.0.18
|
||||
'@fontsource/roboto':
|
||||
specifier: ^5.0.12
|
||||
version: 5.0.12
|
||||
'@thaunknown/ani-resourced':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
|
|
@ -172,6 +166,9 @@ importers:
|
|||
js-levenshtein:
|
||||
specifier: ^1.1.6
|
||||
version: 1.1.6
|
||||
lucide-svelte:
|
||||
specifier: ^0.429.0
|
||||
version: 0.429.0(svelte@4.2.12)
|
||||
p2pt:
|
||||
specifier: github:ThaUnknown/p2pt#modernise
|
||||
version: github.com/ThaUnknown/p2pt/9ad7a56ed6ee43f5664ebad33b803702ee349316
|
||||
|
|
@ -191,8 +188,8 @@ importers:
|
|||
specifier: ^4.2.12
|
||||
version: 4.2.12
|
||||
svelte-keybinds:
|
||||
specifier: ^1.0.8
|
||||
version: 1.0.8
|
||||
specifier: ^1.0.9
|
||||
version: 1.0.9
|
||||
svelte-loader:
|
||||
specifier: ^3.1.9
|
||||
version: 3.1.9(svelte@4.2.12)
|
||||
|
|
@ -202,6 +199,9 @@ importers:
|
|||
svelte-persisted-store:
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(svelte@4.2.12)
|
||||
svelte-radix:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(svelte@4.2.12)
|
||||
svelte-sonner:
|
||||
specifier: ^0.3.19
|
||||
version: 0.3.19(svelte@4.2.12)
|
||||
|
|
@ -5280,7 +5280,7 @@ packages:
|
|||
array-union: 2.1.0
|
||||
dir-glob: 3.0.1
|
||||
fast-glob: 3.3.2
|
||||
ignore: 5.3.1
|
||||
ignore: 5.3.2
|
||||
merge2: 1.4.1
|
||||
slash: 3.0.0
|
||||
|
||||
|
|
@ -5642,11 +5642,11 @@ packages:
|
|||
/ignore@5.3.1:
|
||||
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
||||
engines: {node: '>= 4'}
|
||||
dev: false
|
||||
|
||||
/ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
dev: false
|
||||
|
||||
/immediate-chunk-store@2.2.0:
|
||||
resolution: {integrity: sha512-1bHBna0hCa6arRXicu91IiL9RvvkbNYLVq+mzWdaLGZC3hXvX4doh8e1dLhMKez5siu63CYgO5NrGJbRX5lbPA==}
|
||||
|
|
@ -6335,6 +6335,14 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/lucide-svelte@0.429.0(svelte@4.2.12):
|
||||
resolution: {integrity: sha512-3LwbxQn5i9/zQwMg0ZWT875djOEQoR3eM8ytOGqk0eRk4wkoqqgZX0Gr+aQjkd+RSDEVMXkIEoyW/1zLyXr/6g==}
|
||||
peerDependencies:
|
||||
svelte: ^3 || ^4 || ^5.0.0-next.42
|
||||
dependencies:
|
||||
svelte: 4.2.12
|
||||
dev: false
|
||||
|
||||
/magic-string@0.30.10:
|
||||
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
||||
dependencies:
|
||||
|
|
@ -8622,8 +8630,8 @@ packages:
|
|||
svelte: 4.2.12
|
||||
dev: true
|
||||
|
||||
/svelte-keybinds@1.0.8:
|
||||
resolution: {integrity: sha512-clP0/5XgMWXNlImQALBQYyrfN06fgWKWufoMumeGOABOsewScPsv1jRFZzks9f3Xh2e53XgZIx57Fb/a6d/ptw==}
|
||||
/svelte-keybinds@1.0.9:
|
||||
resolution: {integrity: sha512-bQt9azkXX4SgMJpJzYWQB6D0hj45+Ro2+2Awr4YNtjmuRuKdio+Rxuhky5JJyBBfyRQ7YT63nSR3whH4FACv1A==}
|
||||
dev: false
|
||||
|
||||
/svelte-loader@3.1.9(svelte@4.2.12):
|
||||
|
|
@ -8698,6 +8706,14 @@ packages:
|
|||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
/svelte-radix@1.1.0(svelte@4.2.12):
|
||||
resolution: {integrity: sha512-kyE9wZiJV937INGb+wiBkAjmGtQUUYRPkVL2Q+/gj+9Vog1Ewd2wNvNmpNMUd+c+euxoc5u5YZMuCUgky9EUPw==}
|
||||
peerDependencies:
|
||||
svelte: ^3.54.0 || ^4.0.0 || ^5.0.0
|
||||
dependencies:
|
||||
svelte: 4.2.12
|
||||
dev: false
|
||||
|
||||
/svelte-sonner@0.3.19(svelte@4.2.12):
|
||||
resolution: {integrity: sha512-jpPOgLtHwRaB6Vqo2dUQMv15/yUV/BQWTjKpEqQ11uqRSHKjAYUKZyGrHB2cQsGmyjR0JUzBD58btpgNqINQ/Q==}
|
||||
peerDependencies:
|
||||
|
|
|
|||
Loading…
Reference in a new issue