2
data/www/browserconfig.xml
Executable 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
@ -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
|
After Width: | Height: | Size: 1.1 KiB |
BIN
data/www/favicon/android-icon-144x144.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/www/favicon/android-icon-192x192.png
Executable file
|
After Width: | Height: | Size: 27 KiB |
BIN
data/www/favicon/android-icon-36x36.png
Executable file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
data/www/favicon/android-icon-48x48.png
Executable file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
data/www/favicon/android-icon-72x72.png
Executable file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
data/www/favicon/android-icon-96x96.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
data/www/favicon/apple-icon-114x114.png
Executable file
|
After Width: | Height: | Size: 13 KiB |
BIN
data/www/favicon/apple-icon-120x120.png
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
data/www/favicon/apple-icon-144x144.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/www/favicon/apple-icon-152x152.png
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
data/www/favicon/apple-icon-180x180.png
Executable file
|
After Width: | Height: | Size: 26 KiB |
BIN
data/www/favicon/apple-icon-57x57.png
Executable file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
data/www/favicon/apple-icon-60x60.png
Executable file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
data/www/favicon/apple-icon-72x72.png
Executable file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
data/www/favicon/apple-icon-76x76.png
Executable file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
data/www/favicon/apple-icon-precomposed.png
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
data/www/favicon/apple-icon.png
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
data/www/favicon/favicon-16x16.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
data/www/favicon/favicon-32x32.png
Executable file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
data/www/favicon/favicon-96x96.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
data/www/favicon/ms-icon-144x144.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/www/favicon/ms-icon-150x150.png
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
data/www/favicon/ms-icon-310x310.png
Executable file
|
After Width: | Height: | Size: 61 KiB |
BIN
data/www/favicon/ms-icon-70x70.png
Executable file
|
After Width: | Height: | Size: 6.8 KiB |
@ -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
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
lang/en.json
@ -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"
|
||||
}
|
||||
|
||||
23
lang/fr.json
@ -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"
|
||||
}
|
||||
|
||||
@ -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 [
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
@ -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
|
||||
}
|
||||
@ -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
|
||||
65
src/model/enum/AnimationEntranceEffect.py
Executable 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
|
||||
67
src/model/enum/AnimationExitEffect.py
Executable 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
|
||||
23
src/model/enum/AnimationSpeed.py
Executable 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
|
||||
}
|
||||
@ -5,5 +5,6 @@ class VariableType(Enum):
|
||||
|
||||
BOOL = 'bool'
|
||||
STRING = 'string'
|
||||
SELECT_SINGLE = 'string'
|
||||
INT = 'int'
|
||||
TIMESTAMP = 'timestamp'
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
23
src/utils.py
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1 +1 @@
|
||||
1.4
|
||||
1.5
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||