Merge pull request #9 from jr-k/develop

add footer version
This commit is contained in:
JRK 2024-05-02 16:23:10 +02:00 committed by GitHub
commit c9249e665d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 572 additions and 70 deletions

2
data/www/browserconfig.xml Executable file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/favicon/ms-icon-70x70.png"/><square150x150logo src="/favicon/ms-icon-150x150.png"/><square310x310logo src="/favicon/ms-icon-310x310.png"/><TileColor>#692fbd</TileColor></tile></msapplication></browserconfig>

7
data/www/css/animate.min.css vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -71,6 +71,13 @@ header .logo {
align-items: center;
}
header .logo a {
color: inherit;
display: flex;
justify-content: center;
align-items: center;
}
header .logo img {
width: 32px;
height: 32px;
@ -546,3 +553,16 @@ span.empty {
opacity: 0.5;
color: #999;
}
footer {
align-self: stretch;
justify-content: center;
align-items: center;
flex-direction: column;
display: flex;
padding: 20px;
}
footer .version {
opacity: 0.3;
}

BIN
data/www/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
data/www/favicon/apple-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -21,6 +21,17 @@ jQuery(document).ready(function ($) {
$(document).on('click', '.variable-edit', function () {
const variable = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity'));
if (variable['selectables']) {
var $select = $('<select id="variable-edit-value" name="value" required="required"></select>');
$.each(variable['selectables'], function(index, option) {
$select.append($('<option></option>').val(option.key).html(option.label));
});
$('#variable-edit-value').replaceWith($select);
} else {
$('#variable-edit-value').replaceWith('<input type="text" name="value" id="variable-edit-value" required="required" />');
}
showModal('modal-variable-edit');
$('.modal-variable-edit input:visible:eq(0)').focus().select();
$('#variable-edit-name').val(variable.name);

41
data/www/manifest.json Executable file
View File

@ -0,0 +1,41 @@
{
"name": "App",
"icons": [
{
"src": "\/favicon/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/favicon/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/favicon/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/favicon/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/favicon/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/favicon/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

View File

@ -57,12 +57,17 @@
"settings_variable_form_label_name": "Name",
"settings_variable_form_label_value": "Value",
"settings_variable_form_button_cancel": "Cancel",
"settings_variable_help_lang": "Server language [fr,en] (restart needed)",
"settings_variable_help_fleet_enabled": "Enable fleet screen management view (restart needed)",
"settings_variable_help_external_url": "External url (i.e: https://screen-01.company.com or http://10.10.3.100)",
"settings_variable_desc_lang": "Server language",
"settings_variable_desc_fleet_enabled": "Enable fleet screen management view",
"settings_variable_desc_external_url": "External url (i.e: https://screen-01.company.com or http://10.10.3.100)",
"settings_variable_help_ro_editable": "Last application reboot datetime",
"settings_variable_help_ro_last_slide_update": "Last slide update datetime",
"settings_variable_desc_slide_animation_enabled": "Enable animation effect between slides",
"settings_variable_desc_slide_animation_entrance_effect": "Slide animation entrance effect",
"settings_variable_desc_slide_animation_exit_effect": "Slide animation exit effect (generally better off without it)",
"settings_variable_desc_slide_animation_speed": "Slide animation speed",
"settings_variable_desc_ro_editable": "Last application reboot datetime",
"settings_variable_desc_ro_last_slide_update": "Last slide update datetime",
"sysinfo_page_title": "System infos",
"sysinfo_panel_button_restart": "Restart",
@ -94,5 +99,11 @@
"basic_month_12": "Décember",
"common_unknown_ipaddr": "Unknown IP address",
"common_empty": "[Empty]"
"common_empty": "[Empty]",
"enum_animation_speed_slower": "Slower",
"enum_animation_speed_slow": "Slow",
"enum_animation_speed_normal": "Normal",
"enum_animation_speed_fast": "Fast",
"enum_animation_speed_faster": "Faster"
}

View File

@ -57,12 +57,17 @@
"settings_variable_form_label_name": "Nom",
"settings_variable_form_label_value": "Valeur",
"settings_variable_form_button_cancel": "Annuler",
"settings_variable_help_lang": "Langage de l'application [fr,en]",
"settings_variable_help_fleet_enabled": "Activer la gestion de flotte des écrans",
"settings_variable_help_external_url": "URL externe (i.e: https://screen-01.company.com or http://10.10.3.100)",
"settings_variable_desc_lang": "Langage de l'application",
"settings_variable_desc_fleet_enabled": "Activer la gestion de flotte des écrans",
"settings_variable_desc_external_url": "URL externe (i.e: https://screen-01.company.com or http://10.10.3.100)",
"settings_variable_help_ro_editable": "Date de dernier redémarrage de l'application",
"settings_variable_help_ro_last_slide_update": "Date de dernière modification d'une slide",
"settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
"settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide",
"settings_variable_desc_slide_animation_exit_effect": "Effet d'animation de sortie de la slide (généralement mieux sans)",
"settings_variable_desc_slide_animation_speed": "Vitesse de l'animation de la slide",
"settings_variable_desc_ro_editable": "Date de dernier redémarrage de l'application",
"settings_variable_desc_ro_last_slide_update": "Date de dernière modification d'une slide",
"sysinfo_page_title": "Système",
"sysinfo_panel_button_restart": "Redémarrer",
@ -94,5 +99,11 @@
"basic_month_12": "Décembre",
"common_unknown_ipaddr": "Adresse IP inconnue",
"common_empty": "[Vide]"
"common_empty": "[Vide]",
"enum_animation_speed_slower": "Très lent",
"enum_animation_speed_slow": "Lent",
"enum_animation_speed_normal": "Normal",
"enum_animation_speed_fast": "Rapide",
"enum_animation_speed_faster": "Très rapide"
}

View File

@ -16,7 +16,16 @@ class FleetmodeScreenRestart(ObPlugin):
return 'Fleetmode Screen Restart'
def use_variables(self) -> List[Variable]:
return []
return [
# self.add_variable(
# name="foo",
# description="foo",
# type=VariableType.SELECT_SINGLE,
# value="foo",
# editable=True,
# selectables={"alpha": "Alpha", "beta": "Beta"}
# )
]
def use_hooks_registrations(self) -> List[HookRegistration]:
return [

View File

@ -24,7 +24,11 @@ class PlayerController(ObController):
def player(self):
return render_template(
'player/player.jinja.html',
items=json.dumps(self._get_playlist())
items=json.dumps(self._get_playlist()),
slide_animation_enabled=self._model_store.variable().get_one_by_name('slide_animation_enabled'),
slide_animation_entrance_effect=self._model_store.variable().get_one_by_name('slide_animation_entrance_effect'),
slide_animation_exit_effect=self._model_store.variable().get_one_by_name('slide_animation_exit_effect'),
slide_animation_speed=self._model_store.variable().get_one_by_name('slide_animation_speed')
)
def player_default(self):

View File

@ -55,9 +55,15 @@ class ObPlugin(abc.ABC):
def get_plugin_variable_name(self, name: str) -> str:
return "{}_{}".format(self.get_plugin_variable_prefix(), name)
def add_variable(self, name: str, value, type: VariableType, editable: bool, description: str) -> Variable:
def add_variable(self, name: str, value='', type: VariableType = VariableType.STRING, editable: bool = True, description: str = '', selectables: Optional[Dict[str, str]] = None) -> Variable:
return self._model_store.variable().set_variable(
name=self.get_plugin_variable_name(name), value=value, type=type, editable=editable, description=description, plugin=self.use_id()
name=self.get_plugin_variable_name(name),
value=value,
type=type,
editable=editable,
description=description,
selectables=selectables if isinstance(selectables, dict) else None,
plugin=self.use_id(),
)
def add_static_hook_registration(self, hook: HookType, priority: int = 0) -> StaticHookRegistration:

View File

@ -11,10 +11,12 @@ load_dotenv()
class ConfigManager:
DEFAULT_PORT = 5000
VERSION_FILE = 'version.txt'
def __init__(self, variable_manager: VariableManager):
self._variable_manager = variable_manager
self._CONFIG = {
'version': None,
'port': self.DEFAULT_PORT,
'bind': '0.0.0.0',
'debug': False,
@ -26,6 +28,7 @@ class ConfigManager:
'player_url': 'http://localhost:{}'.format(self.DEFAULT_PORT)
}
self.load_version()
self.load_from_env()
self.load_from_args()
@ -49,9 +52,14 @@ class ConfigManager:
parser.add_argument('--log-file', '-lf', default=self._CONFIG['log_file'], help='Log File path')
parser.add_argument('--log-level', '-ll', default=self._CONFIG['log_level'], help='Log Level')
parser.add_argument('--log-stdout', '-ls', default=self._CONFIG['log_stdout'], action='store_true', help='Log to standard output')
parser.add_argument('--version', '-v', default=None, action='store_true', help='Get version number')
return parser.parse_args()
def load_version(self) -> str:
with open(self.VERSION_FILE, 'r') as file:
self._CONFIG['version'] = file.read()
def load_from_args(self) -> None:
args = self.parse_arguments()
@ -67,6 +75,9 @@ class ConfigManager:
self._CONFIG['log_level'] = args.log_level
if args.log_stdout:
self._CONFIG['log_stdout'] = args.log_stdout
if args.version:
print("Obscreen version v{} (https://github.com/jr-k/obscreen)".format(self._CONFIG['version']))
sys.exit(0)
def load_from_env(self) -> None:
for key in self._CONFIG:

View File

@ -1,10 +1,16 @@
from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Variable import Variable
from src.model.entity.Selectable import Selectable
from src.model.enum.VariableType import VariableType
from src.model.enum.AnimationEntranceEffect import AnimationEntranceEffect
from src.model.enum.AnimationExitEffect import AnimationExitEffect
from src.model.enum.AnimationSpeed import AnimationSpeed
from pysondb import PysonDB
from pysondb.errors import IdDoesNotExistError
from src.utils import get_keys
import time
SELECTABLE_BOOLEAN = {"1": "", "0": ""}
class VariableManager:
@ -15,28 +21,38 @@ class VariableManager:
self._var_map = {}
self.reload()
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str, plugin: Optional[None] = None) -> Variable:
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str, plugin: Optional[None] = None, selectables: Optional[Dict[str, str]] = None) -> Variable:
if isinstance(value, bool) and value:
value = '1'
elif isinstance(value, bool) and not value:
value = '0'
if type == VariableType.BOOL:
selectables = SELECTABLE_BOOLEAN
default_var = {
"name": name,
"value": value,
"type": type.value,
"editable": editable,
"description": description,
"plugin": plugin
"plugin": plugin,
"selectables": ([{"key": key, "label": label} for key, label in selectables.items()]) if isinstance(selectables, dict) else None
}
variable = self.get_one_by_name(default_var['name'])
if not variable:
self.add_form(default_var)
variable = self.get_one_by_name(default_var['name'])
elif variable.description != default_var['description']:
else:
same_selectables = get_keys(default_var, 'selectables') == get_keys(variable, 'selectables')
if variable.description != default_var['description']:
self._db.update_by_id(variable.id, {"description": default_var['description']})
if not same_selectables:
self._db.update_by_id(variable.id, {"selectables": default_var['selectables']})
if variable.name == 'last_restart':
self._db.update_by_id(variable.id, {"value": time.time()})
@ -44,11 +60,15 @@ class VariableManager:
def reload(self, lang_map: Optional[Dict] = None) -> None:
default_vars = [
{"name": "lang", "value": "en", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_lang'] if lang_map else ""},
{"name": "fleet_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_help_fleet_enabled'] if lang_map else ""},
{"name": "external_url", "value": "", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_external_url'] if lang_map else ""},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_help_ro_editable'] if lang_map else ""},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_help_ro_last_slide_update'] if lang_map else ""},
{"name": "lang", "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_lang'] if lang_map else "", "selectables": {"en": "English", "fr": "French"}},
{"name": "fleet_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_desc_fleet_enabled'] if lang_map else ""},
{"name": "external_url", "value": "", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_desc_external_url'] if lang_map else ""},
{"name": "slide_animation_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_enabled'] if lang_map else ""},
{"name": "slide_animation_entrance_effect", "value": AnimationEntranceEffect.FADE_IN.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_entrance_effect'] if lang_map else "", "selectables": AnimationEntranceEffect.get_values()},
{"name": "slide_animation_exit_effect", "value": AnimationExitEffect.NONE.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_exit_effect'] if lang_map else "", "selectables": AnimationExitEffect.get_values()},
{"name": "slide_animation_speed", "value": AnimationSpeed.NORMAL.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": lang_map['settings_variable_desc_slide_animation_speed'] if lang_map else "", "selectables": AnimationSpeed.get_values(lang_map)},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_desc_ro_editable'] if lang_map else ""},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_desc_ro_last_slide_update'] if lang_map else ""},
]
for default_var in default_vars:
@ -76,6 +96,9 @@ class VariableManager:
if id:
raw_variable['id'] = id
if 'selectables' in raw_variable and raw_variable['selectables']:
raw_variable['selectables'] = [Selectable(**selectable) for selectable in raw_variable['selectables']]
return Variable(**raw_variable)
@staticmethod
@ -105,7 +128,8 @@ class VariableManager:
return self.get_one_by(query=lambda v: v['name'] == name)
def get_one_by(self, query) -> Optional[Variable]:
variables = self.hydrate_dict(self._db.get_by_query(query=query))
object = self._db.get_by_query(query=query)
variables = self.hydrate_dict(object)
if len(variables) == 1:
return variables[0]
elif len(variables) > 1:

34
src/model/entity/Selectable.py Executable file
View File

@ -0,0 +1,34 @@
class Selectable:
def __init__(self, key: str = '', label: str = ''):
self._key = key
self._label = label
@property
def key(self) -> str:
return self._key
@key.setter
def key(self, value: str):
self._key = value
@property
def label(self) -> str:
return self._label
@label.setter
def label(self, value: str):
self._label = value
def __str__(self) -> str:
return f"Selectable(" \
f"key='{self.key}',\n" \
f"label='{self.label}',\n" \
f")"
def to_dict(self) -> dict:
return {
"key": self.key,
"label": self.label
}

View File

@ -1,8 +1,9 @@
import json
import time
from typing import Optional, Union
from typing import Optional, Union, Dict, List
from src.model.enum.VariableType import VariableType
from src.model.entity.Selectable import Selectable
from src.utils import str_to_enum
@ -10,7 +11,7 @@ class Variable:
def __init__(self, name: str = '', description: str = '', type: Union[VariableType, str] = VariableType.STRING,
value: Union[int, bool, str] = '', editable: bool = True, id: Optional[str] = None,
plugin: Optional[str] = None):
plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None):
self._id = id if id else None
self._name = name
self._type = str_to_enum(type, VariableType) if isinstance(type, str) else type
@ -18,11 +19,23 @@ class Variable:
self._value = value
self._editable = editable
self._plugin = plugin
self._selectables = selectables
@property
def id(self) -> Union[int, str]:
return self._id
@property
def selectables(self) -> List[Selectable]:
return self._selectables
@selectables.setter
def selectables(self, value: List[Selectable]):
self._selectables = value
def add_selectable(self, value: Selectable):
self._selectables.append(value)
@property
def name(self) -> str:
return self._name
@ -80,6 +93,7 @@ class Variable:
f"description='{self.description}',\n" \
f"editable='{self.editable}',\n" \
f"plugin='{self.plugin}',\n" \
f"selectables='{self.selectables}',\n" \
f")"
def to_json(self) -> str:
@ -94,6 +108,7 @@ class Variable:
"description": self.description,
"editable": self.editable,
"plugin": self.plugin,
"selectables": [selectable.to_dict() for selectable in self.selectables] if isinstance(self._selectables, list) else None
}
def as_bool(self) -> bool:
@ -105,10 +120,20 @@ class Variable:
def as_int(self) -> int:
return int(self._value)
def as_ctime(self):
def as_ctime(self) -> int:
return time.ctime(self._value)
def display(self):
def display(self) -> Union[int, bool, str]:
value = self.eval()
if self.type == VariableType.SELECT_SINGLE:
for selectable in self.selectables:
if selectable.key == value:
return str(selectable.label)
return value
def eval(self) -> Union[int, bool, str]:
if self.type == VariableType.INT:
return self.as_int()
elif self.type == VariableType.BOOL:
@ -118,5 +143,5 @@ class Variable:
return self.as_string()
def is_from_plugin(self):
def is_from_plugin(self) -> Optional[str]:
return self.plugin

View File

@ -0,0 +1,65 @@
from enum import Enum
class AnimationEntranceEffect(Enum):
BACK_IN_DOWN = 'backInDown'
BACK_IN_LEFT = 'backInLeft'
BACK_IN_RIGHT = 'backInRight'
BACK_IN_UP = 'backInUp'
BOUNCE_IN = 'bounceIn'
BOUNCE_IN_DOWN = 'bounceInDown'
BOUNCE_IN_LEFT = 'bounceInLeft'
BOUNCE_IN_RIGHT = 'bounceInRight'
BOUNCE_IN_UP = 'bounceInUp'
FADE_IN = 'fadeIn'
FADE_IN_DOWN = 'fadeInDown'
FADE_IN_DOWN_BIG = 'fadeInDownBig'
FADE_IN_LEFT = 'fadeInLeft'
FADE_IN_LEFT_BIG = 'fadeInLeftBig'
FADE_IN_RIGHT = 'fadeInRight'
FADE_IN_RIGHT_BIG = 'fadeInRightBig'
FADE_IN_UP = 'fadeInUp'
FADE_IN_UP_BIG = 'fadeInUpBig'
FADE_IN_TOP_LEFT = 'fadeInTopLeft'
FADE_IN_TOP_RIGHT = 'fadeInTopRight'
FADE_IN_BOTTOM_LEFT = 'fadeInBottomLeft'
FADE_IN_BOTTOM_RIGHT = 'fadeInBottomRight'
FLIP = 'flip'
FLIP_IN_X = 'flipInX'
FLIP_IN_Y = 'flipInY'
LIGHT_SPEED_IN_RIGHT = 'lightSpeedInRight'
LIGHT_SPEED_IN_LEFT = 'lightSpeedInLeft'
ROTATE_IN = 'rotateIn'
ROTATE_IN_DOWN_LEFT = 'rotateInDownLeft'
ROTATE_IN_DOWN_RIGHT = 'rotateInDownRight'
ROTATE_IN_UP_LEFT = 'rotateInUpLeft'
ROTATE_IN_UP_RIGHT = 'rotateInUpRight'
ROLL_IN = 'rollIn'
JACK_IN_THE_BOX = 'jackInTheBox'
ZOOM_IN = 'zoomIn'
ZOOM_IN_DOWN = 'zoomInDown'
ZOOM_IN_LEFT = 'zoomInLeft'
ZOOM_IN_RIGHT = 'zoomInRight'
ZOOM_IN_UP = 'zoomInUp'
SLIDE_IN_DOWN = 'slideInDown'
SLIDE_IN_LEFT = 'slideInLeft'
SLIDE_IN_RIGHT = 'slideInRight'
SLIDE_IN_UP = 'slideInUp'
@staticmethod
def get_values() -> dict:
values = {}
for enum_item in AnimationEntranceEffect:
values[enum_item.value] = enum_item.value
return values

View File

@ -0,0 +1,67 @@
from enum import Enum
class AnimationExitEffect(Enum):
NONE = 'none'
BACK_OUT_DOWN = 'backOutDown'
BACK_OUT_LEFT = 'backOutLeft'
BACK_OUT_RIGHT = 'backOutRight'
BACK_OUT_UP = 'backOutUp'
BOUNCE_OUT = 'bounceOut'
BOUNCE_OUT_DOWN = 'bounceOutDown'
BOUNCE_OUT_LEFT = 'bounceOutLeft'
BOUNCE_OUT_RIGHT = 'bounceOutRight'
BOUNCE_OUT_UP = 'bounceOutUp'
FADE_OUT = 'fadeOut'
FADE_OUT_DOWN = 'fadeOutDown'
FADE_OUT_DOWN_BIG = 'fadeOutDownBig'
FADE_OUT_LEFT = 'fadeOutLeft'
FADE_OUT_LEFT_BIG = 'fadeOutLeftBig'
FADE_OUT_RIGHT = 'fadeOutRight'
FADE_OUT_RIGHT_BIG = 'fadeOutRightBig'
FADE_OUT_UP = 'fadeOutUp'
FADE_OUT_UP_BIG = 'fadeOutUpBig'
FADE_OUT_TOP_LEFT = 'fadeOutTopLeft'
FADE_OUT_TOP_RIGHT = 'fadeOutTopRight'
FADE_OUT_BOTTOM_LEFT = 'fadeOutBottomLeft'
FADE_OUT_BOTTOM_RIGHT = 'fadeOutBottomRight'
FLIP = 'flip'
FLIP_OUT_X = 'flipOutX'
FLIP_OUT_Y = 'flipOutY'
LIGHT_SPEED_OUT_RIGHT = 'lightSpeedOutRight'
LIGHT_SPEED_OUT_LEFT = 'lightSpeedOutLeft'
ROTATE_OUT = 'rotateOut'
ROTATE_OUT_DOWN_LEFT = 'rotateOutDownLeft'
ROTATE_OUT_DOWN_RIGHT = 'rotateOutDownRight'
ROTATE_OUT_UP_LEFT = 'rotateOutUpLeft'
ROTATE_OUT_UP_RIGHT = 'rotateOutUpRight'
ROLL_OUT = 'rollOut'
HINGE = 'hinge'
ZOOM_OUT = 'zoomOut'
ZOOM_OUT_DOWN = 'zoomOutDown'
ZOOM_OUT_LEFT = 'zoomOutLeft'
ZOOM_OUT_RIGHT = 'zoomOutRight'
ZOOM_OUT_UP = 'zoomOutUp'
SLIDE_OUT_DOWN = 'slideOutDown'
SLIDE_OUT_LEFT = 'slideOutLeft'
SLIDE_OUT_RIGHT = 'slideOutRight'
SLIDE_OUT_UP = 'slideOutUp'
@staticmethod
def get_values() -> dict:
values = {}
for enum_item in AnimationExitEffect:
values[enum_item.value] = enum_item.value
return values

View File

@ -0,0 +1,23 @@
from enum import Enum
class AnimationSpeed(Enum):
SLOWER = 'slower'
SLOW = 'slow'
NORMAL = 'normal'
FAST = 'fast'
FASTER = 'faster'
@staticmethod
def get_values(lang_map: dict) -> dict:
if lang_map is None:
return {}
return {
AnimationSpeed.SLOWER.value: lang_map['enum_animation_speed_slower'], # 3s
AnimationSpeed.SLOW.value: lang_map['enum_animation_speed_slow'], # 2s
AnimationSpeed.NORMAL.value: lang_map['enum_animation_speed_normal'], # 1s
AnimationSpeed.FAST.value: lang_map['enum_animation_speed_fast'], # 800ms
AnimationSpeed.FASTER.value: lang_map['enum_animation_speed_faster'] # 500ms
}

View File

@ -5,5 +5,6 @@ class VariableType(Enum):
BOOL = 'bool'
STRING = 'string'
SELECT_SINGLE = 'string'
INT = 'int'
TIMESTAMP = 'timestamp'

View File

@ -22,6 +22,7 @@ class TemplateRenderer:
globals = dict(
STATIC_PREFIX="/{}/{}/".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS),
FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(),
VERSION=self._model_store.config().map().get('version'),
LANG=self._model_store.variable().map().get('lang').as_string(),
HOOK=self._render_hook,
)

View File

@ -2,10 +2,31 @@ import re
import subprocess
import platform
from typing import Optional
from typing import Optional, List
from enum import Enum
def get_keys(dict_or_object, key_list_name: str, key_attr_name: str = 'key') -> Optional[List]:
if dict_or_object is None:
return None
is_dict = isinstance(dict_or_object, dict)
is_object = not is_dict and isinstance(dict_or_object, object)
if is_dict:
iterable = dict_or_object[key_list_name]
if iterable is None:
return None
return [item[key_attr_name] for item in iterable]
if is_object:
iterable = getattr(dict_or_object, key_list_name)
if iterable is None:
return None
return [getattr(item, key_attr_name) for item in iterable]
return None
def str_to_enum(str_val: str, enum_class) -> Enum:
for enum_item in enum_class:
if enum_item.value == str_val:

View File

@ -0,0 +1,13 @@
@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
@xscreensaver -no-splash
#@point-rpi
@xset s off
@xset -dpms
@xset s noblank
@unclutter -display :0 -noevents -grab
@sed -i 's/"exited_cleanly": false/"exited_cleanly": true/' ~/.config/chromium/Default/Preferences
#@sleep 10
@chromium-browser --disable-features=Translate --ignore-certificate-errors --disable-web-security --disable-restore-session-state --autoplay-policy=no-user-gesture-required --start-maximized --allow-running-insecure-content --remember-cert-error-decisions --disable-restore-session-state --noerrdialogs --kiosk --incognito --window-position=0,0 --display=:0 http://localhost:5000

View File

@ -1 +1 @@
1.4
1.5

View File

@ -6,6 +6,24 @@
</title>
<meta name="robots" content="noindex, nofollow">
<meta name="google" content="notranslate">
<link rel="apple-touch-icon" sizes="57x57" href="{{ STATIC_PREFIX }}favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="{{ STATIC_PREFIX }}favicon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="{{ STATIC_PREFIX }}favicon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="{{ STATIC_PREFIX }}favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="{{ STATIC_PREFIX }}favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="{{ STATIC_PREFIX }}favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="{{ STATIC_PREFIX }}favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="{{ STATIC_PREFIX }}favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="{{ STATIC_PREFIX }}favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="{{ STATIC_PREFIX }}favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{ STATIC_PREFIX }}favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="{{ STATIC_PREFIX }}favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{ STATIC_PREFIX }}favicon/favicon-16x16.png">
<link rel="manifest" href="{{ STATIC_PREFIX }}/manifest.json">
<meta name="msapplication-TileColor" content="#692fbd">
<meta name="msapplication-TileImage" content="{{ STATIC_PREFIX }}favicon/ms-icon-144x144.png">
<meta name="theme-color" content="#692fbd">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
@ -22,8 +40,10 @@
{% if not fleet_mode %}
<header>
<h1 class="logo">
<a href="{{ url_for('slideshow_slide_list') }}">
<img src="{{ STATIC_PREFIX }}img/logo.png" />
Obscreen
</a>
</h1>
<nav>
<ul>
@ -65,6 +85,8 @@
{% block footer %}
<footer>
{{ HOOK(H_ROOT_FOOTER) }}
<p class="version">Obscreen version {{ VERSION }}</p>
</footer>
{% endblock %}
</div>

View File

@ -7,21 +7,21 @@
<div class="form-group">
<label for="screen-add-name">{{ l.fleet_screen_form_label_name }}</label>
<div class="widget">
<input name="name" type="text" id="screen-add-name" required="required"/>
<input name="name" type="text" id="screen-add-name" required="required" />
</div>
</div>
<div class="form-group">
<label for="screen-add-host">{{ l.fleet_screen_form_label_host }}</label>
<div class="widget">
<input type="text" name="host" id="screen-add-host" required="required"/>
<input type="text" name="host" id="screen-add-host" required="required" />
</div>
</div>
<div class="form-group">
<label for="screen-add-port">{{ l.fleet_screen_form_label_port }}</label>
<div class="widget">
<input type="number" name="port" id="screen-add-port" required="required"/>
<input type="number" name="port" id="screen-add-port" required="required" />
</div>
</div>

View File

@ -4,26 +4,26 @@
</h2>
<form action="/fleet/screen/edit" method="POST">
<input type="hidden" name="id" id="screen-edit-id"/>
<input type="hidden" name="id" id="screen-edit-id" />
<div class="form-group">
<label for="screen-edit-name">{{ l.fleet_screen_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="screen-edit-name" required="required"/>
<input type="text" name="name" id="screen-edit-name" required="required" />
</div>
</div>
<div class="form-group">
<label for="screen-edit-host">{{ l.fleet_screen_form_label_host }}</label>
<div class="widget">
<input type="text" name="host" id="screen-edit-host" required="required"/>
<input type="text" name="host" id="screen-edit-host" required="required" />
</div>
</div>
<div class="form-group">
<label for="screen-edit-port">{{ l.fleet_screen_form_label_port }}</label>
<div class="widget">
<input type="number" name="port" id="screen-edit-port" required="required"/>
<input type="number" name="port" id="screen-edit-port" required="required" />
</div>
</div>

View File

@ -4,6 +4,7 @@
<title>Obscreen</title>
<meta name="robots" content="noindex, nofollow">
<meta name="google" content="notranslate">
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/animate.min.css" />
<style>
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background-color: white; display: flex; flex-direction: row; justify-content: center; align-items: center; }
.slide { display: flex; flex-direction: row; justify-content: center; align-items: center; background: black; }
@ -13,10 +14,10 @@
</style>
</head>
<body>
<div id="FirstSlide" class="slide" style="visibility: hidden;">
<div id="FirstSlide" class="slide" style="z-index: 1000;">
<iframe src="/player/default"></iframe>
</div>
<div id="SecondSlide" class="slide" style="visibility: visible;">
<div id="SecondSlide" class="slide" style="z-index: 500">
<iframe src="/player/default"></iframe>
</div>
<script type="text/javascript">
@ -37,8 +38,15 @@
});
}, playlistCheck);
var animate = {{ 'true' if slide_animation_enabled.eval() else 'false' }};
var animate_transitions = [
"animate__{{ slide_animation_entrance_effect.eval()|default("fadeIn") }}",
"animate__{{ slide_animation_exit_effect.eval()|default("none") }}"
];
var animate_speed = "animate__{{ slide_animation_speed.eval()|default("normal") }}";
function main() {
preloadIntoFirstSlide();
preloadIntoSecondSlide();
}
function loadContent(element, callbackReady) {
@ -108,11 +116,28 @@
function moveToFirstSlide() {
//console.log('moveToFirstSlide', items[curUrl]);
var firstSlide = document.querySelector('#FirstSlide');
var secondSlide = document.querySelector('#SecondSlide');
duration = items[curUrl].duration * 1000;
curUrl = (curUrl + 1) === items.length ? 0 : curUrl + 1;
document.querySelector('#FirstSlide').style.visibility = 'visible';
document.querySelector('#SecondSlide').style.visibility = 'hidden';
firstSlide.style.zIndex = 1000;
secondSlide.style.zIndex = 500;
if (animate) {
firstSlide.classList.add('animate__animated', animate_transitions[0], animate_speed);
firstSlide.onanimationend = () => {
firstSlide.classList.remove(animate_transitions[0], animate_speed);
preloadIntoSecondSlide();
};
secondSlide.classList.add('animate__animated', animate_transitions[1], animate_speed);
secondSlide.onanimationend = () => {
secondSlide.classList.remove(animate_transitions[1], animate_speed);
};
} else {
preloadIntoSecondSlide();
}
}
function preloadIntoSecondSlide() {
@ -136,11 +161,27 @@
function moveToSecondSlide() {
//console.log('moveToSecondSlide', items[curUrl]);
var firstSlide = document.querySelector('#FirstSlide');
var secondSlide = document.querySelector('#SecondSlide');
duration = items[curUrl].duration * 1000;
curUrl = (curUrl + 1) === items.length ? 0 : curUrl + 1;
document.querySelector('#FirstSlide').style.visibility = 'hidden';
document.querySelector('#SecondSlide').style.visibility = 'visible';
secondSlide.style.zIndex = 1000;
firstSlide.style.zIndex = 500;
if (animate) {
secondSlide.classList.add('animate__animated', animate_transitions[0], animate_speed);
secondSlide.onanimationend = () => {
secondSlide.classList.remove(animate_transitions[0], animate_speed);
preloadIntoFirstSlide();
};
firstSlide.classList.add('animate__animated', animate_transitions[1], animate_speed);
firstSlide.onanimationend = () => {
firstSlide.classList.remove(animate_transitions[1], animate_speed);
};
} else {
preloadIntoFirstSlide();
}
}
main();

View File

@ -7,9 +7,9 @@
</tr>
</thead>
<tbody>
{% set last_plugin = '' %}
{% set ns = namespace(last_plugin='') %}
{% for variable in variables %}
{% if variable.plugin and last_plugin != variable.plugin %}
{% if variable.plugin and ns.last_plugin != variable.plugin %}
<tr>
<td colspan="2">
<h3>
@ -27,7 +27,15 @@
</td>
<td>
{% if variable.value %}
{{ variable.value }}
{% if variable.type.value == 'bool' %}
{% if variable.display() %}
{% else %}
{% endif %}
{% else %}
{{ variable.display() }}
{% endif %}
{% else %}
<span class="empty">{{ l.common_empty }}</span>
{% endif %}
@ -38,7 +46,7 @@
</a>
</td>
</tr>
{% set last_plugin = variable.plugin %}
{% set ns.last_plugin = variable.plugin %}
{% endfor %}
</tbody>
</table>

View File

@ -4,19 +4,19 @@
</h2>
<form action="/settings/variable/edit" method="POST">
<input type="hidden" name="id" id="variable-edit-id"/>
<input type="hidden" name="id" id="variable-edit-id" />
<div class="form-group">
<label for="variable-edit-name">{{ l.settings_variable_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="variable-edit-name" required="required" disabled="disabled"/>
<input type="text" name="name" id="variable-edit-name" required="required" disabled="disabled" />
</div>
</div>
<div class="form-group">
<label for="variable-edit-value">{{ l.settings_variable_form_label_value }}</label>
<div class="widget">
<input type="text" name="value" id="variable-edit-value" required="required"/>
<input type="text" name="value" id="variable-edit-value" required="required" />
</div>
</div>

View File

@ -7,7 +7,7 @@
<div class="form-group">
<label for="slide-add-name">{{ l.slideshow_slide_form_label_name }}</label>
<div class="widget">
<input name="name" type="text" id="slide-add-name" required="required"/>
<input name="name" type="text" id="slide-add-name" required="required" />
</div>
</div>
<div class="form-group">
@ -24,16 +24,16 @@
<div class="form-group object-input">
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_object }}</label>
<div class="widget">
<input type="text" name="object" id="slide-add-object-input-text" class="slide-add-object-input"/>
<input type="text" name="object" id="slide-add-object-input-text" class="slide-add-object-input" />
<input type="file" name="object" id="slide-add-object-input-upload"
class="slide-add-object-input hidden" disabled="disabled"/>
class="slide-add-object-input hidden" disabled="disabled" />
</div>
</div>
<div class="form-group">
<label for="slide-add-duration">{{ l.slideshow_slide_form_label_duration }}</label>
<div class="widget">
<input type="number" name="duration" id="slide-add-duration" required="required"/>
<input type="number" name="duration" id="slide-add-duration" required="required" />
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
</div>
</div>

View File

@ -4,12 +4,12 @@
</h2>
<form action="/slideshow/slide/edit" method="POST">
<input type="hidden" name="id" id="slide-edit-id"/>
<input type="hidden" name="id" id="slide-edit-id" />
<div class="form-group">
<label for="slide-edit-name">{{ l.slideshow_slide_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="slide-edit-name" required="required"/>
<input type="text" name="name" id="slide-edit-name" required="required" />
</div>
</div>
<div class="form-group">
@ -26,14 +26,14 @@
<div class="form-group">
<label for="slide-edit-location">{{ l.slideshow_slide_form_label_location }}</label>
<div class="widget">
<input type="text" name="location" id="slide-edit-location" disabled="disabled"/>
<input type="text" name="location" id="slide-edit-location" disabled="disabled" />
</div>
</div>
<div class="form-group">
<label for="slide-edit-duration">{{ l.slideshow_slide_form_label_duration }}</label>
<div class="widget">
<input type="number" name="duration" id="slide-edit-duration" required="required"/>
<input type="number" name="duration" id="slide-edit-duration" required="required" />
<span>{{ l.slideshow_slide_form_label_duration_unit }}</span>
</div>
</div>

View File

@ -34,13 +34,37 @@
{% for ro_variable in ro_variables %}
<tr>
<td>{{ ro_variable.description }}</td>
<td>{{ ro_variable.display() }}</td>
<td>
{% if ro_variable.value %}
{% if ro_variable.type.value == 'bool' %}
{% if ro_variable.display() %}
{% else %}
{% endif %}
{% else %}
{{ ro_variable.display() }}
{% endif %}
{% else %}
<span class="empty">{{ l.common_empty }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% for env_key, env_value in env_variables.items() %}
<tr>
<td>{{ env_key }}</td>
<td>{{ env_value }}</td>
<td>
{% if env_value == true %}
{% elif env_value == false %}
{% elif env_value == none %}
<span class="empty">{{ l.common_empty }}</span>
{% else %}
{{ env_value }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>