diff --git a/openlp/core/display/html/display.css b/openlp/core/display/html/display.css index be87359be..cb79612e6 100644 --- a/openlp/core/display/html/display.css +++ b/openlp/core/display/html/display.css @@ -25,10 +25,18 @@ sup { .reveal .slides > section, .reveal .slides > section > section { padding: 0; + max-height: 100%; } -.reveal > .backgrounds > .present { - visibility: hidden !important; +.reveal .slides > section.text-slides { + /* Need to override reveal styles to use our text aligment */ + display: flex !important; + flex-direction: column; + word-wrap: break-word; +} + +.reveal .slides > section p { + margin: 0; } #global-background { diff --git a/openlp/core/display/html/display.js b/openlp/core/display/html/display.js index ed54504a9..1a55d8507 100644 --- a/openlp/core/display/html/display.js +++ b/openlp/core/display/html/display.js @@ -28,19 +28,19 @@ var GradientType = { * Horizontal alignment enumeration */ var HorizontalAlign = { - Left: "left", - Right: "right", - Center: "center", - Justify: "justify" + Left: 0, + Right: 1, + Center: 2, + Justify: 3 }; /** * Vertical alignment enumeration */ var VerticalAlign = { - Top: "top", - Middle: "middle", - Bottom: "bottom" + Top: 0, + Middle: 1, + Bottom: 2 }; /** @@ -349,7 +349,7 @@ var Display = { * Start up reveal and do any other initialisation */ init: function () { - Reveal.initialize(this._revealConfig); + Reveal.initialize(Display._revealConfig); }, /** * Reinitialise Reveal @@ -369,13 +369,17 @@ var Display = { */ clearSlides: function () { $(".slides")[0].innerHTML = ""; - this._slides = {}; + Display._slides = {}; }, /** * Checks if the present slide content fits within the slide */ doesContentFit: function () { - var currSlide = $(".slides")[0]; + var currSlide = $("section.text-slides"); + if (currSlide.length === 0) { + currSlide = $(".slides"); + } + currSlide = currSlide[0]; console.debug("scrollHeight: " + currSlide.scrollHeight + ", clientHeight: " + currSlide.clientHeight); return currSlide.clientHeight >= currSlide.scrollHeight; }, @@ -594,34 +598,35 @@ var Display = { * Add a slide. If the slide exists but the HTML is different, update the 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 {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) { + addTextSlide: function (verse, text, footerText, reinit=true) { var slide; var html = _prepareText(text); - if (this._slides.hasOwnProperty(verse)) { + if (Display._slides.hasOwnProperty(verse)) { slide = $("#" + verse)[0]; if (slide.innerHTML != html) { slide.innerHTML = html; } - } - else { - var slidesDiv = $(".slides")[0]; + } 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; - slidesDiv.appendChild(slide); - var slides = $(".slides > section"); - this._slides[verse] = slides.length - 1; + parent.appendChild(slide); + Display._slides[verse] = parent.children.length - 1; if (footerText) { $(".footer")[0].innerHTML = footerText; } - } - if ((arguments.length > 3) && (arguments[3] === true)) { - this.reinit(); - } - else if (arguments.length == 3) { - this.reinit(); + if (reinit) { + Display.reinit(); + } } }, /** @@ -636,6 +641,19 @@ var Display = { Display.reinit(); Display.goToSlide(0); }, + /** + * Create the
that will contain text slides (vertical slides in react) + */ + _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); + } + }, /** * Set image slides * @param {Object[]} slides - A list of images to add as JS objects [{"path": "url/to/file"}] @@ -643,6 +661,7 @@ var Display = { setImageSlides: function (slides) { Display.clearSlides(); var slidesDiv = $(".slides")[0]; + var parentSection = document.createElement("section"); slides.forEach(function (slide, index) { var section = document.createElement("section"); section.setAttribute("id", index); @@ -652,9 +671,10 @@ var Display = { img.src = slide.path; img.setAttribute("style", "max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);"); section.appendChild(img); - slidesDiv.appendChild(section); + parentSection.appendChild(section); Display._slides[index.toString()] = index; }); + slidesDiv.appendChild(parentSection); Display.reinit(); }, /** @@ -662,7 +682,7 @@ var Display = { * @param {Object} video - The video to show as a JS object: {"path": "url/to/file"} */ setVideo: function (video) { - this.clearSlides(); + Display.clearSlides(); var section = document.createElement("section"); section.setAttribute("data-background", "#000"); var videoElement = document.createElement("video"); @@ -692,7 +712,7 @@ var Display = { }); section.appendChild(videoElement); $(".slides")[0].appendChild(section); - this.reinit(); + Display.reinit(); }, /** * Play a video @@ -782,21 +802,21 @@ var Display = { * @param slide The slide number or name, e.g. "v1", 0 */ goToSlide: function (slide) { - if (this._slides.hasOwnProperty(slide)) { - Reveal.slide(this._slides[slide]); + if (Display._slides.hasOwnProperty(slide)) { + Reveal.slide(0, Display._slides[slide]); } else { - Reveal.slide(slide); + Reveal.slide(0, slide); } }, /** * Go to the next slide in the list */ - next: Reveal.next, + next: Reveal.nextFragment, /** * Go to the previous slide in the list */ - prev: Reveal.prev, + prev: Reveal.prevFragment, /** * Blank the screen */ @@ -835,20 +855,20 @@ var Display = { * @param fontSize The font size in pts */ calculateLineCount: function (fontSize) { - var p = $(".slides > section > p"); + var p = $(".slides > section > section > p"); if (p.length == 0) { - this.addSlide("v1", "Arky arky"); - p = $(".slides > section > p"); + Display.addSlide("v1", "Arky arky"); + p = $(".slides > section > section > p"); } p = p[0]; p.style.fontSize = "" + fontSize + "pt"; - var d = $(".slides")[0]; + var d = $(".slides > section")[0]; var lh = parseFloat(_getStyle(p, "line-height")); var dh = parseFloat(_getStyle(d, "height")); return Math.floor(dh / lh); }, setTheme: function (theme) { - this._theme = theme; + Display._theme = theme; // Set the background var globalBackground = $("#global-background")[0]; var backgroundStyle = {}; @@ -912,44 +932,72 @@ var Display = { globalBackground.innerHTML = backgroundHtml; } // set up the main area - mainStyle = { - "word-wrap": "break-word", - /*"margin": "0", - "padding": "0"*/ - }; + 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; } + // 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.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.color = theme.font_main_color; mainStyle["line-height"] = "" + (100 + theme.font_main_line_adjustment) + "%"; - mainStyle["text-align"] = theme.display_horizontal_align; - if (theme.display_horizontal_align != HorizontalAlign.Justify) { - mainStyle["white-space"] = "pre-wrap"; + // Using text-align-last because there is a
seperating each line + switch (theme.display_horizontal_align) { + case HorizontalAlign.Justify: + mainStyle["text-align"] = "justify"; + mainStyle["text-align-last"] = "justify"; + break; + case HorizontalAlign.Center: + mainStyle["text-align"] = "center"; + mainStyle["text-align-last"] = "center"; + break; + case HorizontalAlign.Left: + mainStyle["text-align"] = "left"; + mainStyle["text-align-last"] = "left"; + break; + case HorizontalAlign.Right: + mainStyle["text-align"] = "right"; + mainStyle["text-align-last"] = "right"; + break; + default: + mainStyle["text-align"] = "center"; + mainStyle["text-align-last"] = "center"; + } + switch (theme.display_vertical_align) { + case VerticalAlign.Middle: + mainStyle["justify-content"] = "center"; + break; + case VerticalAlign.Top: + mainStyle["justify-content"] = "flex-start"; + break; + 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"; + break; + default: + mainStyle["justify-content"] = "center"; } - mainStyle["vertical-align"] = theme.display_vertical_align; if (theme.hasOwnProperty('font_main_shadow_size')) { - mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "px " + - theme.font_main_shadow_size + "px"; + mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "pt " + + theme.font_main_shadow_size + "pt"; } - mainStyle["padding-bottom"] = theme.display_vertical_align == VerticalAlign.Bottom ? "0.5em" : "0"; - mainStyle["padding-left"] = !!theme.font_main_outline ? "" + (theme.font_main_outline_size * 2) + "pt" : "0"; - // These need to be fixed, in the Python they use a width passed in as a parameter - mainStyle.position = "absolute"; - mainStyle.width = "" + (window.innerWidth - (theme.font_main_outline_size * 4)) + "px"; - mainStyle.height = "" + (window.innerHeight - (theme.font_main_outline_size * 4)) + "px"; - mainStyle.left = "" + theme.font_main_x + "px"; - mainStyle.top = "" + theme.font_main_y + "px"; - var slidesDiv = $(".slides")[0]; - slidesDiv.style.cssText = ""; - for (var mainKey in mainStyle) { - if (mainStyle.hasOwnProperty(mainKey)) { - slidesDiv.style.setProperty(mainKey, mainStyle[mainKey]); + 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]); + } } } // Set up the footer @@ -959,11 +1007,11 @@ var Display = { footerStyle.position = "absolute"; footerStyle.left = "" + theme.font_footer_x + "px"; footerStyle.top = "" + theme.font_footer_y + "px"; - footerStyle.bottom = "" + (window.innerHeight - theme.font_footer_y - theme.font_footer_height) + "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.color = theme.font_footer_color; footerStyle["white-space"] = theme.font_footer_wrap ? "normal" : "nowrap"; var footer = $(".footer")[0]; footer.style.cssText = ""; diff --git a/package.json b/package.json index 2e00925e2..b0ebf53ad 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,9 @@ "test": "karma start --single-run", "lint": "jshint openlp/core/display/html/display.js" }, + "jshintConfig": { + "esversion": 6 + }, "author": "OpenLP Developers", "license": "GPL-3.0-or-later" } diff --git a/tests/js/test_display.js b/tests/js/test_display.js index fd051804e..a8b50dfa1 100644 --- a/tests/js/test_display.js +++ b/tests/js/test_display.js @@ -147,7 +147,7 @@ describe("The Display object", function () { Display._slides["v1"] = 0; Display.goToSlide("v1"); - expect(Reveal.slide).toHaveBeenCalledWith(0); + expect(Reveal.slide).toHaveBeenCalledWith(0, 0); }); it("should have an alert() method", function () { @@ -458,8 +458,8 @@ describe("Display.addTextSlide", function () { Display.addTextSlide(verse, text, footer); expect(Display._slides[verse]).toEqual(0); - expect($(".slides > section").length).toEqual(1); - expect($(".slides > section")[0].innerHTML).toEqual(_prepareText(text)); + expect($(".slides > section > section").length).toEqual(1); + expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); expect(Display.reinit).toHaveBeenCalled(); }); @@ -472,8 +472,8 @@ describe("Display.addTextSlide", function () { Display.addTextSlide(verse, text, footer, false); expect(Display._slides[verse]).toEqual(0); - expect($(".slides > section").length).toEqual(1); - expect($(".slides > section")[0].innerHTML).toEqual(_prepareText(text)); + expect($(".slides > section > section").length).toEqual(1); + expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); expect(Display.reinit).not.toHaveBeenCalled(); }); @@ -484,12 +484,12 @@ describe("Display.addTextSlide", function () { Display.addTextSlide(verse, "Amazing grace,\nhow sweet the sound", footer, false); spyOn(Display, "reinit"); - Display.addTextSlide(verse, text, true); + Display.addTextSlide(verse, text, footer, true); expect(Display._slides[verse]).toEqual(0); - expect($(".slides > section").length).toEqual(1); - expect($(".slides > section")[0].innerHTML).toEqual(_prepareText(text)); - expect(Display.reinit).toHaveBeenCalled(); + expect($(".slides > section > section").length).toEqual(1); + expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); + expect(Display.reinit).toHaveBeenCalledTimes(0); }); }); @@ -526,12 +526,12 @@ describe("Display.setTextSlides", function () { expect(Display.clearSlides).toHaveBeenCalledTimes(1); expect(Display._slides["v1"]).toEqual(0); expect(Display._slides["v2"]).toEqual(1); - expect($(".slides > section").length).toEqual(2); + expect($(".slides > section > section").length).toEqual(2); expect(Display.reinit).toHaveBeenCalledTimes(1); - expect(Reveal.slide).toHaveBeenCalledWith(0); + expect(Reveal.slide).toHaveBeenCalledWith(0, 0); }); - xit("should correctly set outline width (skipped for now)", function () { + it("should correctly set outline width", function () { const slides = [ { "verse": "v1", @@ -549,14 +549,68 @@ describe("Display.setTextSlides", function () { spyOn(Display, "reinit"); spyOn(Reveal, "slide"); - Display.setTextSlides(slides); Display.setTheme(theme); + Display.setTextSlides(slides); - const slidesDiv = $(".slides")[0]; + const slidesDiv = $(".text-slides")[0]; expect(slidesDiv.style['-webkit-text-stroke']).toEqual('42pt red'); - expect(slidesDiv.style['padding-left']).toEqual('84pt'); expect(slidesDiv.style['-webkit-text-fill-color']).toEqual('yellow'); }) + + it("should correctly set text alignment,\ + (check the order of alignments in the emuns are the same in both js and python)", function () { + const slides = [ + { + "verse": "v1", + "text": "Amazing grace, how sweet the sound\nThat saved a wretch like me\n" + + "I once was lost, but now I'm found\nWas blind but now I see", + "footer": "Public Domain" + } + ]; + // + const theme = { + 'display_horizontal_align': 3, + 'display_vertical_align': 1 + }; + spyOn(Display, "reinit"); + spyOn(Reveal, "slide"); + + Display.setTheme(theme); + Display.setTextSlides(slides); + + const slidesDiv = $(".text-slides")[0]; + expect(slidesDiv.style['text-align-last']).toEqual('justify'); + expect(slidesDiv.style['justify-content']).toEqual('center'); + }) + + it("should correctly set slide size position to theme size when adding a text slide", function () { + const slides = [ + { + "verse": "v1", + "text": "Amazing grace, how sweet the sound\nThat saved a wretch like me\n" + + "I once was lost, but now I'm found\nWas blind but now I see", + "footer": "Public Domain" + } + ]; + // + const theme = { + 'font_main_y': 789, + 'font_main_x': 1000, + 'font_main_width': 1230, + 'font_main_height': 4560 + }; + spyOn(Display, "reinit"); + spyOn(Reveal, "slide"); + + Display.setTheme(theme); + Display.setTextSlides(slides); + + const slidesDiv = $(".text-slides")[0]; + expect(slidesDiv.style['top']).toEqual('789px'); + expect(slidesDiv.style['left']).toEqual('1000px'); + expect(slidesDiv.style['width']).toEqual('1230px'); + expect(slidesDiv.style['height']).toEqual('4560px'); + }) }); describe("Display.setImageSlides", function () { @@ -578,12 +632,12 @@ describe("Display.setImageSlides", function () { expect(Display.clearSlides).toHaveBeenCalledTimes(1); expect(Display._slides["0"]).toEqual(0); expect(Display._slides["1"]).toEqual(1); - expect($(".slides > section").length).toEqual(2); - expect($(".slides > section > img").length).toEqual(2); - expect($(".slides > section > img")[0].getAttribute("src")).toEqual("file:///openlp1.jpg") - expect($(".slides > 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 > img")[1].getAttribute("src")).toEqual("file:///openlp2.jpg") - expect($(".slides > 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($(".slides > section > section").length).toEqual(2); + expect($(".slides > section > section > img").length).toEqual(2); + expect($(".slides > section > section > img")[0].getAttribute("src")).toEqual("file:///openlp1.jpg") + 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); }); });