From a8a6b811acec8f87f44d688b54751c28ed5f7d2f Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Wed, 27 Jul 2022 20:15:57 +0200 Subject: [PATCH 01/76] Add explanation about `addHighlightField` method --- Resources/Public/JavaScript/PageView/PageView.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Public/JavaScript/PageView/PageView.js b/Resources/Public/JavaScript/PageView/PageView.js index 7fa1f0518..995bb198c 100644 --- a/Resources/Public/JavaScript/PageView/PageView.js +++ b/Resources/Public/JavaScript/PageView/PageView.js @@ -645,6 +645,8 @@ dlfViewer.prototype.addCustomControls = function() { /** * Add highlight field * + * Used for SRU search highlighting in DFG Viewer. + * * @param {Array.} highlightField * @param {number} imageIndex * @param {number} width From 3043018a2d5ec95bc0598d84034720fb802e23a6 Mon Sep 17 00:00:00 2001 From: Matthias Richter Date: Mon, 22 Aug 2022 16:27:30 +0200 Subject: [PATCH 02/76] Add Basic JSON Representation for Document and First Navigation Button --- Classes/Common/AbstractDocument.php | 45 +++++++++++++++++++ Classes/Controller/PageViewController.php | 7 +++ .../Private/Templates/Navigation/Main.html | 26 ++++++++--- .../Public/JavaScript/PageView/PageView.js | 22 +++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/Classes/Common/AbstractDocument.php b/Classes/Common/AbstractDocument.php index 42901fc8e..1d2fd38c6 100644 --- a/Classes/Common/AbstractDocument.php +++ b/Classes/Common/AbstractDocument.php @@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use Ubl\Iiif\Presentation\Common\Model\Resources\IiifResourceInterface; use Ubl\Iiif\Tools\IiifHelper; +use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; /** * Document class for the 'dlf' extension @@ -1301,4 +1302,48 @@ private static function setDocumentCache(string $location, AbstractDocument $cur // Save value in cache $cache->set($cacheIdentifier, $currentDocument); } + + public function toArray($uriBuilder, array $config = []) + { + $useInternalProxy = $config['useInternalProxy'] ?? false; + $forceAbsoluteUrl = $config['forceAbsoluteUrl'] ?? false; + $result = []; + $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); + $fileGrpsImages = array_reverse(GeneralUtility::trimExplode(',', $extConf['fileGrpImages'])); + for ($page = 1; $page <= $this->numPages; $page++) { + foreach ($fileGrpsImages as $fileGrpImages) { + // Get image link. + if (!empty($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages])) { + $image['url'] = $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); + $image['mimetype'] = $this->getFileMimeType($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); + + // Only deliver static images via the internal PageViewProxy. + // (For IIP and IIIF, the viewer needs to build and access a separate metadata URL, see `getMetadataURL`.) + if ($useInternalProxy && strpos($image['mimetype'], 'image/') === 0) { + // Configure @action URL for form. + $uri = $uriBuilder + ->reset() + ->setTargetPageUid($GLOBALS['TSFE']->id) + ->setCreateAbsoluteUri($forceAbsoluteUrl) + ->setArguments([ + 'eID' => 'tx_dlf_pageview_proxy', + 'url' => $image['url'], + 'uHash' => GeneralUtility::hmac($image['url'], 'PageViewProxy') + ]) + ->build(); + $image['url'] = $uri; + } + $result[] = $image; + break; + } else { + $this->logger->notice('No image file found for page "' . $page . '" in fileGrp "' . $fileGrpImages . '"'); + } + } + if (empty($image)) { + $this->logger->warning('No image file found for page "' . $page . '" in fileGrps "' . $extConf['fileGrpImages'] . '"'); + } + } + + return $result; + } } diff --git a/Classes/Controller/PageViewController.php b/Classes/Controller/PageViewController.php index 78b830535..c5ace713a 100644 --- a/Classes/Controller/PageViewController.php +++ b/Classes/Controller/PageViewController.php @@ -462,6 +462,11 @@ protected function getFulltext(int $page): array */ protected function addViewerJS(): void { + $config = [ + 'forceAbsoluteUrl' => !empty($this->settings['forceAbsoluteUrl']), + 'useInternalProxy' => !empty($this->settings['useInternalProxy']), + ]; + if (count($this->documentArray) > 1) { $jsViewer = 'tx_dlf_viewer = [];'; $i = 0; @@ -498,6 +503,7 @@ protected function addViewerJS(): void 'div' => "tx-dfgviewer-map-' . $i . '", 'progressElementId' => $this->settings['progressElementId'], 'counter' => $i, + 'document' => $this->document->getCurrentDocument()->toArray($this->uriBuilder, $config), 'images' => $docImage, 'fulltexts' => $docFulltext, 'score' => $docScore, @@ -534,6 +540,7 @@ protected function addViewerJS(): void 'controls' => $this->controls, 'div' => $this->settings['elementId'], 'progressElementId' => $this->settings['progressElementId'], + 'document' => $this->document->getCurrentDocument()->toArray($this->uriBuilder, $config), 'images' => $this->images, 'fulltexts' => $this->fulltexts, 'score' => $this->scores, diff --git a/Resources/Private/Templates/Navigation/Main.html b/Resources/Private/Templates/Navigation/Main.html index 3b1020388..b2e581015 100644 --- a/Resources/Private/Templates/Navigation/Main.html +++ b/Resources/Private/Templates/Navigation/Main.html @@ -72,7 +72,7 @@
- @@ -84,13 +84,27 @@
+ +
- @@ -108,7 +122,7 @@
- @@ -150,7 +164,7 @@
- @@ -168,7 +182,7 @@
- @@ -186,7 +200,7 @@
- diff --git a/Resources/Public/JavaScript/PageView/PageView.js b/Resources/Public/JavaScript/PageView/PageView.js index 995bb198c..e599bd9ab 100644 --- a/Resources/Public/JavaScript/PageView/PageView.js +++ b/Resources/Public/JavaScript/PageView/PageView.js @@ -36,8 +36,12 @@ * fulltexts?: FulltextDesc[] | []; * scores?: ScoreDesc[] | []; * controls?: ('OverviewMap' | 'ZoomPanel')[]; +<<<<<<< HEAD * measureCoords?: MeasureDesc[] | []; * measureIdLinks?: MeasureDesc[] | []; +======= + * document?: any; +>>>>>>> 64fb3677 (Add Basic JSON Representation for Document and First Navigation Button) * }} DlfViewerConfig */ @@ -218,6 +222,8 @@ var dlfViewer = function (settings) { */ this.ovView = null; + this.document = dlfUtils.exists(settings.document) ? settings.document : null; + /** * @type {Boolean|false} * @private @@ -250,6 +256,7 @@ var dlfViewer = function (settings) { */ this.useInternalProxy = dlfUtils.exists(settings.useInternalProxy) ? settings.useInternalProxy : false; + this.registerEvents(); this.init(dlfUtils.exists(settings.controls) ? settings.controls : []); }; @@ -916,6 +923,21 @@ dlfViewer.prototype.init = function(controlNames) { this.initCropping(); }; +dlfViewer.prototype.registerEvents = function() { + $(document.body).on('tx-dlf-pageChanged', e => { + const page = e.originalEvent.detail.page; + const entry = this.document[page - 1]; + const url = entry.url; + const mimetype = entry.mimetype; + + // TODO don't forget double page mode + this.initLayer([entry]) + .done(layers => { + this.map.setLayers(layers); + }); + }); +}; + dlfViewer.prototype.updateLayerSize = function() { this.map.updateSize(); }; From fc967168c9e94103368fef8ce6fd5464ae2e8046 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Mon, 22 Aug 2022 21:05:18 +0200 Subject: [PATCH 03/76] Refactor: Format code - Remove unused import - Replace tabs with spaces --- Classes/Common/AbstractDocument.php | 3 +-- .../Private/Templates/Navigation/Main.html | 26 +++++++++---------- .../Public/JavaScript/PageView/PageView.js | 6 ++--- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Classes/Common/AbstractDocument.php b/Classes/Common/AbstractDocument.php index 1d2fd38c6..442b438cb 100644 --- a/Classes/Common/AbstractDocument.php +++ b/Classes/Common/AbstractDocument.php @@ -19,7 +19,6 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use Ubl\Iiif\Presentation\Common\Model\Resources\IiifResourceInterface; use Ubl\Iiif\Tools\IiifHelper; -use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; /** * Document class for the 'dlf' extension @@ -1312,7 +1311,7 @@ public function toArray($uriBuilder, array $config = []) $fileGrpsImages = array_reverse(GeneralUtility::trimExplode(',', $extConf['fileGrpImages'])); for ($page = 1; $page <= $this->numPages; $page++) { foreach ($fileGrpsImages as $fileGrpImages) { - // Get image link. + // Get image link. if (!empty($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages])) { $image['url'] = $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); $image['mimetype'] = $this->getFileMimeType($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); diff --git a/Resources/Private/Templates/Navigation/Main.html b/Resources/Private/Templates/Navigation/Main.html index b2e581015..f9f7dc795 100644 --- a/Resources/Private/Templates/Navigation/Main.html +++ b/Resources/Private/Templates/Navigation/Main.html @@ -85,19 +85,19 @@
- + diff --git a/Resources/Public/JavaScript/PageView/PageView.js b/Resources/Public/JavaScript/PageView/PageView.js index e599bd9ab..94088a8b7 100644 --- a/Resources/Public/JavaScript/PageView/PageView.js +++ b/Resources/Public/JavaScript/PageView/PageView.js @@ -222,7 +222,7 @@ var dlfViewer = function (settings) { */ this.ovView = null; - this.document = dlfUtils.exists(settings.document) ? settings.document : null; + this.document = dlfUtils.exists(settings.document) ? settings.document : null; /** * @type {Boolean|false} @@ -256,7 +256,7 @@ var dlfViewer = function (settings) { */ this.useInternalProxy = dlfUtils.exists(settings.useInternalProxy) ? settings.useInternalProxy : false; - this.registerEvents(); + this.registerEvents(); this.init(dlfUtils.exists(settings.controls) ? settings.controls : []); }; @@ -939,7 +939,7 @@ dlfViewer.prototype.registerEvents = function() { }; dlfViewer.prototype.updateLayerSize = function() { - this.map.updateSize(); + this.map.updateSize(); }; /** From 12e2fa21cea86212239c9673c4bf360e245bbebe Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Mon, 22 Aug 2022 21:13:31 +0200 Subject: [PATCH 04/76] Refactor: Move JS from template to new Navigation.js --- Configuration/TypoScript/setup.typoscript | 3 ++- .../Private/Templates/Navigation/Main.html | 14 ---------- .../Public/Css/DlfMediaPlayerStyles.css.map | 1 + Resources/Public/Css/DlfMediaVendor.css.map | 1 + .../Public/JavaScript/PageView/Navigation.js | 27 +++++++++++++++++++ 5 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 Resources/Public/Css/DlfMediaPlayerStyles.css.map create mode 100644 Resources/Public/Css/DlfMediaVendor.css.map create mode 100644 Resources/Public/JavaScript/PageView/Navigation.js diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index 120789456..63c0a42e6 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -49,6 +49,7 @@ page { # PageView plugin kitodo-openLayers = EXT:dlf/Resources/Public/JavaScript/OpenLayers/openlayers.js +<<<<<<< HEAD kitodo-pageView-utility = EXT:dlf/Resources/Public/JavaScript/PageView/Utility.js kitodo-pageView-ol = EXT:dlf/Resources/Public/JavaScript/PageView/OL.js kitodo-pageView-olStyles = EXT:dlf/Resources/Public/JavaScript/PageView/OLStyles.js @@ -65,8 +66,8 @@ page { kitodo-pageView-syncControl = EXT:dlf/Resources/Public/JavaScript/PageView/SyncControl.js kitodo-pageView-searchInDocument = EXT:dlf/Resources/Public/JavaScript/PageView/SearchInDocument.js kitodo-pageView-pageView = EXT:dlf/Resources/Public/JavaScript/PageView/PageView.js + kitodo-pageView-navigation = EXT:dlf/Resources/Public/JavaScript/PageView/Navigation.js kitodo-search-suggest = EXT:dlf/Resources/Public/JavaScript/Search/Suggester.js - } } diff --git a/Resources/Private/Templates/Navigation/Main.html b/Resources/Private/Templates/Navigation/Main.html index f9f7dc795..4d600ec81 100644 --- a/Resources/Private/Templates/Navigation/Main.html +++ b/Resources/Private/Templates/Navigation/Main.html @@ -84,20 +84,6 @@
- - diff --git a/Resources/Public/Css/DlfMediaPlayerStyles.css.map b/Resources/Public/Css/DlfMediaPlayerStyles.css.map new file mode 100644 index 000000000..f5f3e1721 --- /dev/null +++ b/Resources/Public/Css/DlfMediaPlayerStyles.css.map @@ -0,0 +1 @@ +{"version":3,"file":"Css/DlfMediaPlayerStyles.css","mappings":";;;AAAA;;;;;;;;;EASE;ACTF;EACI;ADWJ;AEXI;EACI;EAEA;EACA;AFYR;AEhBI;EAOQ;EACA;EACA;AFYZ;AERI;EACI;EAEA;EACA;AFSR;AEbI;EAOQ;EACA;EACA;AFSZ;AElBI;EAYY;AFShB;AEnCA;EAgCQ;EACA;EACA;EACA;EACA;EACA;AFMR;AE3CA;EAyCQ;EACA;EACA;AFKR;AEHQ;EACI;EACA;EACA;AFKZ;AErDA;EAqDQ;AFGR;AEAI;EACI;AFER;AE3DA;EA6DQ;AFCR;AE9DA;EAiEQ;AFAR;AEIQ;;EAEI;AFFZ;AEMI;EACI;EACA;EAEA;AFLR;AECI;EAOQ;AFLZ;AEFI;EAWQ;AFNZ;AELI;EAgBQ;AFRZ;AERI;EAoBQ;AFTZ;AEXI;EAwBQ;EACA;AFVZ;AG1FA;EH4FE,4EAA4E;EIiB5E;EJfA,uEAAuE;EIkBvE;EJhBA;WACS;AACX;AIkBE;;EAEE;AJhBJ;AKjGA;EJCI;EDmGF,eAAe;EClGb;EDoGF,WAAW;ECnGT;EDqGF,mBAAmB;ECpGjB;EDsGF,4BAA4B;ECrG1B;EDuGF,2BAA2B;ECtGzB;EDwGF;wEACsE;EK3GpE;AL6GJ;AKzGI;EACI;AL2GR;AK/FA;EARQ;AL0GR;AKlGA;EAJQ;ALyGR;AKjGA;EACI;EACA;ALmGJ;AKhGA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;ALkGJ;AK/FI;EACI;ALiGR;AK7FA;EACI;AL+FJ;AK5FA;EACI;AL8FJ;AK3FA;EAGI;AL2FJ;AKxFA;EACI;IACI;IACA;EL0FN;EKvFE;IACI;ELyFN;EKtFE;IACI;IACA;ELwFN;AACF;AKrFA;EAGI;EACA;EACA;EACA;ALqFJ;AKnFI;EACI;ALqFR;AKjFA;EAEQ;ALkFR;AK/EI;;EAQQ;AL2EZ;AKnFI;EAYQ;AL0EZ;AKrEA;EJ9GI;EDsLF,eAAe;ECrLb;EDuLF,WAAW;ECtLT;EDwLF,mBAAmB;ECvLjB;EDyLF,4BAA4B;ECxL1B;ED0LF,2BAA2B;ECzLzB;ED2LF;wEACsE;EK9EpE;EACA;EAGA;EACA;EAGA;EAEA;EACA;EACA;AL2EJ;AK5FA;EAoBQ;AL2ER;AK/FA;EAwBQ;EACA;EACA;AL0ER;AKpGA;EA8BQ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;ALyER;AKvEQ;EACI;ALyEZ;AKjHA;EA4CY;EACA;EACA;EACA;ALwEZ;AKvHA;EAoDQ;EAEA;EACA;EACA;EACA;ALqER;AK9HA;EA4DY;EACA;EACA;ALqEZ;AKnIA;EAkEY;ALoEZ;AKjEQ;EACI;ALmEZ;AK9DA;EAGI;AL8DJ;AKtDA;EACI;IACI;IACA;IACA;IACA;ELwDN;EMrQF;INuQI,4EAA4E;II5J9E;IJ8JE,uEAAuE;II3JzE;IJ6JE;WACO;IC9PP;IKTA;IACA;IACA;IACA;IACA;IACA;EN0QF;EIlKA;;IAEE;EJoKF;EMxRF;IAaQ;IACA;IACA;IACA;EN8QN;EM9RF;IAoBQ;IACA;IACA;IACA;IACA;IACA;IACA;EN6QN;EMvSF;IA8BQ;IACA;IACA;IACA;IACA;IACA;IACA;EN4QN;AACF;AK9FA;EACI;IACI;IACA;IACA;IACA;ELgGN;EMxTF;IN0TI,4EAA4E;II/M9E;IJiNE,uEAAuE;II9MzE;IJgNE;WACO;ICjTP;IKTA;IACA;IACA;IACA;IACA;IACA;EN6TF;EIrNA;;IAEE;EJuNF;EM3UF;IAaQ;IACA;IACA;IACA;ENiUN;EMjVF;IAoBQ;IACA;IACA;IACA;IACA;IACA;IACA;ENgUN;EM1VF;IA8BQ;IACA;IACA;IACA;IACA;IACA;IACA;EN+TN;AACF;AOtWA;EACI;APwWJ;AOzWA;EAIQ;APwWR;AO5WA;EAOY;APwWZ;AO/WA;;EAWgB;EACA;EACA;APwWhB;AOrXA;EAmBQ;EACA;APqWR;AOzXA;EAuBY;APqWZ;AOlWQ;EACI;APoWZ;AO/XA;EAgCQ;APkWR;AOlYA;EAmCY;EACA;EACA;EACA;EACA;EACA;APkWZ;AO1YA;EA8CY;EACA;AP+VZ;AO7VY;EACI;AP+VhB;AOjZA;EAuDY;AP6VZ;AQpZA;EACI;EAGA;EACA;EACA;ARoZJ;AQ1ZA;EASQ;ARoZR;AQ7ZA;EAaQ;EACA;ARmZR;AQjZQ;EACI;ARmZZ;AQpaA;EAsBQ;EACA;EACA;ARiZR;AQzaA;EA2BY;EACA;EACA;ARiZZ;AQ9aA;EAiCY;ARgZZ;AQjbA;;EAsCY;AR+YZ;AQrbA;;EA0CgB;AR+YhB;AQ1YY;EACI;AR4YhB;AQ5bA;EAoDgB;AR2YhB;AQ/bA;EAyDY;EACA;ARyYZ;AQncA;EA8DY;EACA;ARwYZ;ASvcA;EAEQ;EACA;EACA;EAEA;ATucR;AS7cA;EASY;EACA;ATucZ;ASjdA;EAagB;EACA;ATuchB;ASrdA;EAkBgB;ATschB;ASxdA;EAqBoB;EACA;EACA;ATscpB;AS7dA;EA4BgB;ATochB;ASheA;EAgCgB;ATmchB;ASneA;EAoCgB;ATkchB;ASteA;EAyCY;EACA;EAEA;EACA;EACA;AT+bZ;AS7eA;EAkDY;EACA;EAEA;EACA;EACA;AT6bZ;ASzbI;EAAA;IAEQ;ET2bV;AACF;ASxbI;EAAA;IAEQ;ET0bV;AACF;ASvbI;EAAA;IAEQ;ETybV;ES3bE;IAMQ;ETwbV;AACF;AUtgBA;EAEQ;AVugBR;AUzgBA;EAMQ;AVsgBR;AU5gBA;EAWY;EACA;AVogBZ;AUhhBA;EAgBY;AVmgBZ;AUnhBA;;EAoBY;AVmgBZ;AUvhBA;EAwBY;AVkgBZ;AU1hBA;EA4BY;AVigBZ;AU7hBA;EAgCY;AVggBZ;AUhiBA;EAmCgB;EACA;EACA;AVggBhB;AU9fgB;EACI;AVggBpB;AU7fgB;EACI;AV+fpB;AU3iBA;;EAmDY;AV4fZ;AU/iBA;EAuDY;EACA;AV2fZ;AUnjBA;EA4DY;EACA;EACA;EACA;AV0fZ;AUzjBA;EAmEY;AVyfZ;AUrfI;EAEQ;AVsfZ;AUxfI;EAMQ;AVqfZ;AU3fI;ETnDA;EACA;EACA;EACA;EACA;EACA;EACA;ADijBJ;AK9jBI;EACI;ALgkBR;AWrkBA;ENSQ;AL+jBR;AWxkBA;ENaQ;AL8jBR;AW3kBA;EAIQ;AX0kBR;AWtkBA;;;;;EAMQ;AXukBR;AWnkBA;EAEI;AXokBJ;AWtkBA;EAQQ;EACA;AXikBR;AW7jBA;EASI;EACA;EACA;EAEA;AXsjBJ;AWlkBI;EACI;AXokBR;AWrkBI;EAIQ;AXokBZ;AW1jBI;EACI;AX4jBR;AWzjBI;EACI;AX2jBR;AW/kBA;EAwBQ;EACA;EACA;EACA;AX0jBR;AWrlBA;EA+BQ;AXyjBR;AWpjBI;EAAA;IACI;EXujBN;AACF;AW1jBA;EAMQ;AXujBR;AW7jBA;EAUQ;AXsjBR;AWljBA;EACI;EACA;AXojBJ;AWjjBA;EACI;AXmjBJ;AWhjBA;;;;EAII;AXkjBJ;AW/iBA;EACI;;;;IAII;EXijBN;EW9iBE;;IAEI;EXgjBN;AACF;AW7iBA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AX+iBJ;AW7iBI;EACI;EACA;EACA;AX+iBR;AW3iBA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;AX4iBJ;AW1iBI;EAAA;IACI;IACA;IACA;EX6iBN;AACF;AWhkBA;EVrHI;EACA;EACA;EACA;EACA;EACA;EACA;EUsII;AXmjBR;AW1kBA;EA2BQ;EACA;EACA;EACA;EACA;AXkjBR;AWjlBA;EAmCQ;EACA;AXijBR;AWrlBA;EAuCY;EACA;EACA;EACA;AXijBZ;AW3lBA;EA+CQ;EACA;AX+iBR;AW/lBA;EAqDQ;AX6iBR","sources":["webpack://kitodo-presentation/../Resources/Private/Less/DlfMediaPlayer.less","webpack://kitodo-presentation/../Resources/Private/Less/DlfMediaPlayer/util.less","webpack://kitodo-presentation/../Resources/Private/Less/DlfMediaPlayer/ShakaFrontend.less","webpack://kitodo-presentation/../Resources/Private/Less/DlfMediaPlayer/WaveForm.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/general.less","webpack://kitodo-presentation/../Resources/Private/Less/DlfMediaPlayer/DlfMediaPlayer.less","webpack://kitodo-presentation/../Resources/Private/Less/DlfMediaPlayer/FlatSeekBar.less","webpack://kitodo-presentation/../Resources/Private/Less/SlubMediaPlayer/modals/BookmarkModal.less","webpack://kitodo-presentation/../Resources/Private/Less/SlubMediaPlayer/modals/HelpModal.less","webpack://kitodo-presentation/../Resources/Private/Less/SlubMediaPlayer/modals/ScreenshotModal.less","webpack://kitodo-presentation/../Resources/Private/Less/SlubMediaPlayer/components/MarkerTable.less","webpack://kitodo-presentation/../Resources/Private/Less/SlubMediaPlayer/SlubMediaPlayer.less"],"sourcesContent":["/*\n *\n * Variables\n * ================================================\n * Value settings for type, breakpoints and\n * base settings for calculations\n *\n * Author: Thomas Jung \n *\n */\n.dlf-visible {\n visibility: visible !important;\n}\n.dlf-shaka[data-mode=\"video\"] {\n --controls-color: white;\n --volume-base-color: rgba(255, 255, 255, 0.54);\n --volume-level-color: rgba(255, 255, 255);\n}\n.dlf-shaka[data-mode=\"video\"] .dlf-media-flat-seek-bar {\n --base-color: rgba(255, 255, 255, 0.3);\n --buffered-color: rgba(255, 255, 255, 0.54);\n --played-color: #ffffff;\n}\n.dlf-shaka[data-mode=\"audio\"] {\n --controls-color: #2a2b2c;\n --volume-base-color: rgba(0, 0, 0, 0.4);\n --volume-level-color: rgba(0, 0, 0, 0.8);\n}\n.dlf-shaka[data-mode=\"audio\"] .dlf-media-flat-seek-bar {\n --base-color: rgba(0, 0, 0, 0.3);\n --buffered-color: rgba(0, 0, 0, 0.54);\n --played-color: #2a2b2c;\n}\n.dlf-shaka[data-mode=\"audio\"] .dlf-media-flat-seek-bar .dlf-media-chapter-marker {\n background-color: #c3e5ec;\n}\n.dlf-shaka .dlf-media-shaka-box {\n background-color: black;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n.dlf-shaka .dlf-media-error {\n display: none;\n color: white;\n z-index: 1;\n}\n.dlf-shaka .dlf-media-error.dlf-visible {\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.dlf-shaka .shaka-scrim-container {\n display: none;\n}\n.dlf-shaka[data-mode=\"\"] {\n display: none;\n}\n.dlf-shaka .dlf-media-error {\n color: var(--controls-color);\n}\n.dlf-shaka .shaka-controls-button-panel > * {\n color: var(--controls-color) !important;\n}\n.dlf-shaka .shaka-volume-bar::-webkit-slider-thumb,\n.dlf-shaka .shaka-volume-bar::-moz-range-thumb {\n background: var(--volume-level-color);\n}\n.dlf-shaka[data-mode=\"audio\"] {\n height: 3.5em;\n width: 100%;\n background-color: rgba(79, 179, 199, 0.6);\n}\n.dlf-shaka[data-mode=\"audio\"] video {\n display: none;\n}\n.dlf-shaka[data-mode=\"audio\"] .dlf-media-poster {\n display: none !important;\n}\n.dlf-shaka[data-mode=\"audio\"] .dlf-media-shaka-box {\n background-color: transparent;\n}\n.dlf-shaka[data-mode=\"audio\"] .shaka-spinner-container {\n display: none;\n}\n.dlf-shaka[data-mode=\"audio\"] .shaka-bottom-controls {\n width: 100%;\n padding-bottom: 0.4em;\n}\n.shaka-bottom-controls dlf-waveform {\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n}\n.shaka-controls-container[shown=\"true\"] .shaka-bottom-controls dlf-waveform,\n.shaka-controls-container[casting=\"true\"] .shaka-bottom-controls dlf-waveform {\n opacity: 1;\n}\n.dlf-media-player {\n -webkit-touch-callout: none;\n /* iOS Safari */\n -webkit-user-select: none;\n /* Safari */\n -khtml-user-select: none;\n /* Konqueror HTML */\n -moz-user-select: none;\n /* Old versions of Firefox */\n -ms-user-select: none;\n /* Internet Explorer/Edge */\n user-select: none;\n /* Non-prefixed version, currently\n supported by Chrome, Edge, Opera and Firefox */\n position: relative;\n}\ndlf-media:not(:defined) {\n display: none;\n}\ndlf-media dlf-chapter {\n display: none;\n}\ndlf-media dlf-media-controls {\n display: none;\n}\n.dlf-media {\n width: 100%;\n height: 100%;\n}\n.dlf-media-poster {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: contain;\n background-color: black;\n}\n.dlf-media-poster[src].dlf-visible {\n display: block;\n}\n.shaka-video-container {\n height: 100%;\n}\n.shaka-bottom-controls {\n visibility: hidden;\n}\n.shaka-controls-button-panel {\n justify-content: flex-start !important;\n}\n@media all and (min-width: calc(768px + 1px)) {\n .shaka-controls-button-panel button {\n margin-left: 8px;\n margin-right: 8px;\n }\n .shaka-overflow-menu-button {\n margin-left: 0 !important;\n }\n .shaka-fullscreen-button {\n margin-left: 3px !important;\n margin-right: 4px !important;\n }\n}\n.shaka-current-time {\n flex-shrink: 1 !important;\n white-space: nowrap;\n overflow-x: scroll;\n position: relative;\n}\n.shaka-current-time::-webkit-scrollbar {\n display: none;\n}\nbody .shaka-video-container {\n touch-action: none !important;\n}\nbody.seek-or-scrub .shaka-play-button,\nbody.seek-or-scrub .shaka-controls-button-panel {\n pointer-events: none;\n}\nbody.seek-or-scrub * {\n cursor: grabbing !important;\n}\n.dlf-media-thumbnail-preview {\n -webkit-touch-callout: none;\n /* iOS Safari */\n -webkit-user-select: none;\n /* Safari */\n -khtml-user-select: none;\n /* Konqueror HTML */\n -moz-user-select: none;\n /* Old versions of Firefox */\n -ms-user-select: none;\n /* Internet Explorer/Edge */\n user-select: none;\n /* Non-prefixed version, currently\n supported by Chrome, Edge, Opera and Firefox */\n visibility: hidden;\n position: absolute;\n bottom: 0px;\n padding-bottom: 25px;\n padding-top: 50px;\n z-index: 1;\n cursor: pointer;\n text-align: center;\n}\n.dlf-media-thumbnail-preview .displayed {\n display: block !important;\n}\n.dlf-media-thumbnail-preview .content-box {\n background-color: rgba(23, 30, 30, 0.6);\n border-radius: 4px;\n padding: 1em;\n}\n.dlf-media-thumbnail-preview .display {\n display: none;\n position: relative;\n width: 160px;\n height: 90px;\n border: 1px solid white;\n box-sizing: content-box;\n margin-bottom: 0.75em;\n overflow: hidden;\n}\n.dlf-media-thumbnail-preview .display.is-open {\n display: block;\n}\n.dlf-media-thumbnail-preview .display img {\n visibility: hidden;\n position: absolute;\n top: 0;\n left: 0;\n}\n.dlf-media-thumbnail-preview .info {\n display: inline-block;\n color: white;\n font-size: 14px;\n line-height: 110%;\n max-width: 160px;\n}\n.dlf-media-thumbnail-preview .info .chapter-text {\n line-height: 110%;\n margin-bottom: 0.4em;\n display: none;\n}\n.dlf-media-thumbnail-preview .info .timecode-text {\n display: block;\n}\n.dlf-media-thumbnail-preview .info.on-chapter-marker .chapter-text {\n font-weight: bold;\n}\n.dlf-media-chapter-marker {\n z-index: 0;\n}\n@media all and (pointer: fine) {\n .dlf-media-chapter-marker {\n width: 4px;\n height: 4px;\n background-color: #d5dfdf;\n border-radius: 2px;\n }\n .dlf-media-flat-seek-bar {\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n -webkit-tap-highlight-color: transparent;\n touch-action: none;\n position: relative;\n margin: 0px 6px;\n padding-top: 6px;\n height: 16px;\n cursor: pointer;\n }\n .shaka-controls-container[shown=\"true\"] .dlf-media-flat-seek-bar,\n .shaka-controls-container[casting=\"true\"] .dlf-media-flat-seek-bar {\n opacity: 1;\n }\n .dlf-media-flat-seek-bar .range {\n position: absolute;\n width: 100%;\n height: 4px;\n border-radius: 4px;\n }\n .dlf-media-flat-seek-bar .seek-marker {\n position: absolute;\n left: 45%;\n height: 4px;\n width: 1px;\n background-color: #4e6666;\n z-index: -1;\n visibility: hidden;\n }\n .dlf-media-flat-seek-bar .seek-thumb-bar {\n position: absolute;\n left: 45%;\n height: 4px;\n width: 1px;\n background-color: rgba(78, 102, 102, 0.4);\n z-index: -2;\n visibility: hidden;\n }\n}\n@media all and (pointer: coarse) {\n .dlf-media-chapter-marker {\n width: 1px;\n height: 5px;\n margin-top: 1px;\n background-color: rgba(213, 223, 223, 0.3);\n }\n .dlf-media-flat-seek-bar {\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n -webkit-tap-highlight-color: transparent;\n touch-action: none;\n position: relative;\n margin: 0px 6px;\n padding-top: 8px;\n height: 23px;\n cursor: pointer;\n }\n .shaka-controls-container[shown=\"true\"] .dlf-media-flat-seek-bar,\n .shaka-controls-container[casting=\"true\"] .dlf-media-flat-seek-bar {\n opacity: 1;\n }\n .dlf-media-flat-seek-bar .range {\n position: absolute;\n width: 100%;\n height: 7px;\n border-radius: 7px;\n }\n .dlf-media-flat-seek-bar .seek-marker {\n position: absolute;\n left: 45%;\n height: 7px;\n width: 1px;\n background-color: #4e6666;\n z-index: -1;\n visibility: hidden;\n }\n .dlf-media-flat-seek-bar .seek-thumb-bar {\n position: absolute;\n left: 45%;\n height: 7px;\n width: 1px;\n background-color: rgba(78, 102, 102, 0.4);\n z-index: -2;\n visibility: hidden;\n }\n}\n.bookmark-modal {\n text-align: left;\n}\n.bookmark-modal .share-buttons {\n margin-bottom: 1em;\n}\n.bookmark-modal .share-buttons a {\n margin-right: 1em;\n}\n.bookmark-modal .share-buttons a img,\n.bookmark-modal .share-buttons a .material-icons-round {\n width: 40px;\n height: 40px;\n font-size: 40px;\n}\n.bookmark-modal .url-qrcode {\n padding-top: 1em;\n display: none;\n}\n.bookmark-modal .url-qrcode hr {\n margin-bottom: 1.4em;\n}\n.bookmark-modal .url-qrcode.dlf-visible {\n display: block;\n}\n.bookmark-modal .url-line {\n display: flex;\n}\n.bookmark-modal .url-line input {\n width: 90%;\n height: 2rem;\n font-size: 1rem;\n line-height: 1rem;\n color: #4e6666;\n margin-right: 4pt;\n}\n.bookmark-modal .start-at div {\n display: none;\n margin-top: 1em;\n}\n.bookmark-modal .start-at div.shown {\n display: block;\n}\n.bookmark-modal .start-at label {\n padding-left: 2pt;\n}\n.help-modal {\n width: auto;\n left: 50% !important;\n right: unset !important;\n transform: translateX(-50%) translateY(-50%) !important;\n}\n.help-modal .body-container {\n padding: 0 1em !important;\n}\n.help-modal .subheader {\n text-align: left;\n font-weight: bold;\n}\n.help-modal .subheader:not(:nth-child(1)) {\n margin-top: 2em;\n}\n.help-modal .keybindings-table {\n line-height: 1.5rem;\n font-size: 80%;\n white-space: nowrap;\n}\n.help-modal .keybindings-table th.kb-group {\n padding-left: 0.2em;\n text-align: left;\n font-weight: bold;\n}\n.help-modal .keybindings-table tbody tr[aria-disabled=\"true\"] {\n opacity: 0.4;\n}\n.help-modal .keybindings-table thead[aria-disabled=\"true\"],\n.help-modal .keybindings-table tbody[aria-disabled=\"true\"] {\n opacity: 0.4;\n}\n.help-modal .keybindings-table thead[aria-disabled=\"true\"] tr,\n.help-modal .keybindings-table tbody[aria-disabled=\"true\"] tr {\n opacity: 1;\n}\n.help-modal .keybindings-table tbody tr:first-child {\n border-top: 1px solid #d5dfdf;\n}\n.help-modal .keybindings-table tbody tr td {\n color: white;\n}\n.help-modal .keybindings-table td.key {\n width: 40%;\n text-align: right;\n}\n.help-modal .keybindings-table td.action {\n padding-left: 3em;\n text-align: left;\n}\n.screenshot-modal .body-container {\n display: grid;\n grid-template-columns: 3fr 1fr;\n column-gap: 1em;\n text-align: left;\n}\n.screenshot-modal .body-container .screenshot-config {\n grid-column: 2;\n grid-row: 1;\n}\n.screenshot-modal .body-container .screenshot-config h4 {\n margin-bottom: 0.6em;\n font-size: 120%;\n}\n.screenshot-modal .body-container .screenshot-config section {\n margin-bottom: 0.8em;\n}\n.screenshot-modal .body-container .screenshot-config section h1 {\n font-weight: bold;\n font-size: 100%;\n margin-bottom: 0.3em;\n}\n.screenshot-modal .body-container .screenshot-config .metadata-overlay label {\n padding-left: 2pt;\n}\n.screenshot-modal .body-container .screenshot-config .file-format-option {\n margin-left: 0.3em;\n}\n.screenshot-modal .body-container .screenshot-config .download-link {\n margin-top: 0.2em;\n}\n.screenshot-modal .body-container .snap-tip {\n display: flex;\n gap: 0.4em;\n margin-top: 2em;\n line-height: 1.25;\n color: #d5dfdf;\n}\n.screenshot-modal .body-container canvas {\n grid-column: 1;\n grid-row: 1;\n background-color: black;\n width: 100%;\n height: auto;\n}\n@media screen and (max-width: 1600px) {\n .screenshot-modal .body-container {\n grid-template-columns: 2fr 1fr;\n }\n}\n@media screen and (max-width: 1200px) {\n .screenshot-modal .body-container {\n grid-template-columns: 1fr 1fr;\n }\n}\n@media screen and (max-width: 480px) {\n .screenshot-modal .body-container {\n display: block;\n }\n .screenshot-modal canvas {\n display: none;\n }\n}\n.dlf-media-markers h2 {\n margin-bottom: 0.4em;\n}\n.dlf-media-markers .dlf-media-markers-empty-msg {\n display: none;\n}\n.dlf-media-markers .dlf-media-markers-list table {\n margin-top: 0.4em;\n line-height: normal;\n}\n.dlf-media-markers .dlf-media-markers-list th {\n border-bottom: 1px solid black;\n}\n.dlf-media-markers .dlf-media-markers-list td,\n.dlf-media-markers .dlf-media-markers-list th {\n padding: 0.1em 2em 0.1em 0;\n}\n.dlf-media-markers .dlf-media-markers-list tbody tr:hover {\n background-color: #eee;\n}\n.dlf-media-markers .dlf-media-markers-list tr.active-segment {\n color: darkcyan;\n}\n.dlf-media-markers .dlf-media-markers-list .marker-id-col {\n width: 50%;\n}\n.dlf-media-markers .dlf-media-markers-list .marker-id-col input {\n font: inherit;\n background: transparent;\n border: none;\n}\n.dlf-media-markers .dlf-media-markers-list .marker-id-col input:focus {\n outline: none;\n}\n.dlf-media-markers .dlf-media-markers-list .marker-id-col input::placeholder {\n font-style: italic;\n}\n.dlf-media-markers .dlf-media-markers-list .marker-start-col,\n.dlf-media-markers .dlf-media-markers-list .marker-end-col {\n cursor: pointer;\n}\n.dlf-media-markers .dlf-media-markers-list .marker-buttons-col {\n width: 0;\n white-space: nowrap;\n}\n.dlf-media-markers .dlf-media-markers-list button {\n cursor: pointer;\n background: transparent;\n border: none;\n outline: none;\n}\n.dlf-media-markers .dlf-media-markers-list .material-icons-round {\n font-size: 20px;\n}\n.dlf-media-markers.is-empty .dlf-media-markers-empty-msg {\n display: block;\n}\n.dlf-media-markers.is-empty .dlf-media-markers-list {\n display: none;\n}\n.dlf-media-markers.is-empty kbd {\n white-space: pre;\n font-family: monospace;\n font-weight: bold;\n background-color: rgba(238, 238, 238, 0.3);\n border-radius: 3px;\n border: 1px solid #b4b4b4;\n padding: 4px 7px;\n}\nslub-media:not(:defined) {\n display: none;\n}\nslub-media dlf-chapter {\n display: none;\n}\nslub-media dlf-media-controls {\n display: none;\n}\nslub-media dlf-meta {\n display: none;\n}\nbody[data-has-video] .page-control,\nbody[data-has-video] .document-functions li.doublepage,\nbody[data-has-video] .view-functions li.rotate,\nbody[data-has-video] .view-functions li.zoom .in,\nbody[data-has-video] .view-functions li.zoom .out {\n display: none;\n}\n.control-bar {\n border-right: none !important;\n}\n.control-bar .offcanvas-toggle {\n bottom: unset;\n top: 5px;\n}\n.combined-container {\n position: absolute;\n width: 100%;\n height: 100%;\n display: grid;\n}\n.combined-container:fullscreen {\n background-color: white;\n}\n.combined-container:fullscreen .media-panel {\n padding: 1em 1em;\n}\n.combined-container[data-mode=\"audio\"] {\n grid-template-rows: 1fr auto;\n}\n.combined-container[data-mode=\"video\"] {\n grid-template-rows: 0 1fr;\n}\n.combined-container .media-panel {\n grid-row: 1;\n margin: 6em 1em;\n text-align: left;\n overflow-y: scroll;\n}\n.combined-container .tx-dlf-view {\n grid-row: 2;\n}\n@media screen and (max-width: 1023px) {\n .document-view {\n top: 50px;\n }\n}\n.document-view .tx-dlf-view {\n position: relative;\n}\n.document-view .media-viewport {\n height: 100%;\n}\n.dlf-media-player {\n width: 100%;\n height: 100%;\n}\n.inline-icon {\n vertical-align: middle;\n}\n.sxnd-waveform-button,\n.sxnd-screenshot-button,\n.sxnd-bookmark-button,\n.sxnd-help-button {\n font-size: 22px !important;\n}\n@media all and (max-width: 768px) {\n .sxnd-waveform-button,\n .sxnd-screenshot-button,\n .sxnd-bookmark-button,\n .sxnd-help-button {\n display: none !important;\n }\n .shaka-volume-bar-container,\n .shaka-mute-button {\n display: none !important;\n }\n}\n.sxnd-modal-cover {\n visibility: hidden;\n position: absolute;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n z-index: 100000;\n background-color: black;\n opacity: 0;\n}\n.sxnd-modal-cover.shown {\n visibility: visible;\n opacity: 0.33;\n transition: opacity 200ms linear;\n}\n.sxnd-modal {\n position: absolute;\n display: none;\n top: 50%;\n -ms-transform: translateY(-50%);\n transform: translateY(-50%);\n background-color: rgba(78, 102, 102, 0.9);\n color: white;\n border-radius: 5px;\n z-index: 999999;\n padding: 2rem 1.5rem 2rem 0.6rem;\n left: 1rem;\n right: 1rem;\n}\n@media screen and (min-width: 1200px) {\n .sxnd-modal {\n padding: 3rem;\n left: 5rem;\n right: 5rem;\n }\n}\n.sxnd-modal kbd {\n white-space: pre;\n font-family: monospace;\n font-weight: bold;\n background-color: rgba(238, 238, 238, 0.3);\n border-radius: 3px;\n border: 1px solid #b4b4b4;\n padding: 4px 7px;\n color: white;\n}\n.sxnd-modal button {\n background-color: rgba(255, 255, 255, 0.8);\n border: 1px solid black;\n padding: 0.5em 1em;\n border-radius: 4px;\n font-size: 100%;\n}\n.sxnd-modal .headline-container {\n position: relative;\n padding-bottom: 1rem;\n}\n.sxnd-modal .headline-container .modal-close {\n position: absolute;\n top: 0;\n right: 0;\n cursor: pointer;\n}\n.sxnd-modal .body-container {\n padding: 2rem;\n overflow-y: auto;\n}\n.sxnd-modal h3 {\n font-weight: 700;\n}\n",".dlf-visible {\n visibility: visible !important;\n}\n\n// Thanks https://stackoverflow.com/a/4407335\n.noselect() {\n -webkit-touch-callout: none; /* iOS Safari */\n -webkit-user-select: none; /* Safari */\n -khtml-user-select: none; /* Konqueror HTML */\n -moz-user-select: none; /* Old versions of Firefox */\n -ms-user-select: none; /* Internet Explorer/Edge */\n user-select: none; /* Non-prefixed version, currently\n supported by Chrome, Edge, Opera and Firefox */\n}\n\n.no-tap-highlight() {\n -webkit-tap-highlight-color: transparent;\n}\n\n.kbd() {\n white-space: pre; // Allow marking space bar with a couple of spaces\n font-family: monospace;\n font-weight: bold;\n background-color: rgba(#eee, 0.3);\n border-radius: 3px;\n border: 1px solid #b4b4b4;\n padding: 4px 7px;\n}\n",".dlf-shaka {\n &[data-mode=\"video\"] {\n --controls-color: white;\n\n --volume-base-color: rgba(255, 255, 255, 0.54);\n --volume-level-color: rgba(255, 255, 255);\n\n .dlf-media-flat-seek-bar {\n --base-color: rgba(255, 255, 255, 0.3);\n --buffered-color: rgba(255, 255, 255, 0.54);\n --played-color: rgb(255, 255, 255);\n }\n }\n\n &[data-mode=\"audio\"] {\n --controls-color: #2a2b2c;\n\n --volume-base-color: rgba(0, 0, 0, 0.4);\n --volume-level-color: rgba(0, 0, 0, 0.8);\n\n .dlf-media-flat-seek-bar {\n --base-color: rgba(0, 0, 0, 0.3);\n --buffered-color: rgba(0, 0, 0, 0.54);\n --played-color: #2a2b2c;\n\n .dlf-media-chapter-marker {\n background-color: lighten(rgb(79, 179, 199), 30%);\n }\n }\n }\n\n .dlf-media-shaka-box {\n background-color: black;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n\n .dlf-media-error {\n display: none;\n color: white;\n z-index: 1;\n\n &.dlf-visible {\n display: flex;\n justify-content: center;\n align-items: center;\n }\n }\n\n .shaka-scrim-container {\n display: none;\n }\n\n &[data-mode=\"\"] {\n display: none;\n }\n\n .dlf-media-error {\n color: var(--controls-color);\n }\n\n .shaka-controls-button-panel > * {\n color: var(--controls-color) !important;\n }\n\n .shaka-volume-bar {\n &::-webkit-slider-thumb,\n &::-moz-range-thumb {\n background: var(--volume-level-color);\n }\n }\n\n &[data-mode=\"audio\"] {\n height: 3.5em;\n width: 100%;\n\n background-color: rgba(79, 179, 199, 0.6);\n\n video {\n display: none;\n }\n\n .dlf-media-poster {\n display: none !important;\n }\n\n // Use background color from parent\n .dlf-media-shaka-box {\n background-color: transparent;\n }\n\n .shaka-spinner-container {\n display: none;\n }\n\n .shaka-bottom-controls {\n width: 100%;\n padding-bottom: 0.4em;\n }\n }\n}\n",".shaka-bottom-controls dlf-waveform {\n .show-when-controls-shown();\n}\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* General utility mixins and classes with broad applicability. */\n\n/* Make a thing unselectable. There are currently no cases where we make it\n * selectable again. */\n.unselectable() {\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n}\n\n.hidden() {\n display: none;\n}\n\n.shaka-hidden {\n /* Make this override equally specific classes.\n * If it's hidden, always hide it! */\n display: none !important;\n}\n\n.fill-container() {\n width: 100%;\n height: 100%;\n}\n\n.bottom-align-children() {\n display: flex;\n justify-content: flex-end;\n flex-direction: column;\n}\n\n.bottom-panels-elements-margin() {\n margin: 1px 6px;\n}\n\n/* For containers which host elements overlaying other things. */\n.overlay-parent() {\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for some children of this container to overlay the\n * others using .overlay-child(). */\n position: relative;\n\n /* Make sure any top or left styles applied from outside don't move this from\n * it's original position, now that it's relative to that original position.\n * This is a defensive move that came out of intensive debugging on IE 11. */\n top: 0;\n left: 0;\n}\n\n/* For things which overlay other things. */\n.overlay-child() {\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for this child to overlay the other children of a\n * .overlay-parent() object. */\n position: absolute;\n\n /* Fill the container by default. */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n margin: 0;\n padding: 0;\n\n .fill-container();\n}\n\n.absolute-position() {\n /* When setting \"position: absolute\" it uses the left,right,top,bottom\n * properties to determine the positioning. We should set all these\n * properties to ensure it is positioned properly on all platforms. */\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n}\n\n/* For things that should not shrink inside a flex container.\n * This will be used for all controls by default. */\n.unshrinkable() {\n flex-shrink: 0;\n}\n\n/* Use this to override .unshrinkable() in particular cases that *should* shrink\n * inside a flex container. */\n.shrinkable() {\n flex-shrink: 1;\n}\n\n.show-when-controls-shown() {\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n .shaka-controls-container[shown=\"true\"] &,\n .shaka-controls-container[casting=\"true\"] & {\n opacity: 1;\n }\n}\n\n.hide-when-shaka-controls-disabled() {\n .shaka-video-container:not([shaka-controls=\"true\"]) & {\n .hidden();\n }\n}\n\n/* The width of the bottom-section controls: seek bar, ad controls, and\nthe control buttons panel. */\n@bottom-controls-width: 96%;\n","@import (reference) \"shaka-player/ui/controls.less\";\n\n@import \"ShakaFrontend.less\";\n@import \"WaveForm.less\";\n\n.dlf-media-player {\n .noselect();\n\n // For height 100%, to make waveform usable (TODO?)\n position: relative;\n}\n\n.dlf-media-base() {\n &:not(:defined) {\n display: none;\n }\n\n dlf-chapter {\n display: none;\n }\n\n dlf-media-controls {\n display: none;\n }\n}\n\ndlf-media {\n .dlf-media-base();\n}\n\n.dlf-media {\n width: 100%;\n height: 100%;\n}\n\n.dlf-media-poster {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: contain;\n background-color: black;\n\n // Show poster only if src is set\n &[src].dlf-visible {\n display: block;\n }\n}\n\n.shaka-video-container {\n height: 100%;\n}\n\n.shaka-bottom-controls {\n visibility: hidden;\n}\n\n.shaka-controls-button-panel {\n // Prefer showing play button rather than fullscreen/overflow menu buttons\n // on tiny screens\n justify-content: flex-start !important;\n}\n\n@media all and (min-width: calc(@tabletViewportWidth + 1px)) {\n .shaka-controls-button-panel button {\n margin-left: 8px;\n margin-right: 8px;\n }\n\n .shaka-overflow-menu-button {\n margin-left: 0 !important;\n }\n\n .shaka-fullscreen-button {\n margin-left: 3px !important;\n margin-right: 4px !important;\n }\n}\n\n.shaka-current-time {\n // On small screens, let the time tracker adapt\n\n flex-shrink: 1 !important;\n white-space: nowrap;\n overflow-x: scroll;\n position: relative;\n\n &::-webkit-scrollbar {\n display: none;\n }\n}\n\nbody {\n .shaka-video-container {\n touch-action: none !important;\n }\n\n &.seek-or-scrub {\n // Don't let the big play button hinder scrubbing,\n // and make sure there are no tooltips when scrubbing.\n //\n // NOTE: We cannot just use a wildcard selector as that would lead to\n // issues on mobile (pointercancel when moving mouse vertically).\n .shaka-play-button,\n .shaka-controls-button-panel {\n pointer-events: none;\n }\n\n * {\n cursor: grabbing !important;\n }\n }\n}\n\n.dlf-media-thumbnail-preview {\n .noselect();\n\n // Use `visibility` instead of `display` because we want to use\n // `offsetWidth`, which is zero for elements with `display: none`.\n visibility: hidden;\n position: absolute;\n // Position 25px above seekbar, but overlap to the bottom (so that the\n // mouse can switch between seekbar and thumbnail preview).\n bottom: 0px;\n padding-bottom: 25px;\n // Allow to move mouse a little over the thumbnail preview without\n // closing it.\n padding-top: 50px;\n // Don't be shadowed by Shaka controlbar\n z-index: 1;\n cursor: pointer;\n text-align: center;\n\n .displayed {\n display: block !important;\n }\n\n .content-box {\n background-color: rgba(darken(@base-color, 25%), 0.6);\n border-radius: 4px;\n padding: 1em;\n }\n\n .display {\n display: none;\n position: relative;\n width: 160px;\n height: 90px;\n border: 1px solid white;\n box-sizing: content-box;\n margin-bottom: 0.75em;\n overflow: hidden;\n\n &.is-open {\n display: block;\n }\n\n img {\n visibility: hidden;\n position: absolute;\n top: 0;\n left: 0;\n }\n }\n\n .info {\n display: inline-block;\n // Same color and font-size as `.shaka-current-time`\n color: white;\n font-size: 14px;\n line-height: 110%;\n max-width: 160px;\n\n .chapter-text {\n line-height: 110%;\n margin-bottom: 0.4em;\n display: none;\n }\n\n .timecode-text {\n display: block;\n }\n\n &.on-chapter-marker .chapter-text {\n font-weight: bold;\n }\n }\n}\n\n.dlf-media-chapter-marker {\n // More than .seek-marker\n // Less than Shaka's tooltips\n z-index: 0;\n}\n\n@seek-bar-height: 4px;\n@seek-bar-margin: 6px;\n\n// Thanks https://stackoverflow.com/a/41585180 (Less variables in media query)\n// TODO: Use CSS variables?\n@media all and (pointer: fine) {\n .dlf-media-chapter-marker {\n width: @seek-bar-height;\n height: @seek-bar-height;\n background-color: @light-color;\n border-radius: @seek-bar-height / 2;\n }\n\n @import (multiple) \"./FlatSeekBar.less\";\n}\n\n@media all and (pointer: coarse) {\n .dlf-media-chapter-marker {\n width: 1px;\n height: @seek-bar-height - 2px;\n margin-top: 1px;\n background-color: rgba(@light-color, 0.3);\n }\n\n @seek-bar-height: 7px;\n @seek-bar-margin: 8px;\n\n @import (multiple) \"./FlatSeekBar.less\";\n}\n","// Imported from DlfMediaPlayer.less\n\n.dlf-media-flat-seek-bar {\n .show-when-controls-shown();\n .no-tap-highlight();\n\n // This lets us use pointer events for touch\n touch-action: none;\n position: relative;\n margin: 0px 6px;\n padding-top: @seek-bar-margin;\n height: @seek-bar-height + 2 * @seek-bar-margin;\n cursor: pointer;\n\n .range {\n position: absolute;\n width: 100%;\n height: @seek-bar-height;\n border-radius: @seek-bar-height;\n }\n\n .seek-marker {\n position: absolute;\n left: 45%;\n height: @seek-bar-height;\n width: 1px;\n background-color: @base-color;\n z-index: -1; // Less than .dlf-media-chapter-marker\n visibility: hidden;\n }\n\n .seek-thumb-bar {\n position: absolute;\n left: 45%;\n height: @seek-bar-height;\n width: 1px;\n background-color: rgba(@base-color, 0.4);\n z-index: -2; // Less than .dlf-media-chapter-marker and .seek-marker\n visibility: hidden;\n }\n}\n",".bookmark-modal {\n text-align: left;\n\n .share-buttons {\n margin-bottom: 1em;\n\n a {\n margin-right: 1em;\n\n img,\n .material-icons-round {\n width: 40px;\n height: 40px;\n font-size: 40px;\n }\n }\n }\n\n .url-qrcode {\n padding-top: 1em;\n display: none;\n\n hr {\n margin-bottom: 1.4em;\n }\n\n &.dlf-visible {\n display: block;\n }\n }\n\n .url-line {\n display: flex;\n\n input {\n width: 90%;\n height: 2rem;\n font-size: 1rem;\n line-height: 1rem;\n color: @base-color;\n margin-right: 4pt;\n }\n }\n\n .start-at {\n div {\n display: none;\n margin-top: 1em;\n\n &.shown {\n display: block;\n }\n }\n\n label {\n padding-left: @label-padding;\n }\n }\n}\n",".help-modal {\n width: auto;\n\n // Center horizontally (TODO: refactor transform into sxnd-modal)\n left: 50% !important;\n right: unset !important;\n transform: translateX(-50%) translateY(-50%) !important;\n\n .body-container {\n padding: 0 1em !important;\n }\n\n .subheader {\n text-align: left;\n font-weight: bold;\n\n &:not(:nth-child(1)) {\n margin-top: 2em;\n }\n }\n\n .keybindings-table {\n line-height: 1.5rem;\n font-size: 80%;\n white-space: nowrap;\n\n th.kb-group {\n padding-left: 0.2em;\n text-align: left;\n font-weight: bold;\n }\n\n tbody tr[aria-disabled=\"true\"] {\n opacity: 0.4;\n }\n\n thead[aria-disabled=\"true\"],\n tbody[aria-disabled=\"true\"] {\n opacity: 0.4;\n\n // Don't reduce opacity twice\n tr {\n opacity: 1;\n }\n }\n\n tbody tr {\n &:first-child {\n border-top: 1px solid @light-color;\n }\n\n td {\n color: white;\n }\n }\n\n td.key {\n width: 40%;\n text-align: right;\n }\n\n td.action {\n padding-left: 3em;\n text-align: left;\n }\n }\n}\n",".screenshot-modal {\n .body-container {\n display: grid;\n grid-template-columns: 3fr 1fr;\n column-gap: 1em;\n\n text-align: left;\n\n .screenshot-config {\n grid-column: 2;\n grid-row: 1;\n\n h4 {\n margin-bottom: 0.6em;\n font-size: 120%;\n }\n\n section {\n margin-bottom: 0.8em;\n\n h1 {\n font-weight: bold;\n font-size: 100%;\n margin-bottom: 0.3em;\n }\n }\n\n .metadata-overlay label {\n padding-left: @label-padding;\n }\n\n .file-format-option {\n margin-left: 0.3em;\n }\n\n .download-link {\n margin-top: 0.2em;\n }\n }\n\n .snap-tip {\n display: flex;\n gap: 0.4em;\n\n margin-top: 2em;\n line-height: 1.25;\n color: @light-color;\n }\n\n canvas {\n grid-column: 1;\n grid-row: 1;\n\n background-color: black;\n width: 100%;\n height: auto;\n }\n }\n\n @media screen and (max-width: 1600px) {\n .body-container {\n grid-template-columns: 2fr 1fr;\n }\n }\n\n @media screen and (max-width: @desktopViewportWidth) {\n .body-container {\n grid-template-columns: 1fr 1fr;\n }\n }\n\n @media screen and (max-width: @phoneLandscapeViewportWidth) {\n .body-container {\n display: block;\n }\n\n canvas {\n display: none;\n }\n }\n}\n",".dlf-media-markers {\n h2 {\n margin-bottom: 0.4em;\n }\n\n .dlf-media-markers-empty-msg {\n display: none;\n }\n\n .dlf-media-markers-list {\n table {\n margin-top: 0.4em;\n line-height: normal;\n }\n\n th {\n border-bottom: 1px solid black;\n }\n\n td, th {\n padding: 0.1em 2em 0.1em 0;\n }\n\n tbody tr:hover {\n background-color: #eee;\n }\n\n tr.active-segment {\n color: darkcyan;\n }\n\n .marker-id-col {\n width: 50%;\n\n input {\n font: inherit;\n background: transparent;\n border: none;\n\n &:focus {\n outline: none;\n }\n\n &::placeholder {\n font-style: italic;\n }\n }\n }\n\n .marker-start-col,\n .marker-end-col {\n cursor: pointer;\n }\n\n .marker-buttons-col {\n width: 0;\n white-space: nowrap;\n }\n\n button {\n cursor: pointer;\n background: transparent;\n border: none;\n outline: none;\n }\n\n .material-icons-round {\n font-size: 20px;\n }\n }\n\n &.is-empty {\n .dlf-media-markers-empty-msg {\n display: block;\n }\n\n .dlf-media-markers-list {\n display: none;\n }\n\n kbd {\n .kbd();\n }\n }\n}\n","@import \"modals/BookmarkModal.less\";\n@import \"modals/HelpModal.less\";\n@import \"modals/ScreenshotModal.less\";\n\n@import \"components/MarkerTable.less\";\n\n@zIndexModalCover: 100000;\n@zIndexModal: 999999;\n\nslub-media {\n .dlf-media-base();\n\n dlf-meta {\n display: none;\n }\n}\n\nbody[data-has-video] {\n .page-control,\n .document-functions li.doublepage,\n .view-functions li.rotate,\n .view-functions li.zoom .in,\n .view-functions li.zoom .out {\n display: none;\n }\n}\n\n.control-bar {\n // Avoid white border/separator between TOC and video\n border-right: none !important;\n\n // These buttons are used to toggle TOC and metadata on mobile (see\n // @slub_digitalcollections). Put them to the top so they won't overlap\n // with video controls.\n .offcanvas-toggle {\n bottom: unset;\n top: 5px;\n }\n}\n\n.combined-container {\n &:fullscreen {\n background-color: white;\n\n .media-panel {\n padding: 1em 1em;\n }\n }\n\n position: absolute;\n width: 100%;\n height: 100%;\n\n display: grid;\n\n &[data-mode=\"audio\"] {\n grid-template-rows: 1fr auto;\n }\n\n &[data-mode=\"video\"] {\n grid-template-rows: 0 1fr;\n }\n\n .media-panel {\n grid-row: 1;\n margin: 6em 1em;\n text-align: left;\n overflow-y: scroll;\n }\n\n .tx-dlf-view {\n grid-row: 2;\n }\n}\n\n.document-view {\n @media screen and (max-width: (@tabletLandscapeViewportWidth - 1px)) {\n top: 50px;\n }\n\n .tx-dlf-view {\n position: relative;\n }\n\n .media-viewport {\n height: 100%;\n }\n}\n\n.dlf-media-player {\n width: 100%;\n height: 100%;\n}\n\n.inline-icon {\n vertical-align: middle;\n}\n\n.sxnd-waveform-button,\n.sxnd-screenshot-button,\n.sxnd-bookmark-button,\n.sxnd-help-button {\n font-size: 22px !important;\n}\n\n@media all and (max-width: @tabletViewportWidth) {\n .sxnd-waveform-button,\n .sxnd-screenshot-button,\n .sxnd-bookmark-button,\n .sxnd-help-button {\n display: none !important;\n }\n\n .shaka-volume-bar-container,\n .shaka-mute-button {\n display: none !important;\n }\n}\n\n.sxnd-modal-cover {\n visibility: hidden;\n position: absolute;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n z-index: @zIndexModalCover;\n background-color: black;\n opacity: 0;\n\n &.shown {\n visibility: visible;\n opacity: 0.33;\n transition: opacity 200ms linear;\n }\n}\n\n.sxnd-modal {\n position: absolute;\n display: none;\n top: 50%;\n -ms-transform: translateY(-50%);\n transform: translateY(-50%);\n background-color: rgba(@base-color, 0.9);\n color: white;\n border-radius: 5px;\n z-index: @zIndexModal;\n\n padding: 2rem 1.5rem 2rem 0.6rem;\n left: 1rem;\n right: 1rem;\n\n @media screen and (min-width: @desktopViewportWidth) {\n padding: 3rem;\n left: 5rem;\n right: 5rem;\n }\n\n kbd {\n .kbd();\n color: white;\n }\n\n button {\n background-color: rgba(white, 0.8);\n border: 1px solid black;\n padding: 0.5em 1em;\n border-radius: 4px;\n font-size: 100%;\n }\n\n .headline-container {\n position: relative;\n padding-bottom: 1rem;\n\n .modal-close {\n position: absolute;\n top: 0;\n right: 0;\n cursor: pointer;\n }\n }\n\n .body-container {\n padding: 2rem;\n overflow-y: auto;\n // max-height is set in JS\n }\n\n h3 {\n font-weight: 700;\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/Resources/Public/Css/DlfMediaVendor.css.map b/Resources/Public/Css/DlfMediaVendor.css.map new file mode 100644 index 000000000..e278f218e --- /dev/null +++ b/Resources/Public/Css/DlfMediaVendor.css.map @@ -0,0 +1 @@ +{"version":3,"file":"Css/DlfMediaVendor.css","mappings":";;;AAAA;;;;EAIE;AACF;;;;EAIE;AACF,iEAAiE;AACjE;sBACsB;ACStB;EDPE;sCACoC;ECSpC;ADPF;AACA,gEAAgE;AAChE,2CAA2C;AAC3C;mDACmD;AACnD;6BAC6B;AAC7B;4BAC4B;AAC5B;;;;EAIE;AACF,4EAA4E;AAC5E;2BAC2B;AErB3B;EFuBE;;;;;;;qCAOmC;ECUnC;EDRA;;8EAE4E;ECW5E;EACA;EDTA;kCACgC;EEjChC;EFmCA,kDAAkD;EAClD,yCAAyC;AAC3C;AE1CA;EASI;EACA;AFoCJ;AE9CA;EAeI;AFkCJ;AACA;;;;;qCAKqC;AElBrC;EDrBE;EACA;ECWA;AFgCF;AEvBA;EFyBE;;oBAEkB;EE9BhB;AFgCJ;AE5BA;EDtBE;EACA;ECWA;AF2CF;AEjCA;EFmCE;;oBAEkB;EEzChB;AF2CJ;AEtCA;EDvBE;EACA;ECWA;AFsDF;AE3CA;EF6CE;;oBAEkB;EEpDhB;AFsDJ;AEhDA;EDxBE;EACA;ECWA;AFiEF;AErDA;EFuDE;;oBAEkB;EE/DhB;AFiEJ;AACA;;8CAE8C;AEzD9C;EF2DE;;uDAEqD;AACvD;AACA;;iDAEiD;AExDjD;EF0DE;;;;;;;gCAO8B;EC5D9B;ED8DA,mCAAmC;EC3DnC;EACA;EACA;EACA;EACA;EACA;EAnDA;EACA;EDiHA,wEAAwE;EEtExE;EFwEA,yEAAyE;EErEzE;EFuEA,yDAAyD;EEpEzD;EFsEA,4DAA4D;EEnE5D;EFqEA,mCAAmC;EElEnC;EFoEA;2DACyD;EACzD;qEACmE;EE/DnE;AFiEF;AClCE;EAzGA;AD8IF;AE9FA;EDgCE;ADiEF;AErEE;EFuEA,0CAA0C;AAC5C;AExEE;ED5EA;ADuJF;AACA;4CAC4C;AEnE5C;EACE;EACA;EACA;EFqEA;;;;IAIE;EElEF;AFoEF;AACA;;mBAEmB;AEjEnB;EFmEE,kEAAkE;EEjElE;EACA;EFmEA,2EAA2E;EEhE3E;EACA;EFkEA,gCAAgC;EE/DhC;EFiEA,gCAAgC;EE9DhC;EFgEA,wBAAwB;EE7DxB;EACA;EF+DA;8DAC4D;EE5D5D;EACA;EACA;EF8DA,2CAA2C;EClM3C;EACA;EACA;EACA;EDoMA,4EAA4E;ECrG5E;EDuGA,uEAAuE;ECpGvE;EDsGA;WACS;EACT;6CAC2C;AAC7C;ACtGE;;EAEE;ADwGJ;AEtEE;EFwEA,gCAAgC;EEtE9B;EFwEF,wBAAwB;EErEtB;EFuEF,qCAAqC;EEpEnC;EFsEF,2EAA2E;EC/L3E;EC8HE;EFoEF;cACY;EEjEV;EACA;EACA;AFmEJ;AACA,0EAA0E;AE/D1E;EACE;AFiEF;AACA;;2BAE2B;AE9D3B;EFgEE;kCACgC;EE9DhC;ED9JA;EACA;EA2EA;EDqJA;;uEAEqE;ECvKrE;EACA;EACA;EACA;EACA;EDyKA,0DAA0D;EErE1D;EACA;EACA;AFuEF;AEpEA;EACE;EACA;EAEA;EAEA;EACA;EAEA;EAEA;EACA;EAEA;EACA;EACA;EACA;EFiEA,uCAAuC;EACvC,4EAA4E;EC3K5E;ED6KA,uEAAuE;EC1KvE;ED4KA;WACS;AACX;AC1KE;;EAEE;AD4KJ;AE7FA;EAuBI;EACA;AFyEJ;AEjGA;EA4BI;AFwEJ;AEpEA;EACE;EAEA;EAEA;EACA;AFoEF;AE1EA;EASI;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;AFkEJ;AEhEI;EACE;AFkEN;AEvFA;EA0BI;EAEA;EAEA;EACA;AF8DJ;AE7FA;EAmCI;EAEA;EACA;AF4DJ;AExDA;EACE;EDpPA;EACA;EA2EA;EDqOA;;uEAEqE;ECvPrE;EACA;EACA;EACA;EACA;EDyPA,4EAA4E;ECxO5E;ED0OA,uEAAuE;ECvOvE;EDyOA;WACS;EACT,2EAA2E;EEpE3E;AFsEF;ACzOE;;EAEE;AD2OJ;AEvEA;EFyEE;;uEAEqE;EC7QrE;EACA;EACA;EACA;EACA;ED+QA;;qCAEmC;EE7EnC;EF+EA,mEAAmE;EE5EnE;EACA;EACA;EF8EA;;;;;mBAKiB;EE3EjB;EACA;EF6EA,iEAAiE;EE1EjE;EACA;EACA;AF4EF;AErGA;EA4BI;EACA;AF4EJ;AExEA;EF0EE;sEACoE;EExEpE;EF0EA;+CAC6C;EEvE7C;AFyEF;AACA,2BAA2B;AEtE3B;EFwEE;;uEAEqE;ECxTrE;EACA;EACA;EACA;EACA;EAhEA;EACA;EC8SA;EACA;EACA;EACA;AF8EF;ACjSE;EAzGA;AD6YF;AE5EA;EF8EE;;iCAE+B;EAC/B;sDACoD;EACpD;;;;;;;qCAOmC;ECzXnC;ED2XA;;8EAE4E;ECxX5E;EACA;ECkSA;EACA;EACA;EACA;EACA;EFyFA;0BACwB;EEtFxB;AFwFF;AACA;;;;EAIE;AACF,yCAAyC;AACzC,6EAA6E;AGhb7E;EHkbE;;;;;;8DAM4D;EGhb5D;EACA;EACA;EACA;EHkbA;+EAC6E;EG/a7E;EHibA,oCAAoC;EG9apC;EHgbA,0CAA0C;EG7a1C;EH+aA,eAAe;EG5af;EH8aA;;4BAE0B;EG3a1B;EACA;EACA;EH6aA,8CAA8C;EG1a9C;EH4aA,4EAA4E;ECpX5E;EDsXA,uEAAuE;ECnXvE;EDqXA;WACS;EACT;;;+EAG6E;AAC/E;ACvXE;;EAEE;ADyXJ;AGlbE;EACE;AHobJ;AGjbE;EACE;AHmbJ;AACA;;;EAGE;AG/aF;EACE;EACA;EACA;AHibF;AG/aE;EHibA;8BAC4B;EGpf5B;EACA;EACA;AHsfF;AACA,0DAA0D;AGjb1D;EHmbE;iBACe;AACjB;AGrbA;;EHwbE;2BACyB;EGrbvB;AHubJ;AG3bA;;EAUI;EACA;AHqbJ;AACA;;qDAEqD;AGjbrD;;EAEI;AHmbJ;AACA;;;;EAIE;AACF;;;;;;;;;;;;;;;;;;;0CAmB0C;AAC1C,4DAA4D;AAC5D;;+EAE+E;AAC/E;0DAC0D;AAC1D;kDACkD;AAClD,4CAA4C;AI3b5C;EJ6bE,sDAAsD;EACtD;;;;;;;qCAOmC;ECphBnC;EDshBA;;8EAE4E;ECnhB5E;EACA;EDqhBA,4DAA4D;EItiB5D;EJwiBA,oEAAoE;EIriBpE;EJuiBA,uCAAuC;EIpiBvC;EJsiBA,4EAA4E;EIniB5E;AJqiBF;AIhdA;EACE;AJkdF;AI/cA;EJidE,qDAAqD;EI/frD;EACA;EJigBA,wCAAwC;EACxC;;;;;;;gCAO8B;EChiB9B;EDkiBA,mCAAmC;EC/hBnC;EACA;EACA;EACA;EACA;EACA;EAnDA;EACA;EDqlBA;;gBAEc;EI9gBd;EJghBA;6EAC2E;EI7gB3E;EJ+gBA;yDACuD;EI5gBvD;EJ8gBA,8DAA8D;EAC9D,8CAA8C;AAChD;AI7gBE;EJ+gBA,6CAA6C;EIvkB7C;EJykBA;;wDAEsD;EItkBtD;EJwkBA;qBACmB;EIrkBnB;EACA;EACA;AJukBF;AIthBE;EJwhBA,oEAAoE;EIlkBpE;EJokBA;kBACgB;EIjkBhB;EJmkBA,kDAAkD;EIhkBlD;EACA;EACA;EJkkBA,+BAA+B;EI/jB/B;AJikBF;AI9hBE;EJgiBA,6CAA6C;EIjmB7C;EJmmBA;;wDAEsD;EIhmBtD;EJkmBA;qBACmB;EI/lBnB;EACA;EACA;AJimBF;AIviBE;EJyiBA,oEAAoE;EI5lBpE;EJ8lBA;kBACgB;EI3lBhB;EJ6lBA,kDAAkD;EI1lBlD;EACA;EACA;EJ4lBA,+BAA+B;EIzlB/B;AJ2lBF;AIniBA;EJqiBE,4EAA4E;ECvkB5E;EDykBA,uEAAuE;ECtkBvE;EDwkBA;WACS;AACX;ACtkBE;;EAEE;ADwkBJ;AI3iBA;EJ6iBE;;;;;;;gCAO8B;EChoB9B;EDkoBA,mCAAmC;EC/nBnC;EACA;EACA;EACA;EACA;EACA;EAnDA;EACA;ADqrBF;AACA;;;;;;;;;;;;;;;;;;;;;;;;EAwBE;AACF;iDACiD;AKjtBjD;ELmtBE;;yBAEuB;EACvB;;;;;;;gCAO8B;EClrB9B;EDorBA,mCAAmC;ECjrBnC;EACA;EACA;EACA;EDmrBA,sBAAsB;EK7tBtB;EACA;EL+tBA,uCAAuC;EK5tBvC;EACA;EACA;EACA;AL8tBF;AACA,oDAAoD;AK3tBpD;EACE;EACA;EACA;EL6tBA,8CAA8C;EK1tB9C;EL4tBA,gCAAgC;EKztBhC;AL2tBF;AACA,wBAAwB;AKxtBxB;EACE;IACE;EL0tBF;AACF;AACA,oEAAoE;AKvtBpE;EACE;IACE;IACA;ELytBF;EKttBA;IACE;IACA;ELwtBF;EKrtBA;IACE;IACA;ELutBF;AACF;AACA;;;;EAIE;AACF,2EAA2E;AAC3E;4DAC4D;AMryB5D;ENuyBE,yEAAyE;EMryBzE;ENuyBA,gEAAgE;EC3sBhE;ED6sBA,0DAA0D;EMnyB1D;ENqyBA;iBACe;EMlyBf;ANoyBF;AACA;;;;EAIE;AACF;iEACiE;AOzzBjE;;EP4zBE;2CACyC;EOzzBzC;EACA;EP2zBA,sCAAsC;EOxzBtC;EP0zBA,gCAAgC;EOvzBhC;EACA;EACA;EACA;EACA;EPyzBA,gDAAgD;EAChD,4EAA4E;ECpuB5E;EDsuBA,uEAAuE;ECnuBvE;EDquBA;WACS;EACT;uBACqB;EO1zBrB;EACA;EP4zBA,4BAA4B;EOzzB5B;EACA;EACA;EACA;EP2zBA,iCAAiC;EACjC;4CAC0C;EAC1C;4CAC0C;AAC5C;ACjvBE;;;;EAEE;ADqvBJ;AOn2BA;;EAiCI;EACA;EACA;EACA;EACA;EACA;EPs0BF,yEAAyE;EOn0BvE;EACA;EPq0BF,0DAA0D;EAC1D,oDAAoD;EO9zBlD;EPg0BF,4DAA4D;AAC9D;AOt0BI;;EACE;APy0BN;AOv3BA;;EAsDM;APq0BN;AOl0BI;;EACE;APq0BN;AO/3BA;;EPk4BE,oDAAoD;EOh0BlD;EACA;APk0BJ;AO7zBE;;EPg0BA,oDAAoD;EO9zBlD;APg0BJ;AACA;;;;;uBAKuB;AO5zBvB;EACE;AP8zBF;AACA;uBACuB;AO3zBvB;EACE;EP6zBA,8DAA8D;EO1zB9D;EACA;AP4zBF;AACA;;sDAEsD;AOzzBtD;EP2zBE,0EAA0E;EOzzB1E;AP2zBF;AACA,8DAA8D;AOxzB9D;EP0zBE,oDAAoD;EOvzBlD;APyzBJ;AACA;UACU;AOrzBV;EPuzBE,mEAAmE;EACnE,sCAAsC;AACxC;AOzzBA;EP2zBE,oDAAoD;EOvzBlD;APyzBJ;AO7zBA;EP+zBE,oDAAoD;EOrzBlD;APuzBJ;AACA,yDAAyD;AOnzBzD;EPqzBE,oDAAoD;EOnzBpD;APqzBF;AACA;;;;EAIE;AACF,iBAAiB;AQ37BjB;ER67BE,0EAA0E;EQ37B1E;ER67BA,oEAAoE;AACtE;AQh8BA;EAMI;AR67BJ;AQz7BA;;ER47BE;;uEAEqE;EC73BrE;EACA;EACA;EACA;EACA;ED+3BA;;2BAEyB;AAC3B;AQh8BI;;ERm8BF;6CAC2C;EQj8BvC;ARm8BN;AQ97BA;EPVE;EACA;EA2EA;ADi4BF;AQ/7BE;EACE;ARi8BJ;AQ77BA;EAGE;EACA;EACA;ER67BA;iBACe;EQ17Bf;AR47BF;ACz3BE;EAzGA;ADq+BF;AQx8BA;;EAYI;EACA;ARg8BJ;AQ78BA;EPRE;ADw9BF;AQ37BA;;EPnCE;EACA;EACA;EDk+BA;wBACsB;EQ5/BtB;AR8/BF;AQ/7BA;ERi8BE;8DAC4D;EQ/7B5D;ERi8BA;;;;;IAKE;EQ97BF;EACA;EACA;EACA;ARg8BF;AQ77BA;EACE;EACA;EACA;EAMA;AR07BF;AQ97BE;EACE;ARg8BJ;AQ17BA;EACE;EACA;EACA;AR47BF;AACA;;;;;;;;;;;;;;;;;;;;;;;;EAwBE;AACF;kEACkE;AS1hClE;EACE;ET4hCA,yEAAyE;EACzE;mEACiE;EACjE;oEACkE;AACpE;AS/hCE;EACE;ETiiCF,mDAAmD;AACrD;AS/hCI;;;EACE;ETmiCJ,gDAAgD;EShiC5C;EACA;EACA;EACA;ETkiCJ,YAAY;ES/hCR;EACA;EACA;EACA;ETiiCJ,gBAAgB;ES9hCZ;EACA;ETgiCJ,oEAAoE;ES7hChE;ET+hCJ,0DAA0D;ESnkC1D;EACA;EACA;EACA;EACA;ATqkCF;AS5hCI;;;EACE;ATgiCN;ASzhCI;;;EACE;EAtDJ;EACA;EACA;EACA;EACA;ATolCF;AS1hCI;;;EACE;EA/DJ;EACA;EACA;EACA;EACA;AT8lCF;AU/nCA;EACA;EACA;EACA;EACA;AACA;;ACLA;EACA;EACA;EACA;EACA;AACA;;AAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA","sources":["webpack://kitodo-presentation/./node_modules/shaka-player/ui/controls.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/general.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/containers.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/buttons.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/range_elements.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/spinner.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/other_elements.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/overflow_menu.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/ad_controls.less","webpack://kitodo-presentation/./node_modules/shaka-player/ui/less/tooltip.less","webpack://kitodo-presentation/https:/fonts.googleapis.com/css","webpack://kitodo-presentation/https:/fonts.googleapis.com/icon"],"sourcesContent":["/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/* General utility mixins and classes with broad applicability. */\n/* Make a thing unselectable. There are currently no cases where we make it\n * selectable again. */\n.shaka-hidden {\n /* Make this override equally specific classes.\n * If it's hidden, always hide it! */\n display: none !important;\n}\n/* For containers which host elements overlaying other things. */\n/* For things which overlay other things. */\n/* For things that should not shrink inside a flex container.\n * This will be used for all controls by default. */\n/* Use this to override .unshrinkable() in particular cases that *should* shrink\n * inside a flex container. */\n/* The width of the bottom-section controls: seek bar, ad controls, and\nthe control buttons panel. */\n/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/* All of the top-level containers into which various visible features go. */\n/* A container for the entire video + controls combo. This is the auto-setup\n * div which we populate. */\n.shaka-video-container {\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for some children of this container to overlay the\n * others using .overlay-child(). */\n position: relative;\n /* Make sure any top or left styles applied from outside don't move this from\n * it's original position, now that it's relative to that original position.\n * This is a defensive move that came out of intensive debugging on IE 11. */\n top: 0;\n left: 0;\n /* Without this, the container somehow winds up being a tad taller than it\n * should be (484px vs 480px). */\n display: flex;\n /* Set a special font for material design icons. */\n /* Set the fonts for all other content. */\n}\n.shaka-video-container .material-icons-round {\n font-family: 'Material Icons Round';\n font-size: 24px;\n}\n.shaka-video-container * {\n font-family: Roboto-Regular, Roboto, sans-serif;\n}\n/* Each browser has a different prefixed pseudo-class for fullscreened elements.\n * Define the properties of a fullscreened element in a mixin, then apply to\n * each of the browser-specific pseudo-classes.\n * NOTE: These fullscreen pseudo-classes can't be combined with commas into a\n * single delcaration. Browsers ignore the rest of the list once they hit one\n * pseudo-class they don't support. */\n.shaka-video-container:fullscreen {\n width: 100%;\n height: 100%;\n background-color: black;\n}\n.shaka-video-container:fullscreen .shaka-text-container {\n /* In fullscreen mode, the text displayer's font size should be relative to\n * the either window height or width (whichever is smaller), instead of a\n * fixed size. */\n font-size: 4.4vmin;\n}\n.shaka-video-container:-webkit-full-screen {\n width: 100%;\n height: 100%;\n background-color: black;\n}\n.shaka-video-container:-webkit-full-screen .shaka-text-container {\n /* In fullscreen mode, the text displayer's font size should be relative to\n * the either window height or width (whichever is smaller), instead of a\n * fixed size. */\n font-size: 4.4vmin;\n}\n.shaka-video-container:-moz-full-screen {\n width: 100%;\n height: 100%;\n background-color: black;\n}\n.shaka-video-container:-moz-full-screen .shaka-text-container {\n /* In fullscreen mode, the text displayer's font size should be relative to\n * the either window height or width (whichever is smaller), instead of a\n * fixed size. */\n font-size: 4.4vmin;\n}\n.shaka-video-container:-ms-fullscreen {\n width: 100%;\n height: 100%;\n background-color: black;\n}\n.shaka-video-container:-ms-fullscreen .shaka-text-container {\n /* In fullscreen mode, the text displayer's font size should be relative to\n * the either window height or width (whichever is smaller), instead of a\n * fixed size. */\n font-size: 4.4vmin;\n}\n/* The actual video element. Sits inside .shaka-video-container and gives it a\n * size in non-fullscreen mode. In fullscreen mode, the sizing relationship\n * flips. CSS is just great like that. :-( */\n.shaka-video {\n /* At the moment, nothing special is required here.\n * Note that this should NOT be an overlay-child, as its size could dictate\n * the size of the container for some applications. */\n}\n/* A container for all controls, including the giant play button, seek bar, etc.\n * Sits inside .shaka-video-container, on top of (Z axis) .shaka-video, and\n * below (Y axis) .shaka-play-button-container. */\n.shaka-controls-container {\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for this child to overlay the other children of a\n * .overlay-parent() object. */\n position: absolute;\n /* Fill the container by default. */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n margin: 0;\n padding: 0;\n width: 100%;\n height: 100%;\n /* Without this, the controls container overflows the video container. */\n box-sizing: border-box;\n /* A flex container, to make layout of children easier to reason about. */\n display: flex;\n /* Defines in which direction the children should flow. */\n flex-direction: column;\n /* Pushes the children toward the bottom of the container. */\n justify-content: flex-end;\n /* Centers children horizontally. */\n align-items: center;\n /* By default, do not allow any of our controls to shrink.\n * Specific controls can use .shrinkable() to override. */\n /* Position the controls container in front of the text container, so that\n * the text container doesn't interfere with the control buttons. */\n z-index: 1;\n}\n.shaka-video-container:not([shaka-controls=\"true\"]) .shaka-controls-container {\n display: none;\n}\n.shaka-controls-container * {\n flex-shrink: 0;\n}\n.shaka-controls-container[casting=\"true\"] {\n /* Hide fullscreen button while casting. */\n}\n.shaka-controls-container[casting=\"true\"] .shaka-fullscreen-button {\n display: none;\n}\n/* Container for controls positioned at the bottom of the video container:\n * controls button panel and the seek bar. */\n.shaka-bottom-controls {\n width: 96%;\n padding: 0;\n padding-bottom: 2.5%;\n /* Position the bottom panel in front of other controls (play button and\n * spinner containers).\n * TODO: A different layout arrangement might be a better solution for this.\n * Need to experiment.\n */\n z-index: 1;\n}\n/* This is the container for the horizontal row of controls above the seek bar.\n * It sits above (Y axis) the seek bar, and below (Y axis) the giant play button\n * in the middle. */\n.shaka-controls-button-panel {\n /* Fill the space horizontally, with no extra padding or margin. */\n padding: 0;\n margin: 0;\n /* This is itself a flex container, with children layed out horizontally. */\n display: flex;\n flex-direction: row;\n /* Push children to the right. */\n justify-content: flex-end;\n /* Center children vertically. */\n align-items: center;\n /* TODO: Document why. */\n overflow: hidden;\n min-width: 48px;\n /* Make sure we don't inherit odd font sizes and styles from the environment.\n * TODO: When did this happen? What forced us to do this? */\n font-size: 12px;\n font-weight: normal;\n font-style: normal;\n /* Make sure contents cannot be selected. */\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n /* All buttons, divs, and other controls directly inside this panel should\n * have these characteristics by default. */\n}\n.shaka-controls-container[shown=\"true\"] .shaka-controls-button-panel,\n.shaka-controls-container[casting=\"true\"] .shaka-controls-button-panel {\n opacity: 1;\n}\n.shaka-controls-button-panel > * {\n /* White text or button icons. */\n color: white;\n /* 32px tall controls. */\n height: 32px;\n /* Consistent alignment of buttons. */\n line-height: 0.5;\n /* Consistent margins (external) and padding (internal) between controls. */\n margin: 1px 6px;\n padding: 0;\n /* Transparent backgrounds, no borders, and a pointer when you mouse over\n * them. */\n background: transparent;\n border: 0;\n cursor: pointer;\n}\n/* Buttons hide certain items if they are found inside the control panel */\n.shaka-controls-button-panel .shaka-overflow-menu-only {\n display: none;\n}\n/* The container for the giant play button. Sits above (Y axis) the\n * other video controls and seek bar, in the middle of the video frame, on top\n * of (Z axis) the video. */\n.shaka-play-button-container {\n /* Take up as much space as possible, but shrink (vertically) to accomodate\n * the controls at the bottom. */\n margin: 0;\n width: 100%;\n height: 100%;\n flex-shrink: 1;\n /* When setting \"position: absolute\" it uses the left,right,top,bottom\n * properties to determine the positioning. We should set all these\n * properties to ensure it is positioned properly on all platforms. */\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n /* Keep the play button in the middle of this container. */\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.shaka-statistics-container {\n overflow-x: hidden;\n overflow-y: auto;\n min-width: 300px;\n color: white;\n background-color: rgba(35, 35, 35, 0.9);\n font-size: 14px;\n padding: 5px 10px;\n border-radius: 2px;\n position: absolute;\n z-index: 2;\n left: 15px;\n top: 15px;\n /* Fades out with the other controls. */\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n}\n.shaka-controls-container[shown=\"true\"] .shaka-statistics-container,\n.shaka-controls-container[casting=\"true\"] .shaka-statistics-container {\n opacity: 1;\n}\n.shaka-statistics-container div {\n display: flex;\n justify-content: space-between;\n}\n.shaka-statistics-container span {\n color: #969696;\n}\n.shaka-context-menu {\n background-color: rgba(35, 35, 35, 0.9);\n border-radius: 2px;\n position: absolute;\n z-index: 3;\n}\n.shaka-context-menu button {\n padding: 5px 10px;\n width: 100%;\n display: flex;\n align-items: center;\n color: white;\n background: transparent;\n border: 0;\n cursor: pointer;\n}\n.shaka-context-menu button:hover {\n background-color: rgba(50, 50, 50, 0.9);\n}\n.shaka-context-menu label {\n padding: 0 20px;\n align-items: flex-start;\n color: white;\n cursor: pointer;\n}\n.shaka-context-menu .shaka-current-selection-span {\n align-items: flex-start;\n color: white;\n cursor: pointer;\n}\n.shaka-scrim-container {\n margin: 0;\n width: 100%;\n height: 100%;\n flex-shrink: 1;\n /* When setting \"position: absolute\" it uses the left,right,top,bottom\n * properties to determine the positioning. We should set all these\n * properties to ensure it is positioned properly on all platforms. */\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n /* A black gradient at the bottom, behind the controls, but only so high. */\n background: linear-gradient(to top, #000000 0, rgba(0, 0, 0, 0) 15%);\n}\n.shaka-controls-container[shown=\"true\"] .shaka-scrim-container,\n.shaka-controls-container[casting=\"true\"] .shaka-scrim-container {\n opacity: 1;\n}\n.shaka-text-container {\n /* When setting \"position: absolute\" it uses the left,right,top,bottom\n * properties to determine the positioning. We should set all these\n * properties to ensure it is positioned properly on all platforms. */\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n /* Make sure the text container doesn't steal pointer events from another\n * layer, such as the ad container. There is nothing interactive in this\n * layer, so this should be fine. */\n pointer-events: none;\n /* Place the text container on the bottom of the video container. */\n bottom: 0%;\n width: 100%;\n min-width: 48px;\n /* When the controls fade in or out, it takes 600ms. Thus, when the text\n * container adjusts to the presence or absence of controls, we should wait\n * briefly, so the captions don't end up appearing behind the controls.\n * Instead of being a gradual animation, this is a fast animation with a\n * significant delay, since the captions moving around is a little\n * distracting. */\n transition: bottom cubic-bezier(0.4, 0, 0.6, 1) 100ms;\n transition-delay: 500ms;\n /* These are defaults which are overridden by JS or cue styles. */\n font-size: 20px;\n line-height: 1.4;\n color: #ffffff;\n}\n.shaka-text-container span.shaka-text-wrapper {\n display: inline;\n background: none;\n}\n.shaka-controls-container[shown=\"true\"] ~ .shaka-text-container {\n /* While the controls are shown, the text container should avoid the 15%\n * at the bottom of the video, to avoid overlapping with controls. */\n bottom: 15%;\n /* Disable the transition delay when moving the captions up, so that the\n * controls don't appear over the captions. */\n transition-delay: 0ms;\n}\n/* The buffering spinner. */\n.shaka-spinner-container {\n /* When setting \"position: absolute\" it uses the left,right,top,bottom\n * properties to determine the positioning. We should set all these\n * properties to ensure it is positioned properly on all platforms. */\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n flex-shrink: 1;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.shaka-video-container:not([shaka-controls=\"true\"]) .shaka-spinner-container {\n display: none;\n}\n.shaka-spinner {\n /* This uses the same trickery as the big play button define\n the spinner's width and height. See .shaka-play-button\n for the detailed explanation. */\n /* For the padding thing to work, spinner div needs to be an\n overlay-parent and spinner svg - an overlay child. */\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for some children of this container to overlay the\n * others using .overlay-child(). */\n position: relative;\n /* Make sure any top or left styles applied from outside don't move this from\n * it's original position, now that it's relative to that original position.\n * This is a defensive move that came out of intensive debugging on IE 11. */\n top: 0;\n left: 0;\n margin: 0;\n box-sizing: border-box;\n padding: 7.8%;\n width: 0;\n height: 0;\n /* Add a bit of a white shadow to keep our black spinner visible\n on a black background. */\n filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.5));\n}\n/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/* The main buttons in the UI controls. */\n/* The giant play button, which sits inside .shaka-player-button-container. */\n.shaka-play-button {\n /* Set width & height in a round-about way. By using padding, we can keep\n * a 1:1 aspect ratio and size the button relative to the container width.\n *\n * Since padding is applied equally to top, bottom, left, and right, only use\n * half of the intended percentage for each.\n *\n * Based on tips from https://stackoverflow.com/a/12925343 */\n box-sizing: border-box;\n padding: 7.5%;\n width: 0;\n height: 0;\n /* To be properly positioned in the center, this should have no margin.\n * This might have been set for buttons generally by the app or user-agent. */\n margin: 0;\n /* This makes the button a circle. */\n border-radius: 50%;\n /* A small drop shadow below the button. */\n box-shadow: rgba(0, 0, 0, 0.1) 0 0 20px 0;\n /* No border. */\n border: none;\n /* The play arrow is a picture. It is treated a background image.\n * The following settings ensure it shows only once and in the\n * center of the button. */\n background-size: 50%;\n background-repeat: no-repeat;\n background-position: center center;\n /* A background color behind the play arrow. */\n background-color: rgba(255, 255, 255, 0.9);\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n /* Actual icon images for the two states this could be in.\n * These will be inlined as data URIs when compiled, and so do not need to be\n * deployed separately from the compiled CSS.\n * Note that these URIs should relative to ui/controls.less, not this file. */\n}\n.shaka-controls-container[shown=\"true\"] .shaka-play-button,\n.shaka-controls-container[casting=\"true\"] .shaka-play-button {\n opacity: 1;\n}\n.shaka-play-button[icon=\"play\"] {\n background-image: url(\"data:image/svg+xml,%3Csvg%20fill%3D%22%23000000%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%3Cpath%20d%3D%22M8%205v14l11-7z%22%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%0A%3C%2Fsvg%3E\");\n}\n.shaka-play-button[icon=\"pause\"] {\n background-image: url(\"data:image/svg+xml,%3Csvg%20fill%3D%22%23000000%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%3Cpath%20d%3D%22M6%2019h4V5H6v14zm8-14v14h4V5h-4z%22%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%0A%3C%2Fsvg%3E\");\n}\n/* This button contains the current time and duration of the video.\n * It's only clickable when the content is live, and current time is behind live\n * edge. Otherwise, the button is disabled.\n */\n.shaka-current-time {\n font-size: 14px;\n color: #ffffff;\n cursor: pointer;\n}\n.shaka-current-time[disabled] {\n /* Set the background and the color, otherwise it might be overwritten by\n * the css styles in demo. */\n background-color: transparent;\n color: white;\n cursor: default;\n}\n/* Use a consistent outline focus style across browsers. */\n.shaka-controls-container {\n /* Disable this Mozilla-specific focus ring, since we have an outline defined\n * for focus. */\n}\n.shaka-controls-container button:focus,\n.shaka-controls-container input:focus {\n /* Most browsers will fall back to \"Highlight\" (system setting) color for\n * the focus outline. */\n outline: 1px solid Highlight;\n}\n.shaka-controls-container button:-moz-focus-inner,\n.shaka-controls-container input:-moz-focus-outer {\n outline: none;\n border: 0;\n}\n/* Outline on focus is important for accessibility, but\n * it doesn't look great. This removes the outline for\n * mouse users while leaving it for keyboard users. */\n.shaka-controls-container:not(.shaka-keyboard-navigation) button:focus,\n.shaka-controls-container:not(.shaka-keyboard-navigation) input:focus {\n outline: none;\n}\n/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/* Special styles for input elements with type \"range\".\n *\n * These elements are composed of two main parts: a \"track\", which is the\n * horizontal bar, and the \"thumb\", which is the knob which slides along that\n * bar.\n *\n * In order to style the track across browsers (cough, IE 11), we need to do\n * something a bit tricky. Styling the track is a nightmare, especially if you\n * want the thumb to be larger. On IE 11, this gets clipped at the track size.\n * So a tiny track with a large thumb is not easily achieved. It can be done,\n * but the techniques for it are incompatible with the gradient background we\n * want to apply to it.\n *\n * The solution is to put the input inside a div container, and apply the\n * background gradient styles to the container. The container will act as a\n * visible, virtual track, inside which is contained a larger, invisible track,\n * in which is contained a visible thumb. This way, the thumb is not larger\n * than the actual track (for IE 11's sake), but can be larger than the virtual\n * track. And since we are still using a semantically correct input element,\n * the element is inherently accessible. */\n/* These control the color and size of the various pieces. */\n/* The range container is the div that contains a range element.\n * This div will act as a virtual track to allow us to style the track space.\n * An actual track still exists inside the range element, but is transparent. */\n/* The \"track\" is the pseudo-element inside the range element which represents\n * the horizontal bar on which the \"thumb\" (knob) moves. */\n/* The \"thumb\" is the pseudo-element inside the range element which represents\n * the knob which moves along the \"track\" (bar). */\n/* This is the actual range input element. */\n.shaka-range-container {\n /* This contains an input element which overlays it. */\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for some children of this container to overlay the\n * others using .overlay-child(). */\n position: relative;\n /* Make sure any top or left styles applied from outside don't move this from\n * it's original position, now that it's relative to that original position.\n * This is a defensive move that came out of intensive debugging on IE 11. */\n top: 0;\n left: 0;\n /* Vertical margins to occupy the same space as the thumb. */\n margin: 4px 6px;\n /* Smaller height to contain the background for the virtual track. */\n height: 4px;\n /* Rounded ends on the virtual track. */\n border-radius: 4px;\n /* Until we set a gradient background in JS, this will be the track color. */\n background: white;\n}\n.shaka-volume-bar-container {\n width: 100px;\n}\n.shaka-range-element {\n /* Remove any browser styling of the range element. */\n -webkit-appearance: none;\n background: transparent;\n /* Overlay and fill the container div. */\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for this child to overlay the other children of a\n * .overlay-parent() object. */\n position: absolute;\n /* Fill the container by default. */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n margin: 0;\n padding: 0;\n width: 100%;\n height: 100%;\n /* The range element should be big enough to contain the thumb without\n * clipping it. It is very tricky to make the thumb show outside the track\n * on IE 11. */\n height: 12px;\n /* Position the top of the range element so that it is centered on the\n * container. Note that the container is actually smaller than the thumb. */\n top: -4px;\n /* Make sure clicking at the very top of the bar still takes effect and is not\n * confused with clicking the video to play/pause it. */\n z-index: 1;\n /* Pseudo-elements for Blink-based or WebKit-based browsers. */\n /* Pseudo-elements for Gecko-based browsers. */\n}\n.shaka-range-element::-webkit-slider-runnable-track {\n /* The track should fill the range element. */\n width: 100%;\n /* The track should be tall enough to contain the thumb without clipping it.\n * It is very tricky to make the thumb show outside the track on IE 11, and\n * it is incompatible with our background gradients. */\n height: 12px;\n /* Some browsers have default backgrounds, colors, or borders for this.\n * Hide them all. */\n background: transparent;\n color: transparent;\n border: none;\n}\n.shaka-range-element::-webkit-slider-thumb {\n /* Remove default styles on WebKit-based and Blink-based browsers. */\n -webkit-appearance: none;\n /* On some browsers (IE 11), the thumb has a border, which affects the size.\n * Disable it. */\n border: none;\n /* Make the thumb a circle and set its diameter. */\n border-radius: 12px;\n height: 12px;\n width: 12px;\n /* Give it the desired color. */\n background: white;\n}\n.shaka-range-element::-moz-range-track {\n /* The track should fill the range element. */\n width: 100%;\n /* The track should be tall enough to contain the thumb without clipping it.\n * It is very tricky to make the thumb show outside the track on IE 11, and\n * it is incompatible with our background gradients. */\n height: 12px;\n /* Some browsers have default backgrounds, colors, or borders for this.\n * Hide them all. */\n background: transparent;\n color: transparent;\n border: none;\n}\n.shaka-range-element::-moz-range-thumb {\n /* Remove default styles on WebKit-based and Blink-based browsers. */\n -webkit-appearance: none;\n /* On some browsers (IE 11), the thumb has a border, which affects the size.\n * Disable it. */\n border: none;\n /* Make the thumb a circle and set its diameter. */\n border-radius: 12px;\n height: 12px;\n width: 12px;\n /* Give it the desired color. */\n background: white;\n}\n.shaka-seek-bar-container {\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n}\n.shaka-controls-container[shown=\"true\"] .shaka-seek-bar-container,\n.shaka-controls-container[casting=\"true\"] .shaka-seek-bar-container {\n opacity: 1;\n}\n.shaka-ad-markers {\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for this child to overlay the other children of a\n * .overlay-parent() object. */\n position: absolute;\n /* Fill the container by default. */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n margin: 0;\n padding: 0;\n width: 100%;\n height: 100%;\n}\n/*!\n * @license\n * The SVG/CSS buffering spinner is based on http://codepen.io/jczimm/pen/vEBpoL\n * Some local modifications have been made.\n *\n * Copyright (c) 2016 by jczimm\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n/* This is the spinner SVG itself, which contains a circular path element.\n * It sits inside the play button and fills it. */\n.shaka-spinner-svg {\n /* Because of some sizing hacks in the play button (see comments there), this\n * spinner needs to be an overlay child to be properly sized and positioned\n * within the button. */\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for this child to overlay the other children of a\n * .overlay-parent() object. */\n position: absolute;\n /* Fill the container by default. */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n /* Keep it spinning! */\n animation: rotate 2s linear infinite;\n transform-origin: center center;\n /* The SVG should fill its container. */\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n}\n/* This is the path element, which draws a circle. */\n.shaka-spinner-path {\n stroke: #202124;\n stroke-dasharray: 20, 200;\n stroke-dashoffset: 0;\n /* Animate the stroke of this circular path. */\n animation: dash 1s ease-in-out infinite;\n /* Round the line on the ends. */\n stroke-linecap: round;\n}\n/* Spin the whole SVG. */\n@keyframes rotate {\n 100% {\n transform: rotate(360deg);\n }\n}\n/* Pulse the circle's outline forward and backward while it spins. */\n@keyframes dash {\n 0% {\n stroke-dasharray: 1, 200;\n stroke-dashoffset: 0;\n }\n 50% {\n stroke-dasharray: 89, 200;\n stroke-dashoffset: -35px;\n }\n 100% {\n stroke-dasharray: 89, 200;\n stroke-dashoffset: -124px;\n }\n}\n/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/* UI elements that did not fit into the buttons/range elements category. */\n/* This is a spacer element used to separate elements within the control\n * buttons panel. It's just an empty div of certain width. */\n.shaka-spacer {\n /* This should not have a pointer-style cursor like the other controls. */\n cursor: default;\n /* Make the element shrink to accommodate things to the right. */\n flex-shrink: 1;\n /* Make the element grow to take up the remaining space. */\n flex-grow: 1;\n /* Margins don't shrink. Remove margins in order to be more flexible when\n * shrinking. */\n margin: 0;\n}\n/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/* The overflow menu and all settings submenus. These appear on top of all\n * other controls (Z axis) when the overflow button is clicked. */\n.shaka-overflow-menu,\n.shaka-settings-menu {\n /* It's okay to add a vertical scroll if there are too many items, but\n * horizontal scrolling is not allowed. */\n overflow-x: hidden;\n overflow-y: auto;\n /* Don't wrap text to the next line. */\n white-space: nowrap;\n /* Styles for the menu itself. */\n background: white;\n box-shadow: 0 1px 9px 0 rgba(0, 0, 0, 0.4);\n border-radius: 2px;\n max-height: 250px;\n min-width: 180px;\n /* The menus fade out with the other controls. */\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n /* When displayed as a flex container, elements inside will flow in a\n * vertical column. */\n display: flex;\n flex-direction: column;\n /* Where the menu appears. */\n position: absolute;\n z-index: 2;\n right: 15px;\n bottom: 30px;\n /* The buttons inside the menu. */\n /* These are the elements which contain the material design icons.\n * TODO: Pull MD icon details out of JS. */\n /* If the seekbar is missing, this is positioned lower.\n * TODO: Solve with flex layout instead? */\n}\n.shaka-controls-container[shown=\"true\"] .shaka-overflow-menu,\n.shaka-controls-container[shown=\"true\"] .shaka-settings-menu,\n.shaka-controls-container[casting=\"true\"] .shaka-overflow-menu,\n.shaka-controls-container[casting=\"true\"] .shaka-settings-menu {\n opacity: 1;\n}\n.shaka-overflow-menu button,\n.shaka-settings-menu button {\n font-size: 14px;\n background: transparent;\n color: black;\n border: none;\n min-height: 30px;\n padding: 3.5px 6px;\n /* The button itself is a flex container, with children center-aligned. */\n display: flex;\n align-items: center;\n /* When hovered, the button's background is highlighted. */\n /* The button is clickable, showing cursor pointer */\n cursor: pointer;\n /* The label inside button is also showing cursor pointer */\n}\n.shaka-overflow-menu button:hover,\n.shaka-settings-menu button:hover {\n background: #e0e0e0;\n}\n.shaka-overflow-menu button label,\n.shaka-settings-menu button label {\n cursor: pointer;\n}\n.shaka-keyboard-navigation .shaka-overflow-menu button:focus,\n.shaka-keyboard-navigation .shaka-settings-menu button:focus {\n background: #e0e0e0;\n}\n.shaka-overflow-menu i,\n.shaka-settings-menu i {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n padding-left: 10px;\n padding-right: 10px;\n}\n.shaka-overflow-menu.shaka-low-position,\n.shaka-settings-menu.shaka-low-position {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n bottom: 15px;\n}\n/* The span elements inside the top-level overflow menu contain single lines\n * of text, which are the button name and the current selection. For example,\n * a captions button might have \"Captions\" in one span (the button name), and\n * \"Farsi\" in another (the current selection).\n * These are displayed inside a .shaka-overflow-button-label grouping, to the\n * right of MD icons. */\n.shaka-overflow-menu span {\n text-align: left;\n}\n/* This contains span elements with single lines of text, and appears to the\n * right of MD icons. */\n.shaka-overflow-button-label {\n position: relative;\n /* This is a flex container, whose children flow vertically. */\n display: flex;\n flex-direction: column;\n}\n/* This is the specific span element which shows the current selection from some\n * submenu. For example, it would contain the currently-selected subtitle\n * language, the currently-selected resolution, etc. */\n.shaka-current-selection-span {\n /* This is dimmer than the other span, which is the name of the submenu. */\n color: rgba(0, 0, 0, 0.54);\n}\n/* The submenus have somewhat different margins inside them. */\n.shaka-settings-menu span {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n margin-left: 54px;\n}\n/* This is a button within each submenu that takes you back to the main overflow\n * menu. */\n.shaka-back-to-overflow-button {\n /* The label inside the button, which says something like \"back\". */\n /* The MD icon for the \"back\" arrow. */\n}\n.shaka-back-to-overflow-button span {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n margin-left: 0;\n}\n.shaka-back-to-overflow-button i {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n padding-right: 20px;\n}\n/* The menu item for resolutions which contains \"auto\". */\n.shaka-auto-span {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n left: 17px;\n}\n/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n/* Ad controls. */\n.shaka-controls-container[ad-active=\"true\"] {\n /* While showing an ad, pass pointer events through to the ad container. */\n pointer-events: none;\n /* Except in the bottom controls, which should still be clickable. */\n}\n.shaka-controls-container[ad-active=\"true\"] .shaka-bottom-controls {\n pointer-events: auto;\n}\n.shaka-client-side-ad-container,\n.shaka-server-side-ad-container {\n /* When setting \"position: absolute\" it uses the left,right,top,bottom\n * properties to determine the positioning. We should set all these\n * properties to ensure it is positioned properly on all platforms. */\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n /* IMA SDK adds their own ad UI into an iframe element.\n * Adjust its position to fit in with our UI, when\n * Shaka UI is enabled. */\n}\n.shaka-video-container[shaka-controls=\"true\"] .shaka-client-side-ad-container iframe,\n.shaka-video-container[shaka-controls=\"true\"] .shaka-server-side-ad-container iframe {\n /* This moves the iframe up a little bit, so it\n * doesn't operlap with our controls. */\n height: 90%;\n}\n.shaka-server-side-ad-container {\n width: 100%;\n height: 100%;\n flex-shrink: 1;\n}\n.shaka-server-side-ad-container:not([ad-active=\"true\"]) {\n pointer-events: none;\n}\n.shaka-ad-controls {\n display: flex;\n flex-direction: row;\n z-index: 1;\n /* Add some room between the ad controls and the controls\n button panel. */\n padding-bottom: 1%;\n}\n.shaka-video-container:not([shaka-controls=\"true\"]) .shaka-ad-controls {\n display: none;\n}\n.shaka-ad-controls button,\n.shaka-ad-controls div {\n color: white;\n font-size: initial;\n}\n.shaka-ad-controls div:not(.shaka-skip-ad-counter) {\n margin: 1px 6px;\n}\n.shaka-ad-counter,\n.shaka-ad-position {\n display: flex;\n justify-content: flex-end;\n flex-direction: column;\n /* Give white text a black shadow, so it's visible against a\n * white background. */\n text-shadow: 1px 1px 4px black;\n}\n.shaka-skip-ad-container {\n /* Skip button is positioned at the very right edge of the\n * video container unlike the rest of the bottom controls. */\n position: relative;\n /* This math is determining how far the button is from the right edge.\n * Ad panel's parent is centered and @bottom-controls-width wide, so\n * 100 - @bottom-controls-width = margins from both sides of the container.\n * That divided by 2 is margin on one side, so we take that, and move the\n * button from its normal position to the right by that percentage.\n */\n right: -2%;\n display: flex;\n flex-direction: row;\n margin: 0;\n}\n.shaka-skip-ad-button {\n padding: 5px 15px;\n background: rgba(0, 0, 0, 0.7);\n border: none;\n cursor: pointer;\n}\n.shaka-skip-ad-button:disabled {\n background: rgba(0, 0, 0, 0.3);\n}\n.shaka-skip-ad-counter {\n padding: 5px 5px;\n background: rgba(0, 0, 0, 0.7);\n margin: 0;\n}\n/*!\n * @license\n * The tooltip is based on https://github.com/felipefialho/css-components/\n * Local modifications have been performed.\n *\n * Copyright (c) 2017 Felipe Fialho\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n/* .shaka-tooltips-on enables the tooltips and is only added to the\n * control panel when the 'enableTooltips' option is set to true */\n.shaka-tooltips-on {\n overflow: visible;\n /* Adds an additional attribute for the status in .shaka-tooltip-status */\n /* The first tooltip of the panel is not centered on top of the button\n * but rather aligned with the left border of the control panel */\n /* The last tooltip of the panel is not centered on top of the button\n * but rather aligned with the right border of the control panel */\n}\n.shaka-tooltips-on > [class*=\"shaka-tooltip\"] {\n position: relative;\n /* The :after pseudo-element contains the tooltip */\n}\n.shaka-tooltips-on > [class*=\"shaka-tooltip\"]:hover:after,\n.shaka-tooltips-on > [class*=\"shaka-tooltip\"]:focus-visible:after,\n.shaka-tooltips-on > [class*=\"shaka-tooltip\"]:active:after {\n content: attr(aria-label);\n /* Override .material-icons-round text styling */\n font-family: Roboto-Regular, Roboto, sans-serif;\n line-height: 16px;\n white-space: nowrap;\n font-size: 13px;\n /* Styling */\n background: rgba(35, 35, 35, 0.9);\n color: white;\n border-radius: 3px;\n padding: 5px 10px;\n /* Positioning */\n position: absolute;\n bottom: 37px;\n /* Left attribute is set to half of the width of the parent button */\n left: 16px;\n /* The tooltip is also translated 50% to appear centered */\n -webkit-transform: translateX(-50%);\n -moz-transform: translateX(-50%);\n -ms-transform: translateX(-50%);\n -o-transform: translateX(-50%);\n transform: translateX(-50%);\n}\n.shaka-tooltips-on > .shaka-tooltip-status:hover:after,\n.shaka-tooltips-on > .shaka-tooltip-status:focus-visible:after,\n.shaka-tooltips-on > .shaka-tooltip-status:active:after {\n content: attr(aria-label) \" (\" attr(shaka-status) \")\";\n}\n.shaka-tooltips-on button:first-child:hover:after,\n.shaka-tooltips-on button:first-child:focus-visible:after,\n.shaka-tooltips-on button:first-child:active:after {\n left: 0;\n -webkit-transform: translateX(0%);\n -moz-transform: translateX(0%);\n -ms-transform: translateX(0%);\n -o-transform: translateX(0%);\n transform: translateX(0%);\n}\n.shaka-tooltips-on button:last-child:hover:after,\n.shaka-tooltips-on button:last-child:focus-visible:after,\n.shaka-tooltips-on button:last-child:active:after {\n left: 32px;\n -webkit-transform: translateX(-100%);\n -moz-transform: translateX(-100%);\n -ms-transform: translateX(-100%);\n -o-transform: translateX(-100%);\n transform: translateX(-100%);\n}\n@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-weight: 400;\n src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxP.ttf) format('truetype');\n}\n\n@font-face {\n font-family: 'Material Icons Round';\n font-style: normal;\n font-weight: 400;\n src: url(https://fonts.gstatic.com/s/materialiconsround/v106/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmM.otf) format('opentype');\n}\n\n.material-icons-round {\n font-family: 'Material Icons Round';\n font-weight: normal;\n font-style: normal;\n font-size: 24px;\n line-height: 1;\n letter-spacing: normal;\n text-transform: none;\n display: inline-block;\n white-space: nowrap;\n word-wrap: normal;\n direction: ltr;\n}\n\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* General utility mixins and classes with broad applicability. */\n\n/* Make a thing unselectable. There are currently no cases where we make it\n * selectable again. */\n.unselectable() {\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n}\n\n.hidden() {\n display: none;\n}\n\n.shaka-hidden {\n /* Make this override equally specific classes.\n * If it's hidden, always hide it! */\n display: none !important;\n}\n\n.fill-container() {\n width: 100%;\n height: 100%;\n}\n\n.bottom-align-children() {\n display: flex;\n justify-content: flex-end;\n flex-direction: column;\n}\n\n.bottom-panels-elements-margin() {\n margin: 1px 6px;\n}\n\n/* For containers which host elements overlaying other things. */\n.overlay-parent() {\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for some children of this container to overlay the\n * others using .overlay-child(). */\n position: relative;\n\n /* Make sure any top or left styles applied from outside don't move this from\n * it's original position, now that it's relative to that original position.\n * This is a defensive move that came out of intensive debugging on IE 11. */\n top: 0;\n left: 0;\n}\n\n/* For things which overlay other things. */\n.overlay-child() {\n /* For a detailed explanation of how this achieves an overlay, please refer\n * to https://developer.mozilla.org/en-US/docs/Web/CSS/position .\n *\n * But you don't have to, because we've encapsulated these high level\n * concepts into classes.\n *\n * This makes it possible for this child to overlay the other children of a\n * .overlay-parent() object. */\n position: absolute;\n\n /* Fill the container by default. */\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n margin: 0;\n padding: 0;\n\n .fill-container();\n}\n\n.absolute-position() {\n /* When setting \"position: absolute\" it uses the left,right,top,bottom\n * properties to determine the positioning. We should set all these\n * properties to ensure it is positioned properly on all platforms. */\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n}\n\n/* For things that should not shrink inside a flex container.\n * This will be used for all controls by default. */\n.unshrinkable() {\n flex-shrink: 0;\n}\n\n/* Use this to override .unshrinkable() in particular cases that *should* shrink\n * inside a flex container. */\n.shrinkable() {\n flex-shrink: 1;\n}\n\n.show-when-controls-shown() {\n /* Transparent unless explicitly made opaque through container attributes. */\n opacity: 0;\n\n /* When we show/hide this, do it gradually using cubic-bezier timing. */\n transition: opacity cubic-bezier(0.4, 0, 0.6, 1) 600ms;\n\n /* Show controls when the container's \"shown\" or \"casting\" attributes are\n * set. */\n .shaka-controls-container[shown=\"true\"] &,\n .shaka-controls-container[casting=\"true\"] & {\n opacity: 1;\n }\n}\n\n.hide-when-shaka-controls-disabled() {\n .shaka-video-container:not([shaka-controls=\"true\"]) & {\n .hidden();\n }\n}\n\n/* The width of the bottom-section controls: seek bar, ad controls, and\nthe control buttons panel. */\n@bottom-controls-width: 96%;\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* All of the top-level containers into which various visible features go. */\n\n@transparent: rgba(0, 0, 0, 0);\n\n/* A container for the entire video + controls combo. This is the auto-setup\n * div which we populate. */\n.shaka-video-container {\n .overlay-parent();\n\n /* Without this, the container somehow winds up being a tad taller than it\n * should be (484px vs 480px). */\n display: flex;\n\n /* Set a special font for material design icons. */\n .material-icons-round {\n font-family: 'Material Icons Round';\n font-size: 24px;\n }\n\n /* Set the fonts for all other content. */\n * {\n font-family: Roboto-Regular, Roboto, sans-serif;\n }\n}\n\n/* Each browser has a different prefixed pseudo-class for fullscreened elements.\n * Define the properties of a fullscreened element in a mixin, then apply to\n * each of the browser-specific pseudo-classes.\n * NOTE: These fullscreen pseudo-classes can't be combined with commas into a\n * single delcaration. Browsers ignore the rest of the list once they hit one\n * pseudo-class they don't support. */\n.fullscreen-container() {\n .fill-container();\n\n background-color: black;\n\n .shaka-text-container {\n /* In fullscreen mode, the text displayer's font size should be relative to\n * the either window height or width (whichever is smaller), instead of a\n * fixed size. */\n font-size: 4.4vmin;\n }\n}\n.shaka-video-container:fullscreen { .fullscreen-container(); }\n.shaka-video-container:-webkit-full-screen { .fullscreen-container(); }\n.shaka-video-container:-moz-full-screen { .fullscreen-container(); }\n.shaka-video-container:-ms-fullscreen { .fullscreen-container(); }\n\n/* The actual video element. Sits inside .shaka-video-container and gives it a\n * size in non-fullscreen mode. In fullscreen mode, the sizing relationship\n * flips. CSS is just great like that. :-( */\n.shaka-video {\n /* At the moment, nothing special is required here.\n * Note that this should NOT be an overlay-child, as its size could dictate\n * the size of the container for some applications. */\n}\n\n/* A container for all controls, including the giant play button, seek bar, etc.\n * Sits inside .shaka-video-container, on top of (Z axis) .shaka-video, and\n * below (Y axis) .shaka-play-button-container. */\n.shaka-controls-container {\n .overlay-child();\n\n .hide-when-shaka-controls-disabled();\n\n /* Without this, the controls container overflows the video container. */\n box-sizing: border-box;\n\n /* A flex container, to make layout of children easier to reason about. */\n display: flex;\n\n /* Defines in which direction the children should flow. */\n flex-direction: column;\n\n /* Pushes the children toward the bottom of the container. */\n justify-content: flex-end;\n\n /* Centers children horizontally. */\n align-items: center;\n\n /* By default, do not allow any of our controls to shrink.\n * Specific controls can use .shrinkable() to override. */\n * { .unshrinkable(); }\n\n /* Position the controls container in front of the text container, so that\n * the text container doesn't interfere with the control buttons. */\n z-index: 1;\n\n &[casting=\"true\"] {\n /* Hide fullscreen button while casting. */\n .shaka-fullscreen-button {\n .hidden();\n }\n }\n}\n\n/* Container for controls positioned at the bottom of the video container:\n * controls button panel and the seek bar. */\n.shaka-bottom-controls {\n width: @bottom-controls-width;\n padding: 0;\n padding-bottom: 2.5%;\n\n /* Position the bottom panel in front of other controls (play button and\n * spinner containers).\n * TODO: A different layout arrangement might be a better solution for this.\n * Need to experiment.\n */\n z-index: 1;\n}\n\n/* This is the container for the horizontal row of controls above the seek bar.\n * It sits above (Y axis) the seek bar, and below (Y axis) the giant play button\n * in the middle. */\n.shaka-controls-button-panel {\n /* Fill the space horizontally, with no extra padding or margin. */\n padding: 0;\n margin: 0;\n\n /* This is itself a flex container, with children layed out horizontally. */\n display: flex;\n flex-direction: row;\n\n /* Push children to the right. */\n justify-content: flex-end;\n\n /* Center children vertically. */\n align-items: center;\n\n /* TODO: Document why. */\n overflow: hidden;\n min-width: 48px;\n\n /* Make sure we don't inherit odd font sizes and styles from the environment.\n * TODO: When did this happen? What forced us to do this? */\n font-size: 12px;\n font-weight: normal;\n font-style: normal;\n\n /* Make sure contents cannot be selected. */\n .unselectable();\n\n .show-when-controls-shown();\n\n /* All buttons, divs, and other controls directly inside this panel should\n * have these characteristics by default. */\n & > * {\n /* White text or button icons. */\n color: white;\n\n /* 32px tall controls. */\n height: 32px;\n\n /* Consistent alignment of buttons. */\n line-height: 0.5;\n\n /* Consistent margins (external) and padding (internal) between controls. */\n .bottom-panels-elements-margin();\n\n padding: 0;\n\n /* Transparent backgrounds, no borders, and a pointer when you mouse over\n * them. */\n background: transparent;\n border: 0;\n cursor: pointer;\n }\n}\n\n/* Buttons hide certain items if they are found inside the control panel */\n.shaka-controls-button-panel .shaka-overflow-menu-only {\n display: none;\n}\n\n/* The container for the giant play button. Sits above (Y axis) the\n * other video controls and seek bar, in the middle of the video frame, on top\n * of (Z axis) the video. */\n.shaka-play-button-container {\n /* Take up as much space as possible, but shrink (vertically) to accomodate\n * the controls at the bottom. */\n margin: 0;\n .fill-container();\n .shrinkable();\n .absolute-position();\n\n /* Keep the play button in the middle of this container. */\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.shaka-statistics-container {\n overflow-x: hidden;\n overflow-y: auto;\n\n min-width: 300px;\n\n color: white;\n background-color: rgba(35, 35, 35, 0.9);\n\n font-size: 14px;\n\n padding: 5px 10px;\n border-radius: 2px;\n\n position: absolute;\n z-index: 2;\n left: 15px;\n top: 15px;\n\n /* Fades out with the other controls. */\n .show-when-controls-shown();\n\n div {\n display: flex;\n justify-content: space-between;\n }\n\n span {\n color: rgb(150, 150, 150);\n }\n}\n\n.shaka-context-menu {\n background-color: rgba(35, 35, 35, 0.9);\n\n border-radius: 2px;\n\n position: absolute;\n z-index: 3;\n\n button {\n padding: 5px 10px;\n\n width: 100%;\n display: flex;\n align-items: center;\n\n color: white;\n background: transparent;\n border: 0;\n cursor: pointer;\n\n &:hover {\n background-color: rgba(50, 50, 50, 0.9);\n }\n }\n\n label {\n padding: 0 20px;\n\n align-items: flex-start;\n\n color: white;\n cursor: pointer;\n }\n\n .shaka-current-selection-span {\n align-items: flex-start;\n\n color: white;\n cursor: pointer;\n }\n}\n\n.shaka-scrim-container {\n margin: 0;\n .fill-container();\n .shrinkable();\n .absolute-position();\n .show-when-controls-shown();\n\n /* A black gradient at the bottom, behind the controls, but only so high. */\n background: linear-gradient(to top, rgba(0, 0, 0, 1) 0, @transparent 15%);\n}\n\n.shaka-text-container {\n .absolute-position();\n\n /* Make sure the text container doesn't steal pointer events from another\n * layer, such as the ad container. There is nothing interactive in this\n * layer, so this should be fine. */\n pointer-events: none;\n\n /* Place the text container on the bottom of the video container. */\n bottom: 0%;\n width: 100%;\n min-width: 48px;\n\n /* When the controls fade in or out, it takes 600ms. Thus, when the text\n * container adjusts to the presence or absence of controls, we should wait\n * briefly, so the captions don't end up appearing behind the controls.\n * Instead of being a gradual animation, this is a fast animation with a\n * significant delay, since the captions moving around is a little\n * distracting. */\n transition: bottom cubic-bezier(0.4, 0, 0.6, 1) 100ms;\n transition-delay: 500ms;\n\n /* These are defaults which are overridden by JS or cue styles. */\n font-size: 20px;\n line-height: 1.4; // relative to font size.\n color: rgb(255, 255, 255);\n\n span.shaka-text-wrapper {\n display: inline;\n background: none;\n }\n}\n\n.shaka-controls-container[shown=\"true\"] ~ .shaka-text-container {\n /* While the controls are shown, the text container should avoid the 15%\n * at the bottom of the video, to avoid overlapping with controls. */\n bottom: 15%;\n\n /* Disable the transition delay when moving the captions up, so that the\n * controls don't appear over the captions. */\n transition-delay: 0ms;\n}\n\n/* The buffering spinner. */\n.shaka-spinner-container {\n .absolute-position();\n .fill-container();\n .hide-when-shaka-controls-disabled();\n\n flex-shrink: 1;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n@spinner-size-percentage: 15.6%;\n\n.shaka-spinner {\n /* This uses the same trickery as the big play button define\n the spinner's width and height. See .shaka-play-button\n for the detailed explanation. */\n\n /* For the padding thing to work, spinner div needs to be an\n overlay-parent and spinner svg - an overlay child. */\n .overlay-parent();\n\n margin: 0;\n box-sizing: border-box;\n padding: @spinner-size-percentage / 2;\n width: 0;\n height: 0;\n\n /* Add a bit of a white shadow to keep our black spinner visible\n on a black background. */\n filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.5));\n}\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* The main buttons in the UI controls. */\n\n@play-button-size-percentage: 15%;\n\n.disabled-button() {\n /* Set the background and the color, otherwise it might be overwritten by\n * the css styles in demo. */\n background-color: transparent;\n color: white;\n cursor: default;\n}\n\n/* The giant play button, which sits inside .shaka-player-button-container. */\n.shaka-play-button {\n /* Set width & height in a round-about way. By using padding, we can keep\n * a 1:1 aspect ratio and size the button relative to the container width.\n *\n * Since padding is applied equally to top, bottom, left, and right, only use\n * half of the intended percentage for each.\n *\n * Based on tips from https://stackoverflow.com/a/12925343 */\n box-sizing: border-box;\n padding: @play-button-size-percentage / 2;\n width: 0;\n height: 0;\n\n /* To be properly positioned in the center, this should have no margin.\n * This might have been set for buttons generally by the app or user-agent. */\n margin: 0;\n\n /* This makes the button a circle. */\n border-radius: 50%;\n\n /* A small drop shadow below the button. */\n box-shadow: rgba(0, 0, 0, 0.1) 0 0 20px 0;\n\n /* No border. */\n border: none;\n\n /* The play arrow is a picture. It is treated a background image.\n * The following settings ensure it shows only once and in the\n * center of the button. */\n background-size: 50%;\n background-repeat: no-repeat;\n background-position: center center;\n\n /* A background color behind the play arrow. */\n background-color: rgba(255, 255, 255, 0.9);\n\n .show-when-controls-shown();\n\n /* Actual icon images for the two states this could be in.\n * These will be inlined as data URIs when compiled, and so do not need to be\n * deployed separately from the compiled CSS.\n * Note that these URIs should relative to ui/controls.less, not this file. */\n &[icon=\"play\"] {\n background-image: data-uri('images/play_arrow.svg');\n }\n\n &[icon=\"pause\"] {\n background-image: data-uri('images/pause.svg');\n }\n}\n\n/* This button contains the current time and duration of the video.\n * It's only clickable when the content is live, and current time is behind live\n * edge. Otherwise, the button is disabled.\n */\n.shaka-current-time {\n font-size: 14px;\n color: rgb(255, 255, 255);\n cursor: pointer;\n\n &[disabled] {\n .disabled-button();\n }\n}\n\n/* Use a consistent outline focus style across browsers. */\n.shaka-controls-container {\n button:focus, input:focus {\n /* Most browsers will fall back to \"Highlight\" (system setting) color for\n * the focus outline. */\n outline: 1px solid Highlight;\n }\n\n /* Disable this Mozilla-specific focus ring, since we have an outline defined\n * for focus. */\n button:-moz-focus-inner, input:-moz-focus-outer {\n outline: none;\n border: 0;\n }\n}\n\n/* Outline on focus is important for accessibility, but\n * it doesn't look great. This removes the outline for\n * mouse users while leaving it for keyboard users. */\n.shaka-controls-container:not(.shaka-keyboard-navigation) {\n button:focus, input:focus {\n outline: none;\n }\n}\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* Special styles for input elements with type \"range\".\n *\n * These elements are composed of two main parts: a \"track\", which is the\n * horizontal bar, and the \"thumb\", which is the knob which slides along that\n * bar.\n *\n * In order to style the track across browsers (cough, IE 11), we need to do\n * something a bit tricky. Styling the track is a nightmare, especially if you\n * want the thumb to be larger. On IE 11, this gets clipped at the track size.\n * So a tiny track with a large thumb is not easily achieved. It can be done,\n * but the techniques for it are incompatible with the gradient background we\n * want to apply to it.\n *\n * The solution is to put the input inside a div container, and apply the\n * background gradient styles to the container. The container will act as a\n * visible, virtual track, inside which is contained a larger, invisible track,\n * in which is contained a visible thumb. This way, the thumb is not larger\n * than the actual track (for IE 11's sake), but can be larger than the virtual\n * track. And since we are still using a semantically correct input element,\n * the element is inherently accessible. */\n\n/* These control the color and size of the various pieces. */\n@thumb-color: white;\n@track-default-color: white;\n@thumb-size: 12px;\n@track-height: 4px;\n\n/* The range container is the div that contains a range element.\n * This div will act as a virtual track to allow us to style the track space.\n * An actual track still exists inside the range element, but is transparent. */\n.range-container() {\n /* This contains an input element which overlays it. */\n .overlay-parent();\n\n /* Vertical margins to occupy the same space as the thumb. */\n margin: (@thumb-size - @track-height)/2 6px;\n\n /* Smaller height to contain the background for the virtual track. */\n height: @track-height;\n\n /* Rounded ends on the virtual track. */\n border-radius: @track-height;\n\n /* Until we set a gradient background in JS, this will be the track color. */\n background: @track-default-color;\n}\n\n/* The \"track\" is the pseudo-element inside the range element which represents\n * the horizontal bar on which the \"thumb\" (knob) moves. */\n.track() {\n /* The track should fill the range element. */\n width: 100%;\n\n /* The track should be tall enough to contain the thumb without clipping it.\n * It is very tricky to make the thumb show outside the track on IE 11, and\n * it is incompatible with our background gradients. */\n height: @thumb-size;\n\n /* Some browsers have default backgrounds, colors, or borders for this.\n * Hide them all. */\n background: transparent;\n color: transparent;\n border: none;\n}\n\n/* The \"thumb\" is the pseudo-element inside the range element which represents\n * the knob which moves along the \"track\" (bar). */\n.thumb() {\n /* Remove default styles on WebKit-based and Blink-based browsers. */\n -webkit-appearance: none;\n\n /* On some browsers (IE 11), the thumb has a border, which affects the size.\n * Disable it. */\n border: none;\n\n /* Make the thumb a circle and set its diameter. */\n border-radius: @thumb-size;\n height: @thumb-size;\n width: @thumb-size;\n\n /* Give it the desired color. */\n background: @thumb-color;\n}\n\n/* This is the actual range input element. */\n.range-element() {\n /* Remove any browser styling of the range element. */\n -webkit-appearance: none;\n background: transparent;\n\n /* Overlay and fill the container div. */\n .overlay-child();\n\n /* The range element should be big enough to contain the thumb without\n * clipping it. It is very tricky to make the thumb show outside the track\n * on IE 11. */\n height: @thumb-size;\n\n /* Position the top of the range element so that it is centered on the\n * container. Note that the container is actually smaller than the thumb. */\n top: (@track-height - @thumb-size) / 2;\n\n /* Make sure clicking at the very top of the bar still takes effect and is not\n * confused with clicking the video to play/pause it. */\n z-index: 1;\n\n /* Pseudo-elements for Blink-based or WebKit-based browsers. */\n &::-webkit-slider-runnable-track {\n .track();\n }\n\n &::-webkit-slider-thumb {\n .thumb();\n }\n\n /* Pseudo-elements for Gecko-based browsers. */\n &::-moz-range-track {\n .track();\n }\n\n &::-moz-range-thumb {\n .thumb();\n }\n}\n\n.shaka-range-container {\n .range-container();\n}\n\n.shaka-volume-bar-container {\n width: 100px;\n}\n\n.shaka-range-element {\n .range-element();\n}\n\n.shaka-seek-bar-container {\n .show-when-controls-shown();\n}\n\n.shaka-ad-markers {\n .overlay-child();\n}\n","/*!\n * @license\n * The SVG/CSS buffering spinner is based on http://codepen.io/jczimm/pen/vEBpoL\n * Some local modifications have been made.\n *\n * Copyright (c) 2016 by jczimm\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/* This is the spinner SVG itself, which contains a circular path element.\n * It sits inside the play button and fills it. */\n.shaka-spinner-svg {\n /* Because of some sizing hacks in the play button (see comments there), this\n * spinner needs to be an overlay child to be properly sized and positioned\n * within the button. */\n .overlay-child();\n\n /* Keep it spinning! */\n animation: rotate 2s linear infinite;\n transform-origin: center center;\n\n /* The SVG should fill its container. */\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n}\n\n/* This is the path element, which draws a circle. */\n.shaka-spinner-path {\n stroke: #202124;\n stroke-dasharray: 20, 200;\n stroke-dashoffset: 0;\n\n /* Animate the stroke of this circular path. */\n animation: dash 1s ease-in-out infinite;\n\n /* Round the line on the ends. */\n stroke-linecap: round;\n}\n\n/* Spin the whole SVG. */\n@keyframes rotate {\n 100% {\n transform: rotate(360deg);\n }\n}\n\n/* Pulse the circle's outline forward and backward while it spins. */\n@keyframes dash {\n 0% {\n stroke-dasharray: 1, 200;\n stroke-dashoffset: 0;\n }\n\n 50% {\n stroke-dasharray: 89, 200;\n stroke-dashoffset: -35px;\n }\n\n 100% {\n stroke-dasharray: 89, 200;\n stroke-dashoffset: -124px;\n }\n}\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* UI elements that did not fit into the buttons/range elements category. */\n\n/* This is a spacer element used to separate elements within the control\n * buttons panel. It's just an empty div of certain width. */\n.shaka-spacer {\n /* This should not have a pointer-style cursor like the other controls. */\n cursor: default;\n\n /* Make the element shrink to accommodate things to the right. */\n .shrinkable();\n\n /* Make the element grow to take up the remaining space. */\n flex-grow: 1;\n\n /* Margins don't shrink. Remove margins in order to be more flexible when\n * shrinking. */\n margin: 0;\n}\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* The overflow menu and all settings submenus. These appear on top of all\n * other controls (Z axis) when the overflow button is clicked. */\n.shaka-overflow-menu,\n.shaka-settings-menu {\n /* It's okay to add a vertical scroll if there are too many items, but\n * horizontal scrolling is not allowed. */\n overflow-x: hidden;\n overflow-y: auto;\n\n /* Don't wrap text to the next line. */\n white-space: nowrap;\n\n /* Styles for the menu itself. */\n background: white;\n box-shadow: 0 1px 9px 0 rgba(0, 0, 0, 0.4);\n border-radius: 2px;\n max-height: 250px;\n min-width: 180px;\n\n /* The menus fade out with the other controls. */\n .show-when-controls-shown();\n\n /* When displayed as a flex container, elements inside will flow in a\n * vertical column. */\n display: flex;\n flex-direction: column;\n\n /* Where the menu appears. */\n position: absolute;\n z-index: 2;\n right: 15px;\n bottom: 30px;\n\n /* The buttons inside the menu. */\n button {\n font-size: 14px;\n background: transparent;\n color: black;\n border: none;\n min-height: 30px;\n padding: 3.5px 6px;\n\n /* The button itself is a flex container, with children center-aligned. */\n display: flex;\n align-items: center;\n\n /* When hovered, the button's background is highlighted. */\n &:hover {\n background: rgb(224, 224, 224);\n }\n\n /* The button is clickable, showing cursor pointer */\n cursor: pointer;\n\n /* The label inside button is also showing cursor pointer */\n label {\n cursor: pointer;\n }\n\n .shaka-keyboard-navigation &:focus {\n background: rgb(224, 224, 224);\n }\n }\n\n /* These are the elements which contain the material design icons.\n * TODO: Pull MD icon details out of JS. */\n i {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n padding-left: 10px;\n padding-right: 10px;\n }\n\n /* If the seekbar is missing, this is positioned lower.\n * TODO: Solve with flex layout instead? */\n &.shaka-low-position {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n bottom: 15px;\n }\n}\n\n/* The span elements inside the top-level overflow menu contain single lines\n * of text, which are the button name and the current selection. For example,\n * a captions button might have \"Captions\" in one span (the button name), and\n * \"Farsi\" in another (the current selection).\n * These are displayed inside a .shaka-overflow-button-label grouping, to the\n * right of MD icons. */\n.shaka-overflow-menu span {\n text-align: left;\n}\n\n/* This contains span elements with single lines of text, and appears to the\n * right of MD icons. */\n.shaka-overflow-button-label {\n position: relative;\n\n /* This is a flex container, whose children flow vertically. */\n display: flex;\n flex-direction: column;\n}\n\n/* This is the specific span element which shows the current selection from some\n * submenu. For example, it would contain the currently-selected subtitle\n * language, the currently-selected resolution, etc. */\n.shaka-current-selection-span {\n /* This is dimmer than the other span, which is the name of the submenu. */\n color: rgba(0, 0, 0, 0.54);\n}\n\n/* The submenus have somewhat different margins inside them. */\n.shaka-settings-menu {\n span {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n margin-left: 54px;\n }\n}\n\n/* This is a button within each submenu that takes you back to the main overflow\n * menu. */\n.shaka-back-to-overflow-button {\n /* The label inside the button, which says something like \"back\". */\n span {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n margin-left: 0;\n }\n\n /* The MD icon for the \"back\" arrow. */\n i {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n padding-right: 20px;\n }\n}\n\n/* The menu item for resolutions which contains \"auto\". */\n.shaka-auto-span {\n /* TODO(b/116651454): eliminate hard-coded offsets */\n left: 17px;\n}\n","/** @license\n * Shaka Player\n * Copyright 2016 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/* Ad controls. */\n.ad-text-shadow() {\n /* Give white text a black shadow, so it's visible against a\n * white background. */\n text-shadow: 1px 1px 4px black;\n}\n\n.shaka-controls-container[ad-active=\"true\"] {\n /* While showing an ad, pass pointer events through to the ad container. */\n pointer-events: none;\n\n /* Except in the bottom controls, which should still be clickable. */\n .shaka-bottom-controls {\n pointer-events: auto;\n }\n}\n\n.shaka-client-side-ad-container, .shaka-server-side-ad-container {\n .absolute-position();\n\n /* IMA SDK adds their own ad UI into an iframe element.\n * Adjust its position to fit in with our UI, when\n * Shaka UI is enabled. */\n iframe {\n .shaka-video-container[shaka-controls=\"true\"] & {\n /* This moves the iframe up a little bit, so it\n * doesn't operlap with our controls. */\n height: 90%;\n }\n }\n}\n\n.shaka-server-side-ad-container {\n .fill-container();\n .shrinkable();\n\n &:not([ad-active=\"true\"]) {\n pointer-events: none;\n }\n}\n\n.shaka-ad-controls {\n .hide-when-shaka-controls-disabled();\n\n display: flex;\n flex-direction: row;\n z-index: 1;\n\n /* Add some room between the ad controls and the controls\n button panel. */\n padding-bottom: 1%;\n\n button, div {\n color: white;\n font-size: initial;\n }\n\n div:not(.shaka-skip-ad-counter) {\n .bottom-panels-elements-margin();\n }\n}\n\n.shaka-ad-counter, .shaka-ad-position {\n .bottom-align-children();\n .ad-text-shadow();\n}\n\n.shaka-skip-ad-container {\n /* Skip button is positioned at the very right edge of the\n * video container unlike the rest of the bottom controls. */\n position: relative;\n\n /* This math is determining how far the button is from the right edge.\n * Ad panel's parent is centered and @bottom-controls-width wide, so\n * 100 - @bottom-controls-width = margins from both sides of the container.\n * That divided by 2 is margin on one side, so we take that, and move the\n * button from its normal position to the right by that percentage.\n */\n right: (100 - @bottom-controls-width) / 2 * -1;\n display: flex;\n flex-direction: row;\n margin: 0;\n}\n\n.shaka-skip-ad-button {\n padding: 5px 15px;\n background: rgba(0, 0, 0, 0.7);\n border: none;\n\n &:disabled {\n background: rgba(0, 0, 0, 0.3);\n }\n\n cursor: pointer;\n}\n\n.shaka-skip-ad-counter {\n padding: 5px 5px;\n background: rgba(0, 0, 0, 0.7);\n margin: 0;\n}\n","/*!\n * @license\n * The tooltip is based on https://github.com/felipefialho/css-components/\n * Local modifications have been performed.\n *\n * Copyright (c) 2017 Felipe Fialho\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n@material-icons-width: 32px;\n\n.translateX(@percent) {\n -webkit-transform: translateX(percentage(@percent));\n -moz-transform: translateX(percentage(@percent));\n -ms-transform: translateX(percentage(@percent));\n -o-transform: translateX(percentage(@percent));\n transform: translateX(percentage(@percent));\n}\n\n/* .shaka-tooltips-on enables the tooltips and is only added to the\n * control panel when the 'enableTooltips' option is set to true */\n.shaka-tooltips-on {\n overflow: visible;\n\n & > [class*=\"shaka-tooltip\"] {\n position: relative;\n\n /* The :after pseudo-element contains the tooltip */\n &:hover:after, &:focus-visible:after, &:active:after {\n content: attr(aria-label);\n\n /* Override .material-icons-round text styling */\n font-family: Roboto-Regular, Roboto, sans-serif;\n line-height: @material-icons-width / 2;\n white-space: nowrap;\n font-size: 13px;\n\n /* Styling */\n background: rgba(35, 35, 35, 0.9);\n color: white;\n border-radius: 3px;\n padding: 5px 10px;\n\n /* Positioning */\n position: absolute;\n bottom: @material-icons-width + 5px;\n\n /* Left attribute is set to half of the width of the parent button */\n left: @material-icons-width / 2;\n\n /* The tooltip is also translated 50% to appear centered */\n .translateX(-0.5);\n }\n }\n\n /* Adds an additional attribute for the status in .shaka-tooltip-status */\n & > .shaka-tooltip-status {\n &:hover:after, &:focus-visible:after, &:active:after {\n content: attr(aria-label) \" (\" attr(shaka-status) \")\";\n }\n }\n\n /* The first tooltip of the panel is not centered on top of the button\n * but rather aligned with the left border of the control panel */\n button:first-child {\n &:hover:after, &:focus-visible:after, &:active:after {\n left: 0;\n .translateX(0);\n }\n }\n\n /* The last tooltip of the panel is not centered on top of the button\n * but rather aligned with the right border of the control panel */\n button:last-child {\n &:hover:after, &:focus-visible:after,&:active:after {\n left: @material-icons-width;\n .translateX(-1);\n }\n }\n}\n","@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-weight: 400;\n src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxP.ttf) format('truetype');\n}\n","@font-face {\n font-family: 'Material Icons Round';\n font-style: normal;\n font-weight: 400;\n src: url(https://fonts.gstatic.com/s/materialiconsround/v106/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmM.otf) format('opentype');\n}\n\n.material-icons-round {\n font-family: 'Material Icons Round';\n font-weight: normal;\n font-style: normal;\n font-size: 24px;\n line-height: 1;\n letter-spacing: normal;\n text-transform: none;\n display: inline-block;\n white-space: nowrap;\n word-wrap: normal;\n direction: ltr;\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/Resources/Public/JavaScript/PageView/Navigation.js b/Resources/Public/JavaScript/PageView/Navigation.js new file mode 100644 index 000000000..8c6718eeb --- /dev/null +++ b/Resources/Public/JavaScript/PageView/Navigation.js @@ -0,0 +1,27 @@ +/** + * (c) Kitodo. Key to digital objects e.V. + * + * This file is part of the Kitodo and TYPO3 projects. + * + * @license GNU General Public License version 3 or later. + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + */ + +document.addEventListener('DOMContentLoaded', function () { + document.querySelector('.page-first') + .addEventListener('click', function (e) { + e.preventDefault(); + document.body.dispatchEvent( + new CustomEvent( + 'tx-dlf-pageChanged', + { + 'detail': { + 'page': 1, + 'target': e.target + } + } + ) + ); + }); +}); From 60c8230363406ac36666272370c3918756eb87c8 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Mon, 22 Aug 2022 21:20:55 +0200 Subject: [PATCH 05/76] Make loaded document a global variable This will allow the navigation plugin to access the document, e.g., to get the number of pages. --- Classes/Controller/PageViewController.php | 6 ++++-- Resources/Public/JavaScript/PageView/PageView.js | 16 +++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Classes/Controller/PageViewController.php b/Classes/Controller/PageViewController.php index c5ace713a..6abd30482 100644 --- a/Classes/Controller/PageViewController.php +++ b/Classes/Controller/PageViewController.php @@ -503,7 +503,6 @@ protected function addViewerJS(): void 'div' => "tx-dfgviewer-map-' . $i . '", 'progressElementId' => $this->settings['progressElementId'], 'counter' => $i, - 'document' => $this->document->getCurrentDocument()->toArray($this->uriBuilder, $config), 'images' => $docImage, 'fulltexts' => $docFulltext, 'score' => $docScore, @@ -522,6 +521,8 @@ protected function addViewerJS(): void // Viewer configuration. $viewerConfiguration = '$(document).ready(function() { + tx_dlf_loaded_document = ' . json_encode($this->document->getCurrentDocument()->toArray($this->uriBuilder, $config)) . '; + if (dlfUtils.exists(dlfViewer)) { ' . $jsViewer . ' viewerCount = ' . ($i - 1) . '; @@ -540,7 +541,6 @@ protected function addViewerJS(): void 'controls' => $this->controls, 'div' => $this->settings['elementId'], 'progressElementId' => $this->settings['progressElementId'], - 'document' => $this->document->getCurrentDocument()->toArray($this->uriBuilder, $config), 'images' => $this->images, 'fulltexts' => $this->fulltexts, 'score' => $this->scores, @@ -554,6 +554,8 @@ protected function addViewerJS(): void // Viewer configuration. $viewerConfiguration = '$(document).ready(function() { + tx_dlf_loaded_document = ' . json_encode($this->document->getCurrentDocument()->toArray($this->uriBuilder, $config)) . '; + if (dlfUtils.exists(dlfViewer)) { tx_dlf_viewer = new dlfViewer(' . json_encode($viewer) . '); } diff --git a/Resources/Public/JavaScript/PageView/PageView.js b/Resources/Public/JavaScript/PageView/PageView.js index 94088a8b7..083411d67 100644 --- a/Resources/Public/JavaScript/PageView/PageView.js +++ b/Resources/Public/JavaScript/PageView/PageView.js @@ -36,13 +36,11 @@ * fulltexts?: FulltextDesc[] | []; * scores?: ScoreDesc[] | []; * controls?: ('OverviewMap' | 'ZoomPanel')[]; -<<<<<<< HEAD * measureCoords?: MeasureDesc[] | []; * measureIdLinks?: MeasureDesc[] | []; -======= - * document?: any; ->>>>>>> 64fb3677 (Add Basic JSON Representation for Document and First Navigation Button) * }} DlfViewerConfig + * + * @typedef {any} DlfDocument */ /** @@ -222,7 +220,11 @@ var dlfViewer = function (settings) { */ this.ovView = null; - this.document = dlfUtils.exists(settings.document) ? settings.document : null; + /** + * @type {DlfDocument | null} + * @private + */ + this.document = tx_dlf_loaded_document || null; /** * @type {Boolean|false} @@ -925,6 +927,10 @@ dlfViewer.prototype.init = function(controlNames) { dlfViewer.prototype.registerEvents = function() { $(document.body).on('tx-dlf-pageChanged', e => { + if (this.document === undefined) { + return; + } + const page = e.originalEvent.detail.page; const entry = this.document[page - 1]; const url = entry.url; From 949f4dd193d6152d0698fef9955f91bbbb7a03ac Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Mon, 22 Aug 2022 21:59:30 +0200 Subject: [PATCH 06/76] Fix naming pageBack vs. pageStepBack --- Configuration/FlexForms/Navigation.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/FlexForms/Navigation.xml b/Configuration/FlexForms/Navigation.xml index 3f98c73c0..33d3cb147 100644 --- a/Configuration/FlexForms/Navigation.xml +++ b/Configuration/FlexForms/Navigation.xml @@ -81,7 +81,7 @@ measureBack - doublePage,pageFirst,pageBack,pageStepBack,pageSelect,pageForward,pageStepForward,pageLast,listView,zoom,rotation,measureForward,measureBack + doublePage,pageFirst,pageStepBack,pageBack,pageSelect,pageForward,pageStepForward,pageLast,listView,zoom,rotation,measureForward,measureBack 1 From a68f39d970f38f6993c6fe2cb9a43659c6043d66 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Mon, 22 Aug 2022 22:12:22 +0200 Subject: [PATCH 07/76] Handle all forward/back navigation buttons --- Classes/Controller/PageViewController.php | 18 ++- .../Private/Templates/Navigation/Main.html | 13 +- .../Public/JavaScript/PageView/Navigation.js | 118 +++++++++++++++--- .../Public/JavaScript/PageView/PageView.js | 2 +- 4 files changed, 130 insertions(+), 21 deletions(-) diff --git a/Classes/Controller/PageViewController.php b/Classes/Controller/PageViewController.php index 6abd30482..2c01f52bf 100644 --- a/Classes/Controller/PageViewController.php +++ b/Classes/Controller/PageViewController.php @@ -467,6 +467,8 @@ protected function addViewerJS(): void 'useInternalProxy' => !empty($this->settings['useInternalProxy']), ]; + $documentJson = json_encode($this->document->getCurrentDocument()->toArray($this->uriBuilder, $config)); + if (count($this->documentArray) > 1) { $jsViewer = 'tx_dlf_viewer = [];'; $i = 0; @@ -519,9 +521,15 @@ protected function addViewerJS(): void } } + // TODO: Rethink global tx_dlf_loaded // Viewer configuration. $viewerConfiguration = '$(document).ready(function() { - tx_dlf_loaded_document = ' . json_encode($this->document->getCurrentDocument()->toArray($this->uriBuilder, $config)) . '; + tx_dlf_loaded = { + state: { + page: ' . $docPage . ' + }, + document: ' . $documentJson . ' + }; if (dlfUtils.exists(dlfViewer)) { ' . $jsViewer . ' @@ -552,9 +560,15 @@ protected function addViewerJS(): void 'measureIdLinks' => $docMeasures['measureLinks'] ]; + // TODO: Rethink global tx_dlf_loaded // Viewer configuration. $viewerConfiguration = '$(document).ready(function() { - tx_dlf_loaded_document = ' . json_encode($this->document->getCurrentDocument()->toArray($this->uriBuilder, $config)) . '; + tx_dlf_loaded = { + state: { + page: ' . $docPage . ' + }, + document: ' . $documentJson . ' + }; if (dlfUtils.exists(dlfViewer)) { tx_dlf_viewer = new dlfViewer(' . json_encode($viewer) . '); diff --git a/Resources/Private/Templates/Navigation/Main.html b/Resources/Private/Templates/Navigation/Main.html index 4d600ec81..cf8703757 100644 --- a/Resources/Private/Templates/Navigation/Main.html +++ b/Resources/Private/Templates/Navigation/Main.html @@ -90,7 +90,7 @@
- @@ -108,7 +108,7 @@
- @@ -302,4 +302,13 @@ + diff --git a/Resources/Public/JavaScript/PageView/Navigation.js b/Resources/Public/JavaScript/PageView/Navigation.js index 8c6718eeb..810499b48 100644 --- a/Resources/Public/JavaScript/PageView/Navigation.js +++ b/Resources/Public/JavaScript/PageView/Navigation.js @@ -8,20 +8,106 @@ * LICENSE.txt file that was distributed with this source code. */ -document.addEventListener('DOMContentLoaded', function () { - document.querySelector('.page-first') - .addEventListener('click', function (e) { - e.preventDefault(); - document.body.dispatchEvent( - new CustomEvent( - 'tx-dlf-pageChanged', - { - 'detail': { - 'page': 1, - 'target': e.target - } +class dlfNavigation { + /** + * + * @param {object} config + * @param {Record} config.features Which navigation features should + * be handled by this instance. + * @param {number} config.pageSteps Number of pages to skip for long step (currently calculated for + * double page mode) + */ + constructor(config) { + /** @private */ + this.config = config; + + this.registerEvents(); + } + + /** + * @private + */ + registerEvents() { + if (this.config.features.pageStepBack) { + const btn = document.querySelector('.page-step-back'); + if (btn !== null) { + btn.addEventListener('click', e => { + e.preventDefault(); + this.changePage(tx_dlf_loaded.state.page - this.config.pageSteps, e); + }); + } + } + + if (this.config.features.pageBack) { + const btn = document.querySelector('.page-back'); + if (btn !== null) { + btn.addEventListener('click', e => { + e.preventDefault(); + this.changePage(tx_dlf_loaded.state.page - 1, e); + }); + } + } + + if (this.config.features.pageFirst) { + const btn = document.querySelector('.page-first'); + if (btn !== null) { + btn.addEventListener('click', e => { + e.preventDefault(); + this.changePage(1, e); + }); + } + } + + if (this.config.features.pageStepForward) { + const btn = document.querySelector('.page-step-forward'); + if (btn !== null) { + btn.addEventListener('click', e => { + e.preventDefault(); + this.changePage(tx_dlf_loaded.state.page + this.config.pageSteps, e); + }); + } + } + + if (this.config.features.pageForward) { + const btn = document.querySelector('.page-forward'); + if (btn !== null) { + btn.addEventListener('click', e => { + e.preventDefault(); + this.changePage(tx_dlf_loaded.state.page + 1, e); + }); + } + } + + if (this.config.features.pageLast) { + const btn = document.querySelector('.page-last'); + if (btn !== null) { + btn.addEventListener('click', e => { + e.preventDefault(); + this.changePage(tx_dlf_loaded.document.length, e); + }); + } + } + } + + /** + * @param {number} pageNo + * @param {MouseEvent} e + * @private + */ + changePage(pageNo, e) { + const pageNoClamped = Math.max(1, Math.min(tx_dlf_loaded.document.length, pageNo)); + + tx_dlf_loaded.state.page = pageNoClamped; + document.body.dispatchEvent( + new CustomEvent( + 'tx-dlf-pageChanged', + { + 'detail': { + 'page': pageNoClamped, + 'target': e.target } - ) - ); - }); -}); + } + ) + ); + } +} diff --git a/Resources/Public/JavaScript/PageView/PageView.js b/Resources/Public/JavaScript/PageView/PageView.js index 083411d67..9e1279228 100644 --- a/Resources/Public/JavaScript/PageView/PageView.js +++ b/Resources/Public/JavaScript/PageView/PageView.js @@ -224,7 +224,7 @@ var dlfViewer = function (settings) { * @type {DlfDocument | null} * @private */ - this.document = tx_dlf_loaded_document || null; + this.document = tx_dlf_loaded.document || null; /** * @type {Boolean|false} From 9c46d23cc6537f625d2da585ae058eb0d45225ee Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Mon, 22 Aug 2022 23:15:30 +0200 Subject: [PATCH 08/76] Enable/disable nav buttons on client, + refactor --- .../Private/Templates/Navigation/Main.html | 100 +++---------- .../Public/JavaScript/PageView/Navigation.js | 135 +++++++++--------- 2 files changed, 88 insertions(+), 147 deletions(-) diff --git a/Resources/Private/Templates/Navigation/Main.html b/Resources/Private/Templates/Navigation/Main.html index cf8703757..2dfffb6fe 100644 --- a/Resources/Private/Templates/Navigation/Main.html +++ b/Resources/Private/Templates/Navigation/Main.html @@ -70,55 +70,25 @@
- - - - - - - - - - - - + + +
- - - - - - - - - - - - + + +
- - - - - - - - - - - - + + +
@@ -148,55 +118,25 @@
- - - - - - - - - - - - + = {numPages}\", then: \"disabled:\")}" addQueryString="1" additionalParams="{'tx_dlf[page]':'{viewData.requestData.page + 1 + viewData.requestData.double}'}" argumentsToBeExcludedFromQueryString="{0: 'tx_dlf[measure]'}"> + +
-
- - - - - - - - - - - - -
+
+ {numPages - pageSteps}\", then: \"disabled:\")}" addQueryString="1" additionalParams="{'tx_dlf[page]':'{viewData.requestData.page + pageSteps}'}" argumentsToBeExcludedFromQueryString="{0: 'tx_dlf[measure]'}"> + + +
- - - - - - - - - - - - + = {numPages - viewData.requestData.double}\", then: \"disabled:\")}" addQueryString="1" additionalParams="{'tx_dlf[page]':'{numPages - viewData.requestData.double}'}" argumentsToBeExcludedFromQueryString="{0: 'tx_dlf[measure]'}"> + +
diff --git a/Resources/Public/JavaScript/PageView/Navigation.js b/Resources/Public/JavaScript/PageView/Navigation.js index 810499b48..de4e0d5c2 100644 --- a/Resources/Public/JavaScript/PageView/Navigation.js +++ b/Resources/Public/JavaScript/PageView/Navigation.js @@ -21,69 +21,52 @@ class dlfNavigation { /** @private */ this.config = config; + /** + * @private + */ + this.navigationButtons = { + pageStepBack: { + button: document.querySelector('.page-step-back'), + getPage: (prevPageNo) => prevPageNo - this.config.pageSteps, + }, + pageBack: { + button: document.querySelector('.page-back'), + getPage: (prevPageNo) => prevPageNo - 1, + }, + pageFirst: { + button: document.querySelector('.page-first'), + getPage: (prevPageNo) => 1, + }, + pageStepForward: { + button: document.querySelector('.page-step-forward'), + getPage: (prevPageNo) => prevPageNo + this.config.pageSteps, + }, + pageForward: { + button: document.querySelector('.page-forward'), + getPage: (prevPageNo) => prevPageNo + 1, + }, + pageLast: { + button: document.querySelector('.page-last'), + getPage: (prevPageNo) => tx_dlf_loaded.document.length, + }, + } + this.registerEvents(); + this.updateNavigationButtons(); } /** * @private */ registerEvents() { - if (this.config.features.pageStepBack) { - const btn = document.querySelector('.page-step-back'); - if (btn !== null) { - btn.addEventListener('click', e => { - e.preventDefault(); - this.changePage(tx_dlf_loaded.state.page - this.config.pageSteps, e); - }); - } - } - - if (this.config.features.pageBack) { - const btn = document.querySelector('.page-back'); - if (btn !== null) { - btn.addEventListener('click', e => { - e.preventDefault(); - this.changePage(tx_dlf_loaded.state.page - 1, e); - }); - } - } - - if (this.config.features.pageFirst) { - const btn = document.querySelector('.page-first'); - if (btn !== null) { - btn.addEventListener('click', e => { - e.preventDefault(); - this.changePage(1, e); - }); - } - } - - if (this.config.features.pageStepForward) { - const btn = document.querySelector('.page-step-forward'); - if (btn !== null) { - btn.addEventListener('click', e => { - e.preventDefault(); - this.changePage(tx_dlf_loaded.state.page + this.config.pageSteps, e); - }); - } - } - - if (this.config.features.pageForward) { - const btn = document.querySelector('.page-forward'); - if (btn !== null) { - btn.addEventListener('click', e => { + for (const [key, value] of Object.entries(this.navigationButtons)) { + if (this.config.features[key]) { + value.button.addEventListener('click', e => { e.preventDefault(); - this.changePage(tx_dlf_loaded.state.page + 1, e); - }); - } - } - if (this.config.features.pageLast) { - const btn = document.querySelector('.page-last'); - if (btn !== null) { - btn.addEventListener('click', e => { - e.preventDefault(); - this.changePage(tx_dlf_loaded.document.length, e); + const pageNo = value.getPage(tx_dlf_loaded.state.page); + const clampedPageNo = Math.max(1, Math.min(tx_dlf_loaded.document.length, pageNo)); + this.changePage(clampedPageNo, e); }); } } @@ -95,19 +78,37 @@ class dlfNavigation { * @private */ changePage(pageNo, e) { - const pageNoClamped = Math.max(1, Math.min(tx_dlf_loaded.document.length, pageNo)); - - tx_dlf_loaded.state.page = pageNoClamped; - document.body.dispatchEvent( - new CustomEvent( - 'tx-dlf-pageChanged', - { - 'detail': { - 'page': pageNoClamped, - 'target': e.target + if (pageNo !== tx_dlf_loaded.state.page) { + tx_dlf_loaded.state.page = pageNo; + document.body.dispatchEvent( + new CustomEvent( + 'tx-dlf-pageChanged', + { + 'detail': { + 'page': pageNo, + 'target': e.target + } } - } - ) - ); + ) + ); + this.updateNavigationButtons(); + } + } + + /** + * Update DOM state of navigation buttons, for example, to enable/disable + * them depending on current page. + * + * @private + */ + updateNavigationButtons() { + for (const [key, value] of Object.entries(this.navigationButtons)) { + const btnPageNo = value.getPage(tx_dlf_loaded.state.page); + if (btnPageNo !== tx_dlf_loaded.state.page && 1 <= btnPageNo && btnPageNo <= tx_dlf_loaded.document.length) { + value.button.classList.remove('disabled'); + } else { + value.button.classList.add('disabled'); + } + } } } From bf15ca13e951081b34e4bb65208d56ac78636048 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Tue, 23 Aug 2022 11:52:04 +0200 Subject: [PATCH 09/76] Create WIP documentation page --- Documentation/Developers/ClientSide.rst | 23 +++++++++++++++++++++++ Documentation/Developers/Index.rst | 1 + 2 files changed, 24 insertions(+) create mode 100644 Documentation/Developers/ClientSide.rst diff --git a/Documentation/Developers/ClientSide.rst b/Documentation/Developers/ClientSide.rst new file mode 100644 index 000000000..dcfd5d18e --- /dev/null +++ b/Documentation/Developers/ClientSide.rst @@ -0,0 +1,23 @@ +=========== +Client-Side +=========== + +Document Descriptor +=================== + +The method ``Doc::toArray()`` collects all information used by the frontend into a JSON-serializable array. + +* For each page, there is an entry with the following keys: + + * ``url``: URL of the image + * ``mimetype``: MIME type of the image. + +Page Change +=========== + +When an element such as a navigation button wants to change the page, the ``tx-dlf-pageChanged`` event is fired. + +* The event is dispatched on ``document.body``. +* The detail object contains the following properties: + + * ``page``: Number of new page diff --git a/Documentation/Developers/Index.rst b/Documentation/Developers/Index.rst index 3639619a4..024d5a2bb 100644 --- a/Documentation/Developers/Index.rst +++ b/Documentation/Developers/Index.rst @@ -8,5 +8,6 @@ These pages are aimed at developers working on Kitodo.Presentation. Metadata Database + ClientSide Validation Embedded3DViewer From 90e65c5f0001491931ea8f55095e9d2b7d180a84 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Tue, 23 Aug 2022 11:54:53 +0200 Subject: [PATCH 10/76] Include page object in pageChanged event, start typing --- Documentation/Developers/ClientSide.rst | 3 ++- .../Public/JavaScript/PageView/Navigation.js | 1 + Resources/Public/JavaScript/PageView/types.d.ts | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Resources/Public/JavaScript/PageView/types.d.ts diff --git a/Documentation/Developers/ClientSide.rst b/Documentation/Developers/ClientSide.rst index dcfd5d18e..0c46c86c6 100644 --- a/Documentation/Developers/ClientSide.rst +++ b/Documentation/Developers/ClientSide.rst @@ -6,6 +6,7 @@ Document Descriptor =================== The method ``Doc::toArray()`` collects all information used by the frontend into a JSON-serializable array. +See the type ``dlf.PageObject`` for an outline of its structure. * For each page, there is an entry with the following keys: @@ -17,7 +18,7 @@ Page Change When an element such as a navigation button wants to change the page, the ``tx-dlf-pageChanged`` event is fired. -* The event is dispatched on ``document.body``. +* The event is dispatched on ``document.body`` and is of type ``dlf.PageChangeEvent``. * The detail object contains the following properties: * ``page``: Number of new page diff --git a/Resources/Public/JavaScript/PageView/Navigation.js b/Resources/Public/JavaScript/PageView/Navigation.js index de4e0d5c2..6057c1b2a 100644 --- a/Resources/Public/JavaScript/PageView/Navigation.js +++ b/Resources/Public/JavaScript/PageView/Navigation.js @@ -86,6 +86,7 @@ class dlfNavigation { { 'detail': { 'page': pageNo, + 'pageObj': tx_dlf_loaded.document[pageNo - 1], 'target': e.target } } diff --git a/Resources/Public/JavaScript/PageView/types.d.ts b/Resources/Public/JavaScript/PageView/types.d.ts new file mode 100644 index 000000000..90d628cb3 --- /dev/null +++ b/Resources/Public/JavaScript/PageView/types.d.ts @@ -0,0 +1,15 @@ +namespace dlf { + type PageObject = { + url?: string; + mimetype?: string; + /** + * IDs of the logical structures that the page belongs to, ordered by depth. + */ + logSections: string[]; + }; + + type PageChangeEvent = CustomEvent<{ + page: number; + pageObj: PageObject; + }>; +} From 3c2499dd9eccd85db8cbd65a0459290ed1b89189 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Tue, 23 Aug 2022 11:56:42 +0200 Subject: [PATCH 11/76] Update metadata table on page change --- Classes/Common/AbstractDocument.php | 46 ++++++++++-- Classes/Controller/MetadataController.php | 45 ++++++++---- Configuration/FlexForms/Metadata.xml | 10 +++ Configuration/TypoScript/setup.typoscript | 2 +- Documentation/Developers/ClientSide.rst | 15 ++++ Documentation/Plugins/Index.rst | 7 ++ .../Private/Language/de.locallang_be.xlf | 4 + Resources/Private/Language/locallang_be.xlf | 3 + .../Private/Templates/Metadata/Main.html | 10 ++- .../Public/JavaScript/PageView/Metadata.js | 73 +++++++++++++++++++ 10 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 Resources/Public/JavaScript/PageView/Metadata.js diff --git a/Classes/Common/AbstractDocument.php b/Classes/Common/AbstractDocument.php index 442b438cb..6e6833dd6 100644 --- a/Classes/Common/AbstractDocument.php +++ b/Classes/Common/AbstractDocument.php @@ -1302,23 +1302,52 @@ private static function setDocumentCache(string $location, AbstractDocument $cur $cache->set($cacheIdentifier, $currentDocument); } + /** + * Get IDs of logical structures that a page belongs to, indexed by depth. + * + * @param int $pageNo + * @return array + */ + public function getLogicalSectionsOnPage($pageNo) + { + $ids = []; + if (!empty($this->physicalStructure[$pageNo]) && !empty($this->smLinks['p2l'][$this->physicalStructure[$pageNo]])) { + foreach ($this->smLinks['p2l'][$this->physicalStructure[$pageNo]] as $logId) { + $depth = $this->getStructureDepth($logId); + $ids[$depth][] = $logId; + } + } + ksort($ids); + reset($ids); + return $ids; + } + public function toArray($uriBuilder, array $config = []) { $useInternalProxy = $config['useInternalProxy'] ?? false; $forceAbsoluteUrl = $config['forceAbsoluteUrl'] ?? false; + + $this->_getSmLinks(); + $this->_getPhysicalStructure(); + $result = []; $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get(self::$extKey); $fileGrpsImages = array_reverse(GeneralUtility::trimExplode(',', $extConf['fileGrpImages'])); for ($page = 1; $page <= $this->numPages; $page++) { + $pageEntry = [ + 'logSections' => array_merge(...$this->getLogicalSectionsOnPage($page)), + ]; + + // Get image URL foreach ($fileGrpsImages as $fileGrpImages) { // Get image link. if (!empty($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages])) { - $image['url'] = $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); - $image['mimetype'] = $this->getFileMimeType($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); + $pageEntry['url'] = $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); + $pageEntry['mimetype'] = $this->getFileMimeType($this->physicalStructureInfo[$this->physicalStructure[$page]]['files'][$fileGrpImages]); // Only deliver static images via the internal PageViewProxy. // (For IIP and IIIF, the viewer needs to build and access a separate metadata URL, see `getMetadataURL`.) - if ($useInternalProxy && strpos($image['mimetype'], 'image/') === 0) { + if ($useInternalProxy && strpos($pageEntry['mimetype'], 'image/') === 0) { // Configure @action URL for form. $uri = $uriBuilder ->reset() @@ -1326,21 +1355,22 @@ public function toArray($uriBuilder, array $config = []) ->setCreateAbsoluteUri($forceAbsoluteUrl) ->setArguments([ 'eID' => 'tx_dlf_pageview_proxy', - 'url' => $image['url'], - 'uHash' => GeneralUtility::hmac($image['url'], 'PageViewProxy') + 'url' => $pageEntry['url'], + 'uHash' => GeneralUtility::hmac($pageEntry['url'], 'PageViewProxy') ]) ->build(); - $image['url'] = $uri; + $pageEntry['url'] = $uri; } - $result[] = $image; break; } else { $this->logger->notice('No image file found for page "' . $page . '" in fileGrp "' . $fileGrpImages . '"'); } } - if (empty($image)) { + if (empty($pageEntry['url'])) { $this->logger->warning('No image file found for page "' . $page . '" in fileGrps "' . $extConf['fileGrpImages'] . '"'); } + + $result[] = $pageEntry; } return $result; diff --git a/Classes/Controller/MetadataController.php b/Classes/Controller/MetadataController.php index b98b75903..304766d7c 100644 --- a/Classes/Controller/MetadataController.php +++ b/Classes/Controller/MetadataController.php @@ -126,6 +126,7 @@ public function mainAction(): void $data = $this->currentDocument->getToplevelMetadata($this->settings['storagePid']); } $data['_id'] = $topLevelId; + $data['_active'] = true; array_unshift($metadata, $data); } @@ -212,7 +213,7 @@ private function buildIiifData(array $metadata): array foreach ($metadata as $row) { foreach ($row as $key => $group) { - if ($key == '_id') { + if ($key == '_id' || $key === '_active') { continue; } @@ -220,7 +221,7 @@ private function buildIiifData(array $metadata): array $iiifData[$key] = $this->buildIiifDataGroup($key, $group); } else { foreach ($group as $label => $value) { - if ($label == '_id') { + if ($label === '_id' || $label === '_active') { continue; } if (is_array($value)) { @@ -474,18 +475,35 @@ private function getMetadata(): array $metadata = []; if ($this->settings['rootline'] < 2) { // Get current structure's @ID. - $ids = []; - $page = $this->currentDocument->physicalStructure[$this->requestData['page']]; - if (!empty($page) && !empty($this->currentDocument->smLinks['p2l'][$page])) { - foreach ($this->currentDocument->smLinks['p2l'][$page] as $logId) { - $count = $this->currentDocument->getStructureDepth($logId); - $ids[$count][] = $logId; - } - } - ksort($ids); - reset($ids); + $ids = $this->currentDocument->getLogicalSectionsOnPage($this->requestData['page']); + // Check if we should display all metadata up to the root. - if ($this->settings['rootline'] == 1) { + if ($this->settings['prerenderAllSections'] ?? true) { + // Collect IDs of all logical structures. This is a flattened tree, so the + // order also works for rootline configurations. + $allIds = []; + function getIds($toc, &$output) { + foreach ($toc as $entry) { + $output[$entry['id']] = true; + if (is_array($entry['children'])) { + getIds($entry['children'], $output); + } + } + } + getIds($this->currentDocument->tableOfContents, $allIds); + + $idIsActive = []; + foreach ($ids as $id) { + foreach ($id as $sid) { + $idIsActive[$sid] = true; + } + } + + $metadata = $this->getMetadataForIds(array_keys($allIds), $metadata); + foreach ($metadata as &$entry) { + $entry['_active'] = isset($idIsActive[$entry['_id']]); + } + } elseif ($this->settings['rootline'] == 1) { foreach ($ids as $id) { $metadata = $this->getMetadataForIds($id, $metadata); } @@ -520,6 +538,7 @@ private function getMetadataForIds(array $id, array $metadata): array } if (!empty($data)) { $data['_id'] = $sid; + $data['_active'] = true; $metadata[] = $data; } } diff --git a/Configuration/FlexForms/Metadata.xml b/Configuration/FlexForms/Metadata.xml index 34b9f1a1c..c98a5ee37 100644 --- a/Configuration/FlexForms/Metadata.xml +++ b/Configuration/FlexForms/Metadata.xml @@ -82,6 +82,16 @@ + + + 1 + + + check + 1 + + + 1 diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index 63c0a42e6..76205725e 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -49,7 +49,6 @@ page { # PageView plugin kitodo-openLayers = EXT:dlf/Resources/Public/JavaScript/OpenLayers/openlayers.js -<<<<<<< HEAD kitodo-pageView-utility = EXT:dlf/Resources/Public/JavaScript/PageView/Utility.js kitodo-pageView-ol = EXT:dlf/Resources/Public/JavaScript/PageView/OL.js kitodo-pageView-olStyles = EXT:dlf/Resources/Public/JavaScript/PageView/OLStyles.js @@ -67,6 +66,7 @@ page { kitodo-pageView-searchInDocument = EXT:dlf/Resources/Public/JavaScript/PageView/SearchInDocument.js kitodo-pageView-pageView = EXT:dlf/Resources/Public/JavaScript/PageView/PageView.js kitodo-pageView-navigation = EXT:dlf/Resources/Public/JavaScript/PageView/Navigation.js + kitodo-pageView-metadata = EXT:dlf/Resources/Public/JavaScript/PageView/Metadata.js kitodo-search-suggest = EXT:dlf/Resources/Public/JavaScript/Search/Suggester.js } } diff --git a/Documentation/Developers/ClientSide.rst b/Documentation/Developers/ClientSide.rst index 0c46c86c6..0768e12c5 100644 --- a/Documentation/Developers/ClientSide.rst +++ b/Documentation/Developers/ClientSide.rst @@ -12,6 +12,7 @@ See the type ``dlf.PageObject`` for an outline of its structure. * ``url``: URL of the image * ``mimetype``: MIME type of the image. + * ``logSections`` Page Change =========== @@ -22,3 +23,17 @@ When an element such as a navigation button wants to change the page, the ``tx-d * The detail object contains the following properties: * ``page``: Number of new page + * ``pageObj`` + +Metadata +======== + +To dynamically show the metadata sections of the current page: + +* At initial load, all metadata sections are rendered into the HTML markup. + The attribute ``data-dlf-section`` names the ID of the logical section. + Sections that to not belong to the initial page are hidden. +* For each page, the document objects lists the sections that the page belongs to. +* On page change, this information is used to show/hide the sections depending on whether or not the page belongs to it. + +Rootline configuration is considered. diff --git a/Documentation/Plugins/Index.rst b/Documentation/Plugins/Index.rst index b2b898218..43325ba1b 100644 --- a/Documentation/Plugins/Index.rst +++ b/Documentation/Plugins/Index.rst @@ -506,6 +506,13 @@ Metadata :Default: 1 + - :Property: + prerenderAllSections + :Data Type: + :ref:`t3tsref:data-type-boolean` + :Default: + 1 + - :Property: rootline :Data Type: diff --git a/Resources/Private/Language/de.locallang_be.xlf b/Resources/Private/Language/de.locallang_be.xlf index 779d0d3f4..75ec63181 100644 --- a/Resources/Private/Language/de.locallang_be.xlf +++ b/Resources/Private/Language/de.locallang_be.xlf @@ -241,6 +241,10 @@ + + + + diff --git a/Resources/Private/Language/locallang_be.xlf b/Resources/Private/Language/locallang_be.xlf index 7b215dcf1..8004c69f2 100644 --- a/Resources/Private/Language/locallang_be.xlf +++ b/Resources/Private/Language/locallang_be.xlf @@ -392,6 +392,9 @@ + + + diff --git a/Resources/Private/Templates/Metadata/Main.html b/Resources/Private/Templates/Metadata/Main.html index 23428ed1b..17e4bbf06 100644 --- a/Resources/Private/Templates/Metadata/Main.html +++ b/Resources/Private/Templates/Metadata/Main.html @@ -25,7 +25,7 @@ -
+ + diff --git a/Resources/Public/JavaScript/PageView/TableOfContents.js b/Resources/Public/JavaScript/PageView/TableOfContents.js new file mode 100644 index 000000000..57f123037 --- /dev/null +++ b/Resources/Public/JavaScript/PageView/TableOfContents.js @@ -0,0 +1,36 @@ +/** + * (c) Kitodo. Key to digital objects e.V. + * + * This file is part of the Kitodo and TYPO3 projects. + * + * @license GNU General Public License version 3 or later. + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + */ + +class dlfTableOfContents { + constructor() { + /** @private */ + this.tocLinks = document.querySelectorAll('[data-toc-link]'); + this.tocLinks.forEach( link => { + const documentId = link.getAttribute('data-document-id'); + if (documentId && documentId !== tx_dlf_loaded.state.documentId) { + return; + } + + const pageNo = Number(link.getAttribute('data-page')); + link.addEventListener('click', e => { + e.preventDefault(); + // TODO: Avoid redundancy to Controller + tx_dlf_loaded.state.page = pageNo; + document.body.dispatchEvent(new CustomEvent('tx-dlf-stateChanged', { + 'detail': { + 'source': 'navigation', + 'page': pageNo, + } + })); + }); + }); + } + +} From 9c284c42f2eed08c4467fc7868797809b1e2397b Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Thu, 25 Aug 2022 22:43:23 +0200 Subject: [PATCH 36/76] Add separate marker for metadata list --- Documentation/Developers/ClientSide.rst | 1 + Resources/Private/Templates/Metadata/Main.html | 2 +- Resources/Public/JavaScript/PageView/Metadata.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/Developers/ClientSide.rst b/Documentation/Developers/ClientSide.rst index 293fc092e..698385dfa 100644 --- a/Documentation/Developers/ClientSide.rst +++ b/Documentation/Developers/ClientSide.rst @@ -42,6 +42,7 @@ Various * ``data-page-link`` * ``data-file-groups`` + * ``data-metadata-list`` * ``data-dlf-section`` * ``data-text`` diff --git a/Resources/Private/Templates/Metadata/Main.html b/Resources/Private/Templates/Metadata/Main.html index 17e4bbf06..748d5fedb 100644 --- a/Resources/Private/Templates/Metadata/Main.html +++ b/Resources/Private/Templates/Metadata/Main.html @@ -25,7 +25,7 @@
-
diff --git a/Resources/Public/JavaScript/PageView/Controller.js b/Resources/Public/JavaScript/PageView/Controller.js index 11a795b7d..116865ee6 100644 --- a/Resources/Public/JavaScript/PageView/Controller.js +++ b/Resources/Public/JavaScript/PageView/Controller.js @@ -27,7 +27,7 @@ class dlfController { this.updateMultiPage(this.doc.state.simultaneousPages); } - getVisiblePages (firstPageNo = this.doc.state.page) { + getVisiblePages(firstPageNo = this.doc.state.page) { const result = []; for (let i = 0; i < this.doc.state.simultaneousPages; i++) { const pageNo = firstPageNo + i; diff --git a/Resources/Public/JavaScript/PageView/Metadata.js b/Resources/Public/JavaScript/PageView/Metadata.js index f4da7b5aa..664f029a2 100644 --- a/Resources/Public/JavaScript/PageView/Metadata.js +++ b/Resources/Public/JavaScript/PageView/Metadata.js @@ -30,10 +30,13 @@ const dlfRootline = { class dlfMetadata { /** * + * @param {dlfController} docController * @param {object} config * @param {0 | 1 | 2} config.rootline Rootline configuration, see enum {@link dlfRootline}. */ - constructor(config) { + constructor(docController, config) { + /** @protected */ + this.docController = docController; /** @protected */ this.config = config; @@ -54,7 +57,7 @@ class dlfMetadata { updateSectionVisibility() { document.querySelectorAll('[data-metadata-list][data-dlf-section]').forEach((element) => { let isShown = false; - for (const page of tx_dlf_loaded.getVisiblePages()) { + for (const page of this.docController.getVisiblePages()) { if (this.shouldShowSection(page.pageObj, element.getAttribute('data-dlf-section'))) { isShown = true; break; diff --git a/Resources/Public/JavaScript/PageView/TableOfContents.js b/Resources/Public/JavaScript/PageView/TableOfContents.js index da1f29b61..9f441f1f6 100644 --- a/Resources/Public/JavaScript/PageView/TableOfContents.js +++ b/Resources/Public/JavaScript/PageView/TableOfContents.js @@ -15,7 +15,13 @@ const dlfTocState = { }; class dlfTableOfContents { - constructor() { + /** + * + * @param {dlfController} docController + */ + constructor(docController) { + /** @private */ + this.docController = docController; /** @private */ this.tocItems = document.querySelectorAll('[data-toc-item]'); /** @private */ @@ -51,7 +57,7 @@ class dlfTableOfContents { onStateChanged(e) { const activeLogSections = []; // TODO: Add toplevel sections - for (const page of tx_dlf_loaded.getVisiblePages()) { + for (const page of this.docController.getVisiblePages()) { activeLogSections.push(...page.pageObj.logSections); } From a8d2c993f4a7ca7123a1f89514f23352e10c31ac Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Sat, 27 Aug 2022 12:03:08 +0200 Subject: [PATCH 48/76] Refactor: Extract `dlfController::eventTarget` --- Documentation/Developers/ClientSide.rst | 2 +- Resources/Private/Templates/Toolbox/Main.html | 4 ++-- .../Public/JavaScript/PageView/Controller.js | 13 ++++++++++++- .../Public/JavaScript/PageView/Metadata.js | 2 +- .../Public/JavaScript/PageView/Navigation.js | 2 +- .../Public/JavaScript/PageView/PageView.js | 19 ++++++++++++++----- .../JavaScript/PageView/TableOfContents.js | 2 +- .../Public/JavaScript/PageView/Toolbox.js | 10 ++++++++-- 8 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Documentation/Developers/ClientSide.rst b/Documentation/Developers/ClientSide.rst index 818d56e2b..1f4be7940 100644 --- a/Documentation/Developers/ClientSide.rst +++ b/Documentation/Developers/ClientSide.rst @@ -13,7 +13,7 @@ Page Change When an element such as a navigation button wants to change the page, the ``tx-dlf-stateChanged`` event is fired. -* The event is dispatched on ``document.body`` and is of type ``dlf.StateChangeEvent``. +* ``docController.eventTarget`` tells which element the event is dispatched on. The event is of type ``dlf.StateChangeEvent``. * The detail object contains the following properties: * ``page``: Number of new page diff --git a/Resources/Private/Templates/Toolbox/Main.html b/Resources/Private/Templates/Toolbox/Main.html index 9b078f6de..aea1e6a7d 100644 --- a/Resources/Private/Templates/Toolbox/Main.html +++ b/Resources/Private/Templates/Toolbox/Main.html @@ -227,11 +227,11 @@ diff --git a/Resources/Public/JavaScript/PageView/Controller.js b/Resources/Public/JavaScript/PageView/Controller.js index 116865ee6..4f55355ed 100644 --- a/Resources/Public/JavaScript/PageView/Controller.js +++ b/Resources/Public/JavaScript/PageView/Controller.js @@ -13,7 +13,7 @@ class dlfController { /** @private */ this.doc = doc; - document.body.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); + this.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); window.addEventListener('popstate', this.onPopState.bind(this)); // Set initial state, so that browser navigation also works initial page @@ -27,6 +27,17 @@ class dlfController { this.updateMultiPage(this.doc.state.simultaneousPages); } + /** + * Get event target on which stateChanged events are dispatched. + * + * TODO: Either make this customizable, e.g. use some top-level wrapper element, or make dlfController the EventTarget + * + * @returns {EventTarget} + */ + get eventTarget() { + return document.body; + } + getVisiblePages(firstPageNo = this.doc.state.page) { const result = []; for (let i = 0; i < this.doc.state.simultaneousPages; i++) { diff --git a/Resources/Public/JavaScript/PageView/Metadata.js b/Resources/Public/JavaScript/PageView/Metadata.js index 664f029a2..12742859f 100644 --- a/Resources/Public/JavaScript/PageView/Metadata.js +++ b/Resources/Public/JavaScript/PageView/Metadata.js @@ -40,7 +40,7 @@ class dlfMetadata { /** @protected */ this.config = config; - document.body.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); + docController.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); } /** diff --git a/Resources/Public/JavaScript/PageView/Navigation.js b/Resources/Public/JavaScript/PageView/Navigation.js index c3834f716..efa34c422 100644 --- a/Resources/Public/JavaScript/PageView/Navigation.js +++ b/Resources/Public/JavaScript/PageView/Navigation.js @@ -87,7 +87,7 @@ class dlfNavigation { this.changePage(clampedPageNo, e); }); - document.body.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); + this.docController.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); } /** diff --git a/Resources/Public/JavaScript/PageView/PageView.js b/Resources/Public/JavaScript/PageView/PageView.js index c32f4fa0e..74ff7e8af 100644 --- a/Resources/Public/JavaScript/PageView/PageView.js +++ b/Resources/Public/JavaScript/PageView/PageView.js @@ -273,7 +273,6 @@ var dlfViewer = function (settings) { */ this.docController = null; - this.registerEvents(); this.init(dlfUtils.exists(settings.controls) ? settings.controls : []); }; @@ -966,15 +965,25 @@ dlfViewer.prototype.getVisiblePages = function () { } } +/** + * + * @param {dlfController | null} docController + */ dlfViewer.prototype.setDocController = function (docController) { + if (docController === this.docController) { + return; + } + this.docController = docController; -} -dlfViewer.prototype.registerEvents = function () { - $(document.body).on('tx-dlf-stateChanged', () => { + if (docController === null) { + return; + } + + this.docController.eventTarget.addEventListener('tx-dlf-stateChanged', () => { this.loadPages(this.getVisiblePages()); }); -}; +} dlfViewer.prototype.loadPages = function (visiblePages) { if (this.document === undefined) { diff --git a/Resources/Public/JavaScript/PageView/TableOfContents.js b/Resources/Public/JavaScript/PageView/TableOfContents.js index 9f441f1f6..31e8f5c8f 100644 --- a/Resources/Public/JavaScript/PageView/TableOfContents.js +++ b/Resources/Public/JavaScript/PageView/TableOfContents.js @@ -47,7 +47,7 @@ class dlfTableOfContents { }); }); - document.body.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); + docController.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); } /** diff --git a/Resources/Public/JavaScript/PageView/Toolbox.js b/Resources/Public/JavaScript/PageView/Toolbox.js index b580735ea..2f4df1050 100644 --- a/Resources/Public/JavaScript/PageView/Toolbox.js +++ b/Resources/Public/JavaScript/PageView/Toolbox.js @@ -9,11 +9,17 @@ */ class dlfToolbox { - constructor() { + /** + * + * @param {dlfController} docController + */ + constructor(docController) { + /** @private */ + this.docController = docController; /** @private */ this.pageLinks = document.querySelectorAll('[data-page-link]'); - document.body.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); + docController.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); this.updatePageLinks(tx_dlf_loaded.state.page); } From 5556f0a1c7cb4901d239918f6700fc01912d18c8 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Sat, 27 Aug 2022 12:14:27 +0200 Subject: [PATCH 49/76] Refactor: Don't dispatch `stateChanged` manually --- .../Public/JavaScript/PageView/Controller.js | 18 ++++++++++-- .../Public/JavaScript/PageView/Navigation.js | 28 +++---------------- .../JavaScript/PageView/TableOfContents.js | 9 +----- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/Resources/Public/JavaScript/PageView/Controller.js b/Resources/Public/JavaScript/PageView/Controller.js index 4f55355ed..c8eba842e 100644 --- a/Resources/Public/JavaScript/PageView/Controller.js +++ b/Resources/Public/JavaScript/PageView/Controller.js @@ -63,6 +63,21 @@ class dlfController { document.body.dispatchEvent(new CustomEvent('tx-dlf-stateChanged', { detail })); } + /** + * Navigate to given page. + * + * @param {number} pageNo + */ + changePage(pageNo) { + const clampedPageNo = Math.max(1, Math.min(this.doc.document.pages.length, pageNo)); + if (clampedPageNo !== this.doc.state.page) { + this.changeState({ + source: 'navigation', + page: clampedPageNo, + }); + } + } + /** * @param {number} pageNo * @param {boolean} pageGrid @@ -72,7 +87,7 @@ class dlfController { return this.doc.urlTemplate .replace(/DOUBLE_PAGE/, doublePage) .replace(/PAGE_NO/, pageNo) - .replace(/PAGE_GRID/, pageGrid ? "1" : "0"); + .replace(/PAGE_GRID/, pageGrid ? '1' : '0'); } /** @@ -103,7 +118,6 @@ class dlfController { if (state.page !== this.doc.state.page) { e.preventDefault(); - this.changeState({ 'source': 'history', 'page': state.page, diff --git a/Resources/Public/JavaScript/PageView/Navigation.js b/Resources/Public/JavaScript/PageView/Navigation.js index efa34c422..8f0735541 100644 --- a/Resources/Public/JavaScript/PageView/Navigation.js +++ b/Resources/Public/JavaScript/PageView/Navigation.js @@ -73,18 +73,16 @@ class dlfNavigation { e.preventDefault(); const pageNo = value.getPage(tx_dlf_loaded.state.page); - const clampedPageNo = Math.max(1, Math.min(tx_dlf_loaded.document.pages.length, pageNo)); - this.changePage(clampedPageNo, e); + this.docController.changePage(pageNo); }); } } - this.pageSelect.addEventListener('change', e => { + this.pageSelect.addEventListener('change', (e) => { e.preventDefault(); - const pageNo = e.target.value; - const clampedPageNo = Math.max(1, Math.min(tx_dlf_loaded.document.pages.length, pageNo)); - this.changePage(clampedPageNo, e); + const pageNo = Number(e.target.value); + this.docController.changePage(pageNo); }); this.docController.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); @@ -98,24 +96,6 @@ class dlfNavigation { this.updateNavigationControls(); } - /** - * @param {number} pageNo - * @param {MouseEvent} e - * @private - */ - changePage(pageNo, e) { - if (pageNo !== tx_dlf_loaded.state.page) { - // TODO: Avoid redundancy to Controller - tx_dlf_loaded.state.page = pageNo; - document.body.dispatchEvent(new CustomEvent('tx-dlf-stateChanged', { - 'detail': { - 'source': 'navigation', - 'page': pageNo, - } - })); - } - } - /** * Number of pages to jump in long step (e.g., 10 pages in single page mode * vs. 20 pages in double page mode). diff --git a/Resources/Public/JavaScript/PageView/TableOfContents.js b/Resources/Public/JavaScript/PageView/TableOfContents.js index 31e8f5c8f..d2f620c02 100644 --- a/Resources/Public/JavaScript/PageView/TableOfContents.js +++ b/Resources/Public/JavaScript/PageView/TableOfContents.js @@ -36,14 +36,7 @@ class dlfTableOfContents { const pageNo = Number(link.getAttribute('data-page')); link.addEventListener('click', e => { e.preventDefault(); - // TODO: Avoid redundancy to Controller - tx_dlf_loaded.state.page = pageNo; - document.body.dispatchEvent(new CustomEvent('tx-dlf-stateChanged', { - 'detail': { - 'source': 'navigation', - 'page': pageNo, - } - })); + this.docController.changePage(pageNo); }); }); From 16a101ad0b9c4fc16a54c0944fda43ef7f527251 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Sat, 27 Aug 2022 13:27:05 +0200 Subject: [PATCH 50/76] Refactor: Dissolve global `tx_dlf_loaded` --- Classes/Controller/DocumentController.php | 4 +- .../Public/JavaScript/PageView/Controller.js | 96 ++++++++++++++----- .../Public/JavaScript/PageView/Navigation.js | 25 ++--- .../Public/JavaScript/PageView/PageView.js | 24 +++-- .../JavaScript/PageView/TableOfContents.js | 4 +- .../Public/JavaScript/PageView/Toolbox.js | 40 +++++--- .../Public/JavaScript/PageView/types.d.ts | 25 +++-- 7 files changed, 147 insertions(+), 71 deletions(-) diff --git a/Classes/Controller/DocumentController.php b/Classes/Controller/DocumentController.php index 0ed76dad2..c057b81a9 100644 --- a/Classes/Controller/DocumentController.php +++ b/Classes/Controller/DocumentController.php @@ -69,11 +69,9 @@ public function mainAction() 'document' => $this->document->getCurrentDocument()->toArray($this->uriBuilder, $config), ]; - // TODO: Rethink global tx_dlf_loaded $docConfiguration = ' - tx_dlf_loaded = ' . json_encode($tx_dlf_loaded) . '; - window.addEventListener("DOMContentLoaded", function() { + const tx_dlf_loaded = ' . json_encode($tx_dlf_loaded) . '; window.dispatchEvent(new CustomEvent("tx-dlf-documentLoaded", { detail: { docController: new dlfController(tx_dlf_loaded) diff --git a/Resources/Public/JavaScript/PageView/Controller.js b/Resources/Public/JavaScript/PageView/Controller.js index c8eba842e..559732f10 100644 --- a/Resources/Public/JavaScript/PageView/Controller.js +++ b/Resources/Public/JavaScript/PageView/Controller.js @@ -9,6 +9,10 @@ */ class dlfController { + /** + * + * @param {dlf.Loaded} doc + */ constructor(doc) { /** @private */ this.doc = doc; @@ -19,12 +23,10 @@ class dlfController { // Set initial state, so that browser navigation also works initial page history.replaceState(/** @type {dlf.PageHistoryState} */({ type: 'tx-dlf-page-state', - documentId: this.doc.state.documentId, - page: this.doc.state.page, - simultaneousPages: this.doc.state.simultaneousPages, + ...this.doc.state }), ''); - this.updateMultiPage(this.doc.state.simultaneousPages); + this.updateMultiPage(this.simultaneousPages); } /** @@ -38,9 +40,21 @@ class dlfController { return document.body; } + get documentId() { + return this.doc.state.documentId; + } + + get currentPageNo() { + return this.doc.state.page; + } + + get simultaneousPages() { + return this.doc.state.simultaneousPages; + } + getVisiblePages(firstPageNo = this.doc.state.page) { const result = []; - for (let i = 0; i < this.doc.state.simultaneousPages; i++) { + for (let i = 0; i < this.simultaneousPages; i++) { const pageNo = firstPageNo + i; const pageObj = this.doc.document.pages[pageNo - 1]; if (pageObj !== undefined) { @@ -48,12 +62,53 @@ class dlfController { } } return result; - }; + } + + /** + * + * @param {number} pageNo + * @returns {dlf.PageObject | undefined} + */ + getPageByNo(pageNo) { + return this.doc.document.pages[pageNo - 1]; + } + + get numPages() { + return this.doc.document.pages.length; + } + + /** + * + * @param {number} pageNo + * @param {string[]} fileGroups + * @returns {dlf.ResourceLocator | undefined} + */ + findFileByGroup(pageNo, fileGroups) { + const pageObj = this.getPageByNo(pageNo); + if (pageObj === undefined) { + return; + } + + return dlfUtils.findFirstSet(pageObj.files, fileGroups); + } + + /** + * + * @param {number} pageNo + * @param {dlf.FileKind} fileKind + * @returns {dlf.ResourceLocator | undefined} + */ + findFileByKind(pageNo, fileKind) { + return this.findFileByGroup(pageNo, this.doc.fileGroups[fileKind]); + } /** * @param {dlf.StateChangeDetail} detail */ changeState(detail) { + // TODO: Consider passing full new state in stateChanged event, + // then reduce usage of currentPageNo and simultaneousPages properties + if (detail.page !== undefined) { this.doc.state.page = detail.page; } @@ -69,7 +124,7 @@ class dlfController { * @param {number} pageNo */ changePage(pageNo) { - const clampedPageNo = Math.max(1, Math.min(this.doc.document.pages.length, pageNo)); + const clampedPageNo = Math.max(1, Math.min(this.numPages, pageNo)); if (clampedPageNo !== this.doc.state.page) { this.changeState({ source: 'navigation', @@ -83,7 +138,7 @@ class dlfController { * @param {boolean} pageGrid */ makePageUrl(pageNo, pageGrid = false) { - const doublePage = this.doc.state.simultaneousPages >= 2 ? 1 : 0; + const doublePage = this.simultaneousPages >= 2 ? 1 : 0; return this.doc.urlTemplate .replace(/DOUBLE_PAGE/, doublePage) .replace(/PAGE_NO/, pageNo) @@ -116,21 +171,12 @@ class dlfController { return; } - if (state.page !== this.doc.state.page) { - e.preventDefault(); - this.changeState({ - 'source': 'history', - 'page': state.page, - }); - } - - if (state.simultaneousPages !== this.doc.state.simultaneousPages) { - e.preventDefault(); - this.changeState({ - 'source': 'history', - 'simultaneousPages': state.simultaneousPages, - }); - } + e.preventDefault(); + this.changeState({ + 'source': 'history', + 'page': state.page === this.currentPageNo ? undefined : state.page, + 'simultaneousPages': state.simultaneousPages === this.simultaneousPages ? undefined : state.simultaneousPages + }); } /** @@ -145,9 +191,7 @@ class dlfController { history.pushState(/** @type {dlf.PageHistoryState} */({ type: 'tx-dlf-page-state', - documentId: this.doc.state.documentId, - page: this.doc.state.page, - simultaneousPages: this.doc.state.simultaneousPages, + ...this.doc.state }), '', this.makePageUrl(this.doc.state.page)); } diff --git a/Resources/Public/JavaScript/PageView/Navigation.js b/Resources/Public/JavaScript/PageView/Navigation.js index 8f0735541..7a2122ed0 100644 --- a/Resources/Public/JavaScript/PageView/Navigation.js +++ b/Resources/Public/JavaScript/PageView/Navigation.js @@ -36,7 +36,7 @@ class dlfNavigation { pageBack: { button: document.querySelector('.page-back'), // When we're on second page in double-page mode, make sure the "back" button is still shown - getPage: (prevPageNo) => Math.max(1, prevPageNo - tx_dlf_loaded.state.simultaneousPages), + getPage: (prevPageNo) => Math.max(1, prevPageNo - this.docController.simultaneousPages), }, pageFirst: { button: document.querySelector('.page-first'), @@ -48,11 +48,11 @@ class dlfNavigation { }, pageForward: { button: document.querySelector('.page-forward'), - getPage: (prevPageNo) => prevPageNo + tx_dlf_loaded.state.simultaneousPages, + getPage: (prevPageNo) => prevPageNo + this.docController.simultaneousPages, }, pageLast: { button: document.querySelector('.page-last'), - getPage: (prevPageNo) => tx_dlf_loaded.document.pages.length - (tx_dlf_loaded.state.simultaneousPages - 1), + getPage: (prevPageNo) => this.docController.numPages - (this.docController.simultaneousPages - 1), }, } @@ -69,10 +69,10 @@ class dlfNavigation { registerEvents() { for (const [key, value] of Object.entries(this.navigationButtons)) { if (this.config.features[key]) { - value.button.addEventListener('click', e => { + value.button.addEventListener('click', (e) => { e.preventDefault(); - const pageNo = value.getPage(tx_dlf_loaded.state.page); + const pageNo = value.getPage(this.docController.currentPageNo); this.docController.changePage(pageNo); }); } @@ -104,26 +104,29 @@ class dlfNavigation { * @returns {number} */ getLongStep() { - return this.config.basePageSteps * tx_dlf_loaded.state.simultaneousPages; + return this.config.basePageSteps * this.docController.simultaneousPages; } /** * Update DOM state of navigation buttons and dropdown. (For example, * enable/disable the buttons depending on current page.) * + * @param {number} pageNo * @private */ updateNavigationControls() { + const currentPageNo = this.docController.currentPageNo; + for (const [key, value] of Object.entries(this.navigationButtons)) { - const btnPageNo = value.getPage(tx_dlf_loaded.state.page); - const isBtnPageVisible = this.docController.getVisiblePages(btnPageNo).some(page => page.pageNo === tx_dlf_loaded.state.page); - if (!isBtnPageVisible && 1 <= btnPageNo && btnPageNo <= tx_dlf_loaded.document.pages.length) { + const btnPageNo = value.getPage(currentPageNo); + const isBtnPageVisible = this.docController.getVisiblePages(btnPageNo).some(page => page.pageNo === currentPageNo); + if (!isBtnPageVisible && 1 <= btnPageNo && btnPageNo <= this.docController.numPages) { value.button.classList.remove('disabled'); } else { value.button.classList.add('disabled'); } // TODO: check if it needs to be done always or only for not disabled buttons - this.updateUrl(value.button, value.getPage(tx_dlf_loaded.state.page)); + this.updateUrl(value.button, btnPageNo); const textTemplate = value.button.getAttribute('data-text'); if (textTemplate) { @@ -132,7 +135,7 @@ class dlfNavigation { } if (this.pageSelect instanceof HTMLSelectElement) { - this.pageSelect.value = tx_dlf_loaded.state.page; + this.pageSelect.value = currentPageNo.toString(); } } diff --git a/Resources/Public/JavaScript/PageView/PageView.js b/Resources/Public/JavaScript/PageView/PageView.js index 74ff7e8af..d08284927 100644 --- a/Resources/Public/JavaScript/PageView/PageView.js +++ b/Resources/Public/JavaScript/PageView/PageView.js @@ -122,7 +122,6 @@ var dlfViewer = function (settings) { * @private */ this.fulltexts = dlfUtils.exists(settings.fulltexts) ? settings.fulltexts : []; - //this.fulltextUrls = tx_dlf_loaded.fulltextUrls || null /** * Loaded scores (as jQuery deferred object). @@ -212,12 +211,6 @@ var dlfViewer = function (settings) { */ this.ovView = null; - /** - * @type {DlfDocument | null} - * @private - */ - this.document = tx_dlf_loaded.document || null; - /** * @type {Boolean|false} * @private @@ -985,15 +978,21 @@ dlfViewer.prototype.setDocController = function (docController) { }); } +/** + * + * @param {any} visiblePages + * @private + * @returns + */ dlfViewer.prototype.loadPages = function (visiblePages) { - if (this.document === undefined) { + if (this.docController === null) { return; } const pages = []; const files = []; for (const page of visiblePages) { - const file = dlfUtils.findFirstSet(page.pageObj.files, tx_dlf_loaded.fileGroups['images']); + const file = this.docController.findFileByKind(page.pageNo, 'images'); if (file === undefined) { console.warn(`No image file found on page ${page.pageNo}`); continue; @@ -1072,13 +1071,18 @@ dlfViewer.prototype.initLoadScores = function () { * @private */ dlfViewer.prototype.initLoadFulltexts = function (visiblePages) { + if (this.docController === null) { + // TODO: Make it work then docController === null + return; + } + var cnt = Math.min(visiblePages.length, this.images.length); var xOffset = 0; for (var i = 0; i < cnt; i++) { const image = this.images[i]; const key = `${visiblePages[i].pageNo}-${i}`; - const fulltext = dlfUtils.findFirstSet(visiblePages[i].pageObj.files, tx_dlf_loaded.fileGroups['fulltext']); + const fulltext = this.docController.findFileByKind(visiblePages[i].pageNo, 'fulltext'); if (fulltext !== undefined) { if (!(key in this.fulltextsLoaded_) && dlfUtils.isFulltextDescriptor(fulltext)) { fulltextEntry = dlfFullTextUtils.fetchFullTextDataFromServer(fulltext.url, image, xOffset); diff --git a/Resources/Public/JavaScript/PageView/TableOfContents.js b/Resources/Public/JavaScript/PageView/TableOfContents.js index d2f620c02..549589515 100644 --- a/Resources/Public/JavaScript/PageView/TableOfContents.js +++ b/Resources/Public/JavaScript/PageView/TableOfContents.js @@ -27,9 +27,9 @@ class dlfTableOfContents { /** @private */ this.tocLinks = document.querySelectorAll('[data-toc-link]'); - this.tocLinks.forEach(link => { + this.tocLinks.forEach((link) => { const documentId = link.getAttribute('data-document-id'); - if (documentId && documentId !== tx_dlf_loaded.state.documentId) { + if (documentId && documentId !== this.docController.documentId) { return; } diff --git a/Resources/Public/JavaScript/PageView/Toolbox.js b/Resources/Public/JavaScript/PageView/Toolbox.js index 2f4df1050..0852849c4 100644 --- a/Resources/Public/JavaScript/PageView/Toolbox.js +++ b/Resources/Public/JavaScript/PageView/Toolbox.js @@ -20,7 +20,7 @@ class dlfToolbox { this.pageLinks = document.querySelectorAll('[data-page-link]'); docController.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); - this.updatePageLinks(tx_dlf_loaded.state.page); + this.updatePageLinks(this.docController.currentPageNo); } /** @@ -40,22 +40,19 @@ class dlfToolbox { updatePageLinks(firstPageNo) { this.pageLinks.forEach(element => { const offset = Number(element.getAttribute('data-page-link')); - const pageObj = tx_dlf_loaded.document.pages[firstPageNo - 1 + offset]; - if (!pageObj) { + const pageNo = firstPageNo + offset; + + const fileGroups = this.getFileGroups(element); + const file = fileGroups !== null + ? this.docController.findFileByGroup(pageNo, fileGroups) + : this.docController.findFileByKind(pageNo, 'download'); + + if (file === undefined) { $(element).hide(); return; } $(element).show(); - const fileGroupsJson = element.getAttribute('data-file-groups'); - const fileGroups = fileGroupsJson - ? JSON.parse(fileGroupsJson) - : tx_dlf_loaded.fileGroups['download']; - const file = dlfUtils.findFirstSet(pageObj.files, fileGroups); - if (!file) { - return; - } - if (element instanceof HTMLAnchorElement) { element.href = file.url; } else { @@ -82,4 +79,23 @@ class dlfToolbox { } }); } + + /** + * @private + * @param {Element} element + * @return {string[] | null} + */ + getFileGroups(element) { + const fileGroupsJson = element.getAttribute('data-file-groups'); + try { + const fileGroups = JSON.parse(fileGroupsJson); + if (Array.isArray(fileGroups) && fileGroups.every(entry => typeof entry === 'string')) { + return fileGroups; + } + } catch (e) { + // + } + + return null; + } } diff --git a/Resources/Public/JavaScript/PageView/types.d.ts b/Resources/Public/JavaScript/PageView/types.d.ts index 7ad2e3179..9c353fa03 100644 --- a/Resources/Public/JavaScript/PageView/types.d.ts +++ b/Resources/Public/JavaScript/PageView/types.d.ts @@ -19,7 +19,22 @@ namespace dlf { pages: PageObjects[]; query: { minPage: number; - } + }; + }; + + type PageDisplayState = { + documentId: string | number; + page: number; + simultaneousPages: number; + }; + + type FileKind = 'images' | 'fulltext' | 'download'; + + type Loaded = { + state: PageDisplayState; + urlTemplate: string; + fileGroups: Record; + document: Document; }; type StateChangeEvent = CustomEvent; @@ -32,9 +47,7 @@ namespace dlf { * * `history`: Event is triggered by user navigation. */ source: 'history' | 'navigation'; - page?: number; - simultaneousPages?: number; - }; + } & Partial; /** * State of document stored in `window.history`. @@ -42,7 +55,5 @@ namespace dlf { type PageHistoryState = { type: 'tx-dlf-page-state'; documentId: string | number; - page: number; - simultaneousPages: number; - }; + } & PageDisplayState; } From 80611c3430cd21dfdba5598a78429ab2944fd198 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Sat, 27 Aug 2022 13:41:45 +0200 Subject: [PATCH 51/76] [EXT] Reinstate registry --- Classes/Common/AbstractDocument.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Classes/Common/AbstractDocument.php b/Classes/Common/AbstractDocument.php index 48465350c..828741d4b 100644 --- a/Classes/Common/AbstractDocument.php +++ b/Classes/Common/AbstractDocument.php @@ -572,8 +572,10 @@ public static function &getInstance(string $location, array $settings = [], bool $iiif = null; if (!$forceReload) { - $instance = self::getDocumentCache($location); - if ($instance !== false) { + if (isset(self::$registry[$location])) { + return self::$registry[$location]; + } elseif ($instance = self::getDocumentCache($location)) { + self::$registry[$location] = $instance; return $instance; } } @@ -619,7 +621,7 @@ public static function &getInstance(string $location, array $settings = [], bool $instance = new IiifManifest($pid, $location, $iiif); } - if ($instance !== null) { + if ($instance) { self::setDocumentCache($location, $instance); } From f3dd1405b4230667d23315ea6511b8ff67d488f2 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Sat, 27 Aug 2022 13:55:49 +0200 Subject: [PATCH 52/76] [EXT] Memoize `MetsDocument::getStructureDepth()` --- Classes/Common/MetsDocument.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Classes/Common/MetsDocument.php b/Classes/Common/MetsDocument.php index 29b223773..c905ffbf0 100644 --- a/Classes/Common/MetsDocument.php +++ b/Classes/Common/MetsDocument.php @@ -418,8 +418,10 @@ protected function getLogicalStructureInfo(SimpleXMLElement $structure, bool $re 'pagination' => '', 'type' => isset($attributes['TYPE']) ? (string) $attributes['TYPE'] : '', 'description' => '', - 'thumbnailId' => null, + 'thumbnailId' => '', 'files' => [], + // Structure depth is determined and cached on demand + 'structureDepth' => null ]; // Set volume and year information only if no label is set and this is the toplevel structure element. @@ -1173,12 +1175,23 @@ public function getFullText(string $id): string */ public function getStructureDepth(string $logId) { + if (isset($this->logicalUnits[$logId]['structureDepth'])) { + return $this->logicalUnits[$logId]['structureDepth']; + } + $ancestors = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $logId . '"]/ancestor::*'); if (!empty($ancestors)) { - return count($ancestors); + $structureDepth = count($ancestors); } else { - return 0; + $structureDepth = 0; } + + // NOTE: Don't just set $this->logicalUnits[$logId] here, because it may not yet be loaded + if (isset($this->logicalUnits[$logId])) { + $this->logicalUnits[$logId]['structureDepth'] = $structureDepth; + } + + return $structureDepth; } /** From e48d5c4d8c4e106ed9cf40d0989ef6aafd8a4fe3 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Sat, 27 Aug 2022 23:53:37 +0200 Subject: [PATCH 53/76] Fetch prerendererd metadata dynamically On large documents, rendering metadata of all sections can take a considerable amount of time. Instead, allow to just render the current metadata, and fetch the additional metadata in an API call. --- Classes/Controller/DocumentController.php | 15 ++++++++ Classes/Controller/MetadataController.php | 2 +- Configuration/FlexForms/Document.xml | 20 ++++++++++ Configuration/FlexForms/Metadata.xml | 2 +- Documentation/Plugins/Index.rst | 20 ++++++++++ .../Private/Language/de.locallang_be.xlf | 4 ++ Resources/Private/Language/locallang_be.xlf | 3 ++ .../Private/Templates/Metadata/Main.html | 38 ++++++++++--------- .../Public/JavaScript/PageView/Controller.js | 14 +++++++ .../Public/JavaScript/PageView/Metadata.js | 16 ++++++++ .../Public/JavaScript/PageView/types.d.ts | 1 + 11 files changed, 116 insertions(+), 19 deletions(-) diff --git a/Classes/Controller/DocumentController.php b/Classes/Controller/DocumentController.php index c057b81a9..c2c603656 100644 --- a/Classes/Controller/DocumentController.php +++ b/Classes/Controller/DocumentController.php @@ -45,9 +45,23 @@ public function mainAction() $this->setPage(); + if (!empty($this->settings['targetPidMetadata'])) { + $metadataUrl = $this->uriBuilder + ->reset() + ->setTargetPageUid((int) $this->settings['targetPidMetadata']) + ->setCreateAbsoluteUri(true) + ->setArguments([ + 'tx_dlf' => [ + 'id' => $this->requestData['id'], + ], + ]) + ->build(); + } + $filesConfiguration = $this->extConf['files']; $imageFileGroups = array_reverse(GeneralUtility::trimExplode(',', $filesConfiguration ['fileGrpImages'])); $fulltextFileGroups = GeneralUtility::trimExplode(',', $filesConfiguration ['fileGrpFulltext']); + $config = [ 'forceAbsoluteUrl' => !empty($this->settings['forceAbsoluteUrl']), 'proxyFileGroups' => !empty($this->settings['useInternalProxy']) @@ -61,6 +75,7 @@ public function mainAction() 'simultaneousPages' => (int) $this->requestData['double'] + 1, ], 'urlTemplate' => $this->getUrlTemplate(), + 'metadataUrl' => $metadataUrl, 'fileGroups' => [ 'images' => $imageFileGroups, 'fulltext' => $fulltextFileGroups, diff --git a/Classes/Controller/MetadataController.php b/Classes/Controller/MetadataController.php index 9007288d7..cae03a6a7 100644 --- a/Classes/Controller/MetadataController.php +++ b/Classes/Controller/MetadataController.php @@ -478,7 +478,7 @@ private function getMetadata(): array $ids = $this->currentDocument->getLogicalSectionsOnPage((int) $this->requestData['page']); // Check if we should display all metadata up to the root. - if ($this->settings['prerenderAllSections'] ?? true) { + if ($this->settings['prerenderAllSections'] ?? false) { // Collect IDs of all logical structures. This is a flattened tree, so the // order also works for rootline configurations. $allIds = []; diff --git a/Configuration/FlexForms/Document.xml b/Configuration/FlexForms/Document.xml index 9db84e9b0..20a7fbaed 100644 --- a/Configuration/FlexForms/Document.xml +++ b/Configuration/FlexForms/Document.xml @@ -40,6 +40,26 @@ + + + 1 + + + group + db + pages + 1 + 1 + 1 + 1 + + + suggest + + + + + diff --git a/Configuration/FlexForms/Metadata.xml b/Configuration/FlexForms/Metadata.xml index c98a5ee37..251ca6082 100644 --- a/Configuration/FlexForms/Metadata.xml +++ b/Configuration/FlexForms/Metadata.xml @@ -88,7 +88,7 @@ check - 1 + 0 diff --git a/Documentation/Plugins/Index.rst b/Documentation/Plugins/Index.rst index 1d1222f1f..ea7c4d08a 100644 --- a/Documentation/Plugins/Index.rst +++ b/Documentation/Plugins/Index.rst @@ -308,6 +308,26 @@ Document :Default: Default + - :Property: + excludeOther_ + :Data Type: + :ref:`t3tsref:data-type-boolean` + :Default: + 1 + + - :Property: + useInternalProxy + :Data Type: + :ref:`t3tsref:data-type-boolean` + :Default: + 0 + + - :Property: + targetPidMetadata + :Data Type: + :ref:`t3tsref:data-type-page-id` + :Default: + Embedded 3D Viewer ----------- diff --git a/Resources/Private/Language/de.locallang_be.xlf b/Resources/Private/Language/de.locallang_be.xlf index b32d3bd53..6021b5d03 100644 --- a/Resources/Private/Language/de.locallang_be.xlf +++ b/Resources/Private/Language/de.locallang_be.xlf @@ -177,6 +177,10 @@ + + + + diff --git a/Resources/Private/Language/locallang_be.xlf b/Resources/Private/Language/locallang_be.xlf index 6e7ebbaec..5958e6918 100644 --- a/Resources/Private/Language/locallang_be.xlf +++ b/Resources/Private/Language/locallang_be.xlf @@ -386,6 +386,9 @@ + + + diff --git a/Resources/Private/Templates/Metadata/Main.html b/Resources/Private/Templates/Metadata/Main.html index 4efae022e..cc99e4641 100644 --- a/Resources/Private/Templates/Metadata/Main.html +++ b/Resources/Private/Templates/Metadata/Main.html @@ -19,27 +19,31 @@ NOTE: We assume unescaped values - - - - - - - - - - + diff --git a/Resources/Public/JavaScript/PageView/Controller.js b/Resources/Public/JavaScript/PageView/Controller.js index 559732f10..e4d7986b3 100644 --- a/Resources/Public/JavaScript/PageView/Controller.js +++ b/Resources/Public/JavaScript/PageView/Controller.js @@ -27,6 +27,16 @@ class dlfController { }), ''); this.updateMultiPage(this.simultaneousPages); + + if (doc.metadataUrl !== null) { + this.metadataPromise = fetch(doc.metadataUrl) + .then(response => response.text()) + .then(html => ({ + htmlCode: html, + })); + } else { + this.metadataPromise = Promise.reject(); + } } /** @@ -102,6 +112,10 @@ class dlfController { return this.findFileByGroup(pageNo, this.doc.fileGroups[fileKind]); } + fetchMetadata() { + return this.metadataPromise; + } + /** * @param {dlf.StateChangeDetail} detail */ diff --git a/Resources/Public/JavaScript/PageView/Metadata.js b/Resources/Public/JavaScript/PageView/Metadata.js index 12742859f..794c7fcaf 100644 --- a/Resources/Public/JavaScript/PageView/Metadata.js +++ b/Resources/Public/JavaScript/PageView/Metadata.js @@ -32,6 +32,7 @@ class dlfMetadata { * * @param {dlfController} docController * @param {object} config + * @param {HTMLElement} config.container * @param {0 | 1 | 2} config.rootline Rootline configuration, see enum {@link dlfRootline}. */ constructor(docController, config) { @@ -41,6 +42,21 @@ class dlfMetadata { this.config = config; docController.eventTarget.addEventListener('tx-dlf-stateChanged', this.onStateChanged.bind(this)); + + // TODO: Add spinner or so + docController.fetchMetadata() + .then((metadata) => { + const element = document.createElement('div'); + element.innerHTML = metadata.htmlCode; + const metadataContainer = element.querySelector('.dlf-metadata-container'); + if (metadataContainer !== null) { + config.container.replaceWith(metadataContainer); + this.updateSectionVisibility(); + } + }) + .catch(() => { + console.warn("Could not fetch additional metadata"); + }); } /** diff --git a/Resources/Public/JavaScript/PageView/types.d.ts b/Resources/Public/JavaScript/PageView/types.d.ts index 9c353fa03..00f4052db 100644 --- a/Resources/Public/JavaScript/PageView/types.d.ts +++ b/Resources/Public/JavaScript/PageView/types.d.ts @@ -33,6 +33,7 @@ namespace dlf { type Loaded = { state: PageDisplayState; urlTemplate: string; + metadataUrl: string | null; fileGroups: Record; document: Document; }; From f32e3cfb7ae95a46280f93512a9afcbce06d8de4 Mon Sep 17 00:00:00 2001 From: Kajetan Dvoracek Date: Sun, 28 Aug 2022 00:10:30 +0200 Subject: [PATCH 54/76] Convert `TODO` to `TODO(client-side)` --- Classes/Controller/PageViewController.php | 2 +- Classes/Controller/ToolboxController.php | 1 + Documentation/Developers/ClientSide.rst | 5 +++++ Resources/Private/Templates/Metadata/Main.html | 2 +- Resources/Private/Templates/Toolbox/Main.html | 2 +- Resources/Public/Css/Kitodo.css | 2 +- Resources/Public/JavaScript/PageView/Controller.js | 6 +++--- Resources/Public/JavaScript/PageView/Metadata.js | 2 +- Resources/Public/JavaScript/PageView/Navigation.js | 2 +- Resources/Public/JavaScript/PageView/PageView.js | 4 ++-- Resources/Public/JavaScript/PageView/TableOfContents.js | 6 +++--- 11 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Classes/Controller/PageViewController.php b/Classes/Controller/PageViewController.php index f88405521..2ab8a96fb 100644 --- a/Classes/Controller/PageViewController.php +++ b/Classes/Controller/PageViewController.php @@ -462,7 +462,7 @@ protected function getFulltext(int $page): array */ protected function addViewerJS(): void { - // TODO avoid redundancy to documentController + // TODO(client-side): Avoid redundancy to DocumentController $filesConfiguration = $this->extConf['files']; $imageFileGroups = array_reverse(GeneralUtility::trimExplode(',', $filesConfiguration['fileGrpImages'])); $fulltextFileGroups = GeneralUtility::trimExplode(',', $filesConfiguration['fileGrpFulltext']); diff --git a/Classes/Controller/ToolboxController.php b/Classes/Controller/ToolboxController.php index e4af848c7..80fdaa916 100644 --- a/Classes/Controller/ToolboxController.php +++ b/Classes/Controller/ToolboxController.php @@ -173,6 +173,7 @@ private function getImage(int $page, array $fileGrps): array * * @return void */ + // TODO(client-side) private function renderAnnotationTool(): void { if ($this->isDocMissingOrEmpty()) { diff --git a/Documentation/Developers/ClientSide.rst b/Documentation/Developers/ClientSide.rst index 1f4be7940..85a4eff13 100644 --- a/Documentation/Developers/ClientSide.rst +++ b/Documentation/Developers/ClientSide.rst @@ -67,3 +67,8 @@ Various * ``shown-if-single`` * ``shown-if-double`` + +Code +==== + +* ``TODO(client-side)`` diff --git a/Resources/Private/Templates/Metadata/Main.html b/Resources/Private/Templates/Metadata/Main.html index cc99e4641..42dddcde4 100644 --- a/Resources/Private/Templates/Metadata/Main.html +++ b/Resources/Private/Templates/Metadata/Main.html @@ -40,7 +40,7 @@