diff --git a/.bzrignore b/.bzrignore index adae3204e..f60d6cfff 100644 --- a/.bzrignore +++ b/.bzrignore @@ -45,4 +45,11 @@ resources/innosetup/Output resources/windows/warnOpenLP.txt *.ropeproject tags +output +htmlcov +node_modules +openlp-test-projectordb.sqlite +package-lock.json +.cache +test tests.kdev4 diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 000000000..2a1038a0e --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,77 @@ +module.exports = function(config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: "", + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine"], + + // list of files / patterns to load in the browser + files: [ + "tests/js/polyfill.js", + "tests/js/fake_webchannel.js", + "openlp/core/display/html/reveal.js", + "openlp/core/display/html/display.js", + "tests/js/test_*.js" + ], + + // list of files to exclude + exclude: [ + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + // source files, that you wanna generate coverage for + // do not include tests or libraries + // (these files will be instrumented by Istanbul) + "display.js": ["coverage"] + }, + + // test results reporter to use + // possible values: "dots", "progress" + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "coverage"], + + // configure the coverateReporter + coverageReporter: { + type : "html", + dir : "htmlcov/" + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_DEBUG, + + // loggers + /* loggers: [ + {"type": "file", "filename": "karma.log"} + ],*/ + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["PhantomJS"], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + + client: { + captureConsole: true + } + }) +} diff --git a/nose2.cfg b/nose2.cfg index ae73407d7..451737d6c 100644 --- a/nose2.cfg +++ b/nose2.cfg @@ -1,5 +1,5 @@ [unittest] -verbose = True +verbose = true plugins = nose2.plugins.mp [log-capture] @@ -9,19 +9,19 @@ filter = -nose log-level = ERROR [test-result] -always-on = True -descriptions = True +always-on = true +descriptions = true [coverage] -always-on = True +always-on = true coverage = openlp coverage-report = html [multiprocess] -always-on = False +always-on = false processes = 4 [output-buffer] -always-on = True -stderr = True -stdout = False +always-on = true +stderr = true +stdout = true diff --git a/openlp/.version b/openlp/.version index c8e38b614..a4b9b4eef 100644 --- a/openlp/.version +++ b/openlp/.version @@ -1 +1 @@ -2.9.0 +2.5.dev2856 \ No newline at end of file diff --git a/openlp/core/api/deploy.py b/openlp/core/api/deploy.py index e336019d3..6bc2517a4 100644 --- a/openlp/core/api/deploy.py +++ b/openlp/core/api/deploy.py @@ -25,7 +25,7 @@ Download and "install" the remote web client from zipfile import ZipFile from openlp.core.common.applocation import AppLocation -from openlp.core.common.httputils import download_file, get_web_page, get_url_file_size +from openlp.core.common.httputils import download_file, get_url_file_size, get_web_page from openlp.core.common.registry import Registry diff --git a/openlp/core/api/endpoint/controller.py b/openlp/core/api/endpoint/controller.py index 7aa75b182..7af852d91 100644 --- a/openlp/core/api/endpoint/controller.py +++ b/openlp/core/api/endpoint/controller.py @@ -34,6 +34,7 @@ from openlp.core.common.settings import Settings from openlp.core.lib import create_thumb from openlp.core.lib.serviceitem import ItemCapabilities + log = logging.getLogger(__name__) controller_endpoint = Endpoint('controller') @@ -48,7 +49,7 @@ def controller_text(request): :param request: the http request - not used """ - log.debug("controller_text ") + log.debug('controller_text') live_controller = Registry().get('live_controller') current_item = live_controller.service_item data = [] @@ -57,13 +58,14 @@ def controller_text(request): item = {} # Handle text (songs, custom, bibles) if current_item.is_text(): - if frame['verseTag']: - item['tag'] = str(frame['verseTag']) + if frame['verse']: + item['tag'] = str(frame['verse']) else: item['tag'] = str(index + 1) - item['chords_text'] = str(frame['chords_text']) - item['text'] = str(frame['text']) - item['html'] = str(frame['html']) + # TODO: Figure out rendering chords + item['chords_text'] = str(frame.get('chords_text', '')) + item['text'] = frame['text'] + item['html'] = current_item.get_rendered_frame(index) # Handle images, unless a custom thumbnail is given or if thumbnails is disabled elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'): item['tag'] = str(index + 1) diff --git a/openlp/core/api/endpoint/core.py b/openlp/core/api/endpoint/core.py index 43a2b8459..d7fee2817 100644 --- a/openlp/core/api/endpoint/core.py +++ b/openlp/core/api/endpoint/core.py @@ -30,8 +30,8 @@ from openlp.core.api.http.endpoint import Endpoint from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.registry import Registry from openlp.core.lib import image_to_byte -from openlp.core.lib.plugin import StringContent -from openlp.core.lib.plugin import PluginStatus +from openlp.core.lib.plugin import PluginStatus, StringContent + template_dir = 'templates' static_dir = 'static' diff --git a/openlp/core/api/endpoint/remote.py b/openlp/core/api/endpoint/remote.py index 0e637339f..62b44f73b 100644 --- a/openlp/core/api/endpoint/remote.py +++ b/openlp/core/api/endpoint/remote.py @@ -24,6 +24,7 @@ import logging from openlp.core.api.endpoint.core import TRANSLATED_STRINGS from openlp.core.api.http.endpoint import Endpoint + log = logging.getLogger(__name__) remote_endpoint = Endpoint('remote', template_dir='remotes') diff --git a/openlp/core/api/endpoint/service.py b/openlp/core/api/endpoint/service.py index b642c3151..737fd7a7a 100644 --- a/openlp/core/api/endpoint/service.py +++ b/openlp/core/api/endpoint/service.py @@ -26,6 +26,7 @@ from openlp.core.api.http import requires_auth from openlp.core.api.http.endpoint import Endpoint from openlp.core.common.registry import Registry + log = logging.getLogger(__name__) service_endpoint = Endpoint('service') diff --git a/openlp/core/api/http/server.py b/openlp/core/api/http/server.py index 914ce2278..68a8e6781 100644 --- a/openlp/core/api/http/server.py +++ b/openlp/core/api/http/server.py @@ -30,22 +30,21 @@ from PyQt5 import QtCore, QtWidgets from waitress.server import create_server from openlp.core.api.deploy import download_and_check, download_sha256 -from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint -from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint +from openlp.core.api.endpoint.controller import api_controller_endpoint, controller_endpoint +from openlp.core.api.endpoint.core import blank_endpoint, chords_endpoint, main_endpoint, stage_endpoint from openlp.core.api.endpoint.remote import remote_endpoint -from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint -from openlp.core.api.http import application -from openlp.core.api.http import register_endpoint +from openlp.core.api.endpoint.service import api_service_endpoint, service_endpoint +from openlp.core.api.http import application, register_endpoint from openlp.core.api.poll import Poller from openlp.core.common.applocation import AppLocation -from openlp.core.common.i18n import UiStrings -from openlp.core.common.i18n import translate +from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import create_paths from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.threading import ThreadWorker, run_thread + log = logging.getLogger(__name__) diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py index aa90a28aa..f8f0cdd20 100644 --- a/openlp/core/api/http/wsgiapp.py +++ b/openlp/core/api/http/wsgiapp.py @@ -33,6 +33,7 @@ from webob.static import DirectoryApp from openlp.core.api.http.errors import HttpError, NotFound, ServerError from openlp.core.common.applocation import AppLocation + ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE) log = logging.getLogger(__name__) diff --git a/openlp/core/api/tab.py b/openlp/core/api/tab.py index 61e922c32..ac8af9007 100644 --- a/openlp/core/api/tab.py +++ b/openlp/core/api/tab.py @@ -31,6 +31,7 @@ from openlp.core.common.settings import Settings from openlp.core.lib.settingstab import SettingsTab from openlp.core.ui.icons import UiIcons + ZERO_URL = '0.0.0.0' @@ -43,9 +44,9 @@ class ApiTab(SettingsTab): advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced') super(ApiTab, self).__init__(parent, 'api', advanced_translated) - def setupUi(self): + def setup_ui(self): self.setObjectName('ApiTab') - super(ApiTab, self).setupUi() + super(ApiTab, self).setup_ui() self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column) self.server_settings_group_box.setObjectName('server_settings_group_box') self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box) @@ -154,7 +155,7 @@ class ApiTab(SettingsTab): self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed) self.address_edit.textChanged.connect(self.set_urls) - def retranslateUi(self): + def retranslate_ui(self): self.tab_title_visible = translate('RemotePlugin.RemoteTab', 'Remote Interface') self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings')) self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:')) diff --git a/openlp/core/api/websockets.py b/openlp/core/api/websockets.py index 5528b4250..3dcaafea6 100644 --- a/openlp/core/api/websockets.py +++ b/openlp/core/api/websockets.py @@ -35,6 +35,7 @@ from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.threading import ThreadWorker, run_thread + log = logging.getLogger(__name__) diff --git a/openlp/core/app.py b/openlp/core/app.py index bc030f61c..18718d6c7 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -33,27 +33,28 @@ import time from datetime import datetime from traceback import format_exception -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets # noqa from openlp.core.state import State from openlp.core.common import is_macosx, is_win from openlp.core.common.applocation import AppLocation from openlp.core.loader import loader from openlp.core.common.i18n import LanguageManager, UiStrings, translate -from openlp.core.common.path import create_paths, copytree +from openlp.core.common.path import copytree, create_paths from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.display.screens import ScreenList from openlp.core.resources import qInitResources -from openlp.core.ui.splashscreen import SplashScreen +from openlp.core.server import Server from openlp.core.ui.exceptionform import ExceptionForm from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm from openlp.core.ui.mainwindow import MainWindow +from openlp.core.ui.splashscreen import SplashScreen from openlp.core.ui.style import get_application_stylesheet -from openlp.core.server import Server from openlp.core.version import check_for_update, get_version + __all__ = ['OpenLP', 'main'] @@ -74,7 +75,8 @@ class OpenLP(QtWidgets.QApplication): """ self.is_event_loop_active = True result = QtWidgets.QApplication.exec() - self.server.close_server() + if hasattr(self, 'server'): + self.server.close_server() return result def run(self, args): @@ -317,7 +319,7 @@ def set_up_logging(log_path): file_path = log_path / 'openlp.log' # TODO: FileHandler accepts a Path object in Py3.6 logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8') - logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s')) + logfile.setFormatter(logging.Formatter('%(asctime)s %(threadName)s %(name)-55s %(levelname)-8s %(message)s')) log.addHandler(logfile) if log.isEnabledFor(logging.DEBUG): print('Logging to: {name}'.format(name=file_path)) @@ -330,7 +332,8 @@ def main(args=None): :param args: Some args """ args = parse_options(args) - qt_args = [] + qt_args = ['--disable-web-security'] + # qt_args = [] if args and args.loglevel.lower() in ['d', 'debug']: log.setLevel(logging.DEBUG) elif args and args.loglevel.lower() in ['w', 'warning']: diff --git a/openlp/core/common/actions.py b/openlp/core/common/actions.py index 94822bbac..59b2f3540 100644 --- a/openlp/core/common/actions.py +++ b/openlp/core/common/actions.py @@ -29,6 +29,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common.settings import Settings + log = logging.getLogger(__name__) @@ -113,7 +114,6 @@ class CategoryActionList(object): if item[1] == action: self.actions.remove(item) return - log.warning('Action "{action}" does not exist.'.format(action=action)) class CategoryList(object): diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py index 7b7c5781d..8d2f04105 100644 --- a/openlp/core/common/applocation.py +++ b/openlp/core/common/applocation.py @@ -29,10 +29,11 @@ import sys import appdirs import openlp -from openlp.core.common import get_frozen_path, is_win, is_macosx +from openlp.core.common import get_frozen_path, is_macosx, is_win from openlp.core.common.path import Path, create_paths from openlp.core.common.settings import Settings + log = logging.getLogger(__name__) FROZEN_APP_PATH = Path(sys.argv[0]).parent diff --git a/openlp/core/common/db.py b/openlp/core/common/db.py index ef5b7b2b7..b3e66aef7 100644 --- a/openlp/core/common/db.py +++ b/openlp/core/common/db.py @@ -27,6 +27,7 @@ from copy import deepcopy import sqlalchemy + log = logging.getLogger(__name__) diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index 54173a8d2..5eab07047 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -34,6 +34,7 @@ from openlp.core.common import trace_error_handler from openlp.core.common.registry import Registry from openlp.core.common.settings import ProxyMode, Settings + log = logging.getLogger(__name__ + '.__init__') USER_AGENTS = { diff --git a/openlp/core/common/i18n.py b/openlp/core/common/i18n.py index b2579d132..985d2f486 100644 --- a/openlp/core/common/i18n.py +++ b/openlp/core/common/i18n.py @@ -29,10 +29,11 @@ from collections import namedtuple from PyQt5 import QtCore, QtWidgets -from openlp.core.common import is_win, is_macosx +from openlp.core.common import is_macosx, is_win from openlp.core.common.applocation import AppLocation from openlp.core.common.settings import Settings + log = logging.getLogger(__name__) diff --git a/openlp/core/common/mixins.py b/openlp/core/common/mixins.py index c78611077..9b58ffa63 100644 --- a/openlp/core/common/mixins.py +++ b/openlp/core/common/mixins.py @@ -28,6 +28,7 @@ import logging from openlp.core.common import is_win, trace_error_handler from openlp.core.common.registry import Registry + DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed', 'preview_size_changed', 'resizeEvent'] diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 64ec64a94..b3d96da71 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -25,6 +25,7 @@ from contextlib import suppress from openlp.core.common import is_win + if is_win(): from pathlib import WindowsPath as PathVariant # pragma: nocover else: diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 39ebe8d83..628bf4750 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -27,6 +27,7 @@ import sys from openlp.core.common import de_hump, trace_error_handler + log = logging.getLogger(__name__) @@ -143,6 +144,7 @@ class Registry(object): if event in self.functions_list: for function in self.functions_list[event]: try: + log.debug('Running function {} for {}'.format(function, event)) result = function(*args, **kwargs) if result: results.append(result) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 403b82d97..d31506420 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -33,7 +33,8 @@ from PyQt5 import QtCore, QtGui from openlp.core.common import SlideLimits, ThemeLevel, is_linux, is_win from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder -from openlp.core.common.path import Path, str_to_path, files_to_paths +from openlp.core.common.path import Path, files_to_paths, str_to_path + log = logging.getLogger(__name__) @@ -70,6 +71,34 @@ def media_players_conv(string): return string +def upgrade_screens(number, x_position, y_position, height, width, can_override, is_display_screen): + """ + Upgrade them monitor setting from a few single entries to a composite JSON entry + + :param int number: The old monitor number + :param int x_position: The X position + :param int y_position: The Y position + :param bool can_override: Are the screen positions overridden + :param bool is_display_screen: Is this a display screen + :returns dict: Dictionary with the new value + """ + geometry_key = 'geometry' + if can_override: + geometry_key = 'custom_geometry' + return { + number: { + 'number': number, + geometry_key: { + 'x': x_position, + 'y': y_position, + 'height': height, + 'width': width + }, + 'is_display': is_display_screen + } + } + + class Settings(QtCore.QSettings): """ Class to wrap QSettings. @@ -175,6 +204,7 @@ class Settings(QtCore.QSettings): # circular dependency. 'core/display on monitor': True, 'core/override position': False, + 'core/monitor': {}, 'core/application version': '0.0', 'images/background color': '#000000', 'media/players': 'system,webkit', @@ -276,6 +306,8 @@ class Settings(QtCore.QSettings): ('songuasge/db hostname', 'songusage/db hostname', []), ('songuasge/db database', 'songusage/db database', []), ('presentations / Powerpoint Viewer', '', []), + (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override', + 'core/display on monitor'], 'core/screens', [(upgrade_screens, [1, 0, 0, None, None, False, False])]), ('bibles/proxy name', '', []), # Just remove these bible proxy settings. They weren't used in 2.4! ('bibles/proxy address', '', []), ('bibles/proxy username', '', []), @@ -545,7 +577,7 @@ class Settings(QtCore.QSettings): :param value: The value to save :rtype: None """ - if isinstance(value, Path) or (isinstance(value, list) and value and isinstance(value[0], Path)): + if isinstance(value, (Path, dict)) or (isinstance(value, list) and value and isinstance(value[0], Path)): value = json.dumps(value, cls=OpenLPJsonEncoder) super().setValue(key, value) @@ -568,8 +600,11 @@ class Settings(QtCore.QSettings): # An empty list saved to the settings results in a None type being returned. elif isinstance(default_value, list): return [] + # An empty dictionary saved to the settings results in a None type being returned. + elif isinstance(default_value, dict): + return {} elif isinstance(setting, str): - if '__Path__' in setting: + if '__Path__' in setting or setting.startswith('{'): return json.loads(setting, cls=OpenLPJsonDecoder) # Convert the setting to the correct type. if isinstance(default_value, bool): @@ -578,6 +613,8 @@ class Settings(QtCore.QSettings): # Sometimes setting is string instead of a boolean. return setting == 'true' if isinstance(default_value, int): + if setting is None: + return 0 return int(setting) return setting diff --git a/openlp/core/display/html/black.css b/openlp/core/display/html/black.css new file mode 100644 index 000000000..96e4fd480 --- /dev/null +++ b/openlp/core/display/html/black.css @@ -0,0 +1,292 @@ +/** + * Black theme for reveal.js. This is the opposite of the 'white' theme. + * + * By Hakim El Hattab, http://hakim.se + */ +@import url(../../lib/font/source-sans-pro/source-sans-pro.css); +section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 { + color: #222; } + +/********************************************* + * GLOBAL STYLES + *********************************************/ +body { + background: #222; + background-color: #222; } + +.reveal { + font-family: "Source Sans Pro", Helvetica, sans-serif; + font-size: 42px; + font-weight: normal; + color: #fff; } + +::selection { + color: #fff; + background: #bee4fd; + text-shadow: none; } + +::-moz-selection { + color: #fff; + background: #bee4fd; + text-shadow: none; } + +.reveal .slides > section, +.reveal .slides > section > section { + line-height: 1.3; + font-weight: inherit; } + +/********************************************* + * HEADERS + *********************************************/ +.reveal h1, +.reveal h2, +.reveal h3, +.reveal h4, +.reveal h5, +.reveal h6 { + margin: 0 0 20px 0; + color: #fff; + font-family: "Source Sans Pro", Helvetica, sans-serif; + font-weight: 600; + line-height: 1.2; + letter-spacing: normal; + text-transform: uppercase; + text-shadow: none; + word-wrap: break-word; } + +.reveal h1 { + font-size: 2.5em; } + +.reveal h2 { + font-size: 1.6em; } + +.reveal h3 { + font-size: 1.3em; } + +.reveal h4 { + font-size: 1em; } + +.reveal h1 { + text-shadow: none; } + +/********************************************* + * OTHER + *********************************************/ +.reveal p { + margin: 20px 0; + line-height: 1.3; } + +/* Ensure certain elements are never larger than the slide itself */ +.reveal img, +.reveal video, +.reveal iframe { + max-width: 95%; + max-height: 95%; } + +.reveal strong, +.reveal b { + font-weight: bold; } + +.reveal em { + font-style: italic; } + +.reveal ol, +.reveal dl, +.reveal ul { + display: inline-block; + text-align: left; + margin: 0 0 0 1em; } + +.reveal ol { + list-style-type: decimal; } + +.reveal ul { + list-style-type: disc; } + +.reveal ul ul { + list-style-type: square; } + +.reveal ul ul ul { + list-style-type: circle; } + +.reveal ul ul, +.reveal ul ol, +.reveal ol ol, +.reveal ol ul { + display: block; + margin-left: 40px; } + +.reveal dt { + font-weight: bold; } + +.reveal dd { + margin-left: 40px; } + +.reveal q, +.reveal blockquote { + quotes: none; } + +.reveal blockquote { + display: block; + position: relative; + width: 70%; + margin: 20px auto; + padding: 5px; + font-style: italic; + background: rgba(255, 255, 255, 0.05); + box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); } + +.reveal blockquote p:first-child, +.reveal blockquote p:last-child { + display: inline-block; } + +.reveal q { + font-style: italic; } + +.reveal pre { + display: block; + position: relative; + width: 90%; + margin: 20px auto; + text-align: left; + font-size: 0.55em; + font-family: monospace; + line-height: 1.2em; + word-wrap: break-word; + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); } + +.reveal code { + font-family: monospace; } + +.reveal pre code { + display: block; + padding: 5px; + overflow: auto; + max-height: 400px; + word-wrap: normal; } + +.reveal table { + margin: auto; + border-collapse: collapse; + border-spacing: 0; } + +.reveal table th { + font-weight: bold; } + +.reveal table th, +.reveal table td { + text-align: left; + padding: 0.2em 0.5em 0.2em 0.5em; + border-bottom: 1px solid; } + +.reveal table th[align="center"], +.reveal table td[align="center"] { + text-align: center; } + +.reveal table th[align="right"], +.reveal table td[align="right"] { + text-align: right; } + +.reveal table tbody tr:last-child th, +.reveal table tbody tr:last-child td { + border-bottom: none; } + +.reveal sup { + vertical-align: super; } + +.reveal sub { + vertical-align: sub; } + +.reveal small { + display: inline-block; + font-size: 0.6em; + line-height: 1.2em; + vertical-align: top; } + +.reveal small * { + vertical-align: top; } + +/********************************************* + * LINKS + *********************************************/ +.reveal a { + color: #42affa; + text-decoration: none; + -webkit-transition: color .15s ease; + -moz-transition: color .15s ease; + transition: color .15s ease; } + +.reveal a:hover { + color: #8dcffc; + text-shadow: none; + border: none; } + +.reveal .roll span:after { + color: #fff; + background: #068de9; } + +/********************************************* + * IMAGES + *********************************************/ +.reveal section img { + margin: 15px 0px; + background: rgba(255, 255, 255, 0.12); + border: 4px solid #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); } + +.reveal section img.plain { + border: 0; + box-shadow: none; } + +.reveal a img { + -webkit-transition: all .15s linear; + -moz-transition: all .15s linear; + transition: all .15s linear; } + +.reveal a:hover img { + background: rgba(255, 255, 255, 0.2); + border-color: #42affa; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); } + +/********************************************* + * NAVIGATION CONTROLS + *********************************************/ +.reveal .controls .navigate-left, +.reveal .controls .navigate-left.enabled { + border-right-color: #42affa; } + +.reveal .controls .navigate-right, +.reveal .controls .navigate-right.enabled { + border-left-color: #42affa; } + +.reveal .controls .navigate-up, +.reveal .controls .navigate-up.enabled { + border-bottom-color: #42affa; } + +.reveal .controls .navigate-down, +.reveal .controls .navigate-down.enabled { + border-top-color: #42affa; } + +.reveal .controls .navigate-left.enabled:hover { + border-right-color: #8dcffc; } + +.reveal .controls .navigate-right.enabled:hover { + border-left-color: #8dcffc; } + +.reveal .controls .navigate-up.enabled:hover { + border-bottom-color: #8dcffc; } + +.reveal .controls .navigate-down.enabled:hover { + border-top-color: #8dcffc; } + +/********************************************* + * PROGRESS BAR + *********************************************/ +.reveal .progress { + background: rgba(0, 0, 0, 0.2); } + +.reveal .progress span { + background: #42affa; + -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); + -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); + transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } diff --git a/openlp/core/display/html/checkerboard.png b/openlp/core/display/html/checkerboard.png new file mode 100644 index 000000000..52b51242f Binary files /dev/null and b/openlp/core/display/html/checkerboard.png differ diff --git a/openlp/core/display/html/display.html b/openlp/core/display/html/display.html new file mode 100644 index 000000000..83e0bac90 --- /dev/null +++ b/openlp/core/display/html/display.html @@ -0,0 +1,39 @@ + + + + Display Window + + + + + + + +
+
+
+ +
+ + diff --git a/openlp/core/display/html/display.js b/openlp/core/display/html/display.js new file mode 100644 index 000000000..9c6eda634 --- /dev/null +++ b/openlp/core/display/html/display.js @@ -0,0 +1,789 @@ +/** + * display.js is the main Javascript file that is used to drive the display. + */ + +/** + * Background type enumeration + */ +var BackgroundType = { + Transparent: "transparent", + Solid: "solid", + Gradient: "gradient", + Video: "video", + Image: "image" +}; + +/** + * Gradient type enumeration + */ +var GradientType = { + Horizontal: "horizontal", + LeftTop: "leftTop", + LeftBottom: "leftBottom", + Vertical: "vertical", + Circular: "circular" +}; + +/** + * Horizontal alignment enumeration + */ +var HorizontalAlign = { + Left: "left", + Right: "right", + Center: "center", + Justify: "justify" +}; + +/** + * Vertical alignment enumeration + */ +var VerticalAlign = { + Top: "top", + Middle: "middle", + Bottom: "bottom" +}; + +/** + * Audio state enumeration + */ +var AudioState = { + Playing: "playing", + Paused: "paused", + Stopped: "stopped" +}; + +/** + * Return an array of elements based on the selector query + * @param {string} selector - The selector to find elements + * @returns {array} An array of matching elements + */ +function $(selector) { + return Array.from(document.querySelectorAll(selector)); +} + +/** + * Build linear gradient CSS + * @private + * @param {string} startDir - Starting direction + * @param {string} endDir - Ending direction + * @param {string} startColor - The starting color + * @param {string} endColor - The ending color + * @returns {string} A string of the gradient CSS + */ +function _buildLinearGradient(startDir, endDir, startColor, endColor) { + return "-webkit-gradient(linear, " + startDir + ", " + endDir + ", from(" + startColor + "), to(" + endColor + ")) fixed"; +} + +/** + * Build radial gradient CSS + * @private + * @param {string} width - Width of the gradient + * @param {string} startColor - The starting color + * @param {string} endColor - The ending color + * @returns {string} A string of the gradient CSS + */ +function _buildRadialGradient(width, startColor, endColor) { + return "-webkit-gradient(radial, " + width + " 50%, 100, " + width + " 50%, " + width + ", from(" + startColor + "), to(" + endColor + ")) fixed"; +} + +/** + * Get a style value from an element (computed or manual) + * @private + * @param {Object} element - The element whose style we want + * @param {string} style - The name of the style we want + * @returns {(Number|string)} The style value (type depends on the style) + */ +function _getStyle(element, style) { + return document.defaultView.getComputedStyle(element).getPropertyValue(style); +} + +/** + * Convert newlines to
tags + * @private + * @param {string} text - The text to parse + * @returns {string} The text now with
tags + */ +function _nl2br(text) { + return text.replace("\r\n", "\n").replace("\n", "
"); +} + +/** + * Prepare text by creating paragraphs and calling _nl2br to convert newlines to
tags + * @private + * @param {string} text - The text to parse + * @returns {string} The text now with

and
tags + */ +function _prepareText(text) { + return "

" + _nl2br(text) + "

"; +} + +/** + * The paths we get are JSON versions of Python Path objects, so let's just fix that. + * @private + * @param {object} path - The Path object + * @returns {string} The actual file path + */ +function _pathToString(path) { + var filename = path.__Path__.join("/").replace("//", "/"); + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + return filename; +} + +/** + * An audio player with a play list + */ +var AudioPlayer = function (audioElement) { + this._audioElement = null; + this._eventListeners = {}; + this._playlist = []; + this._currentTrack = null; + this._canRepeat = false; + this._state = AudioState.Stopped; + this.createAudioElement(); +}; + +/** + * Call all listeners associated with this event + * @private + * @param {object} event - The event that was emitted + */ +AudioPlayer.prototype._callListener = function (event) { + if (this._eventListeners.hasOwnProperty(event.type)) { + this._eventListeners[event.type].forEach(function (listener) { + listener(event); + }); + } + else { + console.warn("Received unknown event \"" + event.type + "\", doing nothing."); + } +}; + +/** + * Create the