mirror of
https://github.com/holo-gfx/mangadex.git
synced 2024-11-28 19:08:21 -05:00
356 lines
13 KiB
JavaScript
356 lines
13 KiB
JavaScript
|
'use strict'
|
||
|
|
||
|
/* global Renderer */
|
||
|
|
||
|
const utils = {
|
||
|
empty: (node) => {
|
||
|
while (node.firstChild) {
|
||
|
node.removeChild(node.firstChild)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class UI {
|
||
|
constructor(reader) {
|
||
|
this.reader = reader
|
||
|
}
|
||
|
|
||
|
get isSinglePage() { return this.renderingMode === UI.RENDERING_MODE.SINGLE }
|
||
|
get isDoublePage() { return this.renderingMode === UI.RENDERING_MODE.DOUBLE }
|
||
|
get isLongStrip() { return this.renderingMode === UI.RENDERING_MODE.LONG }
|
||
|
get isNoResize() { return this.displayFit === UI.DISPLAY_FIT.NO_RESIZE }
|
||
|
get isFitHeight() { return this.displayFit === UI.DISPLAY_FIT.FIT_HEIGHT }
|
||
|
get isFitWidth() { return this.displayFit === UI.DISPLAY_FIT.FIT_WIDTH }
|
||
|
get isFitBoth() { return this.displayFit === UI.DISPLAY_FIT.FIT_BOTH }
|
||
|
get isDirectionLTR() { return this.direction === UI.DIRECTION.LTR }
|
||
|
get isDirectionRTL() { return this.direction === UI.DIRECTION.RTL }
|
||
|
get renderedPages() {
|
||
|
if (this.renderer == null) {
|
||
|
return 0
|
||
|
} else if (this.isLongStrip) {
|
||
|
return 1
|
||
|
} else {
|
||
|
return this.renderer.renderedPages
|
||
|
}
|
||
|
}
|
||
|
|
||
|
initializeContainer(userIsGuest = false) {
|
||
|
this.container = document.querySelector('div[role="main"]')
|
||
|
this.container.classList.remove('container')
|
||
|
this.container.classList.add('reader', 'row', 'flex-column', 'flex-lg-row', 'no-gutters')
|
||
|
this.imageContainer = this.container.querySelector('.reader-images')
|
||
|
document.querySelector('footer').classList.add('d-none')
|
||
|
document.body.style.removeProperty('margin-bottom')
|
||
|
if (userIsGuest) {
|
||
|
const reportBtn = this.container.querySelector('#report-button')
|
||
|
reportBtn.dataset.toggle = ''
|
||
|
reportBtn.href = '/login'
|
||
|
reportBtn.firstElementChild.classList.replace('fa-flag', 'fa-sign-in-alt')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setRenderer(mode = this.reader.settings.renderingMode, useHistory = true) {
|
||
|
if (this.renderingMode !== mode) {
|
||
|
if (this.renderer != null) {
|
||
|
this.renderer.destroy()
|
||
|
}
|
||
|
this.renderingMode = mode
|
||
|
switch(mode) {
|
||
|
case UI.RENDERING_MODE.LONG:
|
||
|
this.renderer = new Renderer.LongStrip(this.reader)
|
||
|
break
|
||
|
case UI.RENDERING_MODE.DOUBLE:
|
||
|
this.renderer = new Renderer.DoublePage(this.reader)
|
||
|
break
|
||
|
case UI.RENDERING_MODE.SINGLE:
|
||
|
default:
|
||
|
this.renderer = new Renderer.SinglePage(this.reader)
|
||
|
break
|
||
|
case UI.RENDERING_MODE.ALERT:
|
||
|
this.renderer = new Renderer.Alert(this.reader)
|
||
|
break
|
||
|
case UI.RENDERING_MODE.RECS:
|
||
|
this.renderer = new Renderer.Recommendations(this.reader)
|
||
|
break
|
||
|
}
|
||
|
this.container.dataset.renderer = this.renderer.name
|
||
|
if (useHistory) {
|
||
|
this.pushHistory(this.reader.currentPage)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setDirection(direction) {
|
||
|
this.direction = direction
|
||
|
this.container.dataset.direction = UI.DIRECTION.LTR === direction ? 'ltr' : 'rtl'
|
||
|
}
|
||
|
|
||
|
setDisplayFit(fit) {
|
||
|
this.displayFit = fit
|
||
|
this.container.dataset.display = UI.DISPLAY_FIT_STR[fit]
|
||
|
this.container.classList.toggle('fit-horizontal', this.isFitBoth || this.isFitWidth)
|
||
|
this.container.classList.toggle('fit-vertical', this.isFitBoth || this.isFitHeight)
|
||
|
}
|
||
|
|
||
|
onChapterChange(chapter) {
|
||
|
if (this.renderer) {
|
||
|
this.renderer.initialize()
|
||
|
}
|
||
|
this.container.classList.toggle('native-long-strip', chapter.manga.isLongStrip)
|
||
|
this.setRenderer(chapter.manga.isLongStrip ? UI.RENDERING_MODE.LONG : this.reader.settings.renderer, false)
|
||
|
this.setDisplayFit(chapter.manga.isLongStrip ? UI.DISPLAY_FIT.FIT_WIDTH : this.reader.settings.displayFit)
|
||
|
this.resetPageBar(chapter.totalPages)
|
||
|
// TODO?: it's assumed that the input is already escaped; when not, change innerHTML to textContent
|
||
|
this.updateTitles(chapter)
|
||
|
this.updateChapterDropdown(chapter, !this.reader.settings.showDropdownTitles)
|
||
|
this.updatePageDropdown(chapter.totalPages, this.reader.currentPage)
|
||
|
this.updateGroupList(chapter)
|
||
|
this.updateCommentsButton(chapter)
|
||
|
this.updateChapterLinks(chapter)
|
||
|
}
|
||
|
|
||
|
updateSetting(key, value) {
|
||
|
switch (key) {
|
||
|
case 'direction':
|
||
|
this.setDirection(value)
|
||
|
this.updateChapterLinks()
|
||
|
this.setRenderer()
|
||
|
if (this.reader.currentPage) {
|
||
|
this.render(this.reader.currentPage)
|
||
|
}
|
||
|
break
|
||
|
case 'renderingMode':
|
||
|
this.setRenderer(value)
|
||
|
if (this.reader.currentPage) {
|
||
|
this.render(this.reader.currentPage)
|
||
|
}
|
||
|
break
|
||
|
case 'displayFit':
|
||
|
this.setDisplayFit(value)
|
||
|
break
|
||
|
case 'containerWidth':
|
||
|
if (!value) {
|
||
|
value = null
|
||
|
}
|
||
|
this.imageContainer.style.maxWidth = value ? `${value}px` : null
|
||
|
break
|
||
|
case 'showDropdownTitles':
|
||
|
this.updateChapterDropdown(this.reader.chapter, !value)
|
||
|
break
|
||
|
case 'hideHeader':
|
||
|
this.container.classList.toggle('hide-header', value)
|
||
|
document.querySelector('nav.navbar').classList.toggle('d-none', value)
|
||
|
document.querySelector('#fullscreen-button').classList.toggle('active', value)
|
||
|
break
|
||
|
case 'hideSidebar':
|
||
|
this.container.classList.toggle('hide-sidebar', value)
|
||
|
break
|
||
|
case 'hidePagebar':
|
||
|
this.container.classList.toggle('hide-page-bar', value)
|
||
|
break
|
||
|
}
|
||
|
Array.from(this.container.querySelectorAll(`#modal-settings input[data-setting="${key}"]`)).forEach(n => { n.value = value })
|
||
|
Array.from(this.container.querySelectorAll(`#modal-settings select[data-setting="${key}"]`)).forEach(n => { n.value = value })
|
||
|
Array.from(this.container.querySelectorAll(`#modal-settings button[data-setting="${key}"]`)).forEach(n => { n.classList.toggle('active', n.dataset.value == value) })
|
||
|
}
|
||
|
|
||
|
updateTitles(chapter) {
|
||
|
const manga = chapter.manga
|
||
|
document.title = `${manga.title} - ${manga.getChapterTitle(chapter.id)} - MangaDex`
|
||
|
const mangaFlag = this.container.querySelector('.reader-controls-title .lang-flag')
|
||
|
mangaFlag.parentElement.replaceChild(UI.flagImg(manga.langCode, manga.langName), mangaFlag)
|
||
|
const mangaLink = this.container.querySelector('.manga-link')
|
||
|
mangaLink.href = manga.url
|
||
|
mangaLink.title = manga.title
|
||
|
mangaLink.innerHTML = manga.title
|
||
|
this.container.querySelector('.chapter-title').innerHTML = chapter.title
|
||
|
this.container.querySelector('.chapter-tag-h').classList.toggle('d-none', !manga.isHentai)
|
||
|
this.container.querySelector('.chapter-tag-end').classList.toggle('d-none', !chapter.isLastChapter)
|
||
|
this.container.querySelector('.chapter-tag-doujinshi').classList.toggle('d-none', !manga.isDoujinshi)
|
||
|
}
|
||
|
|
||
|
updateChapterDropdown(chapter, hideTitles) {
|
||
|
if (chapter) {
|
||
|
const manga = chapter.manga
|
||
|
const chapters = this.container.querySelector('#jump-chapter')
|
||
|
utils.empty(chapters)
|
||
|
for (let ch of manga.chapterList.slice().reverse()) {
|
||
|
const option = chapters.appendChild(document.createElement('option'))
|
||
|
option.value = ch.id
|
||
|
option.selected = ch.id === chapter.id
|
||
|
option.appendChild(document.createTextNode(manga.getChapterTitle(ch.id, hideTitles)))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updatePageDropdown(totalPages, currentPage) {
|
||
|
const pages = this.container.querySelector('#jump-page')
|
||
|
utils.empty(pages)
|
||
|
for (let i = 1; i <= totalPages; ++i) {
|
||
|
const option = pages.appendChild(document.createElement('option'))
|
||
|
option.value = i
|
||
|
option.selected = currentPage === i
|
||
|
option.appendChild(document.createTextNode(i))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateGroupList(chapter) {
|
||
|
const groups = this.container.querySelector('.reader-controls-groups ul')
|
||
|
utils.empty(groups)
|
||
|
for (let g of chapter.manga.getGroupsOfChapter(chapter.id)) {
|
||
|
const li = groups.appendChild(document.createElement('li'))
|
||
|
const flag = li.appendChild(UI.flagImg(g.lang_code, g.lang_code))
|
||
|
flag.classList.add('mr-1')
|
||
|
const link = li.appendChild(document.createElement(g.id == chapter.id ? 'b' : 'a'))
|
||
|
link.innerHTML = [g.group_name, g.group_name_2, g.group_name_3].filter(n => n).join(' | ')
|
||
|
link.dataset.chapter = g.id
|
||
|
link.href = `/chapter/${g.id}`
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateCommentsButton(chapter) {
|
||
|
this.container.querySelector('#comment-button').href = this.pageURL(chapter.id) + '/comments'
|
||
|
this.container.querySelector('.comment-amount').textContent = chapter.comments || ''
|
||
|
}
|
||
|
|
||
|
updateChapterLinks(chapter) {
|
||
|
if (chapter) {
|
||
|
const update = (toLeft) => {
|
||
|
if (this.direction != null) {
|
||
|
let id = (toLeft === this.isDirectionLTR) ? chapter.prevChapterId : chapter.nextChapterId
|
||
|
return (a) => {
|
||
|
a.dataset.chapter = id
|
||
|
a.href = this.pageURL(id)
|
||
|
a.title = chapter.manga.getChapterTitle(id) || 'Back to manga'
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Array.from(this.container.querySelectorAll('a.chapter-link-left')).forEach(update(true))
|
||
|
Array.from(this.container.querySelectorAll('a.chapter-link-right')).forEach(update(false))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updatePageLinks(chapter, pg) {
|
||
|
if (chapter && typeof pg === 'number') {
|
||
|
const pgStr = `${pg}${this.renderedPages===2 ? ` - ${pg + 1}` : ''}`
|
||
|
this.container.querySelector('.reader-controls-pages .current-page').textContent = pgStr
|
||
|
this.container.querySelector('.reader-controls-pages .total-pages').textContent = chapter.totalPages
|
||
|
this.container.querySelector('.reader-controls-pages .page-link-left').href = this.pageLeftURL(chapter.id, 1)
|
||
|
this.container.querySelector('.reader-controls-pages .page-link-right').href = this.pageRightURL(chapter.id, 1)
|
||
|
this.container.querySelector('#jump-page').value = pg
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pageURL(id, pg) {
|
||
|
if (id != null && id > 0) {
|
||
|
if (pg != null) {
|
||
|
if (pg === 0) {
|
||
|
return this.pageURL(this.reader.chapter.prevChapterId, -1)
|
||
|
} else if (pg > this.reader.chapter.totalPages) {
|
||
|
return this.pageURL(this.reader.chapter.nextChapterId)
|
||
|
}
|
||
|
return this.isTestReader ? `/?page=chapter_test&id=${id}&p=${pg}` : `/chapter/${id}/${pg}`
|
||
|
}
|
||
|
return this.isTestReader ? `/?page=chapter_test&id=${id}` : `/chapter/${id}`
|
||
|
}
|
||
|
return this.manga.url
|
||
|
}
|
||
|
pageLeftURL(id, pages = this.isDoublePage ? 2 : 1) {
|
||
|
return this.pageURL(id, Math.min(this.reader.currentPage + (this.isDirectionLTR ? -pages : pages)), 0)
|
||
|
}
|
||
|
pageRightURL(id, pages = this.isDoublePage ? 2 : 1) {
|
||
|
return this.pageURL(id, Math.min(this.reader.currentPage + (this.isDirectionLTR ? pages : -pages)), 0)
|
||
|
}
|
||
|
|
||
|
resetPageBar(totalPages) {
|
||
|
if (totalPages) {
|
||
|
const notches = this.container.querySelector('.reader-page-bar .notches')
|
||
|
utils.empty(notches)
|
||
|
for (let i = 1; i <= totalPages; ++i) {
|
||
|
const notch = notches.appendChild(document.createElement('div'))
|
||
|
notch.classList.add('notch', 'col')
|
||
|
notch.style.order = i
|
||
|
notch.dataset.page = i
|
||
|
notch.title = `Page ${i}`
|
||
|
// notch.classList.toggle('trail', i <= pg-this.renderedPages)
|
||
|
// notch.classList.toggle('thumb', i > pg-this.renderedPages && i <= pg)
|
||
|
}
|
||
|
this.updatePageBar(totalPages, 1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updatePageBar(totalPages, pg) {
|
||
|
if (totalPages && typeof pg === 'number') {
|
||
|
const trail = this.container.querySelector('.reader-page-bar .trail')
|
||
|
const thumb = this.container.querySelector('.reader-page-bar .thumb')
|
||
|
const notchSize = 100 / Math.max(totalPages, 1)
|
||
|
trail.style.width = Math.min(pg * notchSize, 100) + '%'
|
||
|
thumb.style.width = (100 / pg * Math.max(this.reader.renderedPages, 1)) + '%'
|
||
|
trail.style.right = this.reader.isDirectionLTR ? null : 0
|
||
|
thumb.style.float = this.reader.isDirectionLTR ? 'right' : 'left'
|
||
|
}
|
||
|
}
|
||
|
|
||
|
render(chapter, pg) {
|
||
|
if (!pg || chapter == null || this.ui.renderer == null) {
|
||
|
return Promise.reject()
|
||
|
}
|
||
|
return this.renderer.render(pg).then(() => {
|
||
|
this.updatePageLinks(chapter, this.reader.currentPage)
|
||
|
this.updatePageBar(chapter, this.reader.currentPage + this.renderedPages - 1)
|
||
|
}).catch((err) => this.renderError(err))
|
||
|
}
|
||
|
|
||
|
renderError(err) {
|
||
|
console.error('Render error', err)
|
||
|
this.setRenderer(UI.RENDERING_MODE.ALERT)
|
||
|
return this.renderer.render({ type: 'danger', 'message': err.message, 'err': err })
|
||
|
}
|
||
|
|
||
|
|
||
|
static flagImg (langCode = 'jp', langName = 'Unknown') {
|
||
|
const flag = document.createElement('img')
|
||
|
flag.classList.add('lang-flag')
|
||
|
flag.src = `https://mangadex.org/images/flags/${langCode}.png`
|
||
|
flag.alt = langName
|
||
|
flag.title = langName
|
||
|
return flag
|
||
|
}
|
||
|
}
|
||
|
UI.RENDERING_MODE = {
|
||
|
SINGLE: 1,
|
||
|
DOUBLE: 2,
|
||
|
LONG: 3,
|
||
|
ALERT: 4,
|
||
|
RECS: 5,
|
||
|
}
|
||
|
UI.DIRECTION = {
|
||
|
LTR: 1,
|
||
|
RTL: 2,
|
||
|
}
|
||
|
UI.DISPLAY_FIT = {
|
||
|
FIT_BOTH: 1,
|
||
|
FIT_WIDTH: 2,
|
||
|
FIT_HEIGHT: 3,
|
||
|
NO_RESIZE: 4,
|
||
|
}
|
||
|
UI.DISPLAY_FIT_STR = {
|
||
|
1: 'fit-both',
|
||
|
2: 'fit-width',
|
||
|
3: 'fit-height',
|
||
|
4: 'no-resize',
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
if (typeof exports === 'object' && typeof module === 'object') {
|
||
|
module.exports = UI
|
||
|
} else if (typeof define === 'function' && define.amd) {
|
||
|
define([], function () {
|
||
|
return UI
|
||
|
})
|
||
|
} else if (typeof exports === 'object') {
|
||
|
exports.UI = UI
|
||
|
} else {
|
||
|
(typeof window !== 'undefined' ? window : this).UI = UI
|
||
|
}
|