mirror of
https://github.com/danbulant/Mangades
synced 2026-07-08 04:20:52 +00:00
302 lines
No EOL
8.9 KiB
Svelte
302 lines
No EOL
8.9 KiB
Svelte
<script>
|
|
import { url } from "@roxi/routify/runtime/helpers";
|
|
import Chapter from "../../components/chapter.svelte";
|
|
import { EpubGenerator } from "../../util/generateEpub";
|
|
import { CBZGenerator } from "../../util/generateCbz";
|
|
import request from "../../util/request";
|
|
import { BaseGenerator } from "../../util/baseGenerator";
|
|
|
|
export var scoped;
|
|
|
|
var mangaId = scoped.id;
|
|
$: mangaId = scoped.id;
|
|
var manga = scoped.manga;
|
|
$: manga = scoped.manga;
|
|
var relationships = scoped.mangaRelationships;
|
|
$: relationships = scoped.mangaRelationships;
|
|
|
|
async function getMangaChapters(id) {
|
|
const data = await request("manga/" + id + "/feed?limit=500&translatedLanguage[]=en");
|
|
data.results.sort((a, b) => a.data.attributes.chapter - b.data.attributes.chapter);
|
|
return data;
|
|
}
|
|
|
|
$: chapters = getMangaChapters(mangaId);
|
|
|
|
console.log(manga);
|
|
console.log(chapters);
|
|
console.log(relationships);
|
|
|
|
var progress = 0;
|
|
var state = "idle";
|
|
const defaultText = "Choose a chapter to view online or download";
|
|
var text = defaultText;
|
|
var pagesDone = 0;
|
|
var totalPages = 0;
|
|
|
|
$: progress = pagesDone / (totalPages || 1);
|
|
$: if(totalPages) text = `Saving page ${pagesDone + 1} of ${totalPages}`;
|
|
|
|
/**
|
|
* @type {BaseGenerator[]}
|
|
*/
|
|
var queue = [];
|
|
/**
|
|
* @type {BaseGenerator}
|
|
*/
|
|
var processing = null;
|
|
/**
|
|
* @type {Map<string, number>}
|
|
*/
|
|
var progressMap = new Map();
|
|
async function processQueue() {
|
|
if(processing) return;
|
|
processing = queue.shift();
|
|
if(!processing) return processing = null;
|
|
state = "active";
|
|
processing.opts.callback = async (chapter, link, finished) => {
|
|
console.log(chapter, link, finished);
|
|
if(chapter === -1 || link === -1) return;
|
|
var cs = await chapters;
|
|
var related = cs.results.filter(t => processing.opts.chapters.find(c => t.data.id === c.id));
|
|
var done = processing.opts.chapters.filter(t => parseFloat(t.number) < parseFloat(processing.opts.chapters[chapter].number));
|
|
var linkCount = related.map(t => t.data.attributes[quality].length).reduce((a, b) => a + b);
|
|
console.log(related, done, done.reduce((a, b) => (a.links || []).length + (b.links || []).length, 0), link + 1, linkCount);
|
|
progress = (done.reduce((a, b) => (a.links || []).length + (b.links || []).length, 0) + link + 1) / linkCount;
|
|
progressMap.set(processing.opts.chapters[chapter].id, link + 1);
|
|
progressMap = progressMap;
|
|
};
|
|
await processing.generate();
|
|
processing = null;
|
|
progressMap = new Map();
|
|
progress = 0;
|
|
state = "idle";
|
|
processQueue();
|
|
}
|
|
|
|
const generators = {
|
|
epub: EpubGenerator,
|
|
cbz: CBZGenerator
|
|
}
|
|
const quality = "data";
|
|
|
|
async function downloadSingle(chapter) {
|
|
const file = streamSaver.createWriteStream(`${manga.title.en} ${chapter.data.attributes.chapter}.${format}`, {
|
|
writableStrategy: undefined, // (optional)
|
|
readableStrategy: undefined, // (optional)
|
|
});
|
|
|
|
const generator = new generators[format]({
|
|
file,
|
|
id: chapter.data.id,
|
|
language: chapter.data.attributes.translatedLanguage,
|
|
quality,
|
|
updatedAt: chapter.data.attributes.updatedAt,
|
|
title: manga.title.en,
|
|
author: "Unknown",
|
|
chapters: [{
|
|
hash: chapter.data.attributes.hash,
|
|
id: chapter.data.id,
|
|
links: chapter.data.attributes[quality],
|
|
number: chapter.data.attributes.chapter,
|
|
volume: chapter.data.attributes.volume
|
|
}]
|
|
});
|
|
|
|
console.log(generator);
|
|
queue.push(generator);
|
|
processQueue();
|
|
}
|
|
|
|
var format = "cbz";
|
|
var selected = [];
|
|
function select(chapter) {
|
|
if(selected.includes(chapter)) {
|
|
selected.splice(selected.indexOf(chapter), 1);
|
|
} else {
|
|
selected.push(chapter);
|
|
}
|
|
selected = selected;
|
|
if(selected.length) {
|
|
text = `Selected ${selected.length} chapters`;
|
|
} else {
|
|
text = defaultText;
|
|
}
|
|
}
|
|
function downloadMulti() {
|
|
selected.sort((a, b) => a.data.attributes.chapter - b.data.attributes.chapter);
|
|
if(!selected.length) return;
|
|
if(selected.length === 1) {
|
|
downloadSingle(selected.shift());
|
|
selected = [];
|
|
return;
|
|
}
|
|
const file = streamSaver.createWriteStream(`${manga.title.en}.${format}`, {
|
|
writableStrategy: undefined, // (optional)
|
|
readableStrategy: undefined, // (optional)
|
|
});
|
|
const generator = new generators[format]({
|
|
file,
|
|
quality,
|
|
id: window.location.toString(),
|
|
language: selected[0].data.attributes.translatedLanguage,
|
|
updatedAt: new Date,
|
|
title: manga.title.en,
|
|
author: "Unknown",
|
|
chapters: selected.map(chapter => ({
|
|
hash: chapter.data.attributes.hash,
|
|
id: chapter.data.id,
|
|
links: chapter.data.attributes[quality],
|
|
number: chapter.data.attributes.chapter,
|
|
volume: chapter.data.attributes.volume
|
|
}))
|
|
});
|
|
|
|
console.log(generator);
|
|
queue.push(generator);
|
|
selected = [];
|
|
processQueue();
|
|
}
|
|
|
|
/**
|
|
* @param {BeforeUnloadEvent} e
|
|
*/
|
|
function beforeUnload(e) {
|
|
if(progress) {
|
|
e.preventDefault();
|
|
return "Downloads won't be saved if you exit this page before they're finished.";
|
|
}
|
|
}
|
|
|
|
$: console.log(format);
|
|
</script>
|
|
|
|
<svelte:window on:beforeUnload={beforeUnload} />
|
|
|
|
<svelte:head>
|
|
<title>Chapters of {manga.title.en}</title>
|
|
<meta name="description" value="Read {manga.title.en} online, or download it as EPUB or CBZ file. Free of charge and ads." />
|
|
</svelte:head>
|
|
|
|
<main>
|
|
<h1>{manga.title.en}</h1>
|
|
|
|
<div class="linklist">
|
|
<a href={$url("..")}>Go back to search page</a>
|
|
</div>
|
|
|
|
<br>
|
|
|
|
<div class="state {state}">
|
|
<div class="progress" style="width: {progress * 100}%;"></div>
|
|
|
|
<p>
|
|
{text}
|
|
</p>
|
|
</div>
|
|
|
|
{#if queue.length > 0}
|
|
<p><i>{queue.length} downloads queued.</i></p>
|
|
{/if}
|
|
|
|
<div class="download">
|
|
<select name="format" bind:value={format} id="select-format">
|
|
<option value="cbz"><b>.cbz</b> Comic Book Zip</option>
|
|
<option value="epub"><b>.epub</b> Electronic publication</option>
|
|
</select>
|
|
<button disabled={!selected.length} on:click={downloadMulti}>Download</button>
|
|
</div>
|
|
|
|
<p>
|
|
<b>
|
|
Do not close the tab when a download is in progress.
|
|
</b>
|
|
</p>
|
|
|
|
{#await chapters}
|
|
Loading chapters...
|
|
{:then chapters}
|
|
{#if chapters.results.length === 0}
|
|
<p>No chapters found.</p>
|
|
{/if}
|
|
<table>
|
|
<tbody>
|
|
{#each chapters.results as chapter, i}
|
|
<Chapter progress={(progressMap.get(chapter.data.id) || 0) / chapter.data.attributes[quality].length} {chapter} disabledDownload={!!progress} selected={selected.includes(chapter)} on:select={() => select(chapter)} on:download={() => downloadSingle(chapter)} />
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
{/await}
|
|
|
|
<p>DISCLAIMER: This site isn't distributing any content and is using mangadex.org API. Website is open source, and I don't claim any copyright on the publications.</p>
|
|
</main>
|
|
|
|
<style>
|
|
.download {
|
|
display: flex;
|
|
width: 100%;
|
|
margin-top: 5px;
|
|
}
|
|
.download select {
|
|
flex-grow: 1;
|
|
margin-inline: 5px;
|
|
}
|
|
.download button {
|
|
margin-inline: 5px;
|
|
}
|
|
main {
|
|
font-size: 1.1rem;
|
|
}
|
|
.no-wrap {
|
|
white-space: nowrap;
|
|
}
|
|
tbody {
|
|
list-style-type: disc;
|
|
}
|
|
table {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
}
|
|
|
|
.state {
|
|
border-radius: 10px;
|
|
border-width: 4px;
|
|
border-style: solid;
|
|
padding: 10px;
|
|
position: relative;
|
|
transition: all .3s;
|
|
}
|
|
|
|
.state.idle {
|
|
background: rgb(140, 209, 255);
|
|
border-color: rgb(77, 184, 255);
|
|
}
|
|
|
|
.state.active {
|
|
background: rgb(255, 255, 81);
|
|
border-color: yellow;
|
|
}
|
|
.state.error {
|
|
background: rgb(255, 103, 103);
|
|
border-color: rgb(255, 59, 59);
|
|
}
|
|
|
|
.state p {
|
|
margin: 0;
|
|
z-index: 1;
|
|
position: relative;
|
|
}
|
|
|
|
.progress {
|
|
z-index: 0;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
height: 100%;
|
|
border-top-left-radius: 10px;
|
|
border-bottom-left-radius: 10px;
|
|
}
|
|
.state.active .progress {
|
|
background: rgb(140, 209, 255);
|
|
}
|
|
</style> |