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 ea8e0fe907a..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('visually-hidden'); - 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-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 8fcf9da0213..00000000000 --- a/themes/finna2/js/finna-video-element.js +++ /dev/null @@ -1,347 +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.load( - self.scripts, - () => { - finna.scriptLoader.load( - 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..c8cd004917f --- /dev/null +++ b/themes/finna2/js/finna-video-player.js @@ -0,0 +1,238 @@ +/* global VuFind, finna, CookieConsent */ +finna.videoPlayer = (() => { + + /** + * Scripts to load in order + * @member {object} requiredVideoScripts + */ + const requiredVideoScripts = { + 'videojs': 'vendor/video.min.js', + }; + + /** + * Scripts that depend on the videojs script + * @member {object} dependentVideoScripts + */ + const dependentVideoScripts = { + 'video-popup': 'finna-video-popup.js', + 'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js', + 'videojs-quality': 'vendor/videojs-contrib-quality-levels.js', + 'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js', + }; + + /** + * 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) { + const dialog = container.querySelector('.modal-dialog'); + if (dialog) { + container.classList.add(className); + dialog.classList.add('modal-dialog-centered'); + VuFind.listen('lightbox.closed', () => { + dialog.classList.remove('modal-dialog-centered'); + container.classList.remove(className); + }, {once: true}); + } + } + } + + /** + * Display warning icons when inline video setting is enabled. + * @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.index !== 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.inline) { + 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.url; + if (element.dataset.inline) { + 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'); + } + } + + /** + * Display a cookie consent window warning for the user. + * @param {HTMLElement} element The element which was clicked + */ + function displayConsentWindow(element) + { + const consentModal = document.getElementById('finna-consent-modal-template'); + 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'; + 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.url); + } + const description = wrapper.querySelector('.embedded-content-description'); + if (description) { + const serviceBase = new URL(element.dataset.url); + description.innerText = description.innerText + .replace('%%consentCategories%%', element.dataset.consentTitle) + .replace('%%serviceBaseUrl%%', serviceBase.hostname); + } + let consentHolder; + if (element.dataset.inline) { + consentHolder = document.getElementById('inline-video'); + consentHolder.replaceChildren(wrapper); + } else { + VuFind.lightbox.render(wrapper.outerHTML); + overrideModalClass('finna-consent-modal'); + consentHolder = document.getElementById('modal'); + } + const ccPreferences = consentHolder.querySelector('.embedded-content-actions button'); + if (ccPreferences) { + // 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(); + }); + } + } + } + + /** + * Sets the video elements click event. + * @param {HTMLElement} element Element which displays the video popup + */ + function setIFrameStateFromConsent(element) + { + if (VuFind.cookie.isServiceAllowed(element.dataset.consent)) { + element.addEventListener('click', () => { + document.querySelectorAll('.vc-finna-video-button').forEach(b => b.classList.remove('active-video')); + element.classList.add('active-video'); + onIFrameOpen(element); + }); + if (element.classList.contains('active-video')) { + onIFrameOpen(element); + } + return; + } else { + // We should display a consent information instead of the video + element.addEventListener('click', () => { displayConsentWindow(element); }); + if (element.dataset.inline && element.classList.contains('active-video')) { + displayConsentWindow(element); + } + } + } + + /** + * Provide a selector or HTMLButtonElement to initialize a button for embedded videos. + * @param {HTMLButtonElement|string} elementOrSelector Element or selector + */ + function initIFrameButton(elementOrSelector) + { + const element = typeof elementOrSelector === 'string' + ? document.querySelector(elementOrSelector) + : elementOrSelector; + if (!element || element.classList.contains('initialized')) { + return; + } + + const consentInitialized = VuFind.cookie.getConsentConfig(); + // If consent configuration has not been initialized, wait for it + if (!consentInitialized) { + VuFind.listen('cookie-consent-initialized', () => { + setIFrameStateFromConsent(element); + }); + } else { + setIFrameStateFromConsent(element); + } + } + + /** + * Provide a selector or HTMLButtonElement to initialize a button for videos. + * Handles loading the proper scripts. + * @param {HTMLButtonElement|string} elementOrSelector Element or selector + */ + function initVideoButton(elementOrSelector) + { + const element = typeof elementOrSelector === 'string' + ? document.querySelector(elementOrSelector) + : elementOrSelector; + if (!element || element.classList.contains('initialized')) { + return; + } + finna.scriptLoader.load(requiredVideoScripts, () => { + finna.scriptLoader.load(dependentVideoScripts, () => { + element.addEventListener('click', () => { + document.querySelectorAll('.vc-finna-video-button').forEach(b => b.classList.remove('active-video')); + onVideoOpen(element); + }); + if (element.classList.contains('active-video')) { + element.click(); + } + }); + }); + } + + return { + initVideoButton, + initIFrameButton + }; +})(); 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/scss/finna/search.scss b/themes/finna2/scss/finna/search.scss index 2a67596a793..de67927ad3b 100644 --- a/themes/finna2/scss/finna/search.scss +++ b/themes/finna2/scss/finna/search.scss @@ -145,6 +145,7 @@ mark, .highlight { font-weight: 400; clear: both; word-break: break-all; + text-align: start; .online-source { color: $gray-light; font-size: .9em; diff --git a/themes/finna2/scss/finna/video-player.scss b/themes/finna2/scss/finna/video-player.scss index 8ccd9a0b5eb..748dec50007 100644 --- a/themes/finna2/scss/finna/video-player.scss +++ b/themes/finna2/scss/finna/video-player.scss @@ -28,28 +28,30 @@ 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 { + max-height: $inline-video-player-desktop-height; + @media screen { + @include media-breakpoint-down(md) { + max-height: $inline-video-player-tablet-height; + } + @include media-breakpoint-down(sm) { + max-height: $inline-video-player-mobile-height; } + } + height: 100vh; + 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 +60,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 +122,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 +138,29 @@ finna-video { } } } + +// Search results specific buttons without any padding to make things look smoother +.btn.btn-link.video-link-container { + padding: 0; +} + +#modal.finna-video-modal, +#modal.finna-iframe-modal { + .modal-dialog { + max-width: 1200px; + .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 9b01c0a3e94..275d7367287 100644 --- a/themes/finna2/scss/global/variables.scss +++ b/themes/finna2/scss/global/variables.scss @@ -346,3 +346,7 @@ $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: 600px; +$inline-video-player-tablet-height: 500px; +$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 a15ee9a2aa7..dc3168dd815 100644 --- a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml +++ b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml @@ -58,32 +58,17 @@ $desc = $embeddedVideo === 'data-embed-iframe' ? $this->translate('format_Video') : $text; 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-button', [ + 'url' => $url['url'], + 'embed' => $embeddedVideo === 'data-embed-iframe', + 'index' => $i, + 'videoSources' => $url['videoSources'] ?? [], + 'description' => $desc, + 'inline' => $inlineVideo, + ]); + ?> $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['description'] ?? $url['url']; - if ($desc === $url['url']) { - $desc = $this->truncateUrl($desc); - } + $desc = !empty($url['desc']) ? $url['desc'] : $this->truncateUrl($url['url']); ?> - component('finna-video-button', [ + 'url' => $url['url'], + 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']) === 'data-embed-iframe', + 'index' => $i, + 'videoSources' => $url['videoSources'] ?? [], + 'description' => $desc, + 'class' => "btn btn-link video-link-container $context", + ]); ?> - - embed-parent="video-player" - - - video-sources="" - - > - icon('video-play') ?> - escapeHtml($this->truncate(ucfirst($desc), 30))?> - htmlElement()->getAttributes($currentUrl, 'url-base') ?>>icon($icon) ?> diff --git a/themes/finna2/templates/_ui/components/finna-video-button.phtml b/themes/finna2/templates/_ui/components/finna-video-button.phtml new file mode 100644 index 00000000000..95fa0a360c1 --- /dev/null +++ b/themes/finna2/templates/_ui/components/finna-video-button.phtml @@ -0,0 +1,60 @@ +inline ?? false; + $index = $this->index ?? 0; + $description = $this->description ?? $this->translate('format_Video'); + $videoSources = $this->videoSources ?? []; + $consent = $this->consent ?? 'recordvideo'; + $embeddedVideo = $this->embed ?? false; + $buttonAttributes = [ + 'class' => trim(($this->class ?? 'video-link-container') . " vc-invocation-$this->_invocation $this->_componentClass"), + 'aria-label' => $this->ariaLabel ?? $this->translate('Link to video') . ' ' . $description, + 'data-url' => $this->url ?? '', + 'data-index' => $index, + 'data-inline' => $inlineVideoPlayer, + 'data-embed' => $embeddedVideo, + 'data-consent' => $consent, + 'data-consent-title' => $this->translate($this->cookieConsent()->getCategoryTitleForService($consent)), + 'aria-haspopup' => true, + ]; + + // If inline video player is user and current video index is 0, this video button will be clicked to initialize the player. + // This will not cause the video to start playing. + if ($inlineVideoPlayer && $index === 0) { + $buttonAttributes['class'] .= ' active-video'; + } + // Generate unique selector for the button + $jsSelector = '.' . str_replace(' ', '.', $buttonAttributes['class']); +?> + + { + const embedded = '{$embeddedVideo}'; + if (embedded) { + finna.videoPlayer.initIFrameButton('{$jsSelector}'); + } else { + finna.videoPlayer.initVideoButton('{$jsSelector}'); + } + } + ); + JS; +?> +assetManager()->outputInlineScriptString($script);?> diff --git a/themes/finna2/templates/search/modals.phtml b/themes/finna2/templates/search/modals.phtml index e2e169f2360..98da9a3c51e 100644 --- a/themes/finna2/templates/search/modals.phtml +++ b/themes/finna2/templates/search/modals.phtml @@ -30,7 +30,6 @@
-
@@ -50,4 +49,21 @@
+ + diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php index c009e8abc8e..36f46e6d51b 100644 --- a/themes/finna2/theme.config.php +++ b/themes/finna2/theme.config.php @@ -260,7 +260,6 @@ [ 'file' => 'vendor/cally.iife.js' ], [ 'file' => 'finna-multiselect.js' ], [ 'file' => 'finna-model-viewer.js' ], - [ 'file' => 'finna-video-element.js' ], [ 'file' => 'finna-feed-element.js' ], [ 'file' => 'finna-carousel-manager.js' ], [ 'file' => 'finna-select-a11y.js' ],