From d6d780893488e5264f7fa6ee4cd6fc92d34a95ca Mon Sep 17 00:00:00 2001 From: dkanada Date: Tue, 30 Jun 2026 03:30:37 +0900 Subject: [PATCH] migrate comics and PDFs to new BookOsd component --- src/plugins/bookPlayer/BookOsd/BookOsd.scss | 10 +- src/plugins/bookPlayer/BookOsd/BookOsd.tsx | 49 ++++++++- src/plugins/bookPlayer/plugin.js | 2 +- src/plugins/comicsPlayer/plugin.js | 109 +++++++------------- src/plugins/comicsPlayer/style.scss | 16 +-- src/plugins/pdfPlayer/plugin.js | 71 +++++-------- src/plugins/pdfPlayer/style.scss | 15 +-- src/scripts/settings/userSettings.js | 2 +- src/themes/_base/_theme.scss | 6 -- 9 files changed, 118 insertions(+), 162 deletions(-) diff --git a/src/plugins/bookPlayer/BookOsd/BookOsd.scss b/src/plugins/bookPlayer/BookOsd/BookOsd.scss index 4c9e2b142d..6e582dfc15 100644 --- a/src/plugins/bookPlayer/BookOsd/BookOsd.scss +++ b/src/plugins/bookPlayer/BookOsd/BookOsd.scss @@ -4,6 +4,7 @@ z-index: 1002; display: flex; justify-content: space-between; + pointer-events: none; flex-direction: column; } @@ -14,6 +15,7 @@ height: 5vh; padding: 0 0.8vw; background-color: var(--jf-palette-AppBar-defaultBg); + pointer-events: auto; } // TODO disable landscape on mobile devices @@ -23,6 +25,10 @@ } } +.bookOsdSpacer { + flex: 1; +} + .bookOsdTop { padding-top: env(safe-area-inset-top); } @@ -31,10 +37,6 @@ padding-bottom: env(safe-area-inset-bottom); } -.bookOsdMargin { - margin-left: auto; -} - .bookOsdTitle { font-size: 1.1rem; margin-left: 0.35rem; diff --git a/src/plugins/bookPlayer/BookOsd/BookOsd.tsx b/src/plugins/bookPlayer/BookOsd/BookOsd.tsx index 9df50dc9b8..2374aba272 100644 --- a/src/plugins/bookPlayer/BookOsd/BookOsd.tsx +++ b/src/plugins/bookPlayer/BookOsd/BookOsd.tsx @@ -3,9 +3,11 @@ import React, { type FC, useCallback, useState } from 'react'; import './BookOsd.scss'; import IconButton from '../../../elements/emby-button/IconButton'; import globalize from 'lib/globalize'; +import * as userSettings from '../../../scripts/settings/userSettings'; +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; interface BookOsdProps { - title: string; + item: BaseItemDto; onExit: () => void; onPrevious: () => void; onNext: () => void; @@ -13,11 +15,18 @@ interface BookOsdProps { onRotateTheme?: () => void; onDecreaseFontSize?: () => void; onIncreaseFontSize?: () => void; + onToggleDirection?: () => void; + onToggleLayout?: () => void; onToggleFullscreen?: () => void; } +interface ComicsPlayerSettings { + langDir?: string; + pagesPerView?: number; +} + const BookOsd: FC = ({ - title, + item, onExit, onPrevious, onNext, @@ -25,10 +34,26 @@ const BookOsd: FC = ({ onRotateTheme, onDecreaseFontSize, onIncreaseFontSize, + onToggleDirection, + onToggleLayout, onToggleFullscreen }) => { + const settings = userSettings.getComicsPlayerSettings(item.Id!) as ComicsPlayerSettings; + + const [direction, setDirection] = useState(settings.langDir === 'rtl'); + const [layout, setLayout] = useState(settings.pagesPerView === 1); const [fullscreen, setFullscreen] = useState(false); + const onClickDirection = useCallback(() => { + onToggleDirection?.(); + setDirection(state => !state); + }, [onToggleDirection]); + + const onClickLayout = useCallback(() => { + onToggleLayout?.(); + setLayout(state => !state); + }, [onToggleLayout]); + const onClickFullscreen = useCallback(() => { onToggleFullscreen?.(); setFullscreen(state => !state); @@ -38,19 +63,19 @@ const BookOsd: FC = ({
- {title} + {item.Name}
+
{onOpenTableOfContents && ( )} @@ -78,6 +103,22 @@ const BookOsd: FC = ({ /> )} + {onToggleDirection && ( + + )} + + {onToggleLayout && ( + + )} + {onToggleFullscreen && ( { + onDirChanged() { let langDir = this.comicsPlayerSettings.langDir; if (!langDir || langDir === 'ltr') { @@ -112,26 +122,16 @@ export class ComicsPlayer { this.changeLanguageDirection(langDir); this.comicsPlayerSettings.langDir = langDir; - }; + } changeLanguageDirection(langDir) { const currentPage = this.currentPage; this.swiperInstance.changeLanguageDirection(langDir); - - const prevIcon = langDir === 'ltr' ? 'arrow_circle_left' : 'arrow_circle_right'; - this.mediaElement.querySelector('.btnToggleLangDir > span').classList.remove(prevIcon); - - const newIcon = langDir === 'ltr' ? 'arrow_circle_right' : 'arrow_circle_left'; - this.mediaElement.querySelector('.btnToggleLangDir > span').classList.add(newIcon); - - const dirTitle = langDir === 'ltr' ? 'Right To Left' : 'Left To Right'; - this.mediaElement.querySelector('.btnToggleLangDir').title = dirTitle; - this.reload(currentPage); } - onViewChanged = () => { + onViewChanged() { let view = this.comicsPlayerSettings.pagesPerView; if (!view || view === 1) { @@ -143,7 +143,7 @@ export class ComicsPlayer { this.changeView(view); this.comicsPlayerSettings.pagesPerView = view; - }; + } changeView(view) { const currentPage = this.currentPage; @@ -151,15 +151,6 @@ export class ComicsPlayer { this.swiperInstance.params.slidesPerView = view; this.swiperInstance.params.slidesPerGroup = view; - const prevIcon = view === 1 ? 'devices_fold' : 'import_contacts'; - this.mediaElement.querySelector('.btnToggleView > span').classList.remove(prevIcon); - - const newIcon = view === 1 ? 'import_contacts' : 'devices_fold'; - this.mediaElement.querySelector('.btnToggleView > span').classList.add(newIcon); - - const viewTitle = view === 1 ? 'Double Page View' : 'Single Page View'; - this.mediaElement.querySelector('.btnToggleView').title = viewTitle; - this.reload(currentPage); } @@ -192,37 +183,17 @@ export class ComicsPlayer { } } - bindMediaElementEvents() { - const elem = this.mediaElement; - - elem?.addEventListener('close', this.onDialogClosed, { once: true }); - elem?.querySelector('.btnExit').addEventListener('click', this.onDialogClosed, { once: true }); - elem?.querySelector('.btnToggleLangDir').addEventListener('click', this.onDirChanged); - elem?.querySelector('.btnToggleView').addEventListener('click', this.onViewChanged); - } - bindEvents() { - this.bindMediaElementEvents(); - + this.mediaElement?.addEventListener('close', this.onDialogClosed, { once: true }); document.addEventListener('keydown', this.onWindowKeyDown); } - unbindMediaElementEvents() { - const elem = this.mediaElement; - - elem?.removeEventListener('close', this.onDialogClosed); - elem?.querySelector('.btnExit').removeEventListener('click', this.onDialogClosed); - elem?.querySelector('.btnToggleLangDir').removeEventListener('click', this.onDirChanged); - elem?.querySelector('.btnToggleView').removeEventListener('click', this.onViewChanged); - } - unbindEvents() { - this.unbindMediaElementEvents(); - + this.mediaElement?.removeEventListener('close', this.onDialogClosed); document.removeEventListener('keydown', this.onWindowKeyDown); } - createMediaElement() { + createMediaElement(options) { let elem = this.mediaElement; if (elem) { return elem; @@ -239,39 +210,25 @@ export class ComicsPlayer { removeOnClose: true }); - const viewIcon = this.comicsPlayerSettings.pagesPerView === 1 ? 'import_contacts' : 'devices_fold'; - const dirIcon = this.comicsPlayerSettings.langDir === 'ltr' ? 'arrow_circle_right' : 'arrow_circle_left'; - elem.id = 'comicsPlayer'; elem.classList.add('slideshowDialog'); - elem.innerHTML = `
+ elem.innerHTML = `
-
-
-
-
- - -
`; dialogHelper.open(elem); } this.mediaElement = elem; - - const dirTitle = this.comicsPlayerSettings.langDir === 'ltr' ? 'Right To Left' : 'Left To Right'; - this.mediaElement.querySelector('.btnToggleLangDir').title = dirTitle; - - const viewTitle = this.comicsPlayerSettings.pagesPerView === 1 ? 'Double Page View' : 'Single Page View'; - this.mediaElement.querySelector('.btnToggleView').title = viewTitle; + this.unmountBookOsd = renderComponent(BookOsd, { + item: options.items[0], + onExit: this.onDialogClosed, + onPrevious: this.previous, + onNext: this.next, + onToggleDirection: this.onDirChanged, + onToggleLayout: this.onViewChanged + }, elem.querySelector('#bookOsdMount')); this.bindEvents(); return elem; @@ -329,10 +286,6 @@ export class ComicsPlayer { slidesPerGroup: this.comicsPlayerSettings.pagesPerView, slidesPerColumn: 1, initialSlide: this.currentPage, - navigation: { - nextEl: '.swiper-button-next', - prevEl: '.swiper-button-prev' - }, pagination: { el: '.swiper-pagination', clickable: true, @@ -356,6 +309,14 @@ export class ComicsPlayer { }); } + next() { + this.swiperInstance?.slideNext(); + } + + previous() { + this.swiperInstance?.slidePrev(); + } + getImgFromUrl(url) { return `
diff --git a/src/plugins/comicsPlayer/style.scss b/src/plugins/comicsPlayer/style.scss index 3061950cdc..5c832f33a0 100644 --- a/src/plugins/comicsPlayer/style.scss +++ b/src/plugins/comicsPlayer/style.scss @@ -2,7 +2,9 @@ background: #fff; .slideshowSwiperContainer { - height: 100%; + position: relative; + top: 5vh; + height: 90vh; } .slider-zoom-container { @@ -27,16 +29,4 @@ transform: translate(-50%, 0%); text-shadow: 0 0 20px #fff; } - - .actionButtons { - right: 0.5vh; - top: 0.5vh; - z-index: 1002; - position: absolute; - } - - .actionButtonIcon { - color: #000; - opacity: 0.7; - } } diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js index 9f6925234e..7235e0ddc2 100644 --- a/src/plugins/pdfPlayer/plugin.js +++ b/src/plugins/pdfPlayer/plugin.js @@ -10,9 +10,11 @@ import dom from '../../utils/dom'; import { appRouter } from '../../components/router/appRouter'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import Events from '../../utils/events.ts'; +import BookOsd from '../bookPlayer/BookOsd/BookOsd'; +import { renderComponent } from '../../utils/reactUtils'; +import 'material-design-icons-iconfont'; import './style.scss'; -import '../../elements/emby-button/paper-icon-button-light'; export class PdfPlayer { constructor() { @@ -21,6 +23,9 @@ export class PdfPlayer { this.id = 'pdfplayer'; this.priority = 1; + this.previous = this.previous.bind(this); + this.next = this.next.bind(this); + this.onDialogClosed = this.onDialogClosed.bind(this); this.onWindowKeyDown = this.onWindowKeyDown.bind(this); this.onTouchStart = this.onTouchStart.bind(this); @@ -34,12 +39,13 @@ export class PdfPlayer { loading.show(); - const elem = this.createMediaElement(); + const elem = this.createMediaElement(options); return this.setCurrentSrc(elem, options); } stop() { this.unbindEvents(); + this.unmountBookOsd?.(); const stopInfo = { src: this.item @@ -133,37 +139,19 @@ export class PdfPlayer { this.stop(); } - bindMediaElementEvents() { - const elem = this.mediaElement; - - elem.addEventListener('close', this.onDialogClosed, { once: true }); - elem.querySelector('.btnExit').addEventListener('click', this.onDialogClosed, { once: true }); - } - bindEvents() { - this.bindMediaElementEvents(); - + this.mediaElement?.addEventListener('close', this.onDialogClosed, { once: true }); document.addEventListener('keydown', this.onWindowKeyDown); document.addEventListener('touchstart', this.onTouchStart); } - unbindMediaElementEvents() { - const elem = this.mediaElement; - - elem.removeEventListener('close', this.onDialogClosed); - elem.querySelector('.btnExit').removeEventListener('click', this.onDialogClosed); - } - unbindEvents() { - if (this.mediaElement) { - this.unbindMediaElementEvents(); - } - + this.mediaElement?.removeEventListener('close', this.onDialogClosed); document.removeEventListener('keydown', this.onWindowKeyDown); document.removeEventListener('touchstart', this.onTouchStart); } - createMediaElement() { + createMediaElement(options) { let elem = this.mediaElement; if (elem) { return elem; @@ -180,19 +168,20 @@ export class PdfPlayer { removeOnClose: true }); - let html = ''; - html += ''; - html += '
'; - html += ''; - html += '
'; - elem.id = 'pdfPlayer'; - elem.innerHTML = html; + elem.innerHTML = '
'; dialogHelper.open(elem); } this.mediaElement = elem; + this.unmountBookOsd = renderComponent(BookOsd, { + item: options.items[0], + onExit: this.onDialogClosed, + onPrevious: this.previous, + onNext: this.next + }, elem.querySelector('#bookOsdMount')); + return elem; } @@ -254,13 +243,6 @@ export class PdfPlayer { Events.trigger(this, 'pause'); } - replaceCanvas(canvas) { - const old = document.getElementById('canvas'); - - canvas.id = 'canvas'; - old.parentNode.replaceChild(canvas, old); - } - loadPage(number) { const prefix = 'page'; const pad = 2; @@ -277,11 +259,13 @@ export class PdfPlayer { if (!this.pages[page]) { this.pages[page] = document.createElement('canvas'); this.renderPage(this.pages[page], parseInt(page.slice(4), 10)); + + this.pages[page].id = 'canvas'; } } // show the requested page - this.replaceCanvas(this.pages[prefix + number], number); + document.querySelector('#canvas')?.replaceWith(this.pages[prefix + number]); // delete all pages outside the cache area for (const page in this.pages) { @@ -294,21 +278,14 @@ export class PdfPlayer { renderPage(canvas, number) { const devicePixelRatio = window.devicePixelRatio || 1; this.book.getPage(number).then(page => { + const current = document.querySelector('#canvas'); const original = page.getViewport({ scale: 1 }); - const scale = Math.min((window.innerHeight / original.height), (window.innerWidth / original.width)) * devicePixelRatio; + const scale = Math.min((current.clientHeight / original.height), (current.clientWidth / original.width)) * devicePixelRatio; const viewport = page.getViewport({ scale }); canvas.width = viewport.width; canvas.height = viewport.height; - if (window.innerWidth < window.innerHeight) { - canvas.style.width = '100%'; - canvas.style.height = 'auto'; - } else { - canvas.style.height = '100%'; - canvas.style.width = 'auto'; - } - const context = canvas.getContext('2d'); const renderContext = { diff --git a/src/plugins/pdfPlayer/style.scss b/src/plugins/pdfPlayer/style.scss index 29c5c7d543..d76dd8cc91 100644 --- a/src/plugins/pdfPlayer/style.scss +++ b/src/plugins/pdfPlayer/style.scss @@ -7,19 +7,10 @@ background: #fff; #canvas { + position: relative; + top: 5vh; + height: 90vh; display: block; margin: auto; } - - .actionButtons { - right: 0.5vh; - top: 0.5vh; - z-index: 1002; - position: absolute; - } - - .actionButtonIcon { - color: #000; - opacity: 0.7; - } } diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 9a3b4f68ed..563268d787 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -667,7 +667,7 @@ export class UserSettings { */ getComicsPlayerSettings(mediaSourceId) { const settings = JSON.parse(this.get('comicsPlayerSettings', false) || '{}'); - return Object.assign(defaultComicsPlayerSettings, settings[mediaSourceId]); + return Object.assign({}, defaultComicsPlayerSettings, settings[mediaSourceId]); } /** diff --git a/src/themes/_base/_theme.scss b/src/themes/_base/_theme.scss index 25d4107843..eaeb98bf27 100644 --- a/src/themes/_base/_theme.scss +++ b/src/themes/_base/_theme.scss @@ -507,12 +507,6 @@ a[data-role=button], color: var(--jf-palette-text-primary, $text-primary); } -#bookPlayer .bookOsdRow, -#comicsPlayer .actionButtonIcon, -#pdfPlayer .actionButtonIcon { - color: var(--jf-palette-text-primary, $text-primary); -} - #dialogToc { background-color: var(--jf-palette-background-default, $background-default); }