api and router work, move settings and tab

This commit is contained in:
Tim Bentley 2016-06-10 17:16:35 +01:00
parent fa97bfce10
commit 6a955a5573
46 changed files with 20414 additions and 26 deletions

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from .errors import NotFound, ServerError
from .httprouter import WSGIApplication
application = WSGIApplication('api')
def _route_from_url(url_prefix, url):
"""
Create a route from the URL
"""
url_prefix = '/{prefix}/'.format(prefix=url_prefix.strip('/'))
if not url:
url = url_prefix[:-1]
else:
url = url_prefix + url
url = url.replace('//', '/')
return url
def register_endpoint(endpoint):
"""
Register an endpoint with the app
"""
print("ep", endpoint)
for url, view_func, method, secure in endpoint.routes:
route = _route_from_url(endpoint.url_prefix, url)
application.add_route(route, view_func, method, secure)
from .endpoint import Endpoint
from .apitab import ApiTab
from .poll import OpenLPPoll
from .wsserver import OpenWSServer
from .httpserver import OpenLPHttpServer
from .apicontroller import ApiController
__all__ = ['OpenLPPoll', 'RemoteController', 'OpenLPHttpServer', 'application']

View File

@ -21,8 +21,9 @@
###############################################################################
import logging
from openlp.core.api import OpenWSServer, OpenLPPoll, OpenLPHttpServer
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties
from openlp.core.lib.api import OpenWSServer, OpenLPPoll, OpenLPHttpServer
from openlp.core.api.uiinterfaces import stage_endpoint
log = logging.getLogger(__name__)
@ -45,7 +46,7 @@ class ApiController(RegistryMixin, OpenLPMixin, RegistryProperties):
Constructor
"""
super(ApiController, self).__init__(parent)
# Registry().register_function('playbackPlay', self.media_play_msg)
print("apic")
def bootstrap_post_set_up(self):
"""

266
openlp/core/api/apitab.py Normal file
View File

@ -0,0 +1,266 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
from openlp.core.common import Settings, translate
from openlp.core.lib import SettingsTab
ZERO_URL = '0.0.0.0'
class ApiTab(SettingsTab):
"""
RemoteTab is the Remotes settings tab in the settings dialog.
"""
def __init__(self, parent):
self.icon_path = ':/plugins/plugin_remote.png'
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
super(ApiTab, self).__init__(parent, 'remotes', advanced_translated)
def setupUi(self):
self.setObjectName('ApiTab')
super(ApiTab, self).setupUi()
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)
self.server_settings_layout.setObjectName('server_settings_layout')
self.address_label = QtWidgets.QLabel(self.server_settings_group_box)
self.address_label.setObjectName('address_label')
self.address_edit = QtWidgets.QLineEdit(self.server_settings_group_box)
self.address_edit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
self))
self.address_edit.setObjectName('address_edit')
self.server_settings_layout.addRow(self.address_label, self.address_edit)
self.twelve_hour_check_box = QtWidgets.QCheckBox(self.server_settings_group_box)
self.twelve_hour_check_box.setObjectName('twelve_hour_check_box')
self.server_settings_layout.addRow(self.twelve_hour_check_box)
self.thumbnails_check_box = QtWidgets.QCheckBox(self.server_settings_group_box)
self.thumbnails_check_box.setObjectName('thumbnails_check_box')
self.server_settings_layout.addRow(self.thumbnails_check_box)
self.left_layout.addWidget(self.server_settings_group_box)
self.http_settings_group_box = QtWidgets.QGroupBox(self.left_column)
self.http_settings_group_box.setObjectName('http_settings_group_box')
self.http_setting_layout = QtWidgets.QFormLayout(self.http_settings_group_box)
self.http_setting_layout.setObjectName('http_setting_layout')
self.port_label = QtWidgets.QLabel(self.http_settings_group_box)
self.port_label.setObjectName('port_label')
self.port_spin_box = QtWidgets.QSpinBox(self.http_settings_group_box)
self.port_spin_box.setMaximum(32767)
self.port_spin_box.setObjectName('port_spin_box')
self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
self.remote_url_label = QtWidgets.QLabel(self.http_settings_group_box)
self.remote_url_label.setObjectName('remote_url_label')
self.remote_url = QtWidgets.QLabel(self.http_settings_group_box)
self.remote_url.setObjectName('remote_url')
self.remote_url.setOpenExternalLinks(True)
self.http_setting_layout.addRow(self.remote_url_label, self.remote_url)
self.stage_url_label = QtWidgets.QLabel(self.http_settings_group_box)
self.stage_url_label.setObjectName('stage_url_label')
self.stage_url = QtWidgets.QLabel(self.http_settings_group_box)
self.stage_url.setObjectName('stage_url')
self.stage_url.setOpenExternalLinks(True)
self.http_setting_layout.addRow(self.stage_url_label, self.stage_url)
self.live_url_label = QtWidgets.QLabel(self.http_settings_group_box)
self.live_url_label.setObjectName('live_url_label')
self.live_url = QtWidgets.QLabel(self.http_settings_group_box)
self.live_url.setObjectName('live_url')
self.live_url.setOpenExternalLinks(True)
self.http_setting_layout.addRow(self.live_url_label, self.live_url)
self.left_layout.addWidget(self.http_settings_group_box)
self.user_login_group_box = QtWidgets.QGroupBox(self.left_column)
self.user_login_group_box.setCheckable(True)
self.user_login_group_box.setChecked(False)
self.user_login_group_box.setObjectName('user_login_group_box')
self.user_login_layout = QtWidgets.QFormLayout(self.user_login_group_box)
self.user_login_layout.setObjectName('user_login_layout')
self.user_id_label = QtWidgets.QLabel(self.user_login_group_box)
self.user_id_label.setObjectName('user_id_label')
self.user_id = QtWidgets.QLineEdit(self.user_login_group_box)
self.user_id.setObjectName('user_id')
self.user_login_layout.addRow(self.user_id_label, self.user_id)
self.password_label = QtWidgets.QLabel(self.user_login_group_box)
self.password_label.setObjectName('password_label')
self.password = QtWidgets.QLineEdit(self.user_login_group_box)
self.password.setObjectName('password')
self.user_login_layout.addRow(self.password_label, self.password)
self.left_layout.addWidget(self.user_login_group_box)
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
self.android_app_group_box.setObjectName('android_app_group_box')
self.right_layout.addWidget(self.android_app_group_box)
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
self.android_qr_layout.setObjectName('android_qr_layout')
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.android_qr_code_label.setObjectName('android_qr_code_label')
self.android_qr_layout.addWidget(self.android_qr_code_label)
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
self.android_qr_description_label.setObjectName('android_qr_description_label')
self.android_qr_description_label.setOpenExternalLinks(True)
self.android_qr_description_label.setWordWrap(True)
self.android_qr_layout.addWidget(self.android_qr_description_label)
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
self.ios_app_group_box.setObjectName('ios_app_group_box')
self.right_layout.addWidget(self.ios_app_group_box)
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
self.ios_qr_layout.setObjectName('ios_qr_layout')
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
self.ios_qr_description_label.setOpenExternalLinks(True)
self.ios_qr_description_label.setWordWrap(True)
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
self.left_layout.addStretch()
self.right_layout.addStretch()
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
self.address_edit.textChanged.connect(self.set_urls)
self.port_spin_box.valueChanged.connect(self.set_urls)
def retranslateUi(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:'))
self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:'))
self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:'))
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.android_qr_description_label.setText(
translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the Android app from Google '
'Play.').format(qr='https://play.google.com/store/apps/details?id=org.openlp.android2'))
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
self.ios_qr_description_label.setText(
translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
def set_urls(self):
"""
Update the display based on the data input on the screen
"""
ip_address = self.get_ip_address(self.address_edit.text())
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.value())
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
http_url_temp = http_url + 'stage'
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
http_url_temp = http_url + 'main'
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
def get_ip_address(self, ip_address):
"""
returns the IP address in dependency of the passed address
ip_address == 0.0.0.0: return the IP address of the first valid interface
else: return ip_address
"""
if ip_address == ZERO_URL:
interfaces = QtNetwork.QNetworkInterface.allInterfaces()
for interface in interfaces:
if not interface.isValid():
continue
if not (interface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)):
continue
for address in interface.addressEntries():
ip = address.ip()
if ip.protocol() == QtNetwork.QAbstractSocket.IPv4Protocol and \
ip != QtNetwork.QHostAddress.LocalHost:
return ip.toString()
return ip_address
def load(self):
"""
Load the configuration and update the server configuration if necessary
"""
self.port_spin_box.setValue(Settings().value(self.settings_section + '/port'))
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
self.twelve_hour_check_box.setChecked(self.twelve_hour)
self.thumbnails = Settings().value(self.settings_section + '/thumbnails')
self.thumbnails_check_box.setChecked(self.thumbnails)
self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled'))
self.user_id.setText(Settings().value(self.settings_section + '/user id'))
self.password.setText(Settings().value(self.settings_section + '/password'))
self.set_urls()
def save(self):
"""
Save the configuration and update the server configuration if necessary
"""
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text() or \
Settings().value(self.settings_section + '/port') != self.port_spin_box.value():
self.settings_form.register_post_process('remotes_config_updated')
Settings().setValue(self.settings_section + '/port', self.port_spin_box.value())
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
Settings().setValue(self.settings_section + '/password', self.password.text())
self.generate_icon()
def on_twelve_hour_check_box_changed(self, check_state):
"""
Toggle the 12 hour check box.
"""
self.twelve_hour = False
# we have a set value convert to True/False
if check_state == QtCore.Qt.Checked:
self.twelve_hour = True
def on_thumbnails_check_box_changed(self, check_state):
"""
Toggle the thumbnail check box.
"""
self.thumbnails = False
# we have a set value convert to True/False
if check_state == QtCore.Qt.Checked:
self.thumbnails = True
def generate_icon(self):
"""
Generate icon for main window
"""
self.remote_server_icon.hide()
icon = QtGui.QImage(':/remote/network_server.png')
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
if Settings().value(self.settings_section + '/authentication enabled'):
overlay = QtGui.QImage(':/remote/network_auth.png')
overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
painter = QtGui.QPainter(icon)
painter.drawImage(20, 0, overlay)
painter.end()
self.remote_server_icon.setPixmap(QtGui.QPixmap.fromImage(icon))
self.remote_server_icon.show()

View File

@ -0,0 +1,34 @@
"""
openlp/core/api/endpoint.py: Endpoint stuff
"""
class Endpoint(object):
"""
This is an endpoint for the API
"""
def __init__(self, url_prefix):
"""
Create an endpoint with a URL prefix
"""
print("init")
self.url_prefix = url_prefix
self.routes = []
def add_url_route(self, url, view_func, method, secure):
"""
Add a url route to the list of routes
"""
self.routes.append((url, view_func, method, secure))
def route(self, rule, method='GET', secure=False):
"""
Set up a URL route
"""
def decorator(func):
"""
Make this a decorator
"""
self.add_url_route(rule, func, method, secure)
return func
return decorator

67
openlp/core/api/errors.py Normal file
View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
HTTP Error classes
"""
class HttpError(Exception):
"""
A base HTTP error (aka status code)
"""
def __init__(self, status, message):
"""
Initialise the exception
"""
super(HttpError, self).__init__(message)
self.status = status
self.message = message
def to_response(self):
"""
Convert this exception to a Response object
"""
return self.message, self.status
class NotFound(HttpError):
"""
A 404
"""
def __init__(self):
"""
Make this a 404
"""
super(NotFound, self).__init__(404, 'Not Found')
class ServerError(HttpError):
"""
A 500
"""
def __init__(self):
"""
Make this a 500
"""
super(ServerError, self).__init__(500, 'Server Error')

9404
openlp/core/api/html/assets/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
******************************************************************************/
body {
background-color: black;
font-family: sans-serif;
overflow: hidden;
}
.size {
position: absolute;
top: 0px;
vertical-align: middle;
height: 100%;
background-size: cover;
background-repeat: no-repeat;
}

View File

@ -0,0 +1,31 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
******************************************************************************/
.ui-icon-blank {
background-image: url(../images/ui-icon-blank.png);
}
.ui-icon-unblank {
background-image: url(../images/ui-icon-unblank.png);
}
/* Overwrite style from jquery-mobile.min.css */
.ui-li .ui-btn-text a.ui-link-inherit{
white-space: normal;
}

View File

@ -0,0 +1,64 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
******************************************************************************/
body {
background-color: black;
font-family: sans-serif;
overflow: hidden;
}
#currentslide {
font-size: 40pt;
color: white;
padding-bottom: 0px;
}
#nextslide {
font-size: 40pt;
color: grey;
padding-top: 0px;
padding-bottom: 0px;
}
#right {
float: right;
}
#clock {
font-size: 30pt;
color: yellow;
text-align: right;
}
#notes {
font-size: 36pt;
color: salmon;
text-align: right;
}
#verseorder {
font-size: 30pt;
color: green;
text-align: left;
}
.currenttag {
color: lightgreen;
font-weight: bold;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html>
<!--
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-->
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
<title>${app_title}</title>
<link rel="stylesheet" href="/assets/jquery.mobile.min.css" />
<link rel="stylesheet" href="/css/openlp.css" />
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
<script type="text/javascript" src="/assets/jquery.min.js"></script>
<script type="text/javascript" src="/js/openlp.js"></script>
<script type="text/javascript" src="/assets/jquery.mobile.min.js"></script>
<script type="text/javascript">
translationStrings = {
"go_live": "${go_live}",
"add_to_service": "${add_to_service}",
"no_results": "${no_results}",
"home": "${home}"
}
</script>
</head>
<body>
<div data-role="page" id="home">
<div data-role="header">
<h1>${app_title}</h1>
</div>
<div data-role="content">
<div data-role="controlgroup">
<a href="#service-manager" data-role="button" data-icon="arrow-r" data-iconpos="right">${service_manager}</a>
<a href="#slide-controller" data-role="button" data-icon="arrow-r" data-iconpos="right">${slide_controller}</a>
<a href="#alerts" data-role="button" data-icon="arrow-r" data-iconpos="right">${alerts}</a>
<a href="#search" data-role="button" data-icon="arrow-r" data-iconpos="right">${search}</a>
</div>
</div>
</div>
<div data-role="page" id="service-manager">
<div data-role="header" data-position="fixed">
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
<h1>${service_manager}</h1>
<a href="#" id="service-refresh" data-role="button" data-icon="refresh">${refresh}</a>
<div data-role="navbar">
<ul>
<li><a href="#service-manager" data-theme="e">${service}</a></li>
<li><a href="#slide-controller">${slides}</a></li>
<li><a href="#alerts">${alerts}</a></li>
<li><a href="#search">${search}</a></li>
</ul>
</div>
</div>
<div data-role="content">
<ul data-role="listview" data-inset="true">
</ul>
</div>
<div data-role="footer" data-theme="b" class="ui-bar" data-position="fixed">
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
<a href="#" id="service-blank" data-role="button" data-icon="blank">${blank}</a>
<a href="#" id="service-theme" data-role="button">${theme}</a>
<a href="#" id="service-desktop" data-role="button">${desktop}</a>
<a href="#" id="service-show" data-role="button" data-icon="unblank" data-iconpos="right">${show}</a>
</div>
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
<a href="#" id="service-previous" data-role="button" data-icon="arrow-l">${prev}</a>
<a href="#" id="service-next" data-role="button" data-icon="arrow-r" data-iconpos="right">${next}</a>
</div>
</div>
</div>
<div data-role="page" id="slide-controller">
<div data-role="header" data-position="fixed">
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
<h1>${slide_controller}</h1>
<a href="#" id="controller-refresh" data-role="button" data-icon="refresh">${refresh}</a>
<div data-role="navbar">
<ul>
<li><a href="#service-manager">${service}</a></li>
<li><a href="#slide-controller" data-theme="e">${slides}</a></li>
<li><a href="#alerts">${alerts}</a></li>
<li><a href="#search">${search}</a></li>
</ul>
</div>
</div>
<div data-role="content">
<ul data-role="listview" data-inset="true">
</ul>
</div>
<div data-role="footer" data-theme="b" class="ui-bar" data-position="fixed">
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
<a href="#" id="controller-blank" data-role="button" data-icon="blank">${blank}</a>
<a href="#" id="controller-theme" data-role="button">${theme}</a>
<a href="#" id="controller-desktop" data-role="button">${desktop}</a>
<a href="#" id="controller-show" data-role="button" data-icon="unblank" data-iconpos="right">${show}</a>
</div>
<div data-role="controlgroup" data-type="horizontal" style="float: left;">
<a href="#" id="controller-previous" data-role="button" data-icon="arrow-l">${prev}</a>
<a href="#" id="controller-next" data-role="button" data-icon="arrow-r" data-iconpos="right">${next}</a>
</div>
</div>
</div>
<div data-role="page" id="alerts">
<div data-role="header">
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
<h1>${alerts}</h1>
<div data-role="navbar">
<ul>
<li><a href="#service-manager">${service}</a></li>
<li><a href="#slide-controller">${slides}</a></li>
<li><a href="#alerts" data-theme="e">${alerts}</a></li>
<li><a href="#search">${search}</a></li>
</ul>
</div>
</div>
<div data-role="content">
<div data-role="fieldcontain">
<label for="alert-text">${text}:</label>
<input type="text" name="alert-text" id="alert-text" value="" />
</div>
<a href="#" id="alert-submit" data-role="button">${show_alert}</a>
</div>
</div>
<div data-role="page" id="search">
<div data-role="header" data-position="fixed">
<a href="#home" data-role="button" data-icon="home" data-iconpos="left">${home}</a>
<h1>${search}</h1>
<div data-role="navbar">
<ul>
<li><a href="#service-manager">${service}</a></li>
<li><a href="#slide-controller">${slides}</a></li>
<li><a href="#alerts">${alerts}</a></li>
<li><a href="#search" data-theme="e">${search}</a></li>
</ul>
</div>
</div>
<div data-role="content">
<div data-role="fieldcontain">
<label for="search-plugin">${search}:</label>
<select name="search-plugin" id="search-plugin" data-native-menu="false"></select>
</div>
<div data-role="fieldcontain">
<label for="search-text">${text}:</label>
<input type="search" name="search-text" id="search-text" value="" />
</div>
<a href="#" id="search-submit" data-role="button">${search}</a>
<ul data-role="listview" data-inset="true"/>
</div>
</div>
<div data-role="page" id="options">
<div data-role="header" data-position="inline" data-theme="b">
<h1>${options}</h1>
</div>
<div data-role="content">
<input type="hidden" id="selected-item" value="" />
<a href="#" id="go-live" data-role="button">${go_live}</a>
<a href="#" id="add-to-service" data-role="button">${add_to_service}</a>
<a href="#" id="add-and-go-to-service" data-role="button">${add_and_go_to_service}</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,50 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
******************************************************************************/
window.OpenLP = {
loadSlide: function (event) {
$.getJSON(
"/main/image",
function (data, status) {
var img = document.getElementById('image');
img.src = data.results.slide_image;
img.style.display = 'block';
}
);
},
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4317/main_poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
if (OpenLP.slideCount != msg.results.slide_count) {
OpenLP.slideCount = msg.results.slide_count;
OpenLP.loadSlide();
}
}
} else {
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}
}
};
$.ajaxSetup({ cache: false });
OpenLP.pollServer();

View File

@ -0,0 +1,390 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
******************************************************************************/
window.OpenLP = {
getElement: function(event) {
var targ;
if (!event) {
var event = window.event;
}
if (event.target) {
targ = event.target;
}
else if (event.srcElement) {
targ = event.srcElement;
}
if (targ.nodeType == 3) {
// defeat Safari bug
targ = targ.parentNode;
}
var isSecure = false;
var isAuthorised = false;
return $(targ);
},
getSearchablePlugins: function () {
$.getJSON(
"/api/plugin/search",
function (data, status) {
var select = $("#search-plugin");
select.html("");
$.each(data.results.items, function (idx, value) {
select.append("<option value='" + value[0] + "'>" + value[1] + "</option>");
});
select.selectmenu("refresh");
}
);
},
loadService: function (event) {
if (event) {
event.preventDefault();
}
$.getJSON(
"/api/service/list",
function (data, status) {
var ul = $("#service-manager > div[data-role=content] > ul[data-role=listview]");
ul.html("");
$.each(data.results.items, function (idx, value) {
var text = value["title"];
if (value["notes"]) {
text += ' - ' + value["notes"];
}
var li = $("<li data-icon=\"false\">").append(
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).text(text));
li.attr("uuid", value["id"])
li.children("a").click(OpenLP.setItem);
ul.append(li);
});
ul.listview("refresh");
}
);
},
loadController: function (event) {
if (event) {
event.preventDefault();
}
$.getJSON(
"/api/controller/live/text",
function (data, status) {
var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
ul.html("");
for (idx in data.results.slides) {
var indexInt = parseInt(idx,10);
var slide = data.results.slides[idx];
var text = slide["tag"];
if (text != "") {
text = text + ": ";
}
if (slide["title"]) {
text += slide["title"]
} else {
text += slide["text"];
}
if (slide["slide_notes"]) {
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["slide_notes"] + "</div>");
}
text = text.replace(/\n/g, '<br />');
if (slide["img"]) {
text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails88x88/") + "'>";
}
var li = $("<li data-icon=\"false\">").append($("<a href=\"#\">").html(text));
if (slide["selected"]) {
li.attr("data-theme", "e");
}
li.children("a").click(OpenLP.setSlide);
li.find("*").attr("value", indexInt );
ul.append(li);
}
OpenLP.currentItem = data.results.item;
ul.listview("refresh");
}
);
},
setItem: function (event) {
event.preventDefault();
var item = OpenLP.getElement(event);
var id = item.attr("value");
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/service/set",
{"data": text},
function (data, status) {
$.mobile.changePage("#slide-controller");
$("#service-manager > div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
while (item[0].tagName != "LI") {
item = item.parent();
}
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
$("#service-manager > div[data-role=content] ul[data-role=listview]").listview("refresh");
}
);
},
setSlide: function (event) {
event.preventDefault();
var slide = OpenLP.getElement(event);
var id = slide.attr("value");
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/controller/live/set",
{"data": text},
function (data, status) {
$("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
while (slide[0].tagName != "LI") {
slide = slide.parent();
}
slide.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
}
);
},
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var data = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
var prevItem = OpenLP.currentItem;
OpenLP.currentSlide = data.results.slide;
OpenLP.currentItem = data.results.item;
OpenLP.isSecure = data.results.isSecure;
OpenLP.isAuthorised = data.results.isAuthorised;
if ($("#service-manager").is(":visible")) {
if (OpenLP.currentService != data.results.service) {
OpenLP.currentService = data.results.service;
OpenLP.loadService();
}
$("#service-manager div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
$("#service-manager div[data-role=content] ul[data-role=listview] li a").each(function () {
var item = $(this);
while (item[0].tagName != "LI") {
item = item.parent();
}
if (item.attr("uuid") == OpenLP.currentItem) {
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
return false;
}
});
$("#service-manager div[data-role=content] ul[data-role=listview]").listview("refresh");
}
if ($("#slide-controller").is(":visible")) {
if (prevItem != OpenLP.currentItem) {
OpenLP.loadController();
return;
}
var idx = 0;
$("#slide-controller div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c");
$("#slide-controller div[data-role=content] ul[data-role=listview] li a").each(function () {
var item = $(this);
if (idx == OpenLP.currentSlide) {
while (item[0].tagName != "LI") {
item = item.parent();
}
item.attr("data-theme", "e").removeClass("ui-btn-up-c").addClass("ui-btn-up-e");
return false;
}
idx++;
});
$("#slide-controller div[data-role=content] ul[data-role=listview]").listview("refresh");
}
}
} else {
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}
},
nextItem: function (event) {
event.preventDefault();
$.getJSON("/api/service/next");
},
previousItem: function (event) {
event.preventDefault();
$.getJSON("/api/service/previous");
},
nextSlide: function (event) {
event.preventDefault();
$.getJSON("/api/controller/live/next");
},
previousSlide: function (event) {
event.preventDefault();
$.getJSON("/api/controller/live/previous");
},
blankDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/blank");
},
themeDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/theme");
},
desktopDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/desktop");
},
showDisplay: function (event) {
event.preventDefault();
$.getJSON("/api/display/show");
},
showAlert: function (event) {
event.preventDefault();
var alert = OpenLP.escapeString($("#alert-text").val())
var text = "{\"request\": {\"text\": \"" + alert + "\"}}";
$.getJSON(
"/api/alert",
{"data": text},
function () {
$("#alert-text").val("");
}
);
},
search: function (event) {
event.preventDefault();
var query = OpenLP.escapeString($("#search-text").val())
var text = "{\"request\": {\"text\": \"" + query + "\"}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/search",
{"data": text},
function (data, status) {
var ul = $("#search > div[data-role=content] > ul[data-role=listview]");
ul.html("");
if (data.results.items.length == 0) {
var li = $("<li data-icon=\"false\">").text(translationStrings["no_results"]);
ul.append(li);
}
else {
$.each(data.results.items, function (idx, value) {
if (typeof value[0] !== "number"){
value[0] = OpenLP.escapeString(value[0])
}
var txt = "";
if (value.length > 2) {
txt = value[1] + " ( " + value[2] + " )";
} else {
txt = value[1];
}
ul.append($("<li>").append($("<a>").attr("href", "#options")
.attr("data-rel", "dialog").attr("value", value[0])
.click(OpenLP.showOptions).text(txt)));
});
}
ul.listview("refresh");
}
);
},
showOptions: function (event) {
event.preventDefault();
var element = OpenLP.getElement(event);
$("#selected-item").val(element.attr("value"));
},
goLive: function (event) {
event.preventDefault();
var id = $("#selected-item").val();
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/live",
{"data": text}
);
$.mobile.changePage("#slide-controller");
},
addToService: function (event) {
event.preventDefault();
var id = $("#selected-item").val();
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/add",
{"data": text},
function () {
$("#options").dialog("close");
}
);
},
addAndGoToService: function (event) {
event.preventDefault();
var id = $("#selected-item").val();
if (typeof id !== "number") {
id = "\"" + id + "\"";
}
var text = "{\"request\": {\"id\": " + id + "}}";
$.getJSON(
"/api/" + $("#search-plugin").val() + "/add",
{"data": text},
function () {
//$("#options").dialog("close");
$.mobile.changePage("#service-manager");
}
);
},
escapeString: function (string) {
return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")
}
}
// Initial jQueryMobile options
$(document).bind("mobileinit", function(){
$.mobile.defaultDialogTransition = "none";
$.mobile.defaultPageTransition = "none";
});
// Service Manager
$("#service-manager").live("pagebeforeshow", OpenLP.loadService);
$("#service-refresh").live("click", OpenLP.loadService);
$("#service-next").live("click", OpenLP.nextItem);
$("#service-previous").live("click", OpenLP.previousItem);
$("#service-blank").live("click", OpenLP.blankDisplay);
$("#service-theme").live("click", OpenLP.themeDisplay);
$("#service-desktop").live("click", OpenLP.desktopDisplay);
$("#service-show").live("click", OpenLP.showDisplay);
// Slide Controller
$("#slide-controller").live("pagebeforeshow", OpenLP.loadController);
$("#controller-refresh").live("click", OpenLP.loadController);
$("#controller-next").live("click", OpenLP.nextSlide);
$("#controller-previous").live("click", OpenLP.previousSlide);
$("#controller-blank").live("click", OpenLP.blankDisplay);
$("#controller-theme").live("click", OpenLP.themeDisplay);
$("#controller-desktop").live("click", OpenLP.desktopDisplay);
$("#controller-show").live("click", OpenLP.showDisplay);
// Alerts
$("#alert-submit").live("click", OpenLP.showAlert);
// Search
$("#search-submit").live("click", OpenLP.search);
$("#search-text").live("keypress", function(event) {
if (event.which == 13)
{
OpenLP.search(event);
}
});
$("#go-live").live("click", OpenLP.goLive);
$("#add-to-service").live("click", OpenLP.addToService);
$("#add-and-go-to-service").live("click", OpenLP.addAndGoToService);
// Poll the server twice a second to get any updates.
$.ajaxSetup({cache: false});
$("#search").live("pageinit", function (event) {
OpenLP.getSearchablePlugins();
});
OpenLP.pollServer();

View File

@ -0,0 +1,178 @@
/******************************************************************************
* OpenLP - Open Source Lyrics Projection *
* --------------------------------------------------------------------------- *
* Copyright (c) 2008-2016 OpenLP Developers *
* --------------------------------------------------------------------------- *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; version 2 of the License. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., 59 *
* Temple Place, Suite 330, Boston, MA 02111-1307 USA *
******************************************************************************/
window.OpenLP = {
loadService: function (event) {
$.getJSON(
"/api/service/list",
function (data, status) {
OpenLP.nextSong = "";
$("#notes").html("");
for (idx in data.results.items) {
idx = parseInt(idx, 10);
if (data.results.items[idx]["selected"]) {
$("#notes").html(data.results.items[idx]["notes"].replace(/\n/g, "<br />"));
if (data.results.items.length > idx + 1) {
OpenLP.nextSong = data.results.items[idx + 1]["title"];
}
break;
}
}
OpenLP.updateSlide();
}
);
},
loadSlides: function (event) {
$.getJSON(
"/api/controller/live/text",
function (data, status) {
OpenLP.currentSlides = data.results.slides;
OpenLP.currentSlide = 0;
OpenLP.currentTags = Array();
var div = $("#verseorder");
div.html("");
var tag = "";
var tags = 0;
var lastChange = 0;
$.each(data.results.slides, function(idx, slide) {
var prevtag = tag;
tag = slide["tag"];
if (tag != prevtag) {
// If the tag has changed, add new one to the list
lastChange = idx;
tags = tags + 1;
div.append("&nbsp;<span>");
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
}
else {
if ((slide["text"] == data.results.slides[lastChange]["text"]) &&
(data.results.slides.length >= idx + (idx - lastChange))) {
// If the tag hasn't changed, check to see if the same verse
// has been repeated consecutively. Note the verse may have been
// split over several slides, so search through. If so, repeat the tag.
var match = true;
for (var idx2 = 0; idx2 < idx - lastChange; idx2++) {
if(data.results.slides[lastChange + idx2]["text"] != data.results.slides[idx + idx2]["text"]) {
match = false;
break;
}
}
if (match) {
lastChange = idx;
tags = tags + 1;
div.append("&nbsp;<span>");
$("#verseorder span").last().attr("id", "tag" + tags).text(tag);
}
}
}
OpenLP.currentTags[idx] = tags;
if (slide["selected"])
OpenLP.currentSlide = idx;
})
OpenLP.loadService();
}
);
},
updateSlide: function() {
// Show the current slide on top. Any trailing slides for the same verse
// are shown too underneath in grey.
// Then leave a blank line between following verses
$("#verseorder span").removeClass("currenttag");
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");
var slide = OpenLP.currentSlides[OpenLP.currentSlide];
var text = "";
// use title if available
if (slide["title"]) {
text = slide["title"];
} else {
text = slide["text"];
}
// use thumbnail if available
if (slide["img"]) {
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
}
// use notes if available
if (slide["slide_notes"]) {
text += '<br />' + slide["slide_notes"];
}
text = text.replace(/\n/g, "<br />");
$("#currentslide").html(text);
text = "";
if (OpenLP.currentSlide < OpenLP.currentSlides.length - 1) {
for (var idx = OpenLP.currentSlide + 1; idx < OpenLP.currentSlides.length; idx++) {
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "<p class=\"nextslide\">";
if (OpenLP.currentSlides[idx]["title"]) {
text = text + OpenLP.currentSlides[idx]["title"];
} else {
text = text + OpenLP.currentSlides[idx]["text"];
}
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "</p>";
else
text = text + "<br />";
}
text = text.replace(/\n/g, "<br />");
$("#nextslide").html(text);
}
else {
text = "<p class=\"nextslide\">" + $("#next-text").val() + ": " + OpenLP.nextSong + "</p>";
$("#nextslide").html(text);
}
},
updateClock: function() {
var div = $("#clock");
var t = new Date();
var h = t.getHours();
if (OpenLP.twelve && h > 12)
h = h - 12;
var m = t.getMinutes();
if (m < 10)
m = '0' + m + '';
div.html(h + ":" + m);
},
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));
OpenLP.twelve = msg.results.twelve;
OpenLP.updateClock();
if (OpenLP.currentItem != msg.results.item ||
OpenLP.currentService != msg.results.service) {
OpenLP.currentItem = msg.results.item;
OpenLP.currentService = msg.results.service;
OpenLP.loadSlides();
}
else if (OpenLP.currentSlide != msg.results.slide) {
OpenLP.currentSlide = parseInt(msg.results.slide, 10);
OpenLP.updateSlide();
}
}
} else {
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}
},
};
$.ajaxSetup({ cache: false });
setInterval("OpenLP.updateClock();", 1000);
OpenLP.pollServer();
OpenLP.updateClock();

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
<!DOCTYPE html>
<html>
<!--
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
@ -19,10 +19,16 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from .poll import OpenLPPoll
from .wsserver import OpenWSServer
from .httpserver import OpenLPHttpServer
from .apicontroller import ApiController
__all__ = ['OpenLPPoll', 'RemoteController', 'OpenLPHttpServer']
-->
<head>
<meta charset="utf-8" />
<title>${live_title}</title>
<link rel="stylesheet" href="/css/main.css" />
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
<script type="text/javascript" src="/assets/jquery.min.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
</head>
<body>
<img id="image" class="size"/>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<!--
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-->
<head>
<meta charset="utf-8" />
<title>${stage_title}</title>
<link rel="stylesheet" href="/css/stage.css" />
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
<script type="text/javascript" src="/assets/jquery.min.js"></script>
<script type="text/javascript" src="/js/stage.js"></script>
</head>
<body>
<input type="hidden" id="next-text" value="${next}" />
<div id="right">
<div id="clock"></div>
<div id="notes"></div>
</div>
<div id="verseorder"></div>
<div id="currentslide"></div>
<div id="nextslide"></div>
</body>
</html>

View File

@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
App stuff
"""
import logging
import json
import re
from webob import Request, Response
from .errors import HttpError, NotFound, ServerError
log = logging.getLogger(__name__)
def _make_response(view_result):
"""
Create a Response object from response
"""
if isinstance(view_result, Response):
return view_result
elif isinstance(view_result, tuple):
content_type = 'text/html'
body = view_result[0]
if isinstance(body, dict):
content_type = 'application/json'
body = json.dumps(body)
response = Response(body=body, status=view_result[1],
content_type=content_type, charset='utf8')
if len(view_result) >= 3:
response.headers = view_result[2]
return response
elif isinstance(view_result, dict):
return Response(body=json.dumps(view_result), status=200,
content_type='application/json', charset='utf8')
elif isinstance(view_result, str):
return Response(body=view_result, status=200,
content_type='text/html', charset='utf8')
def _handle_exception(error):
"""
Handle exceptions
"""
log.exception(error)
if isinstance(error, HttpError):
return error.to_response()
else:
return ServerError().to_response()
class WSGIApplication(object):
"""
This is the core of the API, the WSGI app
"""
def __init__(self, name):
"""
Create the app object
"""
self.name = name
self.route_map = {}
def add_route(self, route, view_func, method, secure):
"""
Add a route
"""
if route not in self.route_map:
self.route_map[route] = {}
self.route_map[route][method.upper()] = {'function': view_func, 'secure': secure}
def dispatch(self, request):
"""
Find the appropriate URL and run the view function
"""
print(request.path)
print(self.route_map.items())
for route, views in self.route_map.items():
if re.match(route, request.path):
if request.method.upper() in views:
log.debug('Found {method} {url}'.format(method=request.method, url=request.path))
view_func = views[request.method.upper()]['function']
return _make_response(view_func(request))
raise NotFound()
def wsgi_app(self, environ, start_response):
"""
The actual WSGI application.
"""
request = Request(environ)
try:
response = self.dispatch(request)
except Exception as e:
response = _make_response(_handle_exception(e))
return response(environ, start_response)
def __call__(self, environ, start_response):
"""
Shortcut for wsgi_app.
"""
return self.wsgi_app(environ, start_response)

View File

@ -26,10 +26,11 @@ with OpenLP. It uses JSON to communicate with the remotes.
"""
import logging
from waitress import serve
from PyQt5 import QtCore
from waitress import serve
from openlp.core.api import application
from openlp.core.common import RegistryProperties, OpenLPMixin
log = logging.getLogger(__name__)
@ -51,8 +52,8 @@ class HttpThread(QtCore.QObject):
"""
Run the thread.
"""
wsgiapp = object()
serve(wsgiapp, host='0.0.0.0', port=4317)
print("start")
serve(application, host='0.0.0.0', port=4318)
def stop(self):
pass

View File

@ -0,0 +1,97 @@
import logging
import os
from openlp.core.api import Endpoint, register_endpoint
log = logging.getLogger(__name__)
stage_endpoint = Endpoint('')
@stage_endpoint.route('$')
@stage_endpoint.route('(stage)$')
@stage_endpoint.route('(main)$')
def file_access(request):
"""
Get a list of songs`
"""
#songs = db.query(Song).get()
#return {'songs': [dictify(song) for song in songs]}
print("AAA")
@stage_endpoint.route('(stage)/(.*)$')
def bespoke_file_access(request):
"""
Allow Stage view to be delivered with custom views.
:param url_path: base path of the URL. Not used but passed by caller
:param file_name: file name with path
:return:
"""
pass
# log.debug('serve file request {name}'.format(name=file_name))
# parts = file_name.split('/')
# if len(parts) == 1:
# file_name = os.path.join(parts[0], 'stage.html')
# elif len(parts) == 3:
# file_name = os.path.join(parts[1], parts[2])
# path = os.path.normpath(os.path.join(self.config_dir, file_name))
# if not path.startswith(self.config_dir):
# return self.do_not_found()
# return _process_file(path)
@stage_endpoint.route('main/image$')
def main_image(request):
"""
Return the latest display image as a byte stream.
"""
# result = {
# 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image))
# }
# self.do_json_header()
# return json.dumps({'results': result}).encode()
pass
@stage_endpoint.route(r'^/(\w+)/thumbnails([^/]+)?/(.*)$')
def main_image(request):
"""
Get a list of songs
"""
# songs = db.query(Song).get()
# return {'songs': [dictify(song) for song in songs]}
print("AAA")
def _process_file(self, path):
"""
Common file processing code
:param path: path to file to be loaded
:return: web resource to be loaded
"""
# content = None
# ext, content_type = self.get_content_type(path)
# file_handle = None
# try:
# if ext == '.html':
# variables = self.template_vars
# content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables)
# else:
# file_handle = open(path, 'rb')
# log.debug('Opened {path}'.format(path=path))
# content = file_handle.read()
# except IOError:
# log.exception('Failed to open {path}'.format(path=path))
# return self.do_not_found()
# finally:
# if file_handle:
# file_handle.close()
# self.send_response(200)
# self.send_header('Content-type', content_type)
# self.end_headers()
# return content
pass
register_endpoint(stage_endpoint)

View File

@ -29,7 +29,6 @@ import asyncio
import websockets
import logging
import time
import sys
from PyQt5 import QtCore
@ -82,7 +81,7 @@ class OpenWSServer(RegistryProperties, OpenLPMixin):
Start the correct server and save the handler
"""
address = Settings().value(self.settings_section + '/ip address')
port = '4318'
port = '4317'
self.start_websocket_instance(address, port)
# If web socket server start listening
if hasattr(self, 'ws_server') and self.ws_server:

View File

@ -34,21 +34,21 @@ from tempfile import gettempdir
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.api import ApiController
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, \
check_directory_exists, translate, is_win, is_macosx, add_actions
from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.common.versionchecker import get_application_version
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
from openlp.core.lib.ui import UiStrings, create_action
from openlp.core.lib.api import ApiController
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
ShortcutListForm, FormattingTagForm, PreviewController
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
from openlp.core.ui.media import MediaController
from openlp.core.ui.printserviceform import PrintServiceForm
from openlp.core.ui.projector.manager import ProjectorManager
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
log = logging.getLogger(__name__)

View File

@ -26,6 +26,7 @@ import logging
from PyQt5 import QtCore, QtWidgets
from openlp.core.api import ApiTab
from openlp.core.common import Registry, RegistryProperties
from openlp.core.lib import build_icon
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
@ -55,6 +56,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
self.projector_tab = None
self.advanced_tab = None
self.player_tab = None
self.api_tab = None
def exec(self):
"""
@ -71,6 +73,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
self.insert_tab(self.advanced_tab)
self.insert_tab(self.player_tab)
self.insert_tab(self.projector_tab)
self.insert_tab(self.api_tab)
for plugin in self.plugin_manager.plugins:
if plugin.settings_tab:
self.insert_tab(plugin.settings_tab, plugin.is_active())
@ -153,10 +156,13 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
self.advanced_tab = AdvancedTab(self)
# Advanced tab
self.player_tab = PlayerTab(self)
# Api tab
self.api_tab = ApiTab(self)
self.general_tab.post_set_up()
self.themes_tab.post_set_up()
self.advanced_tab.post_set_up()
self.player_tab.post_set_up()
self.api_tab.post_set_up()
for plugin in self.plugin_manager.plugins:
if plugin.settings_tab:
plugin.settings_tab.post_set_up()

View File

@ -30,7 +30,7 @@ window.OpenLP = {
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4318/main_poll');
var ws = new WebSocket('ws://' + location.hostname + ':4317/main_poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));

View File

@ -161,7 +161,7 @@ window.OpenLP = {
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4318/poll');
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var data = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));

View File

@ -149,7 +149,7 @@ window.OpenLP = {
pollServer: function () {
if ("WebSocket" in window) {
// Let us open a web socket
var ws = new WebSocket('ws://' + location.hostname + ':4318/poll');
var ws = new WebSocket('ws://' + location.hostname + ':4317/poll');
ws.binaryType = 'arraybuffer';
ws.onmessage = function (evt) {
var msg = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(evt.data)));

View File

@ -33,7 +33,7 @@ log = logging.getLogger(__name__)
__default_settings__ = {
'remotes/twelve hour': True,
'remotes/port': 4316,
'remotes/websocket port': 4318,
'remotes/websocket port': 4317,
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,

View File

@ -26,7 +26,7 @@ import websockets
import random
async def tester():
async with websockets.connect('ws://localhost:4318/poll') as websocket:
async with websockets.connect('ws://localhost:4317/poll') as websocket:
while True:
greeting = await websocket.recv()

View File

@ -26,9 +26,9 @@ import os
import urllib.request
from unittest import TestCase
from openlp.core.api.api import OpenLPPoll
from openlp.core.common import Settings, Registry
from openlp.core.ui import ServiceManager
from openlp.core.lib.api import OpenLPPoll
from openlp.plugins.remotes.lib.httpserver import HttpRouter
from tests.functional import MagicMock, patch, mock_open
from tests.helpers.testmixin import TestMixin