feat: touchpad and keyboard support

This commit is contained in:
ThaUnknown 2023-07-10 21:57:51 +02:00
parent eebb8128d3
commit dc307ddb84
26 changed files with 156 additions and 114 deletions

View file

@ -1,6 +1,6 @@
{
"name": "Miru",
"version": "4.1.10",
"version": "4.1.11",
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"description": "Stream anime torrents, real-time with no waiting for downloads.",
"main": "build/main.js",

View file

@ -1,8 +1,8 @@
const { app, BrowserWindow, protocol, shell, ipcMain, dialog, MessageChannelMain } = require('electron')
const path = require('path')
const { Client } = require('discord-rpc')
const log = require('electron-log')
const { autoUpdater } = require('electron-updater')
import { app, BrowserWindow, protocol, shell, ipcMain, dialog, MessageChannelMain } from 'electron'
import path from 'path'
import { Client } from 'discord-rpc'
import log from 'electron-log'
import { autoUpdater } from 'electron-updater'
const flags = [
['enable-gpu-rasterization'],
@ -144,7 +144,7 @@ function createWindow () {
}
})
mainWindow.webContents.session.webRequest.onHeadersReceived({ urls: ['https://sneedex.moe/api/public/nyaa'] }, ({ responseHeaders, url }, fn) => {
mainWindow.webContents.session.webRequest.onHeadersReceived({ urls: ['https://sneedex.moe/api/public/nyaa', 'http://animetosho.org/storage/torrent/*'] }, ({ responseHeaders }, fn) => {
responseHeaders['Access-Control-Allow-Origin'] = '*'
fn({ responseHeaders })

View file

@ -1,5 +1,6 @@
<script>
import { getContext } from 'svelte'
import { click } from '@/modules/click.js'
export let page
const view = getContext('view')
@ -14,7 +15,7 @@
<div class='w-full z-50 navbar bg-transparent border-0 p-0 d-flex'>
<div class='d-flex h-full draggable align-items-center text-center'>
{#if window.version.platform !== 'darwin'}
<img src='./logo.ico' class='position-absolute w-50 h-50 m-10 pointer' alt='ico' on:pointerdown={close} />
<img src='./logo.ico' class='position-absolute w-50 h-50 m-10 pointer' alt='ico' use:click={close} />
{/if}
</div>
<div class='h-full bg-dark flex-grow-1' />

View file

@ -11,6 +11,7 @@
<script>
import { traceAnime } from '@/modules/anime.js'
import { set } from '../views/Settings.svelte'
import { click } from '@/modules/click.js'
export let search
let searchTextInput
@ -179,7 +180,7 @@
</div>
<div class='col-auto p-10 d-flex'>
<div class='align-self-end'>
<button class='btn btn-square bg-dark-light material-symbols-outlined font-size-18 px-5 align-self-end border-0' type='button' on:click={searchClear} class:text-primary={!!sanitisedSearch?.length}>
<button class='btn btn-square bg-dark-light material-symbols-outlined font-size-18 px-5 align-self-end border-0' type='button' use:click={searchClear} class:text-primary={!!sanitisedSearch?.length}>
delete
</button>
</div>
@ -192,8 +193,8 @@
<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={set.cards === 'small'} on:click={() => changeCardMode('small')}>grid_on</span>
<span class='material-symbols-outlined font-size-24 filled text-dark-light pointer' class:text-muted={set.cards === 'full'} on:click={() => changeCardMode('full')}>grid_view</span>
<span class='material-symbols-outlined font-size-24 mr-10 filled ml-auto text-dark-light pointer' class:text-muted={set.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={set.cards === 'full'} use:click={() => changeCardMode('full')}>grid_view</span>
</div>
</form>

View file

@ -4,7 +4,7 @@
import { media } from '../views/Player/MediaHandler.svelte'
import { platformMap } from '../views/Settings.svelte'
import { addToast } from './Toasts.svelte'
import { wrapEnter } from '@/modules/util.js'
import { click } from '@/modules/click.js'
const view = getContext('view')
export let page
const links = [
@ -97,13 +97,10 @@
<div class='sidebar z-30'>
<div class='sidebar-menu h-full d-flex flex-column justify-content-center align-items-center m-0 pb-5' class:animate={page !== 'player'}>
{#each links as { click, icon, text, image, css, page: _page }, i (i)}
{#each links as { click: _click, icon, text, image, css, page: _page }, i (i)}
<div
class='sidebar-link sidebar-link-with-icon pointer overflow-hidden {css}'
tabindex='0'
role='button'
on:click={click}
on:keydown={wrapEnter(click)}>
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}>

View file

@ -1,6 +1,7 @@
<script>
import { getContext } from 'svelte'
import { TABS } from './Tabs.svelte'
import { click } from '@/modules/click.js'
const tab = {}
const { registerTab, selectTab, selectedTab } = getContext(TABS)
@ -8,6 +9,6 @@
registerTab(tab)
</script>
<div class={'pointer border-bottom ' + ($selectedTab === tab ? 'bg-dark-light' : '')} on:click={() => selectTab(tab)}>
<div class={'pointer border-bottom ' + ($selectedTab === tab ? 'bg-dark-light' : '')} use:click={() => selectTab(tab)}>
<slot />
</div>

View file

@ -1,5 +1,6 @@
<script context='module'>
import { writable } from 'svelte/store'
import { click } from '@/modules/click.js'
const toasts = writable({})
let index = 0
export function addToast (opts) {
@ -25,8 +26,8 @@
<div class='sticky-alerts d-flex flex-column-reverse'>
{#each Object.entries($toasts) as [index, toast] (index)}
<div class='alert alert-{toast.type} filled' class:pointer={toast.click} on:click={toast.click}>
<button class='close' type='button' on:click={() => close(index)}><span aria-hidden='true'>×</span></button>
<div class='alert alert-{toast.type} filled' class:pointer={toast.click} use:click={toast.click}>
<button class='close' type='button' use:click={() => close(index)}><span aria-hidden='true'>×</span></button>
<h4 class='alert-heading'>{toast.title}</h4>
{@html toast.text}
</div>

View file

@ -1,6 +1,7 @@
<script>
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
import { alRequest } from '@/modules/anilist.js'
import { click } from '@/modules/click.js'
export let media
async function toggleStatus () {
@ -68,13 +69,13 @@
</div>
<div class='d-flex flex-row pb-20'>
<button class='btn bg-dark-light px-20 shadow-none border-0'
on:pointerdown|stopPropagation={() => playMedia(media)}>
use:click={() => playMedia(media)}>
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={media.isFavourite} on:pointerdown|stopPropagation={() => toggleFavourite(media)}>
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.isFavourite} use:click={toggleFavourite}>
favorite
</button>
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.mediaListEntry} on:pointerdown|stopPropagation={() => toggleStatus(media)}>
<button class='btn bg-dark-light btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.mediaListEntry} use:click={toggleStatus}>
bookmark
</button>
</div>

View file

@ -1,6 +1,7 @@
<script>
import { statusColorMap } from '@/modules/anime.js'
import EpisodePreviewCard from './EpisodePreviewCard.svelte'
import { hoverClick } from '@/modules/click.js'
import { since } from '@/modules/util'
export let data
@ -9,7 +10,7 @@
const episodeThumbnail = data.episodeData?.image || data.media?.bannerImage || data.media?.coverImage?.extraLarge || ' '
</script>
<div class='d-flex pt-20 px-20 position-relative' on:pointerenter={() => { preview = true }} on:pointerleave={() => { preview = false }} on:pointerdown={data.onclick}>
<div class='d-flex pt-20 px-20 position-relative' on:pointerenter={() => { preview = true }} on:custom-pointerleave={() => { preview = false }} use:hoverClick={data.onclick}>
{#if preview}
<EpisodePreviewCard {data} />
{/if}

View file

@ -2,6 +2,7 @@
import { getContext } from 'svelte'
import PreviewCard from './PreviewCard.svelte'
import { formatMap, statusColorMap } from '@/modules/anime.js'
import { hoverClick } from '@/modules/click.js'
export let media
let preview = false
@ -11,7 +12,7 @@
}
</script>
<div class='d-flex px-20 py-10 position-relative' on:pointerenter={() => { preview = true }} on:pointerleave={() => { preview = false }} on:pointerdown={viewMedia}>
<div class='d-flex px-20 py-10 position-relative' on:pointerenter={() => { preview = true }} on:custom-pointerleave={() => { preview = false }} use:hoverClick={viewMedia}>
{#if preview}
<PreviewCard {media} />
{/if}

View file

@ -1,6 +1,7 @@
<script>
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
import { alRequest } from '@/modules/anilist.js'
import { click } from '@/modules/click.js'
export let media
let hide = true
@ -40,6 +41,10 @@
})
media.isFavourite = !media.isFavourite
}
function play () {
if (media.status === 'NOT_YET_RELEASED') return
playMedia(media)
}
// TODO: add a volume toggle icon for trailer
function volume (video) {
video.volume = 0.05
@ -78,14 +83,14 @@
</div>
<div class='d-flex flex-row pt-5'>
<button class='btn btn-secondary flex-grow-1 text-dark font-weight-bold shadow-none border-0'
on:pointerdown|stopPropagation={() => media.status !== 'NOT_YET_RELEASED' && playMedia(media)}
use:click={play}
disabled={media.status === 'NOT_YET_RELEASED'}>
{playButtonText}
</button>
<button class='btn btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.isFavourite} on:pointerdown|stopPropagation={toggleFavourite}>
<button class='btn btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.isFavourite} use:click={toggleFavourite}>
favorite
</button>
<button class='btn btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.mediaListEntry} on:pointerdown|stopPropagation={toggleStatus}>
<button class='btn btn-square ml-10 material-symbols-outlined font-size-16 shadow-none border-0' class:filled={media.mediaListEntry} use:click={toggleStatus}>
bookmark
</button>
</div>

View file

@ -2,6 +2,7 @@
import { getContext } from 'svelte'
import PreviewCard from './PreviewCard.svelte'
import { formatMap, statusColorMap } from '@/modules/anime.js'
import { hoverClick } from '@/modules/click.js'
export let media
let preview = false
@ -11,7 +12,7 @@
}
</script>
<div class='d-flex p-20 position-relative first-check' on:pointerenter={() => { preview = true }} on:pointerleave={() => { preview = false }} on:pointerdown={viewMedia}>
<div class='d-flex p-20 position-relative first-check' on:pointerenter={() => { preview = true }} on:custom-pointerleave={() => { preview = false }} use:hoverClick={viewMedia}>
{#if preview}
<PreviewCard {media} />
{/if}

View file

@ -0,0 +1,38 @@
let lastTapElement = null
const noop = () => {}
document.addEventListener('pointerup', () => {
lastTapElement?.dispatchEvent(new Event('custom-pointerleave'))
lastTapElement = null
})
export function click (node, cb = noop) {
node.tabIndex = 0
node.role = 'button'
node.addEventListener('pointerup', e => {
e.stopPropagation()
cb(e)
})
node.addEventListener('keydown', e => { if (e.key === 'Enter') cb(e) })
}
export function hoverClick (node, cb = noop) {
node.tabIndex = 0
node.role = 'button'
node.addEventListener('pointerup', e => {
if (e.pointerType === 'mouse') return cb(e)
e.stopPropagation()
lastTapElement?.dispatchEvent(new Event('custom-pointerleave'))
if (lastTapElement === node) {
lastTapElement = null
cb(e)
} else {
lastTapElement = node
}
})
node.addEventListener('keydown', e => { if (e.key === 'Enter') cb(e) })
node.addEventListener('pointerleave', ({ pointerType }) => {
if (pointerType === 'mouse') node.dispatchEvent(new Event('custom-pointerleave'))
})
}

View file

@ -119,12 +119,6 @@ export function debounce (fn, time) {
}
};
export function wrapEnter (fn) {
return ({ key }) => {
if (key === 'Enter') fn()
}
}
export function binarySearch (arr, el) {
let left = 0
let right = arr.length - 1

View file

@ -6,7 +6,7 @@
import Card from '@/components/cards/Card.svelte'
import { search } from '../Search.svelte'
import { page } from '@/App.svelte'
import { wrapEnter } from '@/modules/util.js'
import { click } from '@/modules/click.js'
export let opts
@ -20,7 +20,7 @@
observer.observe(element)
}
function click () {
function _click () {
$search = {
...opts.variables,
load: opts.load
@ -31,10 +31,8 @@
</script>
<span class='d-flex px-20 align-items-end pointer text-decoration-none text-muted'
on:click={click} on:keydown={wrapEnter(click)}
tabindex='0'
use:deferredLoad
role='button'>
use:click={_click}
use:deferredLoad>
<div class='font-size-24 font-weight-semi-bold'>{opts.title}</div>
<div class='pr-10 ml-auto font-size-12'>View More</div>
</span>
@ -57,4 +55,7 @@
background: linear-gradient(270deg, #17191cff 0%, #17191c00 100%);
pointer-events: none;
}
.gallery {
overflow-x: clip
}
</style>

View file

@ -1,5 +1,6 @@
<script>
import { set } from './Settings.svelte'
import { click } from '@/modules/click.js'
let block = false
async function testConnection () {
@ -25,11 +26,11 @@
<div class='font-size-16'>Most features of Miru will not function correctly without being able to connect to Tosho.</div>
<div class='font-size-16'>If you enable a VPN a restart might be required for it to take effect.</div>
<!-- eslint-disable-next-line svelte/valid-compile -->
<div class='font-size-16'>Visit <a class='text-primary pointer' on:click={() => { window.IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>this guide</a> for a tutorial on how to bypass ISP blocks.</div>
<div class='font-size-16'>Visit <a class='text-primary pointer' use:click={() => { window.IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>this guide</a> for a tutorial on how to bypass ISP blocks.</div>
<div class='d-flex w-full mt-20 pt-20'>
<button class='btn ml-auto mr-5' type='button' on:click={() => { block = false }}>I Understand</button>
<button class='btn btn-primary mr-5' type='button' on:click={() => { window.IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>Open Guide</button>
<button class='btn btn-primary' type='button' on:click={testConnection}>Reconnect</button>
<button class='btn ml-auto mr-5' type='button' use:click={() => { block = false }}>I Understand</button>
<button class='btn btn-primary mr-5' type='button' use:click={() => { window.IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>Open Guide</button>
<button class='btn btn-primary' type='button' use:click={testConnection}>Reconnect</button>
</div>
</div>
</div>

View file

@ -5,10 +5,11 @@
import { createEventDispatcher } from 'svelte'
import { alEntry } from '@/modules/anilist.js'
import Subtitles from '@/modules/subtitles.js'
import { toTS, videoRx, fastPrettyBytes, wrapEnter } from '@/modules/util.js'
import { toTS, videoRx, fastPrettyBytes } from '@/modules/util.js'
import { addToast } from '../../components/Toasts.svelte'
import { getChaptersAniSkip } from '@/modules/anime.js'
import Seekbar from 'perfect-seekbar'
import { click } from '@/modules/click.js'
import { w2gEmitter } from '../WatchTogether/WatchTogether.svelte'
import Keybinds, { loadWithDefaults, condition } from 'svelte-keybinds'
@ -880,8 +881,8 @@
on:keypress={resetImmerse}
on:mouseleave={immersePlayer}>
{#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' on:click|self={() => (showKeybinds = false)} on:keydown={wrapEnter(() => (showKeybinds = false))}>
<button class='btn btn-square rounded-circle bg-dark font-size-16 top-0 right-0 m-10 position-absolute' type='button' on:click={() => (showKeybinds = false)} on:keydown={wrapEnter(() => (showKeybinds = false))}>&times;</button>
<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'>
<button class='btn btn-square rounded-circle bg-dark font-size-16 top-0 right-0 m-10 position-absolute' type='button' use:click={() => (showKeybinds = false)}>&times;</button>
<Keybinds let:prop={item} autosave={true} clickable={true}>
<div class:material-symbols-outlined={item?.type} class='bind'>{item?.id || ''}</div>
</Keybinds>
@ -925,7 +926,7 @@
on:leavepictureinpicture={() => (pip = false)} />
{#if stats}
<div class='position-absolute top-0 bg-tp p-10 m-15 text-monospace rounded z-50'>
<button class='close' type='button' on:click={toggleStats}><span>×</span></button>
<button class='close' type='button' use:click={toggleStats}><span>×</span></button>
FPS: {stats.fps}<br />
Presented frames: {stats.presented}<br />
Dropped frames: {stats.dropped}<br />
@ -950,15 +951,15 @@
<div class='col-4' />
</div>
<div class='middle d-flex align-items-center justify-content-center flex-grow-1 position-relative'>
<div class='w-full h-full position-absolute' on:dblclick={toggleFullscreen} on:click|self={() => { if (page === 'player') playPause(); page = 'player' }} on:keydown={wrapEnter(() => { if (page === 'player') playPause(); page = 'player' })} />
<span class='material-symbols-outlined ctrl' class:text-muted={!hasLast} class:disabled={!hasLast} data-name='playLast' on:pointerdown={playLast}> skip_previous </span>
<span class='material-symbols-outlined ctrl' data-name='rewind' on:pointerdown={rewind}> fast_rewind </span>
<span class='material-symbols-outlined ctrl' data-name='playPause' on:pointerdown={playPause}> {ended ? 'replay' : paused ? 'play_arrow' : 'pause'} </span>
<span class='material-symbols-outlined ctrl' data-name='forward' on:pointerdown={forward}> fast_forward </span>
<span class='material-symbols-outlined ctrl' class:text-muted={!hasNext} class:disabled={!hasNext} data-name='playNext' on:pointerdown={playNext}> skip_next </span>
<div class='w-full h-full position-absolute' on:dblclick={toggleFullscreen} on:click|self={() => { if (page === 'player') playPause(); page = 'player' }} />
<span class='material-symbols-outlined ctrl' class:text-muted={!hasLast} class:disabled={!hasLast} data-name='playLast' use:click={playLast}> skip_previous </span>
<span class='material-symbols-outlined ctrl' data-name='rewind' use: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='material-symbols-outlined ctrl' data-name='forward' use:click={forward}> fast_forward </span>
<span class='material-symbols-outlined ctrl' class:text-muted={!hasNext} class:disabled={!hasNext} data-name='playNext' use:click={playNext}> skip_next </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' on:click={skip}>
<button class='skip btn text-dark position-absolute bottom-0 right-0 mr-20 mb-5 font-weight-bold' use:click={skip}>
Skip {currentSkippable}
</button>
{/if}
@ -978,28 +979,28 @@
/>
</div>
<div class='d-flex'>
<span class='material-symbols-outlined ctrl' title='Play/Pause [Space]' data-name='playPause' on:click={playPause} on:keydown={wrapEnter(playPause)}> {ended ? 'replay' : paused ? 'play_arrow' : 'pause'} </span>
<span class='material-symbols-outlined ctrl' title='Play/Pause [Space]' data-name='playPause' use:click={playPause}> {ended ? 'replay' : paused ? 'play_arrow' : 'pause'} </span>
{#if hasLast}
<span class='material-symbols-outlined ctrl' title='Last [B]' data-name='playLast' on:click={playLast} on:keydown={wrapEnter(playLast)}> skip_previous </span>
<span class='material-symbols-outlined ctrl' title='Last [B]' data-name='playLast' use:click={playLast}> skip_previous </span>
{/if}
{#if hasNext}
<span class='material-symbols-outlined ctrl' title='Next [N]' data-name='playNext' on:click={playNext} on:keydown={wrapEnter(playNext)}> skip_next </span>
<span class='material-symbols-outlined ctrl' title='Next [N]' data-name='playNext' use:click={playNext}> skip_next </span>
{/if}
<div class='d-flex w-auto volume'>
<span class='material-symbols-outlined ctrl' title='Mute [M]' data-name='toggleMute' on:click={toggleMute} on:keydown={wrapEnter(toggleMute)}> {muted ? 'volume_off' : 'volume_up'} </span>
<span class='material-symbols-outlined ctrl' title='Mute [M]' data-name='toggleMute' use:click={toggleMute}> {muted ? 'volume_off' : 'volume_up'} </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 mr-auto'>{toTS(targetTime, safeduration > 3600 ? 2 : 3)} / {toTS(safeduration - targetTime, safeduration > 3600 ? 2 : 3)}</div>
<span class='material-symbols-outlined ctrl' title='Keybinds [`]' on:pointerdown={() => (showKeybinds = true)}> keyboard </span>
<span class='material-symbols-outlined ctrl' title='Keybinds [`]' use:click={() => (showKeybinds = true)}> keyboard </span>
{#if 'audioTracks' in HTMLVideoElement.prototype && video?.audioTracks?.length > 1}
<div class='dropdown dropup with-arrow' on:click={toggleDropdown} on:keydown={wrapEnter(toggleDropdown)}>
<div class='dropdown dropup with-arrow' use:click={toggleDropdown}>
<span class='material-symbols-outlined ctrl' title='Audio Tracks'>
queue_music
</span>
<div class='dropdown-menu dropdown-menu-left ctrl custom-radio p-10 pb-5 text-capitalize'>
{#each video.audioTracks as track}
<input name='audio-radio-set' type='radio' id='audio-{track.id}-radio' value={track.id} checked={track.enabled} />
<label for='audio-{track.id}-radio' on:pointerdown|stopPropagation={() => selectAudio(track.id)} class='text-truncate pb-5'>
<label for='audio-{track.id}-radio' use:click={() => selectAudio(track.id)} class='text-truncate pb-5'>
{(track.language || (!Object.values(video.audioTracks).some(track => track.language === 'eng' || track.language === 'en') ? 'eng' : track.label)) + (track.label ? ' - ' + track.label : '')}
</label>
{/each}
@ -1014,7 +1015,7 @@
<div class='dropdown-menu dropdown-menu-left ctrl custom-radio p-10 pb-5 text-capitalize'>
{#each video.videoTracks as track}
<input name='video-radio-set' type='radio' id='video-{track.id}-radio' value={track.id} checked={track.selected} />
<label for='video-{track.id}-radio' on:click|stopPropagation={() => selectVideo(track.id)} class='text-truncate pb-5' on:keydown={wrapEnter(() => selectVideo(track.id))}>
<label for='video-{track.id}-radio' use:click={() => selectVideo(track.id)} class='text-truncate pb-5'>
{(track.language || (!Object.values(video.videoTracks).some(track => track.language === 'eng' || track.language === 'en') ? 'eng' : track.label)) + (track.label ? ' - ' + track.label : '')}
</label>
{/each}
@ -1022,17 +1023,17 @@
</div>
{/if}
{#if subHeaders?.length}
<div class='subtitles dropdown dropup with-arrow' on:click={toggleDropdown} on:keydown={wrapEnter(toggleDropdown)}>
<div class='subtitles dropdown dropup with-arrow' use:click={toggleDropdown}>
<span class='material-symbols-outlined ctrl' title='Subtitles [C]'>
subtitles
</span>
<div class='dropdown-menu dropdown-menu-right ctrl custom-radio p-10 pb-5 text-capitalize w-200'>
<input name='subtitle-radio-set' type='radio' id='subtitle-off-radio' value='off' checked={subHeaders && subs?.current === -1} />
<label for='subtitle-off-radio' on:click|stopPropagation={() => subs.selectCaptions(-1)} class='text-truncate pb-5' on:keydown={wrapEnter(() => subs.selectCaptions(-1))}> OFF </label>
<label for='subtitle-off-radio' use:click={() => subs.selectCaptions(-1)} class='text-truncate pb-5'> OFF </label>
{#each subHeaders as track}
{#if track}
<input name='subtitle-radio-set' type='radio' id='subtitle-{track.number}-radio' value={track.numer} checked={track.number === subs.current} />
<label for='subtitle-{track.nubmer}-radio' on:click={() => subs.selectCaptions(track.number)} on:keydown={wrapEnter(() => subs.selectCaptions(track.number))} class='text-truncate pb-5'>
<label for='subtitle-{track.nubmer}-radio' use:click={() => subs.selectCaptions(track.number)} class='text-truncate pb-5'>
{(track.language || (!Object.values(subs.headers).some(header => header.language === 'eng' || header.language === 'en') ? 'eng' : track.type)) + (track.name ? ' - ' + track.name : '')}
</label>
{/if}
@ -1042,16 +1043,16 @@
</div>
{/if}
{#if 'PresentationRequest' in window && canCast && current}
<span class='material-symbols-outlined ctrl' title='Cast Video [D]' data-name='toggleCast' on:click={toggleCast} on:keydown={wrapEnter(toggleCast)}>
<span class='material-symbols-outlined ctrl' title='Cast Video [D]' data-name='toggleCast' use:click={toggleCast}>
{presentationConnection ? 'cast_connected' : 'cast'}
</span>
{/if}
{#if 'pictureInPictureEnabled' in document}
<span class='material-symbols-outlined ctrl' title='Popout Window [P]' data-name='togglePopout' on:click={togglePopout} on:keydown={wrapEnter(togglePopout)}>
<span class='material-symbols-outlined ctrl' title='Popout Window [P]' data-name='togglePopout' use:click={togglePopout}>
{pip ? 'featured_video' : 'picture_in_picture'}
</span>
{/if}
<span class='material-symbols-outlined ctrl' title='Fullscreen [F]' data-name='toggleFullscreen' on:click={toggleFullscreen} on:keydown={wrapEnter(toggleFullscreen)}>
<span class='material-symbols-outlined ctrl' title='Fullscreen [F]' data-name='toggleFullscreen' use:click={toggleFullscreen}>
{isFullscreen ? 'fullscreen_exit' : 'fullscreen'}
</span>
</div>

View file

@ -1,9 +1,10 @@
<script context='module'>
import { wrapEnter, since } from '@/modules/util.js'
import { since } from '@/modules/util.js'
import { set } from './Settings.svelte'
import { addToast } from '../components/Toasts.svelte'
import { findInCurrent } from './Player/MediaHandler.svelte'
import getRSSEntries from '@/modules/providers/tosho.js'
import { click } from '@/modules/click.js'
import { writable } from 'svelte/store'
@ -72,11 +73,11 @@
$: table && modal?.focus()
</script>
<div class='modal z-40' class:show={table} id='viewAnime' on:keydown={checkClose} tabindex='-1' bind:this={modal}>
<div class='modal z-40' class:show={table} id='viewAnime'>
{#if table}
<div class='modal-dialog p-20' role='document' on:click|self={close} on:keydown|self={wrapEnter(close)}>
<div class='modal-dialog p-20' on:pointerup|self={close} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal}>
<div class='modal-content w-auto'>
<button class='close pointer z-30 right-0 position-absolute' type='button' on:click={close}> &times; </button>
<button class='close pointer z-30 right-0 position-absolute' type='button' use:click={close}> &times; </button>
<table class='table table-hover'>
<thead>
<tr>
@ -92,7 +93,7 @@
</thead>
<tbody class='results pointer'>
{#each table as row, index}
<tr class:text-primary={row.best} on:click={() => play(row)} on:keydown={wrapEnter(() => play(row))} tabindex='0' role='button'>
<tr class:text-primary={row.best} use:click={() => play(row)}>
<th>{index + 1}</th>
<td>{row.title}</td>
<td>{row.size}</td>

View file

@ -1,5 +1,6 @@
<script context='module'>
import { addToast } from '../components/Toasts.svelte'
import { click } from '@/modules/click.js'
export let alToken = localStorage.getItem('ALtoken') || null
const defaults = {
playerAutoplay: true,
@ -183,7 +184,7 @@
</TabLabel>
{/each}
<button
on:click={() => window.IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/')}
use:click={() => window.IPC.emit('open', 'https://github.com/sponsors/ThaUnknown/')}
class='btn btn-primary mx-20 mt-auto'
type='button'
data-toggle='tooltip'
@ -192,7 +193,7 @@
Donate
</button>
<button
on:click={checkUpdate}
use:click={checkUpdate}
class='btn btn-primary mx-20 mt-10'
type='button'
data-toggle='tooltip'
@ -201,7 +202,7 @@
Check For Updates
</button>
<button
on:click={restoreSettings}
use:click={restoreSettings}
class='btn btn-danger mx-20 mt-10'
type='button'
data-toggle='tooltip'
@ -357,12 +358,12 @@
<option value='Erai-raws [Multi-Sub]'>{set.toshoURL + 'rss2?qx=1&q="[Erai-raws] "'}</option>
</datalist>
<div class='input-group-append'>
<button type='button' on:click={() => { settings.rssFeedsNew.splice(i, 1); settings.rssFeedsNew = settings.rssFeedsNew }} class='btn btn-danger btn-lg input-group-append'>Remove</button>
<button type='button' use:click={() => { settings.rssFeedsNew.splice(i, 1); settings.rssFeedsNew = settings.rssFeedsNew }} class='btn btn-danger btn-lg input-group-append'>Remove</button>
</div>
</div>
{/each}
<div class='input-group input-group-lg form-control-lg mb-10 w-500'>
<button type='button' on:click={() => { settings.rssFeedsNew[settings.rssFeedsNew.length] = ['New Releases', null] }} class='btn btn-lg btn-primary mb-10'>Add Feed</button>
<button type='button' use:click={() => { settings.rssFeedsNew[settings.rssFeedsNew.length] = ['New Releases', null] }} class='btn btn-lg btn-primary mb-10'>Add Feed</button>
</div>
<div class='input-group mb-10 w-300 form-control-lg' data-toggle='tooltip' data-placement='top' data-title='What Quality To Find Torrents In'>
<div class='input-group-prepend'>
@ -422,7 +423,7 @@
data-placement='bottom'
data-title='Path To Folder Which To Use To Store Torrent Files'>
<div class='input-group-prepend'>
<button type='button' on:click={handleFolder} class='btn btn-primary input-group-append'>Select Folder</button>
<button type='button' use:click={handleFolder} class='btn btn-primary input-group-append'>Select Folder</button>
</div>
<input type='text' class='form-control' bind:value={settings.torrentPath} placeholder='Folder Path' />
</div>

View file

@ -5,6 +5,7 @@
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 = {
@ -106,12 +107,12 @@
<button
class='btn btn-primary d-flex align-items-center font-weight-bold font-size-24 h-50 mb-5'
type='button'
on:click={() => play(media)}>
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' on:click={toggleStatus}>
<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>
@ -135,17 +136,17 @@
</div>
{/if}
{#if media.trailer}
<button class='btn d-flex align-items-center mb-5 font-weight-bold font-size-16' on:click={() => viewTrailer(media)}>
<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' on:click={() => { openInBrowser(`https://anilist.co/anime/${media.id}`) }}>
<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' on:click={() => { copyToClipboard(`https://miru.watch/anime/${media.id}`) }}>
<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>

View file

@ -1,6 +1,7 @@
<script>
import { alToken } from '../../views/Settings.svelte'
import { alRequest } from '@/modules/anilist.js'
import { click } from '@/modules/click.js'
export let media = null
let following = null
async function updateFollowing (media) {
@ -28,7 +29,7 @@
<img src={friend.user.avatar.medium} alt='avatar' class='w-30 h-30 img-fluid rounded cover-img' />
<span class='my-0 pl-10 mr-auto text-truncate'>{friend.user.name}</span>
<span class='my-0 px-10 text-capitalize'>{statusMap[friend.status]}</span>
<span class='material-symbols-outlined pointer text-primary font-size-18' on:click={() => window.IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
<span class='material-symbols-outlined pointer text-primary font-size-18' use:click={() => window.IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
</div>
{/each}
</div>

View file

@ -1,4 +1,5 @@
<script>
import { click } from '@/modules/click.js'
export let list = null
let showMore = false
function toggleList () {
@ -7,7 +8,7 @@
export let title = 'Relations'
</script>
{#if list?.length}
<span class='d-flex align-items-end pointer text-decoration-none pt-20' on:click={toggleList}>
<span class='d-flex align-items-end pointer text-decoration-none pt-20' use:click={toggleList}>
<h1 class='font-weight-bold text-white'>{title}</h1>
{#if list.length > 4}
<h6 class='ml-auto font-size-12 more text-muted'>{showMore ? 'Show Less' : 'Show More'}</h6>

View file

@ -2,12 +2,12 @@
import { playAnime } from '../RSSView.svelte'
import { alRequest } from '@/modules/anilist.js'
import { getMediaMaxEp } from '@/modules/anime.js'
import { wrapEnter } from '@/modules/util.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')
@ -24,10 +24,10 @@
}
</script>
<div class='modal modal-full z-40' class:show={media} on:keydown={checkClose} tabindex='-1' bind:this={modal}>
<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' on:click={close}> &times; </button>
<button class='close pointer z-30 bg-dark top-20 right-0 position-absolute' type='button' use:click={close}> &times; </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'>
@ -91,9 +91,7 @@
</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'
on:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.id })).data.Media }}
on:keydown={wrapEnter(async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.id })).data.Media })}
tabindex='0' role='button'>
use:click={async () => { $view = null; $view = (await alRequest({ method: '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>
@ -107,16 +105,10 @@
{#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'}"
on:click={() => {
use:click={() => {
playAnime(media, ep)
close()
}}
on:keydown={wrapEnter(() => {
playAnime(media, ep)
close()
})}
tabindex='0' role='button'
>
}}>
<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>
@ -127,9 +119,7 @@
{/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'
on:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.mediaRecommendation.id })).data.Media }}
on:keydown={wrapEnter(async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.mediaRecommendation.id })).data.Media })}
tabindex='0' role='button'>
use:click={async () => { $view = null; $view = (await alRequest({ method: '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>

View file

@ -1,6 +1,6 @@
<script>
import { getContext } from 'svelte'
import { wrapEnter } from '@/modules/util.js'
import { click } from '@/modules/click.js'
const url = getContext('trailer')
@ -14,11 +14,11 @@
$: $url && modal?.focus()
</script>
<div class='modal z-40' class:show={$url} on:keydown={checkClose} tabindex='-1' bind:this={modal}>
<div class='modal z-40'>
{#if $url}
<div class='modal-dialog' role='document' on:click|self={close} on:keydown|self={wrapEnter(close)}>
<div class='modal-dialog' on:pointerup|self={close} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal}>
<div class='modal-content w-three-quarter h-full bg-transparent d-flex justify-content-center flex-column'>
<button class='close pointer z-30 top-20 right-0 position-absolute' type='button' on:click={close}> &times; </button>
<button class='close pointer z-30 top-20 right-0 position-absolute' type='button' use:click={close}> &times; </button>
<div class='trailer w-full position-relative'>
<iframe
id='trailerVideo'

View file

@ -1,4 +1,5 @@
<script>
import { click } from '@/modules/click.js'
export let peers
export let invite
export let cleanup
@ -7,8 +8,8 @@
<div class='d-flex flex-column py-20 root container card'>
<div class='d-flex align-items-center w-full'>
<h1 class='font-weight-bold mr-auto'>Lobby</h1>
<button class='btn btn-success btn-lg ml-20' type='button' on:click={invite}>Invite To Lobby</button>
<button class='btn btn-danger ml-20 btn-lg' type='button' on:click={cleanup}>Leave lobby</button>
<button class='btn btn-success btn-lg ml-20' type='button' use:click={invite}>Invite To Lobby</button>
<button class='btn btn-danger ml-20 btn-lg' type='button' use:click={cleanup}>Leave lobby</button>
</div>
{#each Object.values(peers) as peer}
<div class='d-flex align-items-center pb-10'>
@ -19,10 +20,10 @@
{/if}
<h4 class='my-0 pl-20 mr-auto'>{peer.user?.name || 'Anonymous'}</h4>
{#if peer.user?.name}
<span class='material-symbols-outlined pointer text-primary' on:pointerdown={() => window.IPC.emit('open', 'https://anilist.co/user/' + peer.user?.name)}> open_in_new </span>
<span class='material-symbols-outlined pointer text-primary' use:click={() => window.IPC.emit('open', 'https://anilist.co/user/' + peer.user?.name)}> open_in_new </span>
{/if}
<!-- {#if state === 'host'}
<span class='material-symbols-outlined ml-15 pointer text-danger' on:click={() => peer.peer.pc.close()}> logout </span>
<span class='material-symbols-outlined ml-15 pointer text-danger' use:click={() => peer.peer.pc.close()}> logout </span>
{/if} -->
</div>
{/each}

View file

@ -6,6 +6,7 @@
import { addToast } from '../../components/Toasts.svelte'
import { page } from '@/App.svelte'
import P2PT from 'p2pt'
import { click } from '@/modules/click.js'
import 'browser-event-target-emitter'
export const w2gEmitter = new EventTarget()
@ -176,7 +177,7 @@
<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>
<button class='btn btn-primary btn-lg mt-10 btn-block' type='button' on:click={() => joinLobby()}>Create Lobby</button>
<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>