diff --git a/package.json b/package.json index ff9b95a..dfbf335 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app.html b/src/app.html index 3161b72..e27986f 100644 --- a/src/app.html +++ b/src/app.html @@ -5,8 +5,8 @@ - + %sveltekit.head% diff --git a/src/lib/components/artDialog.svelte b/src/lib/components/artDialog.svelte index 2e413b8..ec865f7 100644 --- a/src/lib/components/artDialog.svelte +++ b/src/lib/components/artDialog.svelte @@ -5,7 +5,10 @@ {#if selectedImage} selectedImage = null} transition:fade={{ duration: 200 }}> - +
+ + +
{/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; } \ No newline at end of file diff --git a/src/lib/components/artList.svelte b/src/lib/components/artList.svelte index 91f529a..ad6a130 100644 --- a/src/lib/components/artList.svelte +++ b/src/lib/components/artList.svelte @@ -18,7 +18,7 @@ export var selectedImage = null; -
+
{#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} - (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} /> +
+ (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} + + {/if} +
{/each}
\ No newline at end of file diff --git a/src/lib/util/anilist.ts b/src/lib/util/anilist.ts index d3dda01..c001e4f 100644 --- a/src/lib/util/anilist.ts +++ b/src/lib/util/anilist.ts @@ -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; } diff --git a/src/lib/util/ratelimit.js b/src/lib/util/ratelimit.js index 1dfcbf2..6d55143 100644 --- a/src/lib/util/ratelimit.js +++ b/src/lib/util/ratelimit.js @@ -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>} */ 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: {} }; diff --git a/src/lib/util/tracing.ts b/src/lib/util/tracing.ts new file mode 100644 index 0000000..eb91e6f --- /dev/null +++ b/src/lib/util/tracing.ts @@ -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_; \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 87eb24d..312a93c 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -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; diff --git a/src/routes/[manga]/+page.svelte b/src/routes/[manga]/+page.svelte index d51ebf9..34b4ee1 100644 --- a/src/routes/[manga]/+page.svelte +++ b/src/routes/[manga]/+page.svelte @@ -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; @@ -346,7 +327,31 @@ - + + {#if selectedCharacter} +
+

{selectedCharacter.node.name.full}

+

{selectedCharacter.node.name.native}

+ Crossed out text may indicate spoilers! +

+ {#if selectedCharacter.node.gender} +
Gender: {selectedCharacter.node.gender}
+ {/if} + {#if selectedCharacter.node.age} +
Age: {selectedCharacter.node.age}
+ {/if} + {#if selectedCharacter.node.dateOfBirth && (selectedCharacter.node.dateOfBirth.day || selectedCharacter.node.dateOfBirth.month || selectedCharacter.node.dateOfBirth.year)} +
Birthday: {selectedCharacter.node.dateOfBirth.day || "Unknown"}/{selectedCharacter.node.dateOfBirth.month || "Unknown"}/{selectedCharacter.node.dateOfBirth.year || "Unknown"}
+ {/if} + {#if selectedCharacter.node.favourites} +
Favourites: {selectedCharacter.node.favourites}
+ {/if} + {#if selectedCharacter.node.bloodType} +
Blood type: {selectedCharacter.node.bloodType}
+ {/if} +
+ {/if} +
{#if anilistData} {#await anilistData then data} {#if data.bannerImage} @@ -360,7 +365,10 @@
{#if relationships.find(t => t.type === "cover_art")} - selectedImage = `https://uploads.mangadex.org/covers/${mangaId}/${relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg`}> +
+ selectedImage = `https://uploads.mangadex.org/covers/${mangaId}/${relationships.find(t => t.type === "cover_art").attributes.fileName}.512.jpg`}> + +
{/if}

{title}

@@ -433,7 +441,7 @@ on:swiper={(e) => console.log(e.detail[0])} > -
+
@@ -483,12 +491,12 @@
-
+
-
+
{#if anilistData} {#await anilistData then data}
@@ -553,10 +561,78 @@ {/if}
+ +
+ {#await anilistData then data}{#if data} + {#each data.characters.edges as character} +
{selectedImage = character.node.image.large; selectedCharacter = character}} > +
+ + +
+
+

{character.node.name.userPreferred}

+ {character.role} +
+
+ {/each} + {/if}{/await} +
+