From 910224c931ec15284720bc678ba99bf97f3e5958 Mon Sep 17 00:00:00 2001 From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:44:59 +0200 Subject: [PATCH 01/29] Removed finna-video-element, adjusted styles alot, created finna-video component --- themes/finna2/js/finna-image-paginator.js | 5 - themes/finna2/js/finna-video-element.js | 339 ------------------ themes/finna2/js/finna-video-player.js | 163 +++++++++ themes/finna2/js/finna.js | 3 +- themes/finna2/scss/finna/video-player.scss | 83 +++-- themes/finna2/scss/global/variables.scss | 3 + .../DefaultRecord/record-video-player.phtml | 35 +- .../DefaultRecord/result-online-urls.phtml | 61 +--- .../_ui/components/finna-video.phtml | 36 ++ themes/finna2/templates/search/modals.phtml | 1 - themes/finna2/theme.config.php | 2 +- 11 files changed, 274 insertions(+), 457 deletions(-) delete mode 100644 themes/finna2/js/finna-video-element.js create mode 100644 themes/finna2/js/finna-video-player.js create mode 100644 themes/finna2/templates/_ui/components/finna-video.phtml diff --git a/themes/finna2/js/finna-image-paginator.js b/themes/finna2/js/finna-image-paginator.js index 8c6aad35860..1e09e12f2f7 100644 --- a/themes/finna2/js/finna-image-paginator.js +++ b/themes/finna2/js/finna-image-paginator.js @@ -726,9 +726,6 @@ FinnaPaginator.prototype.loadImageInformation = function loadImageInformation() if (typeof $('.open-link a').attr('href') !== 'undefined') { _.setDimensions(); } - _.popup.collapseArea.find('finna-video').on('click', () => { - _.setCanvasElement('video'); - }); if ($('.imagepopup-holder .feedback-record')[0] || $('.imagepopup-holder .save-record')[0]) { $('.imagepopup-holder .feedback-record, .imagepopup-holder .save-record').on('click', function onClickActionLink(/*e*/) { $.fn.finnaPopup.closeOpen(); @@ -883,10 +880,8 @@ FinnaPaginator.prototype.createPopupObject = function createPopupObject(popup) { _.canvasElements = { leaflet: popup.find('.leaflet-map-image'), noZoom: popup.find('.popup-nonzoom'), - video: popup.find('.popup-video') }; _.canvasElements.leaflet.attr('id', 'leaflet-map-image'); - _.canvasElements.video.attr('id', 'video-player'); if (_.images.length < 2) { _.popup.covers.parent().hide(); _.popup.leftBrowseBtn.hide(); diff --git a/themes/finna2/js/finna-video-element.js b/themes/finna2/js/finna-video-element.js deleted file mode 100644 index e81d7139a07..00000000000 --- a/themes/finna2/js/finna-video-element.js +++ /dev/null @@ -1,339 +0,0 @@ -/* global VuFind, finna */ - -class VideoElement extends HTMLElement { - - /** - * Get the type of the video, iFrame | video - * @returns {string} Video type - */ - get type() { - return (this.getAttribute('type') || '').toLowerCase(); - } - - /** - * Set the type of the video, iFrame | video - * @param {string} value iFrame | video - */ - set type(value) { - this.setAttribute('type', value); - } - - /** - * Get the parent element to which the video player is being embedded into. - * @returns {string|undefined} Parent element id or undefined - */ - get embedParent() { - return this.getAttribute('embed-parent') || undefined; - } - - /** - * Set the parent element to which the video player is being embedded into. - * Omit to display in a new popup. - * @param {string|undefined} value Parent element id or undefined. - */ - set embedParent(value) { - this.setAttribute('embed-parent', value); - } - - /** - * Get the source of the video. - * @returns {string} Source of the video - */ - get source() { - return this.getAttribute('source') || ''; - } - - /** - * Set the source of the video. - * @param {string} value The video source - */ - set source(value) { - this.setAttribute('source', value); - } - - /** - * Get the video sources as an object. - * @returns {object} Object containing video sources - */ - get videoSources() { - return this.getAttribute('video-sources') ? JSON.parse(this.getAttribute('video-sources')) : {}; - } - - /** - * Set the video sources as an object. - * @param {object} value Video sources object - */ - set videoSources(value) { - this.setAttribute('video-sources', JSON.stringify(value || {})); - } - - /** - * Get the poster url to display in viewer. - * @returns {string} Poster url - */ - get posterUrl() { - return this.getAttribute('poster-url') || ''; - } - - /** - * Set the poster url to display in viewer. - * @param {string} value Poster url - */ - set posterUrl(value) { - this.setAttribute('poster-url', value); - } - - /** - * Get the identity for the popup group. - * @returns {string} Id of the popup group - */ - get popupId() { - return this.getAttribute('popup-id') || ''; - } - - /** - * Set the identity for the popup group. - * @param {string} value Id of the popup group - */ - set popupId(value) { - this.setAttribute('popup-id', value); - } - - /** - * Get consent service required for the video. - * @returns {string} Consent service accepted to display this video - */ - get consentService() { - return this.getAttribute('consent-service') || ''; - } - - /** - * Get consent service required for the video. - * @param {string} value Consent service accepted to display this video - */ - set consentService(value) { - this.setAttribute('consent-service', value); - } - - /** - * Get consent categories required for the video. - * @returns {string} Consent categories - */ - get consentCategories() { - return this.getAttribute('consent-categories') || ''; - } - - /** - * Set consent categories required for the video. - * @param {string} value Consent categories - */ - set consentCategories(value) { - this.setAttribute('consent-categories', value); - } - - /** - * Get index. - * @returns {string} Index of this video element - */ - get index() { - return this.getAttribute('index') || ''; - } - - /** - * Set index. - * @param {number} value Value - */ - set index(value) { - this.setAttribute('index', value); - } - - /** - * Get if the video should be activated on load. - * return true if the value is 'true' as a string. - * @returns {boolean} Is active? - */ - get active() { - return this.getAttribute('active') === 'true'; - } - - /** - * Set if the video should be activated on load. - * @param {boolean} value Is active? - */ - set active(value) { - this.setAttribute('active', value); - } - - /** - * Constructor - */ - constructor() { - super(); - this.modals = { - video: ``, - iframe: ``, - audio: `
- -
` - }; - - this.translations = { - close: VuFind.translate('close'), - next: VuFind.translate('Next Record'), - previous: VuFind.translate('Previous Record'), - }; - this.scripts = { - 'videojs': 'vendor/video.min.js', - 'video-popup': 'finna-video-popup.js' - }; - this.subScripts = { - 'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js', - 'videojs-quality': 'vendor/videojs-contrib-quality-levels.js', - 'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js', - }; - } - - /** - * Called after consent settings have been initialized. - */ - onConsentInitialized() { - // Check if this video is inside a record - const record = this.closest('div.record'); - const self = this; - let classes = 'video-popup'; - let modal = this.modals.video; - switch (this.type) { - case 'iframe': - classes = 'finna-iframe'; - modal = this.modals.iframe; - break; - case 'audio': - classes = 'finna-audio'; - modal = this.modals.audio; - break; - } - const popupSettings = { - id: this.popupId, - modal: modal, - cycle: typeof this.embedParent !== 'undefined', - classes: classes, - parent: this.embedParent, - translations: this.translations, - onPopupInit: (t) => { - if (this.embedParent) { - t.removeClass('active-video'); - } - }, - onPopupOpen: function onPopupOpen() { - if (!self.hasConsent) { - return; - } - if (record) { - const warnings - = record.querySelector(`.video-warning[data-index="${self.index}"]`); - if (this.parent) { - record.querySelectorAll('.active-video').forEach(v => { - v.classList.remove('active-video'); - }); - record.querySelectorAll('.video-warning').forEach(v => { - if (v.dataset.index !== self.index) { - v.classList.add('hidden'); - } else { - v.classList.remove('hidden'); - VuFind.observerManager.observe( - 'LazyImages', - v.querySelectorAll('img[data-src]') - ); - } - }); - this.currentTrigger().addClass('active-video'); - } else { - this.content.css('height', '100%'); - if (warnings) { - const clone = warnings.cloneNode(true); - clone.classList.remove('hidden'); - this.modalHolder.append(clone); - VuFind.observerManager.observe( - 'LazyImages', - clone.querySelectorAll('img[data-src]') - ); - setTimeout(function startFade() { - $(clone).fadeOut(2000); - }, 3000); - } - } - } - switch (self.type) { - case 'video': - finna.scriptLoader.loadInOrder(self.scripts, self.subScripts, () => { - finna.videoPopup.initVideoJs('.video-popup', self.videoSources, self.posterUrl); - }); - break; - case 'iframe': - // If using Chrome + VoiceOver, Chrome crashes if vimeo player video settings button has aria-haspopup=true - document.querySelectorAll('.vp-prefs .js-prefs').forEach(e => { - e.setAttribute('aria-haspopup', false); - }); - this.content.find('iframe').attr('src', this.adjustEmbedLink(self.source)); - break; - case 'audio': - this.content.css('height', '100%'); - this.content.find('audio').attr('src', self.source); - break; - default: - console.warn(`Unknown video type in video element: ${self.type}`); - break; - } - } - }; - this.hasConsent = this.type === 'iframe' - ? VuFind.cookie.isServiceAllowed(this.consentService) - : true; - if (!this.hasConsent) { - finna.scriptLoader.load( - {'cookie-consent': 'finna-cookie-consent-element.js'}, - () => { - const consentModal = document.createElement('finna-consent'); - consentModal.consentCategories = this.consentCategories; - consentModal.serviceUrl = this.source; - - popupSettings.modal = consentModal; - $(this).finnaPopup(popupSettings); - if (this.active) { - this.click(); - } - } - ); - } else { - $(this).finnaPopup(popupSettings); - if (this.active) { - this.click(); - } - } - } - - /** - * When the element is added to the dom - */ - connectedCallback() { - // Wait for the cookie consent to be initialized - if (VuFind.cookie.getConsentConfig() === null) { - VuFind.listen('cookie-consent-initialized', () => this.onConsentInitialized(), {once: true}); - } else { - this.onConsentInitialized(); - } - } - - - /** - * When the element is removed from the dom - */ - disconnectedCallback() { - $(this).trigger('removeclick.finna'); - } -} - -customElements.define('finna-video', VideoElement); diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js new file mode 100644 index 00000000000..da4dd737f6e --- /dev/null +++ b/themes/finna2/js/finna-video-player.js @@ -0,0 +1,163 @@ +/* global VuFind, finna */ +finna.videoPlayer = (() => { + + /** + * These scripts must be loaded first for additional scripts requested to work properly + * @member {object} requiredVideoScripts + */ + const requiredVideoScripts = { + 'videojs': 'vendor/video.min.js', + 'video-popup': 'finna-video-popup.js' + }; + + /** + * Additional scripts required to load when requesting video with videojs + * @member {object} additionalVideoScripts + */ + const additionalVideoScripts = { + 'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js', + 'videojs-quality': 'vendor/videojs-contrib-quality-levels.js', + 'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js', + }; + + /** + * Is the module already initialized? + * @member {boolean} + */ + let initialized = false; + + /** + * Adds a specific class name to VuFind modal and a listener to listen when + * the modal is closed to remove the class name. + * @param {string} className Classname to set for opened modal. + */ + function overrideModalClass(className) + { + const container = document.getElementById('modal'); + if (container) { + container.classList.add(className); + VuFind.listen('lightbox.closed', () => { + container.classList.remove(className); + }, {once: true}); + } + } + + /** + * Display warning icons when using inline video setting set to true. + * @param {HTMLElement} element Element clicked + */ + function showWarningIcons(element) + { + // Open video warnings in inline videos + document.querySelectorAll('.warnings-wrapper .video-warning').forEach(warning => { + finna.getPromise('lazyImages').then(() => { + VuFind.observerManager.observe( + 'LazyImages', + warning.querySelectorAll('img[data-src]') + ); + }); + warning.classList.toggle('hidden', element.dataset.vcIndex !== warning.dataset.index); + }); + } + + /** + * When a video button which uses videojs has been requested. + * @param {HTMLElement} element Element clicked + */ + function onVideoOpen(element) + { + const videoPlayer = document.createElement('video'); + videoPlayer.className = 'video-js vjs-big-play-centered video-popup'; + videoPlayer.controls = ''; + + // Is the video inline video or popup video + let container; + if (element.dataset.vcInline) { + container = document.getElementById('inline-video'); + container.replaceChildren(videoPlayer); + showWarningIcons(element); + } else { + // Try to close any open finna popups so the video can be shown properly + $.fn.finnaPopup.closeOpen(); + VuFind.lightbox.render(videoPlayer.outerHTML); + overrideModalClass('finna-video-modal'); + container = document.getElementById('modal'); + } + const videoSources = JSON.parse(element.dataset.videoSources); + finna.videoPopup.initVideoJs(container, videoSources, element.dataset.posterUrl); + } + + /** + * When a video which uses iframe has been requested. + * @param {HTMLElement} element Element clicked + */ + function onIFrameOpen(element) + { + const iFrame = document.createElement('iframe'); + iFrame.className = 'player'; + iFrame.frameborder = 0; + iFrame.allowFullscreen = 'true'; + iFrame.src = element.dataset.vcUrl; + if (element.dataset.vcInline) { + const container = document.getElementById('inline-video'); + container.replaceChildren(iFrame); + showWarningIcons(element); + } else { + // Try to close any open finna popups so the video can be shown properly + $.fn.finnaPopup.closeOpen(); + VuFind.lightbox.render(iFrame.outerHTML); + overrideModalClass('finna-iframe-modal'); + } + } + + /** + * Provide a selector or HTMLButtonElement to initialize a button for videos. + * @param {HTMLButtonElement|string} elementOrSelector Element or selector + */ + function initVideoButton(elementOrSelector) + { + const element = typeof elementOrSelector === 'string' + ? document.querySelector(elementOrSelector) + : elementOrSelector; + if (!element) { + return; + } + if (element.classList.contains('done')) { + return; + } + element.classList.add('done'); + finna.getPromise('videoScripts').then(() => { + element.addEventListener('click', () => { + document.querySelectorAll('.vc-finna-video').forEach(b => b.classList.remove('active-video')); + if (element.dataset.vcEmbed) { + onIFrameOpen(element); + } else { + onVideoOpen(element); + } + element.classList.add('active-video'); + }); + if (element.classList.contains('active-video')) { + element.click(); + } + }); + } + + /** + * Initialize the module + */ + function init() + { + if (initialized) { + return; + } + initialized = true; + finna.scriptLoader.loadInOrder(requiredVideoScripts, additionalVideoScripts, () => { + finna.resolvePromise('videoScripts'); + }); + } + + return { + initVideoButton, + init + }; +})(); \ No newline at end of file diff --git a/themes/finna2/js/finna.js b/themes/finna2/js/finna.js index 5ab6a4c2dba..3c4da79be19 100644 --- a/themes/finna2/js/finna.js +++ b/themes/finna2/js/finna.js @@ -13,7 +13,8 @@ var finna = (function finnaModule() { * @member {object} */ let promises = { - lazyImages: new Promise((resolve) => { resolves.lazyImages = resolve; }) + lazyImages: new Promise((resolve) => { resolves.lazyImages = resolve; }), + videoScripts: new Promise((resolve) => { resolves.videoScripts = resolve; }) }; var my = { diff --git a/themes/finna2/scss/finna/video-player.scss b/themes/finna2/scss/finna/video-player.scss index 7983cfe6353..461f7289269 100644 --- a/themes/finna2/scss/finna/video-player.scss +++ b/themes/finna2/scss/finna/video-player.scss @@ -28,28 +28,24 @@ height: 100%; width: 100%; } -.inline-video, .video-popup, .finna-iframe { - &.finna-popup { - display: flex; - align-content: center; - justify-content: center; - .popup-iframe-wrapper { - // These values are from examples in https://icareus.fi/videosoittimet/ - position: relative; - width: 100%; - // This padding-top value prevents the player from overflowing - padding-top: 56.25%; - margin: auto; - iframe { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - } - } + +// Template for using proper styles, when appending video viewer inside an element. +%iframe-centering-template { + height: $inline-video-player-desktop-height; + @media (max-width: $screen-sm-max) { + height: $inline-video-player-mobile-height; + } + display: flex; + align-items: center; + justify-content: center; + > iframe, > .video-js { + // This aspect ratio is result of division from 560/315, which is default aspect-ratio notified in icareus website + // It works miracles and using position absolute with padding is nightmare. + aspect-ratio: 1.7778/1; + height: 100%; + } } + .inline-video-container { width: 100vw; position: relative; @@ -58,13 +54,7 @@ background-color: black; margin-bottom: 10px; .inline-video { - .embed-responsive-item { - height: 100%; - width: 100%; - } - .finna-iframe.modal-holder { - max-width: 800px; - } + @extend %iframe-centering-template; } .video-accordion { padding-top: 15px; @@ -126,14 +116,6 @@ } } -finna-video { - display: inline-block; - cursor: pointer; - &.btn.btn-link { - padding: 0; - } -} - .vjs-airplay-button { .vjs-icon-placeholder { background: $icon-airplay--default center center no-repeat; @@ -150,3 +132,32 @@ finna-video { } } } + +// Search results specific buttons without any padding to make things look smoother +.btn.btn-link.video-link-container { + padding: 0; +} + +.finna-video-modal, +.finna-iframe-modal { + display: flex !important; + align-items: center; + justify-content: center; + .modal-dialog { + flex: 1 1 auto; + .modal-content { + width: 100%; + height: 100%; + background-color: transparent; + box-shadow: none; + border: none; + > button.close { + right: -20px; + top: -20px; + } + .modal-body { + @extend %iframe-centering-template; + } + } + } +} diff --git a/themes/finna2/scss/global/variables.scss b/themes/finna2/scss/global/variables.scss index 365cf3da4a1..848ad214a46 100644 --- a/themes/finna2/scss/global/variables.scss +++ b/themes/finna2/scss/global/variables.scss @@ -350,3 +350,6 @@ $item-status-color-available: $success !default; $item-status-color-unavailable: $danger !default; $item-status-color-unknown: $warning !default; $item-status-color-uncertain: $warning !default; + +$inline-video-player-desktop-height: 400px; +$inline-video-player-mobile-height: 200px; diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml index 6f6b0766f0e..f40877b7c7b 100644 --- a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml +++ b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml @@ -57,31 +57,18 @@ $desc = $embeddedVideo === 'data-embed-iframe' ? $this->translate('format_Video') : $desc; break; } - - $popupId = ($embeddedVideo ?? '') === 'data-embed-iframe' ? 'finna-iframe' : 'finna-video'; - $type = ($embeddedVideo ?? '') === 'data-embed-iframe' ? 'iFrame' : 'video'; ?> - - embed-parent="inline-video" - active ="" - - - video-sources="" - - > - icon('video-play', 'video-play-icon') ?> - escapeHtml($this->truncate(ucfirst($desc), 30))?> - + component('finna-video', [ + 'url' => $url, + 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']), + 'index' => $i, + 'videoSources' => $url['videoSources'] ?? [], + 'description' => $desc, + 'inline' => $inlineVideo, + 'active' => $inlineVideo && $i === 0, + ]); + ?>
diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml index fec5f5eeb91..172f8a55008 100644 --- a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml +++ b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml @@ -50,13 +50,9 @@ ?> $url): ?> $maxLinkCount)) { continue; } - - if ('popup' === $context && ++$linkCount > $maxLinkCount) { - break; - } ?> 0 ? '
' : ''?> $this->proxyUrl($url['url']), 'title' => $url['url'], ]; - switch ($url['embed'] ?? '') { - case 'video': - $currentUrl['data-embed-video'] = ''; - break; - case 'iframe': - $currentUrl['data-embed-iframe'] = ''; - break; - default: - if ($this->recordLinker()->getEmbeddedVideo($url['url']) == 'data-embed-iframe') { - $url['embed'] = 'iframe'; - $currentUrl['data-embed-iframe'] = ''; - } - break; - } - $desc = $url['desc'] ?? $url['url']; - if ($desc === $url['url']) { - $desc = $this->truncateUrl($desc); - } + $desc = !empty($url['desc']) ? $url['desc'] : $this->truncateUrl($url['url']); ?> - component('finna-video', [ + 'url' => $url, + 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']), + 'index' => $i, + 'videoSources' => $url['videoSources'] ?? [], + 'description' => $desc, + 'class' => 'btn btn-link video-link-container', + ]); ?> - - embed-parent="video-player" - - - video-sources="" - - > - icon('video-play') ?> - escapeHtml($this->truncate(ucfirst($desc), 30))?> - htmlElement()->getAttributes($currentUrl, 'url-base') ?>>icon('external-link') ?> transEsc('default::link_' . $desc, null, $desc)?> diff --git a/themes/finna2/templates/_ui/components/finna-video.phtml b/themes/finna2/templates/_ui/components/finna-video.phtml new file mode 100644 index 00000000000..6463ae59758 --- /dev/null +++ b/themes/finna2/templates/_ui/components/finna-video.phtml @@ -0,0 +1,36 @@ +url['url'] ?? $this->url; + $embeddedVideo = $this->embed === 'data-embed-iframe' ?: false; + $videoIndex = $this->index; + $active = $this->active ?? false; + $description = $this->description ?? $this->translate('format_Video'); + $videoSources = $this->videoSources ?? []; + $buttonAttributes = [ + 'class' => $this->class ?? 'video-link-container', + 'aria-label' => $this->ariaLabel ?? $this->translate('Link to video') . ' ' . $description, + 'title' => $url, + 'data-vc-url' => $url, + 'data-vc-invocation' => $this->_invocation, + 'data-vc-index' => $this->index ?? -1, + 'data-vc-inline' => $this->inline ?? false, + 'data-vc-embed' => $embeddedVideo, + 'aria-haspopup' => true, + ]; + $buttonAttributes['class'] .= ' ' . $this->_componentClass; + if ($active) { + $buttonAttributes['class'] .= ' active-video'; + } + // Generate unique selector for the button + $jsSelector = '.' . str_replace(' ', '.', $buttonAttributes['class']) . "[data-vc-invocation=\"$this->_invocation\"]"; +?> + + +inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET');?> diff --git a/themes/finna2/templates/search/modals.phtml b/themes/finna2/templates/search/modals.phtml index 07d5f8ace8b..f66a3474bad 100644 --- a/themes/finna2/templates/search/modals.phtml +++ b/themes/finna2/templates/search/modals.phtml @@ -30,7 +30,6 @@
-
diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php index 4c88ff4cb62..34935c8e2fe 100644 --- a/themes/finna2/theme.config.php +++ b/themes/finna2/theme.config.php @@ -259,7 +259,7 @@ 'vendor/cally.iife.js', 'finna-multiselect.js', 'finna-model-viewer.js', - 'finna-video-element.js', + 'finna-video-player.js', 'finna-feed-element.js', 'finna-carousel-manager.js', 'finna-select-a11y.js', From 3d057b267f5492cc896ad17c5be169b75c551758 Mon Sep 17 00:00:00 2001 From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:57:37 +0200 Subject: [PATCH 02/29] Remove finna-cookie-consent element, used lightbox more --- .../finna2/js/finna-cookie-consent-element.js | 116 ------------------ themes/finna2/js/finna-video-player.js | 69 +++++++++-- .../finna2/scss/finna/embedded-content.scss | 10 +- .../_ui/components/finna-video.phtml | 3 +- themes/finna2/templates/search/modals.phtml | 17 +++ 5 files changed, 82 insertions(+), 133 deletions(-) delete mode 100644 themes/finna2/js/finna-cookie-consent-element.js diff --git a/themes/finna2/js/finna-cookie-consent-element.js b/themes/finna2/js/finna-cookie-consent-element.js deleted file mode 100644 index 2e4416cf5b3..00000000000 --- a/themes/finna2/js/finna-cookie-consent-element.js +++ /dev/null @@ -1,116 +0,0 @@ -/* global VuFind, CookieConsent */ -class FinnaCookieConsentElement extends HTMLElement { - - /** - * Get consent categories - * @returns {string} Consent categories - */ - get consentCategories() { - return this.getAttribute('consent-categories') || ''; - } - - /** - * Set consent categories - * @param {string} newValue Value to set - */ - set consentCategories(newValue) { - this.setAttribute('consent-categories', newValue); - } - - /** - * Get service base URL - * @returns {string} Service base URL - */ - get serviceBaseUrl() { - const url = this.getAttribute('service-url'); - if (url) { - try { - return new URL(url).host; - } catch (_) { - return url; - } - } - return ''; - } - - /** - * Get service URL - * @returns {string} Service URL - */ - get serviceUrl() { - return this.getAttribute('service-url') || ''; - } - - /** - * Set service URL - * @param {string} newValue Value to set - */ - set serviceUrl(newValue) { - this.setAttribute('service-url', newValue); - } - - /** - * Constructor - */ - constructor() { - super(); - } - - /** - * When the element is added to the dom - */ - connectedCallback() { - // Create the element - const divInfo = document.createElement('div'); - divInfo.classList.add('embedded-content-cookie-info'); - - const divHeading = document.createElement('div'); - divHeading.classList.add('embedded-content-heading'); - divHeading.append(VuFind.translate('embedded_content_heading')); - divInfo.append(divHeading); - - const divDescription = document.createElement('div'); - divDescription.classList.add('embedded-content-description'); - const replacements = { - '%%serviceBaseUrl%%': this.serviceBaseUrl, - '%%consentCategories%%': this.consentCategories - }; - divDescription.append(VuFind.translate('embedded_content_description', replacements)); - divInfo.append(divDescription); - - const divActions = document.createElement('div'); - divActions.classList.add('embedded-content-actions'); - - const aOuterLink = document.createElement('a'); - aOuterLink.classList.add('btn', 'btn-primary'); - aOuterLink.href = this.serviceUrl || ''; - aOuterLink.target = '_blank'; - aOuterLink.append(VuFind.translate('embedded_content_external_link')); - - const linkIcon = document.createElement('i'); - linkIcon.classList.add('fa', 'fa-new-window'); - linkIcon.setAttribute('aria-hidden', true); - aOuterLink.append(' ', linkIcon); - - const linkSpan = document.createElement('span'); - linkSpan.classList.add('sr-only'); - linkSpan.append(VuFind.translate('Open in a new window')); - aOuterLink.append(linkSpan); - divActions.append(aOuterLink); - - const aShowModal = document.createElement('a'); - aShowModal.classList.add('btn', 'btn-default'); - aShowModal.href = '#'; - aShowModal.setAttribute('aria-haspopup', 'dialog'); - aShowModal.append(VuFind.translate('Cookie Settings')); - aShowModal.addEventListener('click', () => { - $.fn.finnaPopup.closeOpen(); - CookieConsent.showPreferences(); - }); - divActions.append(aShowModal); - divInfo.append(divActions); - this.append(divInfo); - } -} - -customElements.define('finna-consent', FinnaCookieConsentElement); diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js index da4dd737f6e..4980f609864 100644 --- a/themes/finna2/js/finna-video-player.js +++ b/themes/finna2/js/finna-video-player.js @@ -1,4 +1,4 @@ -/* global VuFind, finna */ +/* global VuFind, finna, CookieConsent */ finna.videoPlayer = (() => { /** @@ -110,6 +110,41 @@ finna.videoPlayer = (() => { } } + /** + * Display a consent window warning for the user. + * @param {HTMLElement} element The element which was clicked + */ + function displayConsentWindow(element) + { + const consentModal = document.getElementById('consent-modal'); + if (consentModal) { + const cloned = consentModal.content.cloneNode(true); + const wrapper = document.createElement('div'); + wrapper.className = 'embedded-content-placeholder'; + wrapper.append(cloned); + // Replace %%consentCategories%% and %%serviceBaseUrl%% with proper values + const externalLink = wrapper.querySelector('.embedded-content-actions a[href="%%HREF%%"]'); + if (externalLink) { + externalLink.setAttribute('href', element.dataset.vcUrl); + } + const description = wrapper.querySelector('.embedded-content-description'); + if (description) { + const serviceBase = new URL(element.dataset.vcUrl); + description.innerText = description.innerText.replace('%%consentCategories%%', element.dataset.vcConsent).replace('%%serviceBaseUrl%%', serviceBase.hostname); + } + VuFind.lightbox.render(wrapper.outerHTML); + overrideModalClass('finna-consent-modal'); + const ccPreferences = document.getElementById('modal').querySelector('.embedded-content-actions button'); + if (ccPreferences) { + // Set cookie consent preferences event after the modal has been initialized so it properly initialized + ccPreferences.addEventListener('click', () => { + VuFind.modal('hide'); + CookieConsent.showPreferences(); + }); + } + } + } + /** * Provide a selector or HTMLButtonElement to initialize a button for videos. * @param {HTMLButtonElement|string} elementOrSelector Element or selector @@ -126,20 +161,28 @@ finna.videoPlayer = (() => { return; } element.classList.add('done'); - finna.getPromise('videoScripts').then(() => { - element.addEventListener('click', () => { - document.querySelectorAll('.vc-finna-video').forEach(b => b.classList.remove('active-video')); - if (element.dataset.vcEmbed) { - onIFrameOpen(element); - } else { - onVideoOpen(element); - } - element.classList.add('active-video'); - }); - if (element.classList.contains('active-video')) { - element.click(); + VuFind.listen('cookie-consent-initialized', () => { + if (VuFind.cookie.isServiceAllowed(element.dataset.vcConsent)) { + finna.getPromise('videoScripts').then(() => { + element.addEventListener('click', () => { + document.querySelectorAll('.vc-finna-video').forEach(b => b.classList.remove('active-video')); + if (element.dataset.vcEmbed) { + onIFrameOpen(element); + } else { + onVideoOpen(element); + } + element.classList.add('active-video'); + }); + if (element.classList.contains('active-video')) { + element.click(); + } + }); + } else { + // We should display a consent approval here + element.addEventListener('click', () => { displayConsentWindow(element); }); } }); + } /** diff --git a/themes/finna2/scss/finna/embedded-content.scss b/themes/finna2/scss/finna/embedded-content.scss index 0419be2c96f..a238df79fde 100644 --- a/themes/finna2/scss/finna/embedded-content.scss +++ b/themes/finna2/scss/finna/embedded-content.scss @@ -1,4 +1,4 @@ -finna-consent, .embedded-content-placeholder { +.embedded-content-placeholder { background: black; color: white; display: flex; @@ -19,6 +19,10 @@ finna-consent, .embedded-content-placeholder { } } -finna-consent { - height: 100%; +#modal.finna-consent-modal { + .modal-content { + box-shadow: none; + background-color: transparent; + border: none; + } } diff --git a/themes/finna2/templates/_ui/components/finna-video.phtml b/themes/finna2/templates/_ui/components/finna-video.phtml index 6463ae59758..4164fd6b61a 100644 --- a/themes/finna2/templates/_ui/components/finna-video.phtml +++ b/themes/finna2/templates/_ui/components/finna-video.phtml @@ -14,6 +14,7 @@ 'data-vc-index' => $this->index ?? -1, 'data-vc-inline' => $this->inline ?? false, 'data-vc-embed' => $embeddedVideo, + 'data-vc-consent' => $this->consent ?? 'recordvideo', 'aria-haspopup' => true, ]; $buttonAttributes['class'] .= ' ' . $this->_componentClass; @@ -30,7 +31,7 @@ {finna.videoPlayer.initVideoButton('{$jsSelector}');}); JS; ?> inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET');?> diff --git a/themes/finna2/templates/search/modals.phtml b/themes/finna2/templates/search/modals.phtml index f66a3474bad..6241474ecb3 100644 --- a/themes/finna2/templates/search/modals.phtml +++ b/themes/finna2/templates/search/modals.phtml @@ -49,4 +49,21 @@
+ + From 2ea67a0111733e7a4beff8da430540084f7a0031 Mon Sep 17 00:00:00 2001 From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:51:25 +0200 Subject: [PATCH 03/29] Small tuning --- themes/finna2/js/finna-video-player.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js index 4980f609864..79b7b0637eb 100644 --- a/themes/finna2/js/finna-video-player.js +++ b/themes/finna2/js/finna-video-player.js @@ -130,13 +130,16 @@ finna.videoPlayer = (() => { const description = wrapper.querySelector('.embedded-content-description'); if (description) { const serviceBase = new URL(element.dataset.vcUrl); - description.innerText = description.innerText.replace('%%consentCategories%%', element.dataset.vcConsent).replace('%%serviceBaseUrl%%', serviceBase.hostname); + description.innerText = description.innerText + .replace('%%consentCategories%%', element.dataset.vcConsent) + .replace('%%serviceBaseUrl%%', serviceBase.hostname); } VuFind.lightbox.render(wrapper.outerHTML); overrideModalClass('finna-consent-modal'); const ccPreferences = document.getElementById('modal').querySelector('.embedded-content-actions button'); if (ccPreferences) { - // Set cookie consent preferences event after the modal has been initialized so it properly initialized + // Set cookie consent preferences event after the modal has been initialized as the lightbox handles elements + // as a string, so it loses all the events applied before rendering ccPreferences.addEventListener('click', () => { VuFind.modal('hide'); CookieConsent.showPreferences(); @@ -182,7 +185,6 @@ finna.videoPlayer = (() => { element.addEventListener('click', () => { displayConsentWindow(element); }); } }); - } /** @@ -203,4 +205,4 @@ finna.videoPlayer = (() => { initVideoButton, init }; -})(); \ No newline at end of file +})(); From 41e3f7d868e3a1c585722701b60c37695b1b2e4a Mon Sep 17 00:00:00 2001 From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com> Date: Tue, 18 Feb 2025 09:09:15 +0200 Subject: [PATCH 04/29] Added finna prefix to the modal template --- themes/finna2/js/finna-video-player.js | 4 +++- themes/finna2/templates/search/modals.phtml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js index 79b7b0637eb..3e20e7a09ff 100644 --- a/themes/finna2/js/finna-video-player.js +++ b/themes/finna2/js/finna-video-player.js @@ -116,8 +116,10 @@ finna.videoPlayer = (() => { */ function displayConsentWindow(element) { - const consentModal = document.getElementById('consent-modal'); + const consentModal = document.getElementById('finna-consent-modal'); if (consentModal) { + // Append the cloned element, as templates return a DocumentFragment instead of node, which does not work + // if outerHTML is called. const cloned = consentModal.content.cloneNode(true); const wrapper = document.createElement('div'); wrapper.className = 'embedded-content-placeholder'; diff --git a/themes/finna2/templates/search/modals.phtml b/themes/finna2/templates/search/modals.phtml index 6241474ecb3..5e5e22f0078 100644 --- a/themes/finna2/templates/search/modals.phtml +++ b/themes/finna2/templates/search/modals.phtml @@ -50,7 +50,7 @@ -