mirror of
https://github.com/danbulant/Mangades
synced 2026-05-19 04:08:46 +00:00
better anilist integration, fix tracing, improved styles
This commit is contained in:
parent
96795593ea
commit
d53f829fed
12 changed files with 535 additions and 73 deletions
|
|
@ -13,7 +13,6 @@
|
|||
"@sveltejs/kit": "1.0.0-next.572",
|
||||
"@types/streamsaver": "^2.0.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"nollup": "^0.16.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss-import": "^14.1.0",
|
||||
|
|
@ -27,6 +26,7 @@
|
|||
"vite-plugin-windicss": "^1.8.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elastic/apm-rum": "^5.12.0",
|
||||
"@sentry/browser": "^7.25.0",
|
||||
"@sentry/svelte": "^7.25.0",
|
||||
"@sentry/tracing": "^7.25.0",
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
<script data-goatcounter="https://mangades.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/global.css" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/swiper.min.css" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/global.css" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
|
||||
{#if selectedImage}
|
||||
<dialog open class="open" on:click={() => selectedImage = null} transition:fade={{ duration: 200 }}>
|
||||
<img src={selectedImage} alt="">
|
||||
<div class="inner">
|
||||
<img src={selectedImage} alt="">
|
||||
<slot />
|
||||
</div>
|
||||
<button>Tap to close</button>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
|
@ -30,6 +33,14 @@
|
|||
:global(.dark dialog[open].open.open) {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.inner {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
margin: 2rem;
|
||||
}
|
||||
dialog img {
|
||||
border-radius: 5px;
|
||||
max-height: 100%;
|
||||
|
|
@ -37,7 +48,7 @@
|
|||
}
|
||||
dialog button {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
export var selectedImage = null;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="main">
|
||||
{#await list}
|
||||
Loading art
|
||||
{:then list}
|
||||
|
|
@ -29,27 +29,34 @@
|
|||
height="805"
|
||||
src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{item.attributes.fileName}.512.jpg"
|
||||
alt=""
|
||||
class="color"
|
||||
draggable={false} />
|
||||
{/each}
|
||||
{/await}
|
||||
{#each additionalList as item}
|
||||
<img
|
||||
on:click={() => (selectedImage = item.src)}
|
||||
style="{item.color ? '--box-shadow-color: ' + item.color : ''}; width: 100%; height: 100%; {item.width ? `grid-column: span ${item.width}` : ""}; {item.height ? `grid-row: span ${item.height}` : ""};"
|
||||
src={item.src}
|
||||
alt={item.alt}
|
||||
draggable={false} />
|
||||
<div class="img-container" style="{item.width ? `grid-column: span ${item.width}` : ""}; {item.height ? `grid-row: span ${item.height}` : ""};">
|
||||
<img
|
||||
on:click={() => (selectedImage = item.src)}
|
||||
style="{item.color ? '--box-shadow-color: ' + item.color : ''};"
|
||||
class:color={item.color}
|
||||
src={item.src}
|
||||
alt={item.alt}
|
||||
draggable={false} />
|
||||
{#if !item.color}
|
||||
<img class="img-backdrop" src={item.src} alt="">
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
div {
|
||||
.main {
|
||||
display: grid;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
justify-content: start;
|
||||
align-items: start;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
|
||||
}
|
||||
div img {
|
||||
|
|
@ -60,15 +67,46 @@
|
|||
object-fit: contain;
|
||||
transition: filter 0.2s ease-in-out;
|
||||
filter: drop-shadow(0 0 0 0 var(--box-shadow-color));
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
div img:first-child {
|
||||
div .img-container img:not(.color), div .img-container img:not(.color):hover, div .img-container img:not(.color):active, div .img-container img:not(.color):focus {
|
||||
--box-shadow-color: transparent;
|
||||
filter: none;
|
||||
}
|
||||
.img-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.img-backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
filter: blur(10px) saturate(100%);
|
||||
opacity: 0;
|
||||
z-index: 0;
|
||||
transition: opacity .3s, filter .3s;
|
||||
}
|
||||
.img-container:hover .img-backdrop {
|
||||
filter: blur(20px) saturate(150%);
|
||||
opacity: 1;
|
||||
}
|
||||
div .img-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.main > img:first-child {
|
||||
grid-column: 1 / span 2;
|
||||
grid-row: 1 / span 2;
|
||||
height: 20rem;
|
||||
}
|
||||
div img:hover,
|
||||
div img:active,
|
||||
div img:focus {
|
||||
div img.color:hover,
|
||||
div img.color:active,
|
||||
div img.color:focus {
|
||||
filter: drop-shadow(0 0 0.5rem var(--box-shadow-color));
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import * as Sentry from "@sentry/browser";
|
||||
import { apm } from "./tracing";
|
||||
|
||||
var isLogedInCache: boolean | null = null;
|
||||
var isLogedInCacheTime: number | null = null;
|
||||
|
|
@ -22,6 +23,7 @@ export function getUserID() {
|
|||
const token = localStorage.getItem("token")!;
|
||||
let data = JSON.parse(atob(token.substring(token.indexOf(".") + 1, token.lastIndexOf("."))));
|
||||
Sentry.setUser({ id: data.sub });
|
||||
apm.setUserContext({ id: data.sub });
|
||||
return data.sub;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
const ratelimits = new Map();
|
||||
|
||||
function callback(func) {
|
||||
function callback({ func }) {
|
||||
const params = ratelimits.get(func);
|
||||
console.log(params, func, ratelimits);
|
||||
func(...params.params).then(params.result.resolve).catch(params.result.reject);
|
||||
ratelimits.delete(func);
|
||||
}
|
||||
|
|
@ -14,8 +15,9 @@ function callback(func) {
|
|||
* @returns {Promise<ReturnType<T>>}
|
||||
*/
|
||||
function ratelimit(func, ...params) {
|
||||
console.log("Adding rate limit", func, params);
|
||||
const data = ratelimits.get(func) || {
|
||||
timeout: setTimeout(callback, 200, func),
|
||||
timeout: setTimeout(callback, 200, { func }),
|
||||
params,
|
||||
result: {}
|
||||
};
|
||||
|
|
|
|||
22
src/lib/util/tracing.ts
Normal file
22
src/lib/util/tracing.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { browser } from '$app/environment';
|
||||
import { ApmBase, init as initApm } from '@elastic/apm-rum'
|
||||
|
||||
var apm_: ApmBase;
|
||||
|
||||
if(browser) {
|
||||
apm_ = initApm({
|
||||
// Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
|
||||
serviceName: 'mangades',
|
||||
|
||||
// Set custom APM Server URL (default: http://localhost:8200)
|
||||
serverUrl: 'https://apm.elasticsearch.danbulant.cloud',
|
||||
|
||||
// Set the service version (required for source map feature)
|
||||
serviceVersion: import.meta.env.VITE_SENTRY_RELEASE,
|
||||
|
||||
// Set the service environment
|
||||
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT || 'production'
|
||||
});
|
||||
}
|
||||
|
||||
export const apm = apm_;
|
||||
|
|
@ -5,6 +5,8 @@
|
|||
import * as Sentry from '@sentry/svelte';
|
||||
import { BrowserTracing } from "@sentry/tracing";
|
||||
import { browser } from '$app/environment';
|
||||
import { apm } from "$lib/util/tracing";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
export var data;
|
||||
// @ts-ignore
|
||||
|
|
@ -21,9 +23,13 @@
|
|||
tracePropagationTargets: ["localhost", "manga.danbulant.eu", "tachiyomi.manga-d7tp.pages.dev", "manga-d7tp.pages.dev", /^\/.*/]
|
||||
}),
|
||||
],
|
||||
tracesSampleRate: 1
|
||||
tracesSampleRate: 1,
|
||||
autoSessionTracking: false
|
||||
});
|
||||
}
|
||||
if(browser) {
|
||||
apm.setInitialPageLoadName($page.route.id);
|
||||
}
|
||||
|
||||
let skipFirst = true;
|
||||
let last = typeof window !== "undefined" && window.location.pathname;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
import { EpubGenerator } from "$lib/util/generateEpub";
|
||||
import { CBZGenerator } from "$lib/util/generateCbz";
|
||||
import request, { imageproxy } from "$lib/util/request";
|
||||
import { BaseGenerator } from "$lib/util/baseGenerator";
|
||||
import { arraysEqual } from "$lib/util/arrays";
|
||||
import { makeRequest } from "$lib/util/anilist";
|
||||
import Tabs from "$lib/components/tabs/tabs.svelte";
|
||||
import { slide } from "svelte/transition";
|
||||
import { Swiper, SwiperSlide } from 'swiper/svelte';
|
||||
|
|
@ -18,6 +16,7 @@
|
|||
import { tick } from "svelte";
|
||||
import Favicon from "./favicon.svelte";
|
||||
import RelatedManga from "./relatedManga.svelte";
|
||||
import { anilistInfo } from "./anilistInfo";
|
||||
|
||||
export var data;
|
||||
|
||||
|
|
@ -30,6 +29,9 @@
|
|||
var title = manga.title.en || manga.title.jp || Object.values(manga.title)[0];
|
||||
$: title = manga.title.en || manga.title.jp || Object.values(manga.title)[0];
|
||||
|
||||
let anilistData;
|
||||
$: anilistData = manga.links && manga.links.al && anilistInfo(manga.links.al);
|
||||
|
||||
let cache: { id: string, data: any, total } | null = null;
|
||||
async function getMangaChapters(id) {
|
||||
if(cache?.id === id && cache.data.length >= cache.total) return cache;
|
||||
|
|
@ -246,43 +248,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
const anilistCache = new Map();
|
||||
function anilistInfo(id) {
|
||||
if(!anilistCache.has(id))
|
||||
anilistCache.set(id, makeRequest(`
|
||||
query ($id: Int) {
|
||||
Media(id: $id, format: MANGA) {
|
||||
id
|
||||
type
|
||||
format
|
||||
status
|
||||
chapters
|
||||
volumes
|
||||
countryOfOrigin
|
||||
bannerImage
|
||||
genres
|
||||
synonyms
|
||||
averageScore
|
||||
popularity
|
||||
isFavourite
|
||||
isFavouriteBlocked
|
||||
isAdult
|
||||
siteUrl
|
||||
coverImage {
|
||||
large
|
||||
medium
|
||||
color
|
||||
}
|
||||
}
|
||||
}`, { id }).then(t => t.data.Media));
|
||||
return anilistCache.get(id);
|
||||
}
|
||||
|
||||
let anilistData;
|
||||
$: anilistData = manga.links && manga.links.al && anilistInfo(manga.links.al);
|
||||
|
||||
var selectedTab = "Chapters";
|
||||
const tabs = ["Chapters", "Art", "More information"];
|
||||
const defaultTabs = ["Chapters", "Art", "More info"];
|
||||
var tabs = defaultTabs;
|
||||
|
||||
$: {
|
||||
if(swiper && tabs.indexOf(selectedTab) !== swiper.realIndex) swiper.slideTo(tabs.indexOf(selectedTab));
|
||||
|
|
@ -310,16 +278,27 @@
|
|||
|
||||
$: if(anilistData) anilistData.then(data => {
|
||||
if(data.bannerImage && !additionalImages.find(t => t.src === data.bannerImage)) {
|
||||
additionalImages.push({
|
||||
src: data.coverImage.large,
|
||||
alt: "Cover image from anilist",
|
||||
// color: data.coverImage.color,
|
||||
height: 1,
|
||||
width: 1
|
||||
});
|
||||
additionalImages.push({
|
||||
src: data.bannerImage,
|
||||
alt: "Banner image",
|
||||
color: data.coverImage.color,
|
||||
alt: "Banner image from anilist",
|
||||
// color: data.coverImage.color,
|
||||
height: 1,
|
||||
width: 3
|
||||
});
|
||||
additionalImages = additionalImages;
|
||||
tabs = [...defaultTabs, "Characters"];
|
||||
}
|
||||
});
|
||||
}); else {
|
||||
additionalImages = []
|
||||
tabs = defaultTabs;
|
||||
}
|
||||
|
||||
$: if(chapters) console.log("ch", chapters, chapters.data.length, chapters.total, chapters.data.length < chapters.total);
|
||||
|
||||
|
|
@ -335,6 +314,8 @@
|
|||
}
|
||||
|
||||
$: if(!loadingNextPage && chapters && chapters.data.length < chapters.total && scrollY > 300 && scrollY > document.body.scrollHeight * 0.8) loadNextPage();
|
||||
|
||||
var selectedCharacter = null;
|
||||
</script>
|
||||
|
||||
<svelte:window on:beforeunload={beforeUnload} bind:innerWidth={width} bind:scrollY bind:innerHeight />
|
||||
|
|
@ -346,7 +327,31 @@
|
|||
|
||||
<Navbar transparent={scrollY < 0.2*innerHeight} {title} />
|
||||
|
||||
<ArtDialog bind:selectedImage />
|
||||
<ArtDialog bind:selectedImage>
|
||||
{#if selectedCharacter}
|
||||
<div class="character-info">
|
||||
<h1>{selectedCharacter.node.name.full}</h1>
|
||||
<h2 style="padding: 0; margin: 0 0 0.5rem; color: rgba(255,255,255,0.7);">{selectedCharacter.node.name.native}</h2>
|
||||
<small style="color: rgba(255,255,255,0.7)">Crossed out text may indicate spoilers!</small>
|
||||
<p style="margin: 0 0 1rem;"><SvelteMarkdown source={selectedCharacter.node.description} isInline /></p>
|
||||
{#if selectedCharacter.node.gender}
|
||||
<div>Gender: {selectedCharacter.node.gender}</div>
|
||||
{/if}
|
||||
{#if selectedCharacter.node.age}
|
||||
<div>Age: {selectedCharacter.node.age}</div>
|
||||
{/if}
|
||||
{#if selectedCharacter.node.dateOfBirth && (selectedCharacter.node.dateOfBirth.day || selectedCharacter.node.dateOfBirth.month || selectedCharacter.node.dateOfBirth.year)}
|
||||
<div>Birthday: {selectedCharacter.node.dateOfBirth.day || "Unknown"}/{selectedCharacter.node.dateOfBirth.month || "Unknown"}/{selectedCharacter.node.dateOfBirth.year || "Unknown"}</div>
|
||||
{/if}
|
||||
{#if selectedCharacter.node.favourites}
|
||||
<div>Favourites: {selectedCharacter.node.favourites}</div>
|
||||
{/if}
|
||||
{#if selectedCharacter.node.bloodType}
|
||||
<div>Blood type: {selectedCharacter.node.bloodType}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</ArtDialog>
|
||||
|
||||
{#if anilistData} {#await anilistData then data}
|
||||
{#if data.bannerImage}
|
||||
|
|
@ -360,7 +365,10 @@
|
|||
<main class:smallScreenMode>
|
||||
<div class="flex infoflex">
|
||||
{#if relationships.find(t => t.type === "cover_art")}
|
||||
<img class="cover" class:r18={!["safe", "suggestive"].includes(manga.contentRating)} draggable="false" src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg" alt="" on:click={() => selectedImage = `https://uploads.mangadex.org/covers/${mangaId}/${relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg`}>
|
||||
<div class="cover-container">
|
||||
<img class="cover" class:r18={!["safe", "suggestive"].includes(manga.contentRating)} draggable="false" src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg" alt="" on:click={() => selectedImage = `https://uploads.mangadex.org/covers/${mangaId}/${relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg`}>
|
||||
<img class="cover-backdrop" draggable="false" src="{imageproxy}https://uploads.mangadex.org/covers/{mangaId}/{relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg" alt="">
|
||||
</div>
|
||||
{/if}
|
||||
<div class="info">
|
||||
<h1>{title}</h1>
|
||||
|
|
@ -433,7 +441,7 @@
|
|||
on:swiper={(e) => console.log(e.detail[0])}
|
||||
>
|
||||
<SwiperSlide>
|
||||
<div style="min-height: 30rem;">
|
||||
<div class="chapter-list" style="min-height: 30rem;">
|
||||
<div class="state {state}">
|
||||
<div class="progress" style="width: {progress * 100}%;"></div>
|
||||
|
||||
|
|
@ -483,12 +491,12 @@
|
|||
</div>
|
||||
</SwiperSlide>
|
||||
<SwiperSlide>
|
||||
<div style="min-height: 30rem;">
|
||||
<div class="art-list" style="min-height: 30rem;">
|
||||
<ArtList {mangaId} bind:selectedImage additionalList={additionalImages} />
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
<SwiperSlide>
|
||||
<div style="min-height: 30rem;">
|
||||
<div class="more-info" style="min-height: 30rem;">
|
||||
<div class="flex-wrapped">
|
||||
{#if anilistData} {#await anilistData then data}
|
||||
<div>
|
||||
|
|
@ -553,10 +561,78 @@
|
|||
{/if}
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
<SwiperSlide>
|
||||
<div class="characters" style="min-height: 30rem;">
|
||||
{#await anilistData then data}{#if data}
|
||||
{#each data.characters.edges as character}
|
||||
<div class="character" on:click={() => {selectedImage = character.node.image.large; selectedCharacter = character}} >
|
||||
<div class="container">
|
||||
<img src={character.node.image.large} alt="" />
|
||||
<img class="backdrop" src={character.node.image.large} alt="" />
|
||||
</div>
|
||||
<div>
|
||||
<h4>{character.node.name.userPreferred}</h4>
|
||||
<span class="role">{character.role}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}{/await}
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.art-list {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.chapter-list {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.more-info {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.characters {
|
||||
margin-top: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
grid-gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
.character {
|
||||
cursor: pointer;
|
||||
}
|
||||
.character img {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
object-fit: cover;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
.container .backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
border-radius: 5px;
|
||||
filter: blur(10px) saturate(100%);
|
||||
z-index: 0;
|
||||
transition: opacity .3s, filter .3s;
|
||||
}
|
||||
.container:hover .backdrop {
|
||||
opacity: 1;
|
||||
filter: blur(20px) saturate(150%);
|
||||
}
|
||||
.character .role {
|
||||
font-size: 1rem;
|
||||
color: rgb(175, 175, 175);
|
||||
}
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -578,6 +654,7 @@
|
|||
.infoflex.flex {
|
||||
margin: 15px;
|
||||
justify-content: start;
|
||||
gap: 1rem;
|
||||
}
|
||||
.flex-wrapped {
|
||||
display: flex;
|
||||
|
|
@ -626,13 +703,37 @@
|
|||
.tabbed {
|
||||
min-height: 20rem;
|
||||
}
|
||||
.cover {
|
||||
border-radius: 10px;
|
||||
.cover-container {
|
||||
position: relative;
|
||||
height: 20rem;
|
||||
flex-shrink: 0;
|
||||
margin-right: 15px;
|
||||
transition: height .3s;
|
||||
}
|
||||
.smallScreenMode .cover {
|
||||
.cover {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cover-backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100% + 4px);
|
||||
height: calc(100% + 4px);
|
||||
z-index: 0;
|
||||
filter: blur(18px) saturate(100%);
|
||||
transition: filter .3s;
|
||||
}
|
||||
.cover-container:hover .cover-backdrop {
|
||||
filter: blur(30px) saturate(150%);
|
||||
}
|
||||
.smallScreenMode .cover-container {
|
||||
height: 12rem;
|
||||
}
|
||||
.block {
|
||||
|
|
|
|||
274
src/routes/[manga]/anilistInfo.ts
Normal file
274
src/routes/[manga]/anilistInfo.ts
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
import { makeRequest } from "$lib/util/anilist";
|
||||
|
||||
interface AnilistInfo {
|
||||
Media: Media;
|
||||
}
|
||||
|
||||
interface Media {
|
||||
id: number;
|
||||
title: Title;
|
||||
type: "MANGA";
|
||||
format: "MANGA";
|
||||
status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED";
|
||||
chapters: number | null;
|
||||
volumes: number | null;
|
||||
countryOfOrigin: string;
|
||||
bannerImage: string;
|
||||
genres: string[];
|
||||
synonyms: string[];
|
||||
averageScore: number | null;
|
||||
popularity: number;
|
||||
isFavourite: boolean;
|
||||
isFavouriteBlocked: boolean;
|
||||
isAdult: boolean;
|
||||
siteUrl: string;
|
||||
coverImage: CoverImage;
|
||||
description: string;
|
||||
characters: {
|
||||
edges: {
|
||||
id: number;
|
||||
role: "MAIN" | "SUPPORTING" | "BACKGROUND";
|
||||
node: Character;
|
||||
}[]
|
||||
};
|
||||
tags: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
isMediaSpoiler: boolean;
|
||||
isAdult: boolean;
|
||||
}[];
|
||||
relations: {
|
||||
edges: {
|
||||
id: number;
|
||||
relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS";
|
||||
node: {
|
||||
type: "ANIME" | "MANGA";
|
||||
mediaListEntry: MediaListEntry | null;
|
||||
description: string;
|
||||
coverImage: CoverImage;
|
||||
title: Title
|
||||
}
|
||||
}[]
|
||||
};
|
||||
mediaListEntry: MediaListEntry | null;
|
||||
recommendations: {
|
||||
edges: {
|
||||
node: {
|
||||
id: number;
|
||||
rating: number;
|
||||
mediaRecommendation: {
|
||||
title: Title;
|
||||
mediaListEntry: MediaListEntry | null;
|
||||
description: string;
|
||||
coverImage: CoverImage
|
||||
}
|
||||
}
|
||||
}[]
|
||||
};
|
||||
}
|
||||
|
||||
interface Title {
|
||||
romaji: string;
|
||||
english: string;
|
||||
native: string;
|
||||
userPreferred: string;
|
||||
}
|
||||
|
||||
interface MediaListEntry {
|
||||
progress: number | null;
|
||||
progressVolumes: number | null;
|
||||
repeat: number | null;
|
||||
priority: number | null;
|
||||
private: boolean;
|
||||
notes?: string;
|
||||
score: number | null;
|
||||
customLists: string[];
|
||||
}
|
||||
|
||||
interface Character {
|
||||
name: {
|
||||
first: string;
|
||||
middle: string;
|
||||
last: string;
|
||||
full: string;
|
||||
native: string;
|
||||
userPreferred: string;
|
||||
};
|
||||
image: {
|
||||
large: string;
|
||||
medium: string;
|
||||
};
|
||||
description: string;
|
||||
gender: "Male" | "Female" | null;
|
||||
age: number | null;
|
||||
dateOfBirth: {
|
||||
year: number | null;
|
||||
month: number | null;
|
||||
day: number | null;
|
||||
};
|
||||
favourites: number;
|
||||
bloodType: "A" | "B" | "O" | "AB" | "A+" | "A-" | "B+" | "B-" | "O+" | "O-" | "AB+" | "AB-" | null;
|
||||
}
|
||||
|
||||
interface CoverImage {
|
||||
extraLarge: string;
|
||||
large: string;
|
||||
medium: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const anilistCache = new Map();
|
||||
export function anilistInfo(id): Promise<AnilistInfo> {
|
||||
if(!anilistCache.has(id))
|
||||
anilistCache.set(id, makeRequest(`
|
||||
query($id: Int) {
|
||||
Media(id: $id, format: MANGA) {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
userPreferred
|
||||
}
|
||||
type
|
||||
format
|
||||
status
|
||||
chapters
|
||||
volumes
|
||||
countryOfOrigin
|
||||
bannerImage
|
||||
genres
|
||||
synonyms
|
||||
averageScore
|
||||
popularity
|
||||
isFavourite
|
||||
isFavouriteBlocked
|
||||
isAdult
|
||||
siteUrl
|
||||
coverImage {
|
||||
extraLarge
|
||||
large
|
||||
medium
|
||||
color
|
||||
}
|
||||
characters {
|
||||
edges {
|
||||
id
|
||||
role
|
||||
node {
|
||||
name {
|
||||
first
|
||||
middle
|
||||
last
|
||||
full
|
||||
native
|
||||
userPreferred
|
||||
}
|
||||
description
|
||||
gender
|
||||
age
|
||||
dateOfBirth {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
favourites
|
||||
bloodType
|
||||
image {
|
||||
large
|
||||
medium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tags {
|
||||
id
|
||||
name
|
||||
description
|
||||
isMediaSpoiler
|
||||
isAdult
|
||||
}
|
||||
relations {
|
||||
edges {
|
||||
id
|
||||
relationType
|
||||
node {
|
||||
type
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
userPreferred
|
||||
}
|
||||
mediaListEntry {
|
||||
userId
|
||||
status
|
||||
score
|
||||
progress
|
||||
progressVolumes
|
||||
repeat
|
||||
priority
|
||||
private
|
||||
notes
|
||||
}
|
||||
coverImage {
|
||||
extraLarge
|
||||
large
|
||||
medium
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mediaListEntry {
|
||||
userId
|
||||
status
|
||||
score
|
||||
progress
|
||||
progressVolumes
|
||||
repeat
|
||||
priority
|
||||
private
|
||||
notes
|
||||
}
|
||||
recommendations {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
rating
|
||||
mediaRecommendation {
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
userPreferred
|
||||
}
|
||||
description
|
||||
mediaListEntry {
|
||||
userId
|
||||
status
|
||||
score
|
||||
progress
|
||||
progressVolumes
|
||||
repeat
|
||||
priority
|
||||
private
|
||||
notes
|
||||
}
|
||||
coverImage {
|
||||
extraLarge
|
||||
large
|
||||
medium
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}`, { id }).then(t => t.data.Media));
|
||||
anilistCache.get(id).then(t => console.log("anilist", t));
|
||||
return anilistCache.get(id);
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0 1rem;
|
||||
padding: 0 2rem;
|
||||
transition: background 0.3s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -34,10 +34,13 @@
|
|||
font-size: 2rem;
|
||||
transform: translateX(0);
|
||||
transition: transform 0.2s ease;
|
||||
margin-right: 0.5rem;
|
||||
padding-right: 0;
|
||||
}
|
||||
.navbar a:hover, .navbar a:active, .navbar a:focus {
|
||||
text-decoration: none;
|
||||
transform: translateX(-0.5rem);
|
||||
margin-right: 0;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
.transparent .title {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0 1rem;
|
||||
padding: 0 2rem;
|
||||
transition: background 0.3s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -34,10 +34,13 @@
|
|||
font-size: 2rem;
|
||||
transform: translateX(0);
|
||||
transition: transform 0.2s ease;
|
||||
margin-right: 0.5rem;
|
||||
padding-right: 0;
|
||||
}
|
||||
.navbar a:hover, .navbar a:active, .navbar a:focus {
|
||||
text-decoration: none;
|
||||
transform: translateX(-0.5rem);
|
||||
margin-right: 0;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
.transparent .title {
|
||||
|
|
|
|||
Loading…
Reference in a new issue