Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6c9aef952 |
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$GLOBALS['TL_DCA']['tl_content']['palettes']['blatterbares_pdf'] = '{type_legend},type,headline;{flipbook_legend},flipbookPdfSrc,flipbookInitialPages,flipbookStartMode,flipbookShowNavigation,flipbookPlaySound;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},cssID;{invisible_legend:hide},invisible,start,stop';
|
||||
$GLOBALS['TL_DCA']['tl_content']['palettes']['blatterbares_pdf'] = '{type_legend},type,headline;{flipbook_legend},flipbookPdfSrc,flipbookInitialPages,flipbookStartMode,flipbookSplitSpreads,flipbookShowNavigation,flipbookPlaySound;{template_legend:hide},customTpl;{protected_legend:hide},protected;{expert_legend:hide},cssID;{invisible_legend:hide},invisible,start,stop';
|
||||
|
||||
$GLOBALS['TL_DCA']['tl_content']['fields']['flipbookPdfSrc'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_content']['flipbookPdfSrc'],
|
||||
@@ -32,6 +32,14 @@ $GLOBALS['TL_DCA']['tl_content']['fields']['flipbookStartMode'] = [
|
||||
'sql' => ['type' => 'string', 'length' => 16, 'default' => 'center'],
|
||||
];
|
||||
|
||||
$GLOBALS['TL_DCA']['tl_content']['fields']['flipbookSplitSpreads'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_content']['flipbookSplitSpreads'],
|
||||
'exclude' => true,
|
||||
'inputType' => 'checkbox',
|
||||
'eval' => ['tl_class' => 'w50 m12'],
|
||||
'sql' => ['type' => 'string', 'length' => 1, 'fixed' => true, 'default' => '0'],
|
||||
];
|
||||
|
||||
$GLOBALS['TL_DCA']['tl_content']['fields']['flipbookShowNavigation'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_content']['flipbookShowNavigation'],
|
||||
'exclude' => true,
|
||||
|
||||
@@ -15,5 +15,6 @@ $GLOBALS['TL_LANG']['tl_content']['flipbookStartModeOptions'] = [
|
||||
'center' => 'Zentriert starten',
|
||||
'spread' => 'als Doppelseite starten',
|
||||
];
|
||||
$GLOBALS['TL_LANG']['tl_content']['flipbookSplitSpreads'] = ['Doppelseiten aufteilen', 'Teilt breite PDF-Seiten ab Seite 2 automatisch in linke und rechte Einzelseite.'];
|
||||
$GLOBALS['TL_LANG']['tl_content']['flipbookShowNavigation'] = ['Navigation anzeigen', 'Zeigt Vor-/Zurueck-Buttons unter dem Flipbook an.'];
|
||||
$GLOBALS['TL_LANG']['tl_content']['flipbookPlaySound'] = ['Blättersound abspielen', 'Spielt beim Blättern einen Sound ab.'];
|
||||
|
||||
@@ -15,5 +15,6 @@ $GLOBALS['TL_LANG']['tl_content']['flipbookStartModeOptions'] = [
|
||||
'center' => 'Start centered',
|
||||
'spread' => 'Start with double-page spread',
|
||||
];
|
||||
$GLOBALS['TL_LANG']['tl_content']['flipbookSplitSpreads'] = ['Split double-page spreads', 'Automatically splits wide PDF pages from page 2 onwards into left and right single pages.'];
|
||||
$GLOBALS['TL_LANG']['tl_content']['flipbookShowNavigation'] = ['Show navigation', 'Displays previous/next buttons below the flipbook.'];
|
||||
$GLOBALS['TL_LANG']['tl_content']['flipbookPlaySound'] = ['Play page-turn sound', 'Plays a sound effect while turning pages.'];
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
data-pdf-url="{{ pdfUrl|default('')|e('html_attr') }}"
|
||||
data-initial-pages="{{ initialRenderPages|default(4)|e('html_attr') }}"
|
||||
data-start-mode="{{ startMode|default('center')|e('html_attr') }}"
|
||||
data-split-spreads="{{ splitSpreads ? '1' : '0' }}"
|
||||
data-show-navigation="{{ showNavigation ? '1' : '0' }}"
|
||||
data-play-turn-sound="{{ ((playTurnSound is defined) ? playTurnSound : true) ? '1' : '0' }}"
|
||||
tabindex="0"
|
||||
|
||||
@@ -41,6 +41,7 @@ class PdfFlipbookModule {
|
||||
this.initialPages = Number.isFinite(parsedInitialPages)
|
||||
? Math.min(4, Math.max(2, parsedInitialPages))
|
||||
: 4;
|
||||
this.splitSpreads = root.dataset.splitSpreads === '1';
|
||||
this.showNavigation = root.dataset.showNavigation === '1';
|
||||
this.playTurnSoundEnabled = root.dataset.playTurnSound !== '0';
|
||||
this.startMode = (root.dataset.startMode === 'spread' || root.dataset.startMode === 'cover')
|
||||
@@ -67,7 +68,9 @@ class PdfFlipbookModule {
|
||||
this.pageWidth = 0;
|
||||
this.pageHeight = 0;
|
||||
this.pageGap = 2;
|
||||
this.sourcePageCount = 0;
|
||||
this.totalPages = 0;
|
||||
this.pageDescriptors = [];
|
||||
|
||||
this.instanceId = `pdf-flipbook-${++moduleCounter}`;
|
||||
}
|
||||
@@ -136,6 +139,8 @@ class PdfFlipbookModule {
|
||||
if (this.controlsElement) {
|
||||
this.controlsElement.style.opacity = '1';
|
||||
}
|
||||
|
||||
this.updateNavigationState();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,19 +152,84 @@ class PdfFlipbookModule {
|
||||
});
|
||||
|
||||
this.pdf = await loadingTask.promise;
|
||||
this.totalPages = Number(this.pdf.numPages || 0);
|
||||
this.sourcePageCount = Number(this.pdf.numPages || 0);
|
||||
|
||||
if (this.totalPages <= 0) {
|
||||
if (this.sourcePageCount <= 0) {
|
||||
throw new Error('The selected PDF has no pages.');
|
||||
}
|
||||
|
||||
await this.buildPageDescriptors();
|
||||
this.totalPages = this.pageDescriptors.length;
|
||||
}
|
||||
|
||||
async buildPageDescriptors() {
|
||||
const descriptors = [];
|
||||
|
||||
for (let sourcePageNumber = 1; sourcePageNumber <= this.sourcePageCount; sourcePageNumber += 1) {
|
||||
const page = await this.pdf.getPage(sourcePageNumber);
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
|
||||
if (this.shouldSplitSpread(sourcePageNumber, viewport)) {
|
||||
descriptors.push({
|
||||
sourcePageNumber,
|
||||
segment: 'left',
|
||||
});
|
||||
descriptors.push({
|
||||
sourcePageNumber,
|
||||
segment: 'right',
|
||||
});
|
||||
} else {
|
||||
descriptors.push({
|
||||
sourcePageNumber,
|
||||
segment: 'full',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.pageDescriptors = descriptors;
|
||||
}
|
||||
|
||||
shouldSplitSpread(sourcePageNumber, viewport) {
|
||||
if (!this.splitSpreads) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourcePageNumber <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const width = Number(viewport.width || 0);
|
||||
const height = Number(viewport.height || 0);
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return width >= (height * 1.2);
|
||||
}
|
||||
|
||||
getDescriptor(pageNumber) {
|
||||
if (!Number.isInteger(pageNumber) || pageNumber < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.pageDescriptors[pageNumber - 1] || null;
|
||||
}
|
||||
|
||||
async resolveInitialAspectRatio() {
|
||||
const firstPage = await this.pdf.getPage(1);
|
||||
const viewport = firstPage.getViewport({ scale: 1 });
|
||||
const firstDescriptor = this.getDescriptor(1);
|
||||
|
||||
if (viewport.width > 0 && viewport.height > 0) {
|
||||
this.aspectRatio = viewport.height / viewport.width;
|
||||
if (!firstDescriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstPage = await this.pdf.getPage(firstDescriptor.sourcePageNumber);
|
||||
const viewport = firstPage.getViewport({ scale: 1 });
|
||||
const divisor = firstDescriptor.segment === 'full' ? 1 : 2;
|
||||
const width = viewport.width / divisor;
|
||||
|
||||
if (width > 0 && viewport.height > 0) {
|
||||
this.aspectRatio = viewport.height / width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,8 +301,42 @@ class PdfFlipbookModule {
|
||||
onPageTurn: () => {
|
||||
this.playTurnSound();
|
||||
this.queuePages(this.getLazyCandidates());
|
||||
this.updateNavigationState();
|
||||
},
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
this.updateNavigationState();
|
||||
});
|
||||
}
|
||||
|
||||
updateNavigationState() {
|
||||
if (!this.showNavigation || !this.prevButton || !this.nextButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activePages = Array.from(this.bookElement.querySelectorAll('.c-flipbook__page.is-active'))
|
||||
.map((element) => Number(element.dataset.pageNumber || 0))
|
||||
.filter((pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0);
|
||||
|
||||
let disablePrev = false;
|
||||
let disableNext = false;
|
||||
|
||||
if (activePages.length === 0) {
|
||||
disablePrev = true;
|
||||
disableNext = this.totalPages <= 1;
|
||||
} else {
|
||||
const minActivePage = Math.min(...activePages);
|
||||
const maxActivePage = Math.max(...activePages);
|
||||
|
||||
disablePrev = minActivePage <= 1;
|
||||
disableNext = maxActivePage >= this.totalPages;
|
||||
}
|
||||
|
||||
this.prevButton.disabled = disablePrev;
|
||||
this.nextButton.disabled = disableNext;
|
||||
this.prevButton.setAttribute('aria-disabled', disablePrev ? 'true' : 'false');
|
||||
this.nextButton.setAttribute('aria-disabled', disableNext ? 'true' : 'false');
|
||||
}
|
||||
|
||||
createTurnSound() {
|
||||
@@ -449,14 +553,18 @@ class PdfFlipbookModule {
|
||||
}
|
||||
|
||||
const pageElement = this.pageElements.get(pageNumber);
|
||||
const descriptor = this.getDescriptor(pageNumber);
|
||||
|
||||
if (!pageElement) {
|
||||
if (!pageElement || !descriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const page = await this.pdf.getPage(pageNumber);
|
||||
const page = await this.pdf.getPage(descriptor.sourcePageNumber);
|
||||
const viewportAtScale1 = page.getViewport({ scale: 1 });
|
||||
const scale = this.pageWidth / viewportAtScale1.width;
|
||||
const renderWidth = descriptor.segment === 'full'
|
||||
? viewportAtScale1.width
|
||||
: viewportAtScale1.width / 2;
|
||||
const scale = this.pageWidth / renderWidth;
|
||||
const viewport = page.getViewport({ scale });
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
const outputScale = Math.max(1, devicePixelRatio);
|
||||
@@ -473,19 +581,59 @@ class PdfFlipbookModule {
|
||||
throw new Error('Could not create canvas 2D context.');
|
||||
}
|
||||
|
||||
canvas.width = Math.floor(viewport.width * outputScale);
|
||||
const canvasCssWidth = descriptor.segment === 'full'
|
||||
? Math.floor(viewport.width)
|
||||
: Math.floor(viewport.width / 2);
|
||||
|
||||
canvas.width = Math.floor(canvasCssWidth * outputScale);
|
||||
canvas.height = Math.floor(viewport.height * outputScale);
|
||||
canvas.style.width = `${Math.floor(viewport.width)}px`;
|
||||
canvas.style.width = `${canvasCssWidth}px`;
|
||||
canvas.style.height = `${Math.floor(viewport.height)}px`;
|
||||
|
||||
const renderTask = page.render({
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
transform: outputScale === 1 ? null : [outputScale, 0, 0, outputScale, 0, 0],
|
||||
intent: 'display',
|
||||
});
|
||||
if (descriptor.segment === 'full') {
|
||||
const renderTask = page.render({
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
transform: outputScale === 1 ? null : [outputScale, 0, 0, outputScale, 0, 0],
|
||||
intent: 'display',
|
||||
});
|
||||
|
||||
await renderTask.promise;
|
||||
await renderTask.promise;
|
||||
} else {
|
||||
const spreadCanvas = document.createElement('canvas');
|
||||
const spreadContext = spreadCanvas.getContext('2d', { alpha: false });
|
||||
|
||||
if (!spreadContext) {
|
||||
throw new Error('Could not create split canvas context.');
|
||||
}
|
||||
|
||||
spreadCanvas.width = Math.floor(viewport.width * outputScale);
|
||||
spreadCanvas.height = Math.floor(viewport.height * outputScale);
|
||||
|
||||
const spreadRenderTask = page.render({
|
||||
canvasContext: spreadContext,
|
||||
viewport,
|
||||
transform: outputScale === 1 ? null : [outputScale, 0, 0, outputScale, 0, 0],
|
||||
intent: 'display',
|
||||
});
|
||||
|
||||
await spreadRenderTask.promise;
|
||||
|
||||
const halfWidth = Math.floor(spreadCanvas.width / 2);
|
||||
const sourceX = descriptor.segment === 'left' ? 0 : spreadCanvas.width - halfWidth;
|
||||
|
||||
context.drawImage(
|
||||
spreadCanvas,
|
||||
sourceX,
|
||||
0,
|
||||
halfWidth,
|
||||
spreadCanvas.height,
|
||||
0,
|
||||
0,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
);
|
||||
}
|
||||
|
||||
const loader = pageElement.querySelector('.mod-pdf-flipbook__page-loader');
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class BlatterbaresPdfController extends AbstractContentElementController
|
||||
$template->set('pdfUrl', $this->resolvePdfUrl($model));
|
||||
$template->set('showNavigation', '1' === (string) ($model->flipbookShowNavigation ?? '1'));
|
||||
$template->set('playTurnSound', '1' === (string) ($model->flipbookPlaySound ?? '1'));
|
||||
$template->set('splitSpreads', '1' === (string) ($model->flipbookSplitSpreads ?? '0'));
|
||||
$template->set('initialRenderPages', $this->normalizeInitialRenderPages((string) ($model->flipbookInitialPages ?? '4')));
|
||||
$template->set('startMode', $this->normalizeStartMode((string) ($model->flipbookStartMode ?? 'center')));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user