feat: nicer notifications

This commit is contained in:
ThaUnknown 2023-07-15 21:50:28 +02:00
parent 2597e00f45
commit 4ca90751e6
16 changed files with 114 additions and 168 deletions

View file

@ -48,6 +48,7 @@
"svelte-keybinds": "1.0.5", "svelte-keybinds": "1.0.5",
"svelte-loader": "^3.1.9", "svelte-loader": "^3.1.9",
"svelte-miniplayer": "1.0.3", "svelte-miniplayer": "1.0.3",
"svelte-sonner": "^0.1.1",
"webpack": "^5.85.0", "webpack": "^5.85.0",
"webpack-cli": "^5.1.3", "webpack-cli": "^5.1.3",
"webpack-dev-server": "^4.15.0", "webpack-dev-server": "^4.15.0",

View file

@ -112,6 +112,9 @@ devDependencies:
svelte-miniplayer: svelte-miniplayer:
specifier: 1.0.3 specifier: 1.0.3
version: 1.0.3 version: 1.0.3
svelte-sonner:
specifier: ^0.1.1
version: 0.1.1(svelte@4.0.4)
webpack: webpack:
specifier: ^5.85.0 specifier: ^5.85.0
version: 5.86.0(webpack-cli@5.1.4) version: 5.86.0(webpack-cli@5.1.4)
@ -5485,6 +5488,14 @@ packages:
resolution: {integrity: sha512-++IvuENs/3x0SbHZ/jWNqfq+hmU/ZT73V/ETwn6TqCbHF7asXsZFxVcgyIL1lMaQMKxe227HrYghYRVNJXeFVw==} resolution: {integrity: sha512-++IvuENs/3x0SbHZ/jWNqfq+hmU/ZT73V/ETwn6TqCbHF7asXsZFxVcgyIL1lMaQMKxe227HrYghYRVNJXeFVw==}
dev: true dev: true
/svelte-sonner@0.1.1(svelte@4.0.4):
resolution: {integrity: sha512-jQ/coEZvJxImxxsT5FPnHeXitGx5wQRhAkAz6C8DY8yM1qFPvFowG6mDtC0KPPDEKlHCmocbWigk6zrG27OO/Q==}
peerDependencies:
svelte: ^4.0.0
dependencies:
svelte: 4.0.4
dev: true
/svelte@4.0.4: /svelte@4.0.4:
resolution: {integrity: sha512-DDJavyX1mpNFLZ7jU9FwBKouemh6CJHZXwePBa5GXSaW5GuHZ361L2/1uznBqOCxu2UsUoWu8wRsB2iB8QG5sQ==} resolution: {integrity: sha512-DDJavyX1mpNFLZ7jU9FwBKouemh6CJHZXwePBa5GXSaW5GuHZ361L2/1uznBqOCxu2UsUoWu8wRsB2iB8QG5sQ==}
engines: {node: '>=16'} engines: {node: '>=16'}

View file

@ -22,8 +22,8 @@
import ViewTrailer from './views/ViewAnime/ViewTrailer.svelte' import ViewTrailer from './views/ViewAnime/ViewTrailer.svelte'
import RSSView from './views/RSSView.svelte' import RSSView from './views/RSSView.svelte'
import Menubar from './components/Menubar.svelte' import Menubar from './components/Menubar.svelte'
import Toasts from './components/Toasts.svelte'
import IspBlock from './views/IspBlock.svelte' import IspBlock from './views/IspBlock.svelte'
import { Toaster, toast } from 'svelte-sonner'
setContext('view', view) setContext('view', view)
@ -31,7 +31,7 @@
</script> </script>
<div id='player' /> <div id='player' />
<Toasts /> <Toaster visibleToasts={3} position='top-right' theme='dark' richColors duration={10000} />
<div class='page-wrapper with-sidebar with-transitions bg-dark' data-sidebar-type='overlayed-all'> <div class='page-wrapper with-sidebar with-transitions bg-dark' data-sidebar-type='overlayed-all'>
<div class='sticky-alerts' /> <div class='sticky-alerts' />
<IspBlock /> <IspBlock />

View file

@ -3,7 +3,7 @@
import { alID } from '@/modules/anilist.js' import { alID } from '@/modules/anilist.js'
import { media } from '../views/Player/MediaHandler.svelte' import { media } from '../views/Player/MediaHandler.svelte'
import { platformMap } from '../views/Settings.svelte' import { platformMap } from '../views/Settings.svelte'
import { addToast } from './Toasts.svelte' import { toast } from 'svelte-sonner'
import { click } from '@/modules/click.js' import { click } from '@/modules/click.js'
const view = getContext('view') const view = getContext('view')
export let page export let page
@ -17,11 +17,9 @@
} else { } else {
window.IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth window.IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
if (platformMap[window.version.platform] === 'Linux') { if (platformMap[window.version.platform] === 'Linux') {
addToast({ toast('Support Notification', {
text: "If your linux distribution doesn't support custom protocol handlers, you can simply paste the full URL into the app.", description: "If your linux distribution doesn't support custom protocol handlers, you can simply paste the full URL into the app.",
title: 'Support Notification', duration: 300000
type: 'secondary',
duration: '300000'
}) })
} }
} }

View file

@ -1,56 +0,0 @@
<script context='module'>
import { writable } from 'svelte/store'
import { click } from '@/modules/click.js'
const toasts = writable({})
let index = 0
export function addToast (opts) {
// type, click, title, text
toasts.update(toasts => {
const i = ++index
toasts[i] = opts
setTimeout(() => {
close(i)
}, opts.duration || 10000)
return toasts
})
}
function close (index) {
toasts.update(toasts => {
if (toasts[index]) {
delete toasts[index]
}
return toasts
})
}
</script>
<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} 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>
{/each}
</div>
<style>
.alert {
display: block !important;
animation: 0.3s ease 0s 1 fly-in;
right: 0;
}
.sticky-alerts {
top: 2.5rem
}
@keyframes fly-in {
from {
right: -50rem;
}
to {
right: 0;
}
}
</style>

View file

@ -28,6 +28,20 @@
--dm-button-secondary-bg-color-hover: #ddd !important; --dm-button-secondary-bg-color-hover: #ddd !important;
} }
[data-sonner-toaster][data-theme='dark'] {
--normal-bg: var(--dark-color) !important;
--normal-border: none !important;
--normal-text: var(--dm-base-text-color) !important;
/* --success-bg: var(--success-color) !important; */
--success-border: none !important;
/* --success-text: var(--lm-base-text-color) !important; */
/* --error-bg: hsl(358, 76%, 10%); */
--error-border: none !important;
/* --error-text: hsl(358, 100%, 81%); */
}
.material-symbols-outlined { .material-symbols-outlined {
font-family: "Material Symbols Outlined Variable"; font-family: "Material Symbols Outlined Variable";
font-weight: normal; font-weight: normal;

View file

@ -3,7 +3,7 @@ import { writable } from 'simple-store-svelte'
import Bottleneck from 'bottleneck' import Bottleneck from 'bottleneck'
import { alToken } from '../views/Settings.svelte' import { alToken } from '../views/Settings.svelte'
import { addToast } from '../components/Toasts.svelte' import { toast } from 'svelte-sonner'
import { sleep } from './util.js' import { sleep } from './util.js'
const codes = { const codes = {
@ -106,10 +106,8 @@ if (alToken) {
function printError (error) { function printError (error) {
console.warn(error) console.warn(error)
addToast({ toast.error('Search Failed', {
text: /* html */`Failed making request to anilist!<br>Try again in a minute.<br>${error.status || 429} - ${error.message || codes[error.status || 429]}`, description: `Failed making request to anilist!\nTry again in a minute.\n${error.status || 429} - ${error.message || codes[error.status || 429]}`,
title: 'Search Failed',
type: 'danger',
duration: 3000 duration: 3000
}) })
} }

View file

@ -3,7 +3,7 @@ import { DOMPARSER, PromiseBatch, binarySearch } from './util.js'
import { alRequest, alSearch } from './anilist.js' import { alRequest, alSearch } from './anilist.js'
import anitomyscript from 'anitomyscript' import anitomyscript from 'anitomyscript'
import { media } from '../views/Player/MediaHandler.svelte' import { media } from '../views/Player/MediaHandler.svelte'
import { addToast } from '../components/Toasts.svelte' import { toast } from 'svelte-sonner'
import { view } from '@/App.svelte' import { view } from '@/App.svelte'
import { playAnime } from '../views/RSSView.svelte' import { playAnime } from '../views/RSSView.svelte'
@ -15,7 +15,17 @@ window.addEventListener('paste', ({ clipboardData }) => { // WAIT image lookup o
const item = clipboardData.items[0] const item = clipboardData.items[0]
if (!item) return if (!item) return
const { type } = item const { type } = item
if (type.startsWith('image')) return traceAnime(item.getAsFile()) if (type.startsWith('image')) {
const promise = traceAnime(item.getAsFile())
toast.promise(promise, {
description: 'You can also paste an URL to an image.',
loading: 'Looking up anime for image...',
success: 'Found anime for image!',
error: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.'
})
return
}
if (!type.startsWith('text')) return if (!type.startsWith('text')) return
item.getAsString(text => { item.getAsString(text => {
if (torrentRx.exec(text)) { if (torrentRx.exec(text)) {
@ -28,26 +38,19 @@ window.addEventListener('paste', ({ clipboardData }) => { // WAIT image lookup o
} else if (imageRx.exec(text)) { } else if (imageRx.exec(text)) {
src = text src = text
} }
if (src) traceAnime(src) if (src) {
const promise = traceAnime(src)
toast.promise(promise, {
description: 'You can also paste an URL to an image.',
loading: 'Looking up anime for image...',
success: 'Found anime for image!',
error: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.'
})
}
} }
}) })
}) })
export async function traceAnime (image) { // WAIT lookup logic export async function traceAnime (image) { // WAIT lookup logic
if (image instanceof Blob) {
const reader = new FileReader()
reader.onload = e => {
addToast({
title: 'Looking up anime for image',
text: /* html */`You can also paste an URL to an image!<br><img class="w-200 rounded pt-5" src="${e.target.result}">`
})
}
reader.readAsDataURL(image)
} else {
addToast({
title: 'Looking up anime for image',
text: /* html */`<img class="w-200 rounded pt-5" src="${image}">`
})
}
let options let options
let url = `https://api.trace.moe/search?cutBorders&url=${image}` let url = `https://api.trace.moe/search?cutBorders&url=${image}`
if (image instanceof Blob) { if (image instanceof Blob) {
@ -60,14 +63,12 @@ export async function traceAnime (image) { // WAIT lookup logic
} }
const res = await fetch(url, options) const res = await fetch(url, options)
const { result } = await res.json() const { result } = await res.json()
if (result && result[0].similarity >= 0.85) { if (result?.[0].similarity >= 0.85) {
const res = await alRequest({ method: 'SearchIDSingle', id: result[0].anilist }) const res = await alRequest({ method: 'SearchIDSingle', id: result[0].anilist })
view.set(res.data.Media) view.set(res.data.Media)
} else { } else {
addToast({ throw new Error('Search Failed', {
text: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.', message: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.'
title: 'Search Failed',
type: 'danger'
}) })
} }
} }
@ -315,10 +316,8 @@ export async function resolveSeason (opts) {
const obj = { media, episode: episode - offset, offset, increment, rootMedia, failed: true } const obj = { media, episode: episode - offset, offset, increment, rootMedia, failed: true }
if (!force) { if (!force) {
console.warn('Error in parsing!', obj) console.warn('Error in parsing!', obj)
addToast({ toast('Parsing Error', {
text: /* html */`Failed resolving anime episode!<br>${media.title.userPreferred} - ${episode - offset}`, description: `Failed resolving anime episode!\n${media.title.userPreferred} - ${episode - offset}`
title: 'Parsing Error',
type: 'secondary'
}) })
} }
return obj return obj

View file

@ -45,6 +45,8 @@ export default async function tosho ({ media, episode }) {
found.seeders = complete found.seeders = complete
} }
if (!mapped?.length) throw new Error('no entries found')
return mapped return mapped
} }

View file

@ -1,6 +1,6 @@
import { DOMPARSER } from '@/modules/util.js' import { DOMPARSER } from '@/modules/util.js'
import { set } from '@/views/Settings.svelte' import { set } from '@/views/Settings.svelte'
import { addToast } from '@/components/Toasts.svelte' import { toast } from 'svelte-sonner'
import { add } from '@/modules/torrent.js' import { add } from '@/modules/torrent.js'
import { resolveFileMedia, getEpisodeMetadataForMedia } from './anime.js' import { resolveFileMedia, getEpisodeMetadataForMedia } from './anime.js'
import { hasNextPage } from '@/modules/sections.js' import { hasNextPage } from '@/modules/sections.js'
@ -49,19 +49,15 @@ export async function getRSSContent (url) {
try { try {
const res = await fetch(url) const res = await fetch(url)
if (!res.ok) { if (!res.ok) {
addToast({ toast.error('Search Failed', {
text: 'Failed fetching RSS!<br>' + res.statusText, description: 'Failed fetching RSS!\n' + res.statusText
title: 'Search Failed',
type: 'danger'
}) })
console.error('Failed to fetch rss', res.statusText) console.error('Failed to fetch rss', res.statusText)
} }
return DOMPARSER(await res.text(), 'text/xml') return DOMPARSER(await res.text(), 'text/xml')
} catch (e) { } catch (e) {
addToast({ toast.error('Search Failed', {
text: 'Failed fetching RSS!<br>' + e.message, description: 'Failed fetching RSS!\n' + e.message
title: 'Search Failed',
type: 'danger'
}) })
console.error('Failed to fetch rss', e) console.error('Failed to fetch rss', e)
} }

View file

@ -1,6 +1,6 @@
import { set } from '@/views/Settings.svelte' import { set } from '@/views/Settings.svelte'
export default function scroll (t, { speed = 120, smooth = 10 } = {}) { export default function (t, { speed = 120, smooth = 10 } = {}) {
if (!set.smoothScroll) return if (!set.smoothScroll) return
let moving = false let moving = false
let pos = 0 let pos = 0

View file

@ -6,7 +6,7 @@
import { alEntry } from '@/modules/anilist.js' import { alEntry } from '@/modules/anilist.js'
import Subtitles from '@/modules/subtitles.js' import Subtitles from '@/modules/subtitles.js'
import { toTS, videoRx, fastPrettyBytes } from '@/modules/util.js' import { toTS, videoRx, fastPrettyBytes } from '@/modules/util.js'
import { addToast } from '../../components/Toasts.svelte' import { toast } from 'svelte-sonner'
import { getChaptersAniSkip } from '@/modules/anime.js' import { getChaptersAniSkip } from '@/modules/anime.js'
import Seekbar from 'perfect-seekbar' import Seekbar from 'perfect-seekbar'
import { click } from '@/modules/click.js' import { click } from '@/modules/click.js'
@ -71,10 +71,8 @@
function checkAudio () { function checkAudio () {
if ('audioTracks' in HTMLVideoElement.prototype) { if ('audioTracks' in HTMLVideoElement.prototype) {
if (!video.audioTracks.length) { if (!video.audioTracks.length) {
addToast({ toast.error('Audio Codec Unsupported', {
text: "This torrent's audio codec is not supported, try a different release by disabling Autoplay Torrents in RSS settings.", description: "This torrent's audio codec is not supported, try a different release by disabling Autoplay Torrents in RSS settings."
title: 'Audio Codec Unsupported',
type: 'danger'
}) })
} else if (video.audioTracks.length > 1) { } else if (video.audioTracks.length > 1) {
const preferredTrack = [...video.audioTracks].find(({ language }) => language === set.audioLanguage) const preferredTrack = [...video.audioTracks].find(({ language }) => language === set.audioLanguage)
@ -335,10 +333,8 @@
}) })
]) ])
canvas.remove() canvas.remove()
addToast({ toast.success('Screenshot', {
text: 'Saved screenshot to clipboard.', description: 'Saved screenshot to clipboard.'
title: 'Screenshot',
type: 'success'
}) })
} }
} }
@ -830,31 +826,25 @@
break break
case target.error.MEDIA_ERR_NETWORK: case target.error.MEDIA_ERR_NETWORK:
console.warn('A network error caused the video download to fail part-way.', target.error) console.warn('A network error caused the video download to fail part-way.', target.error)
addToast({ toast.error('Video Network Error', {
text: 'A network error caused the video download to fail part-way. Click here to reload the video.', description: 'A network error caused the video download to fail part-way. Click here to reload the video.',
title: 'Video Network Error',
type: 'danger',
duration: 1000000, duration: 1000000,
click: () => target.load() onClick: () => target.load()
}) })
break break
case target.error.MEDIA_ERR_DECODE: case target.error.MEDIA_ERR_DECODE:
console.warn('The video playback was aborted due to a corruption problem or because the video used features your browser did not support.', target.error) console.warn('The video playback was aborted due to a corruption problem or because the video used features your browser did not support.', target.error)
addToast({ toast.error('Video Decode Error', {
text: 'The video playback was aborted due to a corruption problem. Click here to reload the video.', description: 'The video playback was aborted due to a corruption problem. Click here to reload the video.',
title: 'Video Decode Error',
type: 'danger',
duration: 1000000, duration: 1000000,
click: () => target.load() onClick: () => target.load()
}) })
break break
case target.error.MEDIA_ERR_SRC_NOT_SUPPORTED: case target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
if (target.error.message !== 'MEDIA_ELEMENT_ERROR: Empty src attribute') { if (target.error.message !== 'MEDIA_ELEMENT_ERROR: Empty src attribute') {
console.warn('The video could not be loaded, either because the server or network failed or because the format is not supported.', target.error) console.warn('The video could not be loaded, either because the server or network failed or because the format is not supported.', target.error)
addToast({ toast.error('Video Codec Unsupported', {
text: 'The video could not be loaded, either because the server or network failed or because the format is not supported. Try a different release by disabling Autoplay Torrents in RSS settings.', description: 'The video could not be loaded, either because the server or network failed or because the format is not supported. Try a different release by disabling Autoplay Torrents in RSS settings.',
title: 'Video Codec Unsupported',
type: 'danger',
duration: 300000 duration: 300000
}) })
} }
@ -881,8 +871,7 @@
on:keypress={resetImmerse} on:keypress={resetImmerse}
on:mouseleave={immersePlayer}> on:mouseleave={immersePlayer}>
{#if showKeybinds && !miniplayer} {#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'> <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'>
<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}> <Keybinds let:prop={item} autosave={true} clickable={true}>
<div class:material-symbols-outlined={item?.type} class='bind'>{item?.id || ''}</div> <div class:material-symbols-outlined={item?.type} class='bind'>{item?.id || ''}</div>
</Keybinds> </Keybinds>

View file

@ -1,7 +1,7 @@
<script context='module'> <script context='module'>
import { since } from '@/modules/util.js' import { since } from '@/modules/util.js'
import { set } from './Settings.svelte' import { set } from './Settings.svelte'
import { addToast } from '../components/Toasts.svelte' import { toast } from 'svelte-sonner'
import { findInCurrent } from './Player/MediaHandler.svelte' import { findInCurrent } from './Player/MediaHandler.svelte'
import getRSSEntries from '@/modules/providers/tosho.js' import getRSSEntries from '@/modules/providers/tosho.js'
import { click } from '@/modules/click.js' import { click } from '@/modules/click.js'
@ -73,15 +73,19 @@
async function loadRss ({ media, episode }) { async function loadRss ({ media, episode }) {
if (!media) return if (!media) return
const entries = await getRSSEntries({ media, episode }) const promise = getRSSEntries({ media, episode })
if (!entries?.length) { toast.promise(promise, {
addToast({ loading: `Looking for torrents for ${media.title.userPreferred} Episode ${parseInt(episode)}...`,
text: /* html */`Couldn't find torrent for ${media.title.userPreferred} Episode ${parseInt(episode)}! Try specifying a torrent manually.`, success: `Found torrents for ${media.title.userPreferred} Episode ${parseInt(episode)}.`,
title: 'Search Failed', error: `Couldn't find torrents for ${media.title.userPreferred} Episode ${parseInt(episode)}! Try specifying a torrent manually.`
type: 'danger' })
}) let entries
return try {
entries = await promise
} catch (e) {
return e
} }
entries.sort((a, b) => b.seeders - a.seeders) entries.sort((a, b) => b.seeders - a.seeders)
if (settings.rssAutoplay) { if (settings.rssAutoplay) {
const best = entries.find(entry => entry.best) const best = entries.find(entry => entry.best)
@ -103,10 +107,8 @@
function play (entry) { function play (entry) {
$media = $rss $media = $rss
if (entry.seeders !== '?' && entry.seeders <= 15) { if (entry.seeders !== '?' && entry.seeders <= 15) {
addToast({ toast('Availability Warning', {
text: 'This release is poorly seeded and likely will have playback issues such as buffering!', description: 'This release is poorly seeded and likely will have playback issues such as buffering!'
title: 'Availability Warning',
type: 'secondary'
}) })
} }
add(entry.link) add(entry.link)

View file

@ -1,5 +1,6 @@
<script context='module'> <script context='module'>
import { addToast } from '../components/Toasts.svelte'
import { toast } from 'svelte-sonner'
import { click } from '@/modules/click.js' import { click } from '@/modules/click.js'
export let alToken = localStorage.getItem('ALtoken') || null export let alToken = localStorage.getItem('ALtoken') || null
const defaults = { const defaults = {
@ -64,17 +65,14 @@
window.IPC.on('update-available', () => { window.IPC.on('update-available', () => {
if (!wasUpdated) { if (!wasUpdated) {
wasUpdated = true wasUpdated = true
addToast({ toast('Auto Updater', {
title: 'Auto Updater', description: 'A new version of Miru is available. Downloading!'
text: 'A new version of Miru is available. Downloading!'
}) })
} }
}) })
window.IPC.on('update-downloaded', () => { window.IPC.on('update-downloaded', () => {
addToast({ toast.success('Auto Updater', {
title: 'Auto Updater', description: 'A new version of Miru has downloaded. You can restart to update!'
text: 'A new version of Miru has downloaded. You can restart to update!',
type: 'success'
}) })
}) })
function checkUpdate () { function checkUpdate () {
@ -158,10 +156,8 @@
} }
} catch (error) { } catch (error) {
console.warn(error) console.warn(error)
addToast({ toast.error('File Error', {
text: /* html */`${error.message}<br>Try using a different font.`, description: `${error.message}<br>Try using a different font.`,
title: 'File Error',
type: 'secondary',
duration: 8000 duration: 8000
}) })
} }

View file

@ -2,7 +2,7 @@
import { getContext, setStatus } from 'svelte' import { getContext, setStatus } from 'svelte'
import { getMediaMaxEp, formatMap, playMedia } from '@/modules/anime.js' import { getMediaMaxEp, formatMap, playMedia } from '@/modules/anime.js'
import { playAnime } from '../RSSView.svelte' import { playAnime } from '../RSSView.svelte'
import { addToast } from '../../components/Toasts.svelte' import { toast } from 'svelte-sonner'
import { alRequest } from '@/modules/anilist.js' import { alRequest } from '@/modules/anilist.js'
import { click } from '@/modules/click.js' import { click } from '@/modules/click.js'
import Details from './Details.svelte' import Details from './Details.svelte'
@ -64,11 +64,9 @@
} }
function copyToClipboard (text) { function copyToClipboard (text) {
navigator.clipboard.writeText(text) navigator.clipboard.writeText(text)
addToast({ toast('Copied to clipboard', {
title: 'Copied to clipboard', description: 'Copied share URL to clipboard',
text: 'Copied share URL to clipboard', duration: 5000
type: 'primary',
duration: '5000'
}) })
} }
function openInBrowser (url) { function openInBrowser (url) {

View file

@ -3,7 +3,7 @@
import { alID } from '@/modules/anilist.js' import { alID } from '@/modules/anilist.js'
import { add, client } from '@/modules/torrent.js' import { add, client } from '@/modules/torrent.js'
import { generateRandomHexCode } from '@/modules/util.js' import { generateRandomHexCode } from '@/modules/util.js'
import { addToast } from '../../components/Toasts.svelte' import { toast } from 'svelte-sonner'
import { page } from '@/App.svelte' import { page } from '@/App.svelte'
import P2PT from 'p2pt' import P2PT from 'p2pt'
import { click } from '@/modules/click.js' import { click } from '@/modules/click.js'
@ -148,11 +148,9 @@
function invite () { function invite () {
if (p2pt) { if (p2pt) {
navigator.clipboard.writeText(`https://miru.watch/w2g/${p2pt.identifierString}`) navigator.clipboard.writeText(`https://miru.watch/w2g/${p2pt.identifierString}`)
addToast({ toast('Copied to clipboard', {
title: 'Copied to clipboard', description: 'Copied invite URL to clipboard',
text: 'Copied invite URL to clipboard', duration: 5000
type: 'primary',
duration: '5000'
}) })
} }
} }