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; align-items: center;
} }
header .logo a {
color: inherit;
display: flex;
justify-content: center;
align-items: center;
}
header .logo img { header .logo img {
width: 32px; width: 32px;
height: 32px; height: 32px;
@ -546,3 +553,16 @@ span.empty {
opacity: 0.5; opacity: 0.5;
color: #999; 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 () { $(document).on('click', '.variable-edit', function () {
const variable = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity')); 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'); showModal('modal-variable-edit');
$('.modal-variable-edit input:visible:eq(0)').focus().select(); $('.modal-variable-edit input:visible:eq(0)').focus().select();
$('#variable-edit-name').val(variable.name); $('#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_name": "Name",
"settings_variable_form_label_value": "Value", "settings_variable_form_label_value": "Value",
"settings_variable_form_button_cancel": "Cancel", "settings_variable_form_button_cancel": "Cancel",
"settings_variable_help_lang": "Server language [fr,en] (restart needed)", "settings_variable_desc_lang": "Server language",
"settings_variable_help_fleet_enabled": "Enable fleet screen management view (restart needed)", "settings_variable_desc_fleet_enabled": "Enable fleet screen management view",
"settings_variable_help_external_url": "External url (i.e: https://screen-01.company.com or http://10.10.3.100)", "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_desc_slide_animation_enabled": "Enable animation effect between slides",
"settings_variable_help_ro_last_slide_update": "Last slide update datetime", "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_page_title": "System infos",
"sysinfo_panel_button_restart": "Restart", "sysinfo_panel_button_restart": "Restart",
@ -94,5 +99,11 @@
"basic_month_12": "Décember", "basic_month_12": "Décember",
"common_unknown_ipaddr": "Unknown IP address", "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_name": "Nom",
"settings_variable_form_label_value": "Valeur", "settings_variable_form_label_value": "Valeur",
"settings_variable_form_button_cancel": "Annuler", "settings_variable_form_button_cancel": "Annuler",
"settings_variable_help_lang": "Langage de l'application [fr,en]", "settings_variable_desc_lang": "Langage de l'application",
"settings_variable_help_fleet_enabled": "Activer la gestion de flotte des écrans", "settings_variable_desc_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_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_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
"settings_variable_help_ro_last_slide_update": "Date de dernière modification d'une slide", "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_page_title": "Système",
"sysinfo_panel_button_restart": "Redémarrer", "sysinfo_panel_button_restart": "Redémarrer",
@ -94,5 +99,11 @@
"basic_month_12": "Décembre", "basic_month_12": "Décembre",
"common_unknown_ipaddr": "Adresse IP inconnue", "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' return 'Fleetmode Screen Restart'
def use_variables(self) -> List[Variable]: 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]: def use_hooks_registrations(self) -> List[HookRegistration]:
return [ return [

View File

@ -24,7 +24,11 @@ class PlayerController(ObController):
def player(self): def player(self):
return render_template( return render_template(
'player/player.jinja.html', '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): def player_default(self):

View File

@ -55,9 +55,15 @@ class ObPlugin(abc.ABC):
def get_plugin_variable_name(self, name: str) -> str: def get_plugin_variable_name(self, name: str) -> str:
return "{}_{}".format(self.get_plugin_variable_prefix(), name) 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( 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: def add_static_hook_registration(self, hook: HookType, priority: int = 0) -> StaticHookRegistration:

View File

@ -11,10 +11,12 @@ load_dotenv()
class ConfigManager: class ConfigManager:
DEFAULT_PORT = 5000 DEFAULT_PORT = 5000
VERSION_FILE = 'version.txt'
def __init__(self, variable_manager: VariableManager): def __init__(self, variable_manager: VariableManager):
self._variable_manager = variable_manager self._variable_manager = variable_manager
self._CONFIG = { self._CONFIG = {
'version': None,
'port': self.DEFAULT_PORT, 'port': self.DEFAULT_PORT,
'bind': '0.0.0.0', 'bind': '0.0.0.0',
'debug': False, 'debug': False,
@ -26,6 +28,7 @@ class ConfigManager:
'player_url': 'http://localhost:{}'.format(self.DEFAULT_PORT) 'player_url': 'http://localhost:{}'.format(self.DEFAULT_PORT)
} }
self.load_version()
self.load_from_env() self.load_from_env()
self.load_from_args() 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-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-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('--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() 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: def load_from_args(self) -> None:
args = self.parse_arguments() args = self.parse_arguments()
@ -67,6 +75,9 @@ class ConfigManager:
self._CONFIG['log_level'] = args.log_level self._CONFIG['log_level'] = args.log_level
if args.log_stdout: if args.log_stdout:
self._CONFIG['log_stdout'] = 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: def load_from_env(self) -> None:
for key in self._CONFIG: for key in self._CONFIG:

View File

@ -1,10 +1,16 @@
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Variable import Variable 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.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 import PysonDB
from pysondb.errors import IdDoesNotExistError from pysondb.errors import IdDoesNotExistError
from src.utils import get_keys
import time import time
SELECTABLE_BOOLEAN = {"1": "", "0": ""}
class VariableManager: class VariableManager:
@ -15,28 +21,38 @@ class VariableManager:
self._var_map = {} self._var_map = {}
self.reload() 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: if isinstance(value, bool) and value:
value = '1' value = '1'
elif isinstance(value, bool) and not value: elif isinstance(value, bool) and not value:
value = '0' value = '0'
if type == VariableType.BOOL:
selectables = SELECTABLE_BOOLEAN
default_var = { default_var = {
"name": name, "name": name,
"value": value, "value": value,
"type": type.value, "type": type.value,
"editable": editable, "editable": editable,
"description": description, "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']) variable = self.get_one_by_name(default_var['name'])
if not variable: if not variable:
self.add_form(default_var) self.add_form(default_var)
variable = self.get_one_by_name(default_var['name']) 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']}) 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': if variable.name == 'last_restart':
self._db.update_by_id(variable.id, {"value": time.time()}) 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: def reload(self, lang_map: Optional[Dict] = None) -> None:
default_vars = [ default_vars = [
{"name": "lang", "value": "en", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_lang'] 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_help_fleet_enabled'] if lang_map else ""}, {"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_help_external_url'] 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": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_help_ro_editable'] 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": "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": "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: for default_var in default_vars:
@ -76,6 +96,9 @@ class VariableManager:
if id: if id:
raw_variable['id'] = 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) return Variable(**raw_variable)
@staticmethod @staticmethod
@ -105,7 +128,8 @@ class VariableManager:
return self.get_one_by(query=lambda v: v['name'] == name) return self.get_one_by(query=lambda v: v['name'] == name)
def get_one_by(self, query) -> Optional[Variable]: 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: if len(variables) == 1:
return variables[0] return variables[0]
elif len(variables) > 1: 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 json
import time import time
from typing import Optional, Union from typing import Optional, Union, Dict, List
from src.model.enum.VariableType import VariableType from src.model.enum.VariableType import VariableType
from src.model.entity.Selectable import Selectable
from src.utils import str_to_enum 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, 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, 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._id = id if id else None
self._name = name self._name = name
self._type = str_to_enum(type, VariableType) if isinstance(type, str) else type self._type = str_to_enum(type, VariableType) if isinstance(type, str) else type
@ -18,11 +19,23 @@ class Variable:
self._value = value self._value = value
self._editable = editable self._editable = editable
self._plugin = plugin self._plugin = plugin
self._selectables = selectables
@property @property
def id(self) -> Union[int, str]: def id(self) -> Union[int, str]:
return self._id 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 @property
def name(self) -> str: def name(self) -> str:
return self._name return self._name
@ -80,6 +93,7 @@ class Variable:
f"description='{self.description}',\n" \ f"description='{self.description}',\n" \
f"editable='{self.editable}',\n" \ f"editable='{self.editable}',\n" \
f"plugin='{self.plugin}',\n" \ f"plugin='{self.plugin}',\n" \
f"selectables='{self.selectables}',\n" \
f")" f")"
def to_json(self) -> str: def to_json(self) -> str:
@ -94,6 +108,7 @@ class Variable:
"description": self.description, "description": self.description,
"editable": self.editable, "editable": self.editable,
"plugin": self.plugin, "plugin": self.plugin,
"selectables": [selectable.to_dict() for selectable in self.selectables] if isinstance(self._selectables, list) else None
} }
def as_bool(self) -> bool: def as_bool(self) -> bool:
@ -105,10 +120,20 @@ class Variable:
def as_int(self) -> int: def as_int(self) -> int:
return int(self._value) return int(self._value)
def as_ctime(self): def as_ctime(self) -> int:
return time.ctime(self._value) 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: if self.type == VariableType.INT:
return self.as_int() return self.as_int()
elif self.type == VariableType.BOOL: elif self.type == VariableType.BOOL:
@ -118,5 +143,5 @@ class Variable:
return self.as_string() return self.as_string()
def is_from_plugin(self): def is_from_plugin(self) -> Optional[str]:
return self.plugin 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' BOOL = 'bool'
STRING = 'string' STRING = 'string'
SELECT_SINGLE = 'string'
INT = 'int' INT = 'int'
TIMESTAMP = 'timestamp' TIMESTAMP = 'timestamp'

View File

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

View File

@ -2,10 +2,31 @@ import re
import subprocess import subprocess
import platform import platform
from typing import Optional from typing import Optional, List
from enum import Enum 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: def str_to_enum(str_val: str, enum_class) -> Enum:
for enum_item in enum_class: for enum_item in enum_class:
if enum_item.value == str_val: 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> </title>
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<meta name="google" content="notranslate"> <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.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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"> <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 %} {% if not fleet_mode %}
<header> <header>
<h1 class="logo"> <h1 class="logo">
<a href="{{ url_for('slideshow_slide_list') }}">
<img src="{{ STATIC_PREFIX }}img/logo.png" /> <img src="{{ STATIC_PREFIX }}img/logo.png" />
Obscreen Obscreen
</a>
</h1> </h1>
<nav> <nav>
<ul> <ul>
@ -65,6 +85,8 @@
{% block footer %} {% block footer %}
<footer> <footer>
{{ HOOK(H_ROOT_FOOTER) }} {{ HOOK(H_ROOT_FOOTER) }}
<p class="version">Obscreen version {{ VERSION }}</p>
</footer> </footer>
{% endblock %} {% endblock %}
</div> </div>

View File

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

View File

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

View File

@ -34,13 +34,37 @@
{% for ro_variable in ro_variables %} {% for ro_variable in ro_variables %}
<tr> <tr>
<td>{{ ro_variable.description }}</td> <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> </tr>
{% endfor %} {% endfor %}
{% for env_key, env_value in env_variables.items() %} {% for env_key, env_value in env_variables.items() %}
<tr> <tr>
<td>{{ env_key }}</td> <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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>