diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 545eebed0..63dd0399b 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -333,6 +333,7 @@ class Settings(QtCore.QSettings): 'themes/last directory import': None, 'themes/theme level': ThemeLevel.Global, 'themes/wrap footer': False, + 'themes/item transitions': False, 'user interface/live panel': True, 'user interface/live splitter geometry': QtCore.QByteArray(), 'user interface/lock panel': True, diff --git a/openlp/core/display/html/display.css b/openlp/core/display/html/display.css index 2f37f6323..518594686 100644 --- a/openlp/core/display/html/display.css +++ b/openlp/core/display/html/display.css @@ -22,14 +22,53 @@ sup { font-size: smaller !important; } +body, +.reveal .slides, +.reveal .footer { + transition: none; + z-index: 2; +} + +body.transition, +body.transition .reveal .slides, +body.transition .reveal .footer { + /* Transition speed for content visibility */ + transition: opacity 800ms ease-in-out !important; +} + +.reveal .slide-background { + opacity: 1; + visibility: visible; + transition: none !important; + z-index: 2; + /* important required to override inline attributes */ + background-repeat: no-repeat !important; + background-size: cover !important; + background-position: 50% 50% !important; +} + +.reveal .slide-background:nth-child(n+3) { + /* Hide backgrounds beyond the first 2 (prevents ugly transitions in corner case) */ + visibility: hidden; +} + +.reveal .slide-background.future, +.reveal .slide-background.past { + opacity: 0; +} + +body.transition .reveal .slide-background { + transition: all 800ms ease-in-out !important; +} + .reveal .slides > section, .reveal .slides > section > section { padding: 0; max-height: 100%; } -.reveal .slides > section.text-slides { - /* Need to override reveal styles to use our text aligment */ +.reveal .slides > section { + /* Need to override reveal styles to use our text alignment */ display: flex !important; flex-direction: column; word-wrap: break-word; @@ -318,21 +357,24 @@ sup { .reveal[class*=fade] .slides section.present:not([data-transition]) { transition-delay: 400ms; } -.reveal .slides section.present[data-transition*=fade], +.reveal .slides section.present[data-transition-speed="fast"][data-transition*=fade], .reveal[data-transition-speed="fast"][class*=fade] .slides section.present:not([data-transition]) { transition-delay: 200ms; } -.reveal .slides section.present[data-transition*=fade], +.reveal .slides section.present[data-transition-speed="slow"][data-transition*=fade], .reveal[data-transition-speed="slow"][class*=fade] .slides section.present:not([data-transition]) { transition-delay: 800ms; } -.reveal[class*=fade] .slides section { +.reveal[class*=fade] .slides section, +.reveal .slides section[class*=fade] { transition-duration: 400ms !important; } -.reveal[data-transition-speed="fast"][class*=fade] .slides section { +.reveal[data-transition-speed="fast"][class*=fade] .slides section, +.reveal .slides section[data-transition-speed="fast"][class*=fade] { transition-duration: 200ms !important; } -.reveal[data-transition-speed="slow"][class*=fade] .slides section { +.reveal[data-transition-speed="slow"][class*=fade] .slides section, +.reveal .slides section[data-transition-speed="slow"][class*=fade] { transition-duration: 800ms !important; } .reveal[class*=fade].overview .slides section { diff --git a/openlp/core/display/html/display.js b/openlp/core/display/html/display.js index 67ef7355d..2469f75b8 100644 --- a/openlp/core/display/html/display.js +++ b/openlp/core/display/html/display.js @@ -352,6 +352,9 @@ AudioPlayer.prototype.stop = function () { * The Display object is what we use from OpenLP */ var Display = { + _slidesContainer: null, + _footerContainer: null, + _backgroundsContainer: null, _alerts: [], _slides: {}, _alertSettings: {}, @@ -359,6 +362,8 @@ var Display = { _transitionState: TransitionState.NoTransition, _animationState: AnimationState.NoAnimation, _doTransitions: false, + _doItemTransitions: false, + _themeApplied: true, _revealConfig: { margin: 0.0, minScale: 1.0, @@ -380,9 +385,16 @@ var Display = { /** * Start up reveal and do any other initialisation */ - init: function (doTransitions=false) { + init: function (doTransitions=false, doItemtransitions=false) { + var globalBackground = $("#global-background")[0]; + globalBackground.style.cssText = ""; + globalBackground.style.setProperty("background", "black"); + Display._slidesContainer = $(".slides")[0]; + Display._footerContainer = $(".footer")[0]; + Display._backgroundsContainer = $(".backgrounds")[0]; Display._doTransitions = doTransitions; Reveal.initialize(Display._revealConfig); + Display.setItemTransition(doItemtransitions && doTransitions); displayWatcher.setInitialised(true); }, /** @@ -394,30 +406,79 @@ var Display = { Reveal.slide(0); }, /** - * Set the transition type - * @param {string} transitionType - Can be one of "none", "fade", "slide", "convex", "concave", "zoom" - * @param {string} transitionSpeed - Can be one of "default", "fast", "slow" + * Enable/Disable item transitions */ - setTransition: function (transitionType, transitionSpeed) { - Reveal.configure({"transition": transitionType, "transitionSpeed": transitionSpeed}); + setItemTransition: function (enable) { + Display._doItemTransitions = enable; + var body = $("body")[0]; + if (enable) { + body.classList.add("transition"); + Reveal.configure({"backgroundTransition": "fade", "transitionSpeed": "default"}); + } else { + body.classList.remove("transition"); + Reveal.configure({"backgroundTransition": "none"}); + } }, /** * Clear the current list of slides */ clearSlides: function () { - $(".slides")[0].innerHTML = ""; + Display._slidesContainer.innerHTML = ""; + Display._clearSlidesList(); + }, + /** + * Clear the current list of slides + */ + _clearSlidesList: function () { + Display._footerContainer.innerHTML = ""; Display._slides = {}; }, + /** + * Add new item/slides, replacing the old one + * Clears current list of slides but allows time for a transition animation + * Items are ordered newest to oldest in the slides container + * @param {element} new_slides - New slides to display + * @param {element} is_text - Used to decide if the theme main area constraints should apply + */ + replaceSlides: function (new_slides, is_text=false) { + if (Display._doItemTransitions) { + new_slides.setAttribute('data-transition', "fade"); + new_slides.setAttribute('data-transition-speed', "default"); + } + new_slides.classList.add("future"); + Display.applyTheme(new_slides, is_text); + Display._slidesContainer.prepend(new_slides); + var currentSlide = Reveal.getIndices(); + if (Display._doItemTransitions && Display._slidesContainer.children.length >= 2) { + // Set the slide one section ahead so we'll stay on the old slide after reinit + Reveal.slide(1, currentSlide.v); + Display.reinit(); + // Timeout to allow time to transition before deleting the old slides + setTimeout (Display._removeLastSection, 5000); + } else { + Reveal.slide(0, currentSlide.v); + Reveal.sync(); + Display._removeLastSection(); + } + }, + /** + * Removes the last slides item if there are more than one + */ + _removeLastSection: function () { + if (Display._slidesContainer.children.length > 1) { + Display._slidesContainer.lastChild.remove(); + } + }, /** * Checks if the present slide content fits within the slide */ doesContentFit: function () { var currSlide = $("section.text-slides"); if (currSlide.length === 0) { - currSlide = $(".slides"); + currSlide = Display._footerContainer; + } else { + currSlide = currSlide[0]; } - currSlide = currSlide[0]; - console.debug("scrollHeight: " + currSlide.scrollHeight + ", clientHeight: " + currSlide.clientHeight); return currSlide.clientHeight >= currSlide.scrollHeight; }, /** @@ -426,22 +487,17 @@ var Display = { * @param {string} image - Path to the splash image */ setStartupSplashScreen: function(bg_color, image) { - Display.clearSlides(); - var globalBackground = $("#global-background")[0]; - globalBackground.style.cssText = ""; - globalBackground.style.setProperty("background", bg_color); - var slidesDiv = $(".slides")[0]; + Display._clearSlidesList(); var section = document.createElement("section"); section.setAttribute("id", 0); section.setAttribute("data-background", bg_color); - section.setAttribute("style", "height: 100%; width: 100%; position: relative;"); + section.setAttribute("style", "height: 100%; width: 100%;"); var img = document.createElement('img'); img.src = image; img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-height: 100%; max-width: 100%"); section.appendChild(img); - slidesDiv.appendChild(section); Display._slides['0'] = 0; - Display.reinit(); + Display.replaceSlides(section); }, /** * Set fullscreen image from path @@ -450,10 +506,6 @@ var Display = { */ setFullscreenImage: function(bg_color, image) { Display.clearSlides(); - var globalBackground = $("#global-background")[0]; - globalBackground.style.cssText = ""; - globalBackground.style.setProperty("background", bg_color); - var slidesDiv = $(".slides")[0]; var section = document.createElement("section"); section.setAttribute("id", 0); section.setAttribute("data-background", bg_color); @@ -462,9 +514,8 @@ var Display = { img.src = image; img.setAttribute("style", "height: 100%; width: 100%"); section.appendChild(img); - slidesDiv.appendChild(section); Display._slides['0'] = 0; - Display.reinit(); + Display.replaceSlides(parentSection); }, /** * Set fullscreen image from base64 data @@ -473,10 +524,6 @@ var Display = { */ setFullscreenImageFromData: function(bg_color, image_data) { Display.clearSlides(); - var globalBackground = $("#global-background")[0]; - globalBackground.style.cssText = ""; - globalBackground.style.setProperty("background", bg_color); - var slidesDiv = $(".slides")[0]; var section = document.createElement("section"); section.setAttribute("id", 0); section.setAttribute("data-background", bg_color); @@ -485,7 +532,7 @@ var Display = { img.src = 'data:image/png;base64,' + image_data; img.setAttribute("style", "height: 100%; width: 100%"); section.appendChild(img); - slidesDiv.appendChild(section); + Display._slidesContainer.appendChild(section); Display._slides['0'] = 0; Display.reinit(); }, @@ -632,62 +679,64 @@ var Display = { } }, /** - * Add a slide. If the slide exists but the HTML is different, update the slide. + * Create a text slide. * @param {string} verse - The verse number, e.g. "v1" * @param {string} text - The HTML for the verse, e.g. "line1
line2" - * @param {string} footer_text - The HTML for the footer - * @param {bool} reinit - True if React should reinit when creating a new slide */ - addTextSlide: function (verse, text, footerText, reinit=true) { + _createTextSlide: function (verse, text) { var slide; var html = _prepareText(text); - if (Display._slides.hasOwnProperty(verse)) { - slide = $("#" + verse)[0]; - if (slide.innerHTML != html) { - slide.innerHTML = html; - } - } else { - var parent = $("section.text-slides"); - if (parent.length === 0) { - Display._createTextContainer(); - parent = $("section.text-slides"); - } - parent = parent[0]; - slide = document.createElement("section"); - slide.setAttribute("id", verse); - slide.innerHTML = html; - parent.appendChild(slide); - Display._slides[verse] = parent.children.length - 1; - if (footerText) { - $(".footer")[0].innerHTML = footerText; - } - if (reinit) { - Display.reinit(); - } - } + slide = document.createElement("section"); + slide.setAttribute("id", verse); + slide.innerHTML = html; + return slide; }, /** * Set text slides. * @param {Object[]} slides - A list of slides to add as JS objects: {"verse": "v1", "text": "line 1\nline2"} */ setTextSlides: function (slides) { - Display.clearSlides(); + Display._clearSlidesList(); + var slide_html; + var parentSection = document.createElement("section"); + parentSection.classList = "text-slides"; slides.forEach(function (slide) { - Display.addTextSlide(slide.verse, slide.text, slide.footer, false); + slide_html = Display._createTextSlide(slide.verse, slide.text); + parentSection.appendChild(slide_html); + Display._slides[slide.verse] = parentSection.children.length - 1; + if (slide.footer) { + Display._footerContainer.innerHTML = slide.footer; + } }); - Display.reinit(); + Display.replaceSlides(parentSection, true); }, /** - * Create the
that will contain text slides (vertical slides in react) + * Set a single text slide. This changes the slide with no transition. + * Prevents the need to reapply the theme if only changing content. + * @param String slide - Text to put on the slide */ - _createTextContainer: function () { - var slideContainer = document.createElement("section"); - slideContainer.classList = "text-slides"; - var slidesDiv = $(".slides")[0]; - slidesDiv.appendChild(slideContainer); - // Apply the current theme to the new container - if (!!Display._theme) { - Display.setTheme(Display._theme); + setTextSlide: function (text) { + if (Display._slides.hasOwnProperty("test-slide") && Object.keys(Display._slides).length === 1) { + var slide = $("#" + "test-slide")[0]; + var html = _prepareText(text); + if (slide.innerHTML != html) { + slide.innerHTML = html; + } + if (!Display._themeApplied) { + Display.applyTheme(slide.parent); + } + } else { + Display._clearSlidesList(); + var slide_html; + var parentSection = document.createElement("section"); + parentSection.classList = "text-slides"; + slide_html = Display._createTextSlide("test-slide", text); + parentSection.appendChild(slide_html); + Display._slides["test-slide"] = 0; + Display.applyTheme(parentSection); + Display._slidesContainer.innerHTML = ""; + Display._slidesContainer.prepend(parentSection); + Display.reinit(); } }, /** @@ -695,8 +744,7 @@ var Display = { * @param {Object[]} slides - A list of images to add as JS objects [{"path": "url/to/file"}] */ setImageSlides: function (slides) { - Display.clearSlides(); - var slidesDiv = $(".slides")[0]; + Display._clearSlidesList(); var parentSection = document.createElement("section"); slides.forEach(function (slide, index) { var section = document.createElement("section"); @@ -710,15 +758,14 @@ var Display = { parentSection.appendChild(section); Display._slides[index.toString()] = index; }); - slidesDiv.appendChild(parentSection); - Display.reinit(); + Display.replaceSlides(parentSection); }, /** * Set a video * @param {Object} video - The video to show as a JS object: {"path": "url/to/file"} */ setVideo: function (video) { - Display.clearSlides(); + Display._clearSlidesList(); var section = document.createElement("section"); section.setAttribute("data-background", "#000"); var videoElement = document.createElement("video"); @@ -747,8 +794,7 @@ var Display = { mediaWatcher.has_muted(event.target.muted); }); section.appendChild(videoElement); - $(".slides")[0].appendChild(section); - Display.reinit(); + Display.replaceSlides(section); }, /** * Play a video @@ -856,32 +902,45 @@ var Display = { /** * Blank the screen */ - blankToBlack: function () { + toBlack: function () { + var documentBody = $("body")[0]; + documentBody.style.opacity = 1; if (!Reveal.isPaused()) { Reveal.togglePause(); } - // var slidesDiv = $(".slides")[0]; }, /** - * Blank to theme + * Hide all but theme background */ - blankToTheme: function () { - var slidesDiv = $(".slides")[0]; - slidesDiv.style.visibility = "hidden"; - var footerDiv = $(".footer")[0]; - footerDiv.style.visibility = "hidden"; + toTheme: function () { + var documentBody = $("body")[0]; + documentBody.style.opacity = 1; + Display._slidesContainer.style.opacity = 0; + Display._footerContainer.style.opacity = 0; if (Reveal.isPaused()) { Reveal.togglePause(); } }, + /** + * Hide everything (CAUTION: Causes a invisible mouse barrier) + */ + toTransparent: function () { + Display._slidesContainer.style.opacity = 0; + Display._footerContainer.style.opacity = 0; + var documentBody = $("body")[0]; + documentBody.style.opacity = 0; + if (!Reveal.isPaused()) { + Reveal.togglePause(); + } + }, /** * Show the screen */ show: function () { - var slidesDiv = $(".slides")[0]; - slidesDiv.style.visibility = "visible"; - var footerDiv = $(".footer")[0]; - footerDiv.style.visibility = "visible"; + var documentBody = $("body")[0]; + documentBody.style.opacity = 1; + Display._slidesContainer.style.opacity = 1; + Display._footerContainer.style.opacity = 1; if (Reveal.isPaused()) { Reveal.togglePause(); } @@ -903,13 +962,31 @@ var Display = { var dh = parseFloat(_getStyle(d, "height")); return Math.floor(dh / lh); }, + /** + * Prepare the theme for the next item to be added + * @param theme The theme to be used + */ setTheme: function (theme) { - Display._theme = theme; + if (Display._theme != theme) { + Display._themeApplied = false; + Display._theme = theme; + } + }, + /** + * Apply the theme to the provided element + * @param targetElement The target element to apply the theme (expected to be a
in the slides container) + * @param is_text Used to decide if the main area constraints should be applied + */ + applyTheme: function (targetElement, is_text=true) { + Display._themeApplied = true; + if (!Display._theme) { + return; + } // Set slide transitions var new_transition_type = "none", new_transition_speed = "default"; - if (!!theme.display_slide_transition && Display._doTransitions) { - switch (theme.display_slide_transition_type) { + if (!!Display._theme.display_slide_transition && Display._doTransitions) { + switch (Display._theme.display_slide_transition_type) { case TransitionType.Fade: new_transition_type = "fade"; break; @@ -928,7 +1005,7 @@ var Display = { default: new_transition_type = "fade"; } - switch (theme.display_slide_transition_speed) { + switch (Display._theme.display_slide_transition_speed) { case TransitionSpeed.Normal: new_transition_speed = "default"; break; @@ -941,7 +1018,7 @@ var Display = { default: new_transition_speed = "default"; } - switch (theme.display_slide_transition_direction) { + switch (Display._theme.display_slide_transition_direction) { case TransitionDirection.Vertical: new_transition_type += "-vertical"; break; @@ -949,95 +1026,102 @@ var Display = { default: new_transition_type += "-horizontal"; } - if (theme.display_slide_transition_reverse) { + if (Display._theme.display_slide_transition_reverse) { new_transition_type += "-reverse"; } } - - Display.setTransition(new_transition_type, new_transition_speed); + var slides = targetElement.children; + for (var i = 0; i < slides.length; i++) { + slides[i].setAttribute("data-transition", new_transition_type); + slides[i].setAttribute("data-transition-speed", new_transition_speed); + } // Set the background - var globalBackground = $("#global-background")[0]; - var backgroundStyle = {}; + var backgroundContent = ""; var backgroundHtml = ""; - switch (theme.background_type) { + var globalBackground = $("#global-background")[0]; + globalBackground.style.setProperty("background", "black"); + switch (Display._theme.background_type) { case BackgroundType.Transparent: - backgroundStyle.background = "transparent"; + backgroundContent = "transparent"; + globalBackground.style.setProperty("background", "transparent"); break; case BackgroundType.Solid: - backgroundStyle.background = theme.background_color; + backgroundContent = Display._theme.background_color; break; case BackgroundType.Gradient: - switch (theme.background_direction) { + switch (Display._theme.background_direction) { case GradientType.Horizontal: - backgroundStyle.background = _buildLinearGradient("left top", "left bottom", - theme.background_start_color, - theme.background_end_color); + backgroundContent = _buildLinearGradient("left top", "left bottom", + Display._theme.background_start_color, + Display._theme.background_end_color); break; case GradientType.Vertical: - backgroundStyle.background = _buildLinearGradient("left top", "right top", - theme.background_start_color, - theme.background_end_color); + backgroundContent = _buildLinearGradient("left top", "right top", + Display._theme.background_start_color, + Display._theme.background_end_color); break; case GradientType.LeftTop: - backgroundStyle.background = _buildLinearGradient("left top", "right bottom", - theme.background_start_color, - theme.background_end_color); + backgroundContent = _buildLinearGradient("left top", "right bottom", + Display._theme.background_start_color, + Display._theme.background_end_color); break; case GradientType.LeftBottom: - backgroundStyle.background = _buildLinearGradient("left bottom", "right top", - theme.background_start_color, - theme.background_end_color); + backgroundContent = _buildLinearGradient("left bottom", "right top", + Display._theme.background_start_color, + Display._theme.background_end_color); break; case GradientType.Circular: - backgroundStyle.background = _buildRadialGradient(window.innerWidth / 2, theme.background_start_color, - theme.background_end_color); + backgroundContent = _buildRadialGradient(window.innerWidth / 2, Display._theme.background_start_color, + Display._theme.background_end_color); break; default: - backgroundStyle.background = "#000"; + backgroundContent = "#000"; } break; case BackgroundType.Image: - backgroundStyle["background-image"] = "url('" + theme.background_filename + "')"; - console.warn(backgroundStyle["background-image"]); + backgroundContent = "url('" + Display._theme.background_filename + "')"; + console.warn(backgroundContent); break; case BackgroundType.Video: // never actually used since background type is overridden from video to transparent in window.py - backgroundStyle["background-color"] = theme.background_border_color; - backgroundHtml = ""; + backgroundContent = Display._theme.background_border_color; + backgroundHtml = ""; console.warn(backgroundHtml); break; default: - backgroundStyle.background = "#000"; - } - globalBackground.style.cssText = ""; - for (var bgKey in backgroundStyle) { - if (backgroundStyle.hasOwnProperty(bgKey)) { - globalBackground.style.setProperty(bgKey, backgroundStyle[bgKey]); - } + backgroundContent = "#000"; } + targetElement.style.cssText = ""; + targetElement.setAttribute("data-background", backgroundContent); + targetElement.setAttribute("data-background-size", "cover"); if (!!backgroundHtml) { - globalBackground.innerHTML = backgroundHtml; + background.innerHTML = backgroundHtml; } + // set up the main area + if (!is_text) { + // only transition and background for non text slides + return; + } mainStyle = {}; - if (!!theme.font_main_outline) { - mainStyle["-webkit-text-stroke"] = "" + theme.font_main_outline_size + "pt " + - theme.font_main_outline_color; - mainStyle["-webkit-text-fill-color"] = theme.font_main_color; + if (!!Display._theme.font_main_outline) { + mainStyle["-webkit-text-stroke"] = "" + Display._theme.font_main_outline_size + "pt " + + Display._theme.font_main_outline_color; + mainStyle["-webkit-text-fill-color"] = Display._theme.font_main_color; } // These need to be fixed, in the Python they use a width passed in as a parameter - mainStyle.width = theme.font_main_width + "px"; - mainStyle.height = theme.font_main_height + "px"; - mainStyle["margin-top"] = "" + theme.font_main_y + "px"; - mainStyle.left = "" + theme.font_main_x + "px"; - mainStyle.color = theme.font_main_color; - mainStyle["font-family"] = theme.font_main_name; - mainStyle["font-size"] = "" + theme.font_main_size + "pt"; - mainStyle["font-style"] = !!theme.font_main_italics ? "italic" : ""; - mainStyle["font-weight"] = !!theme.font_main_bold ? "bold" : ""; - mainStyle["line-height"] = "" + (100 + theme.font_main_line_adjustment) + "%"; + mainStyle.width = Display._theme.font_main_width + "px"; + mainStyle.height = Display._theme.font_main_height + "px"; + mainStyle["margin-top"] = "" + Display._theme.font_main_y + "px"; + mainStyle.left = "" + Display._theme.font_main_x + "px"; + mainStyle.color = Display._theme.font_main_color; + mainStyle["font-family"] = Display._theme.font_main_name; + mainStyle["font-size"] = "" + Display._theme.font_main_size + "pt"; + mainStyle["font-style"] = !!Display._theme.font_main_italics ? "italic" : ""; + mainStyle["font-weight"] = !!Display._theme.font_main_bold ? "bold" : ""; + mainStyle["line-height"] = "" + (100 + Display._theme.font_main_line_adjustment) + "%"; // Using text-align-last because there is a
seperating each line - switch (theme.display_horizontal_align) { + switch (Display._theme.display_horizontal_align) { case HorizontalAlign.Justify: mainStyle["text-align"] = "justify"; mainStyle["text-align-last"] = "justify"; @@ -1058,7 +1142,7 @@ var Display = { mainStyle["text-align"] = "center"; mainStyle["text-align-last"] = "center"; } - switch (theme.display_vertical_align) { + switch (Display._theme.display_vertical_align) { case VerticalAlign.Middle: mainStyle["justify-content"] = "center"; break; @@ -1068,23 +1152,19 @@ var Display = { case VerticalAlign.Bottom: mainStyle["justify-content"] = "flex-end"; // This gets around the webkit scroll height bug - mainStyle["padding-bottom"] = "" + (theme.font_main_size / 8) + "px"; + mainStyle["padding-bottom"] = "" + (Display._theme.font_main_size / 8) + "px"; break; default: mainStyle["justify-content"] = "center"; } - if (theme.hasOwnProperty('font_main_shadow_size') && !!theme.font_main_shadow) { - mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "pt " + - theme.font_main_shadow_size + "pt"; + if (Display._theme.hasOwnProperty('font_main_shadow_size') && !!Display._theme.font_main_shadow) { + mainStyle["text-shadow"] = Display._theme.font_main_shadow_color + " " + Display._theme.font_main_shadow_size + "pt " + + Display._theme.font_main_shadow_size + "pt"; } - var slidesDiv = $("section.text-slides"); - if (slidesDiv.length > 0) { - slidesDiv = slidesDiv[0]; - slidesDiv.style.cssText = ""; - for (var mainKey in mainStyle) { - if (mainStyle.hasOwnProperty(mainKey)) { - slidesDiv.style.setProperty(mainKey, mainStyle[mainKey]); - } + targetElement.style.cssText = ""; + for (var mainKey in mainStyle) { + if (mainStyle.hasOwnProperty(mainKey)) { + targetElement.style.setProperty(mainKey, mainStyle[mainKey]); } } // Set up the footer @@ -1092,19 +1172,18 @@ var Display = { "text-align": "left" }; footerStyle.position = "absolute"; - footerStyle.left = "" + theme.font_footer_x + "px"; - footerStyle.top = "" + theme.font_footer_y + "px"; - footerStyle.width = "" + theme.font_footer_width + "px"; - footerStyle.height = "" + theme.font_footer_height + "px"; - footerStyle.color = theme.font_footer_color; - footerStyle["font-family"] = theme.font_footer_name; - footerStyle["font-size"] = "" + theme.font_footer_size + "pt"; - footerStyle["white-space"] = theme.font_footer_wrap ? "normal" : "nowrap"; - var footer = $(".footer")[0]; - footer.style.cssText = ""; + footerStyle.left = "" + Display._theme.font_footer_x + "px"; + footerStyle.top = "" + Display._theme.font_footer_y + "px"; + footerStyle.width = "" + Display._theme.font_footer_width + "px"; + footerStyle.height = "" + Display._theme.font_footer_height + "px"; + footerStyle.color = Display._theme.font_footer_color; + footerStyle["font-family"] = Display._theme.font_footer_name; + footerStyle["font-size"] = "" + Display._theme.font_footer_size + "pt"; + footerStyle["white-space"] = Display._theme.font_footer_wrap ? "normal" : "nowrap"; + Display._footerContainer.style.cssText = ""; for (var footerKey in footerStyle) { if (footerStyle.hasOwnProperty(footerKey)) { - footer.style.setProperty(footerKey, footerStyle[footerKey]); + Display._footerContainer.style.setProperty(footerKey, footerStyle[footerKey]); } } }, diff --git a/openlp/core/display/render.py b/openlp/core/display/render.py index 3b4fe564b..12205d042 100644 --- a/openlp/core/display/render.py +++ b/openlp/core/display/render.py @@ -812,7 +812,7 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow): """ self.clear_slides() self.log_debug('_text_fits_on_slide: 1\n{text}'.format(text=text)) - self.run_javascript('Display.addTextSlide("v1", "{text}", "Dummy Footer");' + self.run_javascript('Display.setTextSlide("{text}");' .format(text=text.replace('"', '\\"')), is_sync=True) self.log_debug('_text_fits_on_slide: 2') does_text_fits = self.run_javascript('Display.doesContentFit();', is_sync=True) diff --git a/openlp/core/display/window.py b/openlp/core/display/window.py index fc64f475c..8cc16fd96 100644 --- a/openlp/core/display/window.py +++ b/openlp/core/display/window.py @@ -244,7 +244,9 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties): Add stuff after page initialisation """ js_is_display = str(self.is_display).lower() - self.run_javascript('Display.init({do_transitions});'.format(do_transitions=js_is_display)) + item_transitions = str(self.settings.value('themes/item transitions')).lower() + self.run_javascript('Display.init({do_transitions}, {do_item_transitions});' + .format(do_transitions=js_is_display, do_item_transitions=item_transitions)) wait_for(lambda: self._is_initialised) if self.scale != 1: self.set_scale(self.scale) @@ -430,12 +432,6 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties): if self.is_display: Registry().execute('live_display_active') - def blank_to_theme(self): - """ - Blank to theme - """ - self.run_javascript('Display.blankToTheme();') - def hide_display(self, mode=HideMode.Screen): """ Hide the display by making all layers transparent Store the images so they can be replaced when required @@ -450,9 +446,11 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties): if mode == HideMode.Screen: self.setVisible(False) elif mode == HideMode.Blank: - self.run_javascript('Display.blankToBlack();') + self.run_javascript('Display.toBlack();') + elif mode == HideMode.Theme: + self.run_javascript('Display.toTheme();') else: - self.run_javascript('Display.blankToTheme();') + self.run_javascript('Display.toTransparent();') if mode != HideMode.Screen: if self.isHidden(): self.setVisible(True) diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 1b159286c..9dbdc836e 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -41,6 +41,7 @@ class HideMode(object): Blank = 1 Theme = 2 Screen = 3 + Transparent = 4 class DisplayControllerType(object): diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 305e1ea21..3383f8c5c 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -40,7 +40,7 @@ from openlp.core.common.path import path_to_str from openlp.core.common.registry import Registry, RegistryBase from openlp.core.lib.serviceitem import ItemCapabilities from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui import DisplayControllerType +from openlp.core.ui import DisplayControllerType, HideMode from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_devicestream_path, \ VIDEO_EXT, AUDIO_EXT from openlp.core.ui.media.remote import register_views @@ -454,7 +454,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): controller.media_info.is_playing = True if not controller.media_info.is_background: display = self._define_display(controller) - display.setVisible(False) + display.hide_display(HideMode.Transparent) + controller._set_theme(controller.service_item) + display.load_verses([{"verse": "v1", "text": "", "footer": " "}]) return True def tick(self, controller): @@ -466,9 +468,14 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): start_again = False if controller.media_info.is_playing and controller.media_info.length > 0: if controller.media_info.timer > controller.media_info.length: - self.media_stop(controller) if controller.media_info.is_looping_playback: start_again = True + else: + self.media_stop(controller) + elif controller.media_info.timer > controller.media_info.length - TICK_TIME * 4: + if not controller.media_info.is_looping_playback: + display = self._define_display(controller) + display.show_display() controller.media_info.timer += TICK_TIME seconds = controller.media_info.timer // 1000 minutes = seconds // 60 @@ -479,7 +486,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): controller.position_label.setText(' %02d:%02d / %02d:%02d' % (minutes, seconds, total_minutes, total_seconds)) if start_again: - self.media_play(controller, True) + controller.seek_slider.setSliderPosition(0) + self.media_play(controller, False) def media_pause_msg(self, msg): """ @@ -560,6 +568,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): controller.media_info.is_playing = False controller.media_info.timer = 1000 controller.media_timer = 0 + display = self._define_display(controller) + display.show_display() def media_volume_msg(self, msg): """ @@ -615,6 +625,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): """ self.set_controls_visible(controller, False) if controller.controller_type in self.current_media_players: + display = self._define_display(controller) + display.show_display() self.current_media_players[controller.controller_type].reset(controller) self.current_media_players[controller.controller_type].set_visible(controller, False) del self.current_media_players[controller.controller_type] diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 13eb9ebb7..730aee2c6 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -855,7 +855,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): self.preview_display.set_theme(theme_data, service_item_type=service_item.service_item_type) # Set theme for displays for display in self.displays: - display.set_theme(service_item.get_theme_data(), service_item_type=service_item.service_item_type) + display.set_theme(theme_data, service_item_type=service_item.service_item_type) def _process_item(self, service_item, slide_no): """ @@ -1168,20 +1168,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): Registry().execute('{text}_slide'.format(text=self.service_item.name.lower()), [self.service_item, self.is_live, row]) else: - # to_display = self.service_item.get_rendered_frame(row) - if self.service_item.is_text(): - for display in self.displays: - display.go_to_slide(row) - # self.display.text(to_display, row != old_selected_row) - else: - if start: - for display in self.displays: - display.load_images(self.service_item.slides) - # self.display.build_html(self.service_item, to_display) - else: - for display in self.displays: - display.go_to_slide(row) - # self.display.image(to_display) + for display in self.displays: + display.go_to_slide(row) + if not self.service_item.is_text(): # reset the store used to display first image self.service_item.bg_image_bytes = None self.selected_row = row diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 7326c9d04..5cca1509e 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -69,6 +69,9 @@ class ThemesTab(SettingsTab): self.wrap_footer_check_box = QtWidgets.QCheckBox(self.universal_group_box) self.wrap_footer_check_box.setObjectName('wrap_footer_check_box') self.universal_group_box_layout.addWidget(self.wrap_footer_check_box) + self.item_transitions_check_box = QtWidgets.QCheckBox(self.universal_group_box) + self.item_transitions_check_box.setObjectName('item_transitions_check_box') + self.universal_group_box_layout.addWidget(self.item_transitions_check_box) self.left_layout.addWidget(self.universal_group_box) self.left_layout.addStretch() self.level_group_box = QtWidgets.QGroupBox(self.right_column) @@ -115,6 +118,7 @@ class ThemesTab(SettingsTab): self.global_group_box.setTitle(translate('OpenLP.ThemesTab', 'Global Theme')) self.universal_group_box.setTitle(translate('OpenLP.ThemesTab', 'Universal Settings')) self.wrap_footer_check_box.setText(translate('OpenLP.ThemesTab', '&Wrap footer text')) + self.item_transitions_check_box.setText(translate('OpenLP.ThemesTab', '&Transition between service items')) self.level_group_box.setTitle(translate('OpenLP.ThemesTab', 'Theme Level')) self.song_level_radio_button.setText(translate('OpenLP.ThemesTab', 'S&ong Level')) self.song_level_label.setText( @@ -139,6 +143,7 @@ class ThemesTab(SettingsTab): self.theme_level = self.settings.value('theme level') self.global_theme = self.settings.value('global theme') self.wrap_footer_check_box.setChecked(self.settings.value('wrap footer')) + self.item_transitions_check_box.setChecked(self.settings.value('item transitions')) self.settings.endGroup() if self.theme_level == ThemeLevel.Global: self.global_level_radio_button.setChecked(True) @@ -155,6 +160,7 @@ class ThemesTab(SettingsTab): self.settings.setValue('theme level', self.theme_level) self.settings.setValue('global theme', self.global_theme) self.settings.setValue('wrap footer', self.wrap_footer_check_box.isChecked()) + self.settings.setValue('item transitions', self.item_transitions_check_box.isChecked()) self.settings.endGroup() self.renderer.set_theme_level(self.theme_level) if self.tab_visited: diff --git a/tests/functional/openlp_core/display/test_window.py b/tests/functional/openlp_core/display/test_window.py index f9e78aaf5..d3bff5b72 100644 --- a/tests/functional/openlp_core/display/test_window.py +++ b/tests/functional/openlp_core/display/test_window.py @@ -32,6 +32,7 @@ from PyQt5 import QtCore sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock() from openlp.core.display.window import DisplayWindow +from openlp.core.ui import HideMode @patch('PyQt5.QtWidgets.QVBoxLayout') @@ -103,6 +104,31 @@ def test_set_scale_initialised(mocked_webengine, mocked_addWidget, mock_settings display_window.run_javascript.assert_called_once_with('Display.setScale(50.0);') +@patch('PyQt5.QtWidgets.QVBoxLayout') +@patch('openlp.core.display.webengine.WebEngineView') +def test_after_loaded(mocked_webengine, mocked_addWidget, mock_settings): + """ + Test the correct steps are taken when the webview is loaded + """ + # GIVEN: An initialised display window and settings for item transitions returns true + display_window = DisplayWindow() + display_window.is_display = True + mock_settings.value.return_value = True + display_window.scale = 2 + display_window._is_initialised = True + display_window.run_javascript = MagicMock() + display_window.set_scale = MagicMock() + display_window.set_startup_screen = MagicMock() + + # WHEN: after_loaded is run + display_window.after_loaded() + + # THEN: The following functions should have been called + display_window.run_javascript.assert_called_once_with('Display.init(true, true);') + display_window.set_scale.assert_called_once_with(2) + display_window.set_startup_screen.assert_called_once() + + @patch('PyQt5.QtWidgets.QVBoxLayout') @patch('openlp.core.display.webengine.WebEngineView') @patch.object(time, 'time') @@ -146,3 +172,75 @@ def test_run_javascript_sync_no_wait(mock_time, mocked_webengine, mocked_addWidg assert result == 1234 webengine_page.runJavaScript.assert_called_once() mock_time.sleep.assert_not_called() + + +@patch('PyQt5.QtWidgets.QVBoxLayout') +@patch('openlp.core.display.webengine.WebEngineView') +def test_hide_display_to_screen(mocked_webengine, mocked_addWidget, mock_settings): + """ + Test hide to screen in the hide_display function + """ + # GIVEN: + display_window = DisplayWindow() + display_window.setVisible = MagicMock() + + # WHEN: Hide display is run with no mode (should default to Screen) + display_window.hide_display() + + # THEN: Should hide the display and set the hide mode + display_window.setVisible.assert_called_once_with(False) + assert display_window.hide_mode == HideMode.Screen + + +@patch('PyQt5.QtWidgets.QVBoxLayout') +@patch('openlp.core.display.webengine.WebEngineView') +def test_hide_display_to_blank(mocked_webengine, mocked_addWidget, mock_settings): + """ + Test hide to screen in the hide_display function + """ + # GIVEN: + display_window = DisplayWindow() + display_window.run_javascript = MagicMock() + + # WHEN: Hide display is run with HideMode.Blank + display_window.hide_display(HideMode.Blank) + + # THEN: Should run the correct javascript on the display and set the hide mode + display_window.run_javascript.assert_called_once_with('Display.toBlack();') + assert display_window.hide_mode == HideMode.Blank + + +@patch('PyQt5.QtWidgets.QVBoxLayout') +@patch('openlp.core.display.webengine.WebEngineView') +def test_hide_display_to_theme(mocked_webengine, mocked_addWidget, mock_settings): + """ + Test hide to screen in the hide_display function + """ + # GIVEN: + display_window = DisplayWindow() + display_window.run_javascript = MagicMock() + + # WHEN: Hide display is run with HideMode.Theme + display_window.hide_display(HideMode.Theme) + + # THEN: Should run the correct javascript on the display and set the hide mode + display_window.run_javascript.assert_called_once_with('Display.toTheme();') + assert display_window.hide_mode == HideMode.Theme + + +@patch('PyQt5.QtWidgets.QVBoxLayout') +@patch('openlp.core.display.webengine.WebEngineView') +def test_hide_display_to_transparent(mocked_webengine, mocked_addWidget, mock_settings): + """ + Test hide to screen in the hide_display function + """ + # GIVEN: + display_window = DisplayWindow() + display_window.run_javascript = MagicMock() + + # WHEN: Hide display is run with HideMode.Transparent + display_window.hide_display(HideMode.Transparent) + + # THEN: Should run the correct javascript on the display and set the hide mode + display_window.run_javascript.assert_called_once_with('Display.toTransparent();') + assert display_window.hide_mode == HideMode.Transparent diff --git a/tests/functional/openlp_core/ui/media/test_mediacontroller.py b/tests/functional/openlp_core/ui/media/test_mediacontroller.py index 169d29198..7fef46827 100644 --- a/tests/functional/openlp_core/ui/media/test_mediacontroller.py +++ b/tests/functional/openlp_core/ui/media/test_mediacontroller.py @@ -324,3 +324,23 @@ def test_setup_display(MockItemMediaInfo, media_env): assert controller.has_audio is False media_env.media_controller._define_display.assert_called_once_with(controller) media_env.media_controller.vlc_player.setup(controller, mocked_display, False) + + +def test_media_play(media_env): + """ + Test that the display/controllers are set up correctly + """ + # GIVEN: A mocked controller where is_background is false + media_env.current_media_players = MagicMock() + Registry().register('settings', MagicMock()) + media_env.live_timer = MagicMock() + mocked_controller = MagicMock() + mocked_controller.media_info.is_background = False + + # WHEN: media_play is called + result = media_env.media_play(mocked_controller) + + # THEN: The web display should become transparent (only tests that the theme is reset here) + # And the function should return true to indicate success + assert result is True + mocked_controller._set_theme.assert_called_once() diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index 9a07bbfd4..a1fd34703 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -43,6 +43,29 @@ def test_initial_slide_controller(registry): assert slide_controller.is_live is False, 'The base slide controller should not be a live controller' +def test_slide_selected(settings): + """ + Test the slide selected method + """ + # GIVEN: A new SlideController instance on slide 4 of 10, and is not a command + slide_controller = SlideController(None) + slide_controller.update_preview = MagicMock() + slide_controller.slide_selected_lock = MagicMock() + slide_controller.service_item = MagicMock() + slide_controller.service_item.is_command.return_value = False + slide_controller.preview_widget = MagicMock() + slide_controller.preview_widget.slide_count.return_value = 10 + slide_controller.preview_widget.current_slide_number.return_value = 4 + mocked_display = MagicMock() + slide_controller.displays = [mocked_display] + + # WHEN: The slide_selected method is run + slide_controller.slide_selected(True) + + # THEN: The display is updated with the slide number + mocked_display.go_to_slide.assert_called_once_with(4) + + def test_text_service_item_blank(settings): """ Test that loading a text-based service item into the slide controller sets the correct blank menu diff --git a/tests/js/test_display.js b/tests/js/test_display.js index 1fb7aaaae..acaf180b9 100644 --- a/tests/js/test_display.js +++ b/tests/js/test_display.js @@ -76,6 +76,7 @@ describe("The function", function () { }); describe("The Display object", function () { + it("should start with a blank _slides object", function () { expect(Display._slides).toEqual({}); }); @@ -107,6 +108,8 @@ describe("The Display object", function () { it("should initialise Reveal when init is called", function () { spyOn(Reveal, "initialize"); + document.body.innerHTML = ""; + _createDiv({"id": "global-background"}); Display.init(); expect(Reveal.initialize).toHaveBeenCalled(); }); @@ -123,16 +126,18 @@ describe("The Display object", function () { expect(Reveal.slide).toHaveBeenCalledWith(0); }); - it("should have a setTransition() method", function () { - expect(Display.setTransition).toBeDefined(); + it("should have a setItemTransition() method", function () { + expect(Display.setItemTransition).toBeDefined(); }); it("should have a correctly functioning clearSlides() method", function () { expect(Display.clearSlides).toBeDefined(); - document.body.innerHTML = ""; var slidesDiv = _createDiv({"class": "slides"}); slidesDiv.innerHTML = "

"; + Display._slidesContainer = slidesDiv; + var footerDiv = _createDiv({"class": "footer"}); + Display._footerContainer = footerDiv; Display.clearSlides(); expect($(".slides")[0].innerHTML).toEqual(""); @@ -167,42 +172,48 @@ describe("Transitions", function () { Display._theme = null; }); - it("should have a correctly functioning setTransition() method", function () { + it("should have a correctly functioning setItemTransition() method", function () { spyOn(Reveal, "configure"); - Display.setTransition("fade", "slow"); - expect(Reveal.configure).toHaveBeenCalledWith({"transition": "fade", "transitionSpeed": "slow"}); + Display.setItemTransition(true); + expect(Reveal.configure).toHaveBeenCalledWith({"backgroundTransition": "fade", "transitionSpeed": "default"}); }); - it("should have enabled transitions when _doTransitions is true and setTheme is run", function () { - spyOn(Display, "setTransition"); + it("should have enabled transitions when _doTransitions is true and applyTheme is run", function () { Display._doTransitions = true; var theme = { "display_slide_transition": true, "display_slide_transition_type": TransitionType.Slide, "display_slide_transition_speed": TransitionSpeed.Fast } - Display.setTheme(theme); + var slidesDiv = _createDiv({"class": "slides"}); + slidesDiv.innerHTML = "

"; + Display._slidesContainer = slidesDiv; - expect(Display.setTransition).toHaveBeenCalledWith("slide-horizontal", "fast"); + Display.applyTheme(Display._slidesContainer.children[0]) + + expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition")).toEqual("slide-horizontal"); + expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition-speed")).toEqual("fast"); }); it("should have not enabled transitions when init() with no transitions and setTheme is run", function () { - spyOn(Display, "setTransition"); Display._doTransitions = false; var theme = { "display_slide_transition": true, "display_slide_transition_type": TransitionType.Slide, "display_slide_transition_speed": TransitionSpeed.Fast, } - Display.setTheme(theme); + var slidesDiv = _createDiv({"class": "slides"}); + slidesDiv.innerHTML = "

"; + Display._slidesContainer = slidesDiv; - expect(Display.setTransition).toHaveBeenCalledWith("none", "default"); + Display.applyTheme(Display._slidesContainer.children[0]) + + expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition")).toEqual("none"); }); it("should have enabled transitions in the correct direction", function () { - spyOn(Display, "setTransition"); Display._doTransitions = true; var theme = { "display_slide_transition": true, @@ -211,10 +222,15 @@ describe("Transitions", function () { "display_slide_transition_direction": TransitionDirection.Vertical, "display_slide_transition_reverse": true, } - Display.setTheme(theme); + var slidesDiv = _createDiv({"class": "slides"}); + slidesDiv.innerHTML = "

"; + Display._slidesContainer = slidesDiv; - expect(Display.setTransition).toHaveBeenCalledWith("convex-vertical-reverse", "slow"); + Display.applyTheme(Display._slidesContainer.children[0]) + + expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition")).toEqual("convex-vertical-reverse"); + expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition-speed")).toEqual("slow"); }); }); @@ -504,63 +520,49 @@ describe("Display.alertAnimationEndEvent", function () { }); }); -describe("Display.addTextSlide", function () { +describe("Display.setTextSlide", function () { beforeEach(function() { document.body.innerHTML = ""; - _createDiv({"class": "slides"}); - _createDiv({"class": "footer"}); + var slides_container = _createDiv({"class": "slides"}); + var footer_container = _createDiv({"class": "footer"}); + Display._slidesContainer = slides_container; + Display._footerContainer = footer_container; Display._slides = {}; }); it("should add a new slide", function () { - var verse = "v1", - text = "Amazing grace,\nhow sweet the sound", - footer = "Public Domain"; + var text = "Amazing grace,\nhow sweet the sound"; spyOn(Display, "reinit"); - Display.addTextSlide(verse, text, footer); + Display.setTextSlide(text); - expect(Display._slides[verse]).toEqual(0); + expect(Display._slides["test-slide"]).toEqual(0); expect($(".slides > section > section").length).toEqual(1); expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); expect(Display.reinit).toHaveBeenCalled(); }); - it("should add a new slide without calling reinit()", function () { - var verse = "v1", - text = "Amazing grace,\nhow sweet the sound", - footer = "Public Domain"; - spyOn(Display, "reinit"); - - Display.addTextSlide(verse, text, footer, false); - - expect(Display._slides[verse]).toEqual(0); - expect($(".slides > section > section").length).toEqual(1); - expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); - expect(Display.reinit).not.toHaveBeenCalled(); - }); - it("should update an existing slide", function () { - var verse = "v1", - text = "Amazing grace, how sweet the sound\nThat saved a wretch like me", - footer = "Public Domain"; - Display.addTextSlide(verse, "Amazing grace,\nhow sweet the sound", footer, false); + var text = "That saved a wretch\nlike me"; spyOn(Display, "reinit"); + Display.setTextSlide("Amazing grace,\nhow sweet the sound"); - Display.addTextSlide(verse, text, footer, true); + Display.setTextSlide(text); - expect(Display._slides[verse]).toEqual(0); + expect(Display._slides["test-slide"]).toEqual(0); expect($(".slides > section > section").length).toEqual(1); expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); - expect(Display.reinit).toHaveBeenCalledTimes(0); + expect(Display.reinit).toHaveBeenCalledTimes(1); // only called once for the first setTextSlide }); }); describe("Display.setTextSlides", function () { beforeEach(function() { document.body.innerHTML = ""; - _createDiv({"class": "slides"}); - _createDiv({"class": "footer"}); + var slides_container = _createDiv({"class": "slides"}); + var footer_container = _createDiv({"class": "footer"}); + Display._slidesContainer = slides_container; + Display._footerContainer = footer_container; _createDiv({"id": "global-background"}); Display._slides = {}; }); @@ -581,15 +583,16 @@ describe("Display.setTextSlides", function () { } ]; spyOn(Display, "clearSlides"); - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); + spyOn(Reveal, "slide"); Display.setTextSlides(slides); - expect(Display.clearSlides).toHaveBeenCalledTimes(1); + expect(Display.clearSlides).toHaveBeenCalledTimes(0); expect(Display._slides["v1"]).toEqual(0); expect(Display._slides["v2"]).toEqual(1); expect($(".slides > section > section").length).toEqual(2); - expect(Display.reinit).toHaveBeenCalledTimes(1); + expect(Reveal.sync).toHaveBeenCalledTimes(1); }); it("should correctly set outline width", function () { @@ -607,7 +610,7 @@ describe("Display.setTextSlides", function () { 'font_main_outline_size': 42, 'font_main_outline_color': 'red' }; - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); spyOn(Reveal, "slide"); Display.setTheme(theme); @@ -633,7 +636,7 @@ describe("Display.setTextSlides", function () { 'display_horizontal_align': 3, 'display_vertical_align': 1 }; - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); spyOn(Reveal, "slide"); Display.setTheme(theme); @@ -659,7 +662,7 @@ describe("Display.setTextSlides", function () { 'font_main_shadow_color': "#000", 'font_main_shadow_size': 5 }; - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); spyOn(Reveal, "slide"); Display.setTheme(theme); @@ -684,7 +687,7 @@ describe("Display.setTextSlides", function () { 'font_main_shadow_color': "#000", 'font_main_shadow_size': 5 }; - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); spyOn(Reveal, "slide"); Display.setTheme(theme); @@ -710,7 +713,7 @@ describe("Display.setTextSlides", function () { 'font_main_width': 1230, 'font_main_height': 4560 }; - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); spyOn(Reveal, "slide"); Display.setTheme(theme); @@ -727,20 +730,21 @@ describe("Display.setTextSlides", function () { describe("Display.setImageSlides", function () { beforeEach(function() { document.body.innerHTML = ""; - _createDiv({"class": "slides"}); - _createDiv({"class": "footer"}); + var slides_container = _createDiv({"class": "slides"}); + var footer_container = _createDiv({"class": "footer"}); + Display._slidesContainer = slides_container; + Display._footerContainer = footer_container; _createDiv({"id": "global-background"}); Display._slides = {}; }); it("should add a list of images", function () { var slides = [{"path": "file:///openlp1.jpg"}, {"path": "file:///openlp2.jpg"}]; - spyOn(Display, "clearSlides"); - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); + spyOn(Reveal, "slide"); Display.setImageSlides(slides); - expect(Display.clearSlides).toHaveBeenCalledTimes(1); expect(Display._slides["0"]).toEqual(0); expect(Display._slides["1"]).toEqual(1); expect($(".slides > section > section").length).toEqual(2); @@ -749,30 +753,30 @@ describe("Display.setImageSlides", function () { expect($(".slides > section > section > img")[0].getAttribute("style")).toEqual("max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);") expect($(".slides > section > section > img")[1].getAttribute("src")).toEqual("file:///openlp2.jpg") expect($(".slides > section > section > img")[1].getAttribute("style")).toEqual("max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);") - expect(Display.reinit).toHaveBeenCalledTimes(1); + expect(Reveal.sync).toHaveBeenCalledTimes(1); }); }); describe("Display.setVideo", function () { beforeEach(function() { document.body.innerHTML = ""; - _createDiv({"class": "slides"}); + var slides_container = _createDiv({"class": "slides"}); + Display._slidesContainer = slides_container; _createDiv({"id": "global-background"}); Display._slides = {}; }); it("should add a video to the page", function () { var video = {"path": "file:///video.mp4"}; - spyOn(Display, "clearSlides"); - spyOn(Display, "reinit"); + spyOn(Reveal, "sync"); + spyOn(Reveal, "slide"); Display.setVideo(video); - expect(Display.clearSlides).toHaveBeenCalledTimes(1); expect($(".slides > section").length).toEqual(1); expect($(".slides > section > video").length).toEqual(1); expect($(".slides > section > video")[0].src).toEqual("file:///video.mp4") - expect(Display.reinit).toHaveBeenCalledTimes(1); + expect(Reveal.sync).toHaveBeenCalledTimes(1); }); });