node player groups wip

This commit is contained in:
jr-k 2024-07-16 17:06:37 +02:00
parent 5dcc3fe33a
commit 6c9c82948e
34 changed files with 519 additions and 200 deletions

File diff suppressed because one or more lines are too long

View File

@ -19,5 +19,30 @@ jQuery(document).ready(function ($) {
$(this).remove();
});
$(document).on('click', '.node-player-group-player-assign', function () {
const route = $(this).attr('data-route');
showPickers('modal-node-player-explr-picker', function(node_player) {
if (!node_player.group_id || (node_player.group_id && confirm(l.js_fleet_node_player_assign_confirmation))) {
document.location.href = route.replace('__id__', node_player.id);
}
});
});
$(document).on('click', '.node-player-group-unassign-player', function () {
if (confirm(l.js_fleet_node_player_delete_confirmation)) {
const $item = $(this).parents('.player-item:eq(0)');
$item.remove();
$.ajax({
method: 'DELETE',
url: $(this).attr('data-route'),
headers: {'Content-Type': 'application/json'},
success: function(response) {
$('.node-player-group-item-'+response.group_id+' .players-counter').text(response.pcounter);
}
});
}
});
main();
});

View File

@ -11,6 +11,15 @@ jQuery(document).ready(function ($) {
$('#node-player-edit-name').val(nodePlayer.name);
$('#node-player-edit-host').val(nodePlayer.host);
$('#node-player-edit-id').val(nodePlayer.id);
$('#node-player-edit-group-label').val(nodePlayer.group_label);
$('.group-edit-link').attr('href', 'javascript:void(0);');
$('.form-group-for-group-id').addClass('hidden');
if (nodePlayer.group_id) {
$('.form-group-for-group-id').removeClass('hidden');
$('.group-edit-link').attr('href', $('.group-edit-link').attr('data-route').replace('__id__', nodePlayer.group_id));
}
inputOperatingSystemUpdate();
};

View File

@ -1,11 +1,11 @@
jQuery(document).ready(function ($) {
const main = function () {
const qrcodeElement = document.getElementById('qrcode');
const $qrcode = $('#qrcode');
if (qrcodeElement) {
new QRCode(qrcodeElement, {
text: qrcodeElement.attributes['data-qrcode-payload'].value,
if ($qrcode.length) {
new QRCode($qrcode.get(0), {
text: $qrcode.attr('data-qrcode-payload'),
width: 128,
height: 128,
colorDark: '#222',

View File

@ -2,6 +2,8 @@ ul.explr-tree {
height: 100% !important;
li {
position: relative;
span {
color: #555;
font-size: 13px;
@ -18,6 +20,33 @@ ul.explr-tree {
}
}
i.main {
font-size: 14px;
}
sup,
sub {
position: absolute;
top: 0;
left: 5px;
background: #777;
border-bottom: 2px solid $neutralGrey;
color: black;
border-radius: $baseRadius;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
padding-top: 2px;
padding-bottom: 1px;
i {
color: white;
font-size: 4px !important;
margin-bottom: 0;
}
}
a {
color: $white;
padding-right: 80px;
@ -41,10 +70,10 @@ ul.explr-tree {
.explr-selection-actions {
display: none;
flex-direction: row;
justify-content: flex-end;
align-items: center;
flex: 1;
flex-direction: row;
justify-content: flex-end;
align-items: center;
flex: 1;
button {
display: none;
@ -125,6 +154,7 @@ ul.explr-dirview {
justify-content: flex-start;
align-items: center;
max-width: 84px;
position: relative;
i {
font-size: 64px;
@ -132,6 +162,31 @@ ul.explr-dirview {
border-radius: 8px;
}
sup,
sub {
position: absolute;
top: 0;
right: -12px;
background: #777;
border-bottom: 2px solid $neutralGrey;
color: black;
border-radius: $baseRadius;
width: 16px;
height: 16px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
padding-top: 2px;
padding-bottom: 1px;
i {
color: white;
font-size: 10px;
margin-bottom: 0;
}
}
input {
width: 100%;
padding: 0 3px;

View File

@ -30,10 +30,15 @@
color: $white;
margin: 1px;
padding: 15px 10px 15px 15px;
border-radius: $baseRadius;
border-bottom: 1px solid transparent;
&:hover,
&.active {
background: #111;
border-left: 4px solid $seaBlue;
border-radius: $baseRadius;
border-bottom: 2px solid #171717;
&:hover {
opacity: 1;

View File

@ -136,6 +136,11 @@ form {
border-radius: 0;
}
&.input-naked {
padding-left: 0;
color: #BBB;
}
&.disabled,
&[disabled] {
border: none;

View File

@ -36,7 +36,7 @@
// Import pages styles
@import 'pages/content';
@import 'pages/logs';
@import 'pages/node_player';
@import 'pages/node-player';
@import 'pages/playlist';
@import 'pages/player-group';
@import 'pages/slideshow';

View File

@ -0,0 +1,139 @@
.view-node-player-list main .main-container {
}
.view-node-player-edit main .main-container {
.bottom-content {
.page-content {
flex: 1;
.form-holder {
margin: 20px 20px 20px 10px;
}
}
}
}
.view-player-group-list main .main-container {
.players-holder {
ul.players {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
align-self: stretch;
margin: 10px 0 0 0;
border: 1px dashed #222;
border-radius: 4px;
padding: 10px;
li.player-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
align-self: stretch;
margin: 0 0 2px 0;
.head {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #999;
font-size: 10px;
padding: 10px;
cursor: default;
}
&:hover {
.infos .title {
color: white;
}
}
.infos {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: #AAA;
font-size: 12px;
margin-right: 5px;
flex: 1;
max-width: 120px;
background: $black;
border: 1px solid #333;
border-radius: $baseRadius;
padding: 3px 7px;
.title {
display: block;
word-break: break-all;
font-size: 13px;
color: #AAA;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.type {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: #AAA;
font-size: 12px;
margin-right: 5px;
}
}
.body {
display: block;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin: 0 10px 0 10px;
background: #1B1B1B;
padding: 10px;
align-self: stretch;
flex: 1;
border-radius: $baseRadius;
color: #CCC;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 360px;
font-size: 12px;
span {
opacity: 0.5;
margin-right: 7px;
font-size: 10px;
}
}
.tail {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
a {
color: white;
}
}
}
}
}
}

View File

@ -1,22 +0,0 @@
.view-node-player-list main .main-container {
}
.view-node-player-edit main .main-container {
.bottom-content {
.page-content {
flex: 1;
.form-holder {
margin: 20px 20px 20px 10px;
}
}
}
}

View File

@ -111,8 +111,13 @@
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancel",
"js_fleet_node_player_delete_confirmation": "Are you sure?",
"js_fleet_node_player_assign_confirmation": "This player is already assigned to a group and will be replaced by your selection, are you sure?",
"fleet_node_player_group_page_title": "Playgroups",
"fleet_node_player_group_page_about": "About playgroup",
"fleet_node_player_group_page_player_management": "Playgroup elements",
"fleet_node_player_group_page_player_management_desc": "You can add, modify, delete players from this list. The playlist associated with this playgroup will be played on all players.",
"fleet_node_player_group_button_add": "Add Playgroup",
"fleet_node_player_group_assign_player": "Assign a player",
"fleet_node_player_group_panel_active": "Active Playgroups",
"fleet_node_player_group_panel_empty": "Currently, there are no playgroup. %link% now.",
"fleet_node_player_group_panel_th_name": "Name",
@ -233,6 +238,7 @@
"common_folder_not_empty_error": "Folder isn't empty, you must delete its content first",
"common_copied": "Element copied in clipboard!",
"common_host_placeholder": "raspberrypi.local or 192.168.1.85",
"common_reachable_at": "Host",
"logout": "Logout",
"login_error_not_found": "Bad credentials",
"login_error_bad_credentials": "Bad credentials",

View File

@ -111,8 +111,13 @@
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancelar",
"js_fleet_node_player_delete_confirmation": "¿Estás seguro?",
"js_fleet_node_player_assign_confirmation": "Este jugador ya está asignado a un grupo y será reemplazado por tu selección, ¿estás seguro?",
"fleet_node_player_group_page_title": "Playgroups",
"fleet_node_player_group_page_about": "Acerca del playgroup",
"fleet_node_player_group_page_player_management": "Elementos del playgroup",
"fleet_node_player_group_page_player_management_desc": "Puedes agregar, modificar y eliminar jugadores de esta lista. La playlist asociada con este playgroup se reproducirá en todos los jugadores.",
"fleet_node_player_group_button_add": "Agregar Playgroup",
"fleet_node_player_group_assign_player": "Asignar un jugador",
"fleet_node_player_group_panel_active": "Playgroup activos",
"fleet_node_player_group_panel_empty": "Actualmente, no hay playgroup. %link% ahora.",
"fleet_node_player_group_panel_th_name": "Nombre",
@ -233,6 +238,7 @@
"common_folder_not_empty_error": "La carpeta no está vacía, primero debes eliminar su contenido",
"common_copied": "¡Elemento copiado!",
"common_host_placeholder": "raspberrypi.local o 192.168.1.85",
"common_reachable_at": "Host",
"logout": "Cerrar sesión",
"login_error_not_found": "Credenciales incorrectas",
"login_error_bad_credentials": "Credenciales incorrectas",

View File

@ -112,8 +112,13 @@
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Annuler",
"js_fleet_node_player_delete_confirmation": "Êtes-vous sûr ?",
"js_fleet_node_player_assign_confirmation": "Ce lecteur est déjà assigné à un groupe et va être remplacé par votre sélection, êtes-vous sûr ?",
"fleet_node_player_group_page_title": "Playgroups",
"fleet_node_player_group_page_about": "À propos du playgroup",
"fleet_node_player_group_page_player_management": "Elements du playgroup",
"fleet_node_player_group_page_player_management_desc": "Vous pouvez ajouter, modifier, supprimer les lecteurs depuis cette liste. La playlist associée à ce playgroup sera jouée sur tous les lecteurs.",
"fleet_node_player_group_button_add": "Ajouter un Playgroup",
"fleet_node_player_group_assign_player": "Assigner un lecteur",
"fleet_node_player_group_panel_active": "Playgroups",
"fleet_node_player_group_panel_empty": "Actuellement, il n'y a pas de playgroup. %link% maintenant.",
"fleet_node_player_group_panel_th_name": "Nom",
@ -234,6 +239,7 @@
"common_folder_not_empty_error": "Le dossier n'est pas vide, vous devez d'abord supprimer son contenu",
"common_copied": "Element copié !",
"common_host_placeholder": "raspberrypi.local ou 192.168.1.85",
"common_reachable_at": "Hôte",
"logout": "Déconnexion",
"login_error_not_found": "Identifiants invalides",
"login_error_bad_credentials": "Identifiants invalides",

View File

@ -111,8 +111,13 @@
"fleet_node_player_form_label_operating_system": "OS",
"fleet_node_player_form_button_cancel": "Cancella",
"js_fleet_node_player_delete_confirmation": "Sei sicuro?",
"js_fleet_node_player_assign_confirmation": "Questo giocatore è già assegnato a un gruppo e verrà sostituito dalla tua selezione, sei sicuro?",
"fleet_node_player_group_page_title": "Playgroups",
"fleet_node_player_group_page_about": "Informazioni sul playgroup",
"fleet_node_player_group_page_player_management": "Elementi del playgroup",
"fleet_node_player_group_page_player_management_desc": "Puoi aggiungere, modificare, eliminare giocatori da questo elenco. La playlist associata a questo playgroup verrà riprodotta su tutti i giocatori.",
"fleet_node_player_group_button_add": "Aggiungi Playgroup",
"fleet_node_player_group_assign_player": "Assegna un giocatore",
"fleet_node_player_group_panel_active": "Playgroup attivi",
"fleet_node_player_group_panel_empty": "Attualmente, non ci sono playgroup. %link% adesso.",
"fleet_node_player_group_panel_th_name": "Nome",
@ -233,6 +238,7 @@
"common_folder_not_empty_error": "La cartella non è vuota, devi prima eliminarne il contenuto",
"common_copied": "Elemento copiato!",
"common_host_placeholder": "raspberrypi.local o 192.168.1.85",
"common_reachable_at": "Host",
"logout": "Logout",
"login_error_not_found": "Credenziali errate",
"login_error_bad_credentials": "Credenziali errate",

View File

@ -33,11 +33,13 @@ class ContentController(ObController):
self._model_store.variable().update_by_name('last_pillmenu_slideshow', 'slideshow_content_list')
working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string()
working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.CONTENT)
slides_with_content = self._model_store.slide().get_all_indexed(attribute='content_id', multiple=True)
return render_template(
'slideshow/contents/list.jinja.html',
foldered_contents=self._model_store.content().get_all_indexed('folder_id', multiple=True),
folders_tree=self._model_store.folder().get_folder_tree(FolderEntity.CONTENT),
slides_with_content=slides_with_content,
working_folder_path=working_folder_path,
working_folder=working_folder,
working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.CONTENT, sort='created_at', ascending=False),

View File

@ -36,9 +36,11 @@ class FleetNodePlayerController(ObController):
self._model_store.variable().update_by_name('last_pillmenu_fleet', 'fleet_node_player_list')
working_folder_path = self._model_store.variable().get_one_by_name('last_folder_node_player').as_string()
working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.NODE_PLAYER)
return render_template(
'fleet/node-players/list.jinja.html',
node_players=self._model_store.node_player().get_all_indexed('folder_id', multiple=True),
foldered_node_players=self._model_store.node_player().get_all_indexed('folder_id', multiple=True),
groups=self._model_store.node_player_group().get_all_labels_indexed(),
folders_tree=self._model_store.folder().get_folder_tree(FolderEntity.NODE_PLAYER),
working_folder_path=working_folder_path,
working_folder=working_folder,

View File

@ -24,6 +24,8 @@ class FleetNodePlayerGroupController(ObController):
self._app.add_url_rule('/fleet/node-player-group/add', 'fleet_node_player_group_add', self.guard_fleet(self._auth(self.fleet_node_player_group_add)), methods=['POST'])
self._app.add_url_rule('/fleet/node-player-group/save', 'fleet_node_player_group_save', self.guard_fleet(self._auth(self.fleet_node_player_group_save)), methods=['POST'])
self._app.add_url_rule('/fleet/node-player-group/delete/<player_group_id>', 'fleet_node_player_group_delete', self.guard_fleet(self._auth(self.fleet_node_player_group_delete)), methods=['GET'])
self._app.add_url_rule('/fleet/node-player-group/unassign-player', 'fleet_node_player_group_unassign_player', self._auth(self.fleet_node_player_group_unassign_player), methods=['DELETE'])
self._app.add_url_rule('/fleet/node-player-group/assign-player/<player_group_id>/<player_id>', 'fleet_node_player_group_assign_player', self._auth(self.fleet_node_player_group_assign_player), methods=['GET'])
def fleet_node_player_group(self):
return redirect(url_for('fleet_node_player_group_list', player_group_id=0))
@ -85,3 +87,43 @@ class FleetNodePlayerGroupController(ObController):
self._model_store.node_player_group().delete(player_group_id)
return redirect(url_for('fleet_node_player_group'))
def fleet_node_player_group_unassign_player(self, node_player_id: int = 0):
node_player_id = request.form['id'] if 'id' in request.form else node_player_id
node_player = self._model_store.node_player().get(node_player_id)
if not node_player:
return redirect(url_for('fleet_node_player_group'))
group_id = node_player.group_id
self._model_store.node_player().update_form(
id=node_player.id,
group_id=False,
)
self._post_update()
pcounter = self._model_store.node_player_group().get_player_counters_by_player_groups(group_id=group_id)
return jsonify({'status': 'ok', 'pcounter': pcounter, 'group_id': group_id})
def fleet_node_player_group_assign_player(self, player_group_id: int = 0, player_id: int = 0):
node_player_group = self._model_store.node_player().get(player_group_id)
if not node_player_group:
return redirect(url_for('fleet_node_player_group'))
node_player = self._model_store.node_player().get(player_id)
if not node_player:
return redirect(url_for('fleet_node_player_group_list', player_group_id=node_player_group.id))
self._model_store.node_player().update_form(
id=node_player.id,
group_id=node_player_group.id,
)
self._post_update()
return redirect(url_for('fleet_node_player_group_list', player_group_id=node_player_group.id))
def _post_update(self):
pass

View File

@ -35,6 +35,7 @@ class PlaylistController(ObController):
durations = self._model_store.playlist().get_durations_by_playlists()
working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string()
working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.CONTENT)
slides_with_content = self._model_store.slide().get_all_indexed(attribute='content_id', multiple=True)
if not current_playlist and len(playlists) > 0:
current_playlist = None
@ -45,6 +46,7 @@ class PlaylistController(ObController):
current_playlist=current_playlist,
playlists=playlists,
durations=durations,
slides_with_content=slides_with_content,
slides=self._model_store.slide().get_slides(playlist_id=current_playlist.id) if current_playlist else [],
foldered_contents=self._model_store.content().get_all_indexed('folder_id', multiple=True),
contents={content.id: {"id": content.id, "name": content.name, "type": content.type.value} for content in self._model_store.content().get_contents()},

View File

@ -113,17 +113,17 @@ class NodePlayerManager(ModelManager):
def post_delete(self, node_player_id: str) -> str:
return node_player_id
def update_form(self, id: int, name: str, host: Optional[str] = None, operating_system: Optional[OperatingSystem] = None, group_id: Optional[int] = None) -> NodePlayer:
def update_form(self, id: int, name: Optional[str] = None, host: Optional[str] = None, operating_system: Optional[OperatingSystem] = None, group_id: Optional[int] = None) -> NodePlayer:
node_player = self.get(id)
if not node_player:
return
form = {
"name": name,
"name": name if name else node_player.name,
"host": host if host else node_player.host,
"operating_system": operating_system.value if operating_system else node_player.operating_system.value,
"group_id": group_id if group_id else node_player.group_id
"group_id": None if group_id is False else group_id if group_id else node_player.group_id
}
self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form))

View File

@ -66,6 +66,20 @@ class SlideManager(ModelManager):
def get_all(self, sort: bool = False) -> List[Slide]:
return self.hydrate_list(self._db.get_all(self.TABLE_NAME, sort="position" if sort else None))
def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, Slide]:
index = {}
for item in self.get_slides():
id = getattr(item, attribute)
if multiple:
if id not in index:
index[id] = []
index[id].append(item)
else:
index[id] = item
return index
def forget_for_user(self, user_id: int):
slides = self.get_by("created_by = '{}' or updated_by = '{}'".format(user_id, user_id))
edits_slides = self.user_manager.forget_user_for_entity(slides, user_id)
@ -83,7 +97,10 @@ class SlideManager(ModelManager):
query = "{} {}".format(query, "AND playlist_id = {}".format(playlist_id))
if content_id:
query = "{} {}".format(query, "AND content_id = {}".format(content_id))
if isinstance(content_id, bool):
query = "{} {}".format(query, "AND content_id IS NOT NULL")
else:
query = "{} {}".format(query, "AND content_id = {}".format(content_id))
return self.get_by(query=query, sort="position")

View File

@ -137,8 +137,8 @@ class VariableManager:
{"name": "last_pillmenu_configuration", "value": "settings_variable_list", "type": VariableType.STRING, "editable": False, "description": None},
{"name": "last_pillmenu_fleet", "value": "fleet_node_player_list", "type": VariableType.STRING, "editable": False, "description": None},
{"name": "last_pillmenu_security", "value": "auth_user_list", "type": VariableType.STRING, "editable": False, "description": None},
{"name": "last_folder_content", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_content')},
{"name": "last_folder_node_player", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_node_player')},
{"name": "last_folder_content", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": None},
{"name": "last_folder_node_player", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": None},
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_last_slide_update')},
{"name": "refresh_player_request", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_refresh_player_request')},

View File

@ -38,3 +38,27 @@ class OperatingSystem(Enum):
return 'fa-display'
return 'fa-server'
def get_color_icon(value: Enum) -> str:
if value == OperatingSystem.RASPBIAN:
return 'raspbian'
elif value == OperatingSystem.WINDOWS:
return 'windows'
elif value == OperatingSystem.MACOS:
return 'apple'
elif value == OperatingSystem.DEBIAN:
return 'debian'
elif value == OperatingSystem.FEDORA:
return 'fedora'
elif value == OperatingSystem.UBUNTU:
return 'ubuntu'
elif value == OperatingSystem.SUSE:
return 'suse'
elif value == OperatingSystem.REDHAT:
return 'redhat'
elif value == OperatingSystem.CENTOS:
return 'centos'
elif value == OperatingSystem.OTHER:
return 'other'
return 'white'

View File

@ -241,6 +241,7 @@
'js_slideshow_content_delete_confirmation': '{{ l.js_slideshow_content_delete_confirmation }}',
'js_fleet_node_player_group_delete_confirmation': '{{ l.js_fleet_node_player_group_delete_confirmation }}',
'js_fleet_node_player_delete_confirmation': '{{ l.js_fleet_node_player_delete_confirmation }}',
'js_fleet_node_player_assign_confirmation': '{{ l.js_fleet_node_player_assign_confirmation }}',
'js_auth_user_delete_confirmation': '{{ l.js_auth_user_delete_confirmation }}',
'js_sysinfo_restart_confirmation': '{{ l.js_sysinfo_restart_confirmation }}',
'js_sysinfo_restart_loading': '{{ l.js_sysinfo_restart_loading }}',

View File

@ -3,7 +3,8 @@
{% set has_children = folder.children or node_player_children %}
<li class="icon-folder li-explr-folder li-explr-folder-{{ folder.id }}">
<a href="{% if use_href %}{{ url_for('fleet_node_player_cd') }}?path={{ folder.path }}{% else %}javascript:void(0);{% endif %}"
<a href="{% if use_href %}{{ url_for('fleet_node_player_cd') }}?path=
{{ folder.path }}{% else %}javascript:void(0);{% endif %}"
class="{% if folder.path == working_folder_path %}active{% endif %} {{ 'explr-pick-folder' if not use_href }}">
{{ folder.name }}
</a>
@ -15,9 +16,18 @@
{% endfor %}
{% for node_player in node_player_children %}
{% set icon = enum_operating_system.get_fa_icon(node_player.operating_system) %}
{% set color = enum_operating_system.get_color_icon(node_player.operating_system) %}
<li class="explr-item">
<i class="fa {{ icon }} {{ color }}"></i>
<a href="{% if use_href %}{{ url_for('fleet_node_player_edit', node_player_id=node_player.id) }}{% else %}javascript:void(0);{% endif %}" class="{{ 'explr-pick-element' if not use_href }}" data-json="{{ json_dumps(node_player.to_dict()) }}">
<i class="fa {{ icon }} {{ color }} main"></i>
{% if node_player.group_id %}
<sub>
<i class="fa fa-layer-group"></i>
</sub>
{% endif %}
<a href="
{% if use_href %}{{ url_for('fleet_node_player_edit', node_player_id=node_player.id) }}{% else %}javascript:void(0);{% endif %}"
class="{{ 'explr-pick-element' if not use_href }}"
data-json="{{ json_dumps(node_player.to_dict()) }}">
{{ node_player.name }}
</a>
</li>

View File

@ -0,0 +1,33 @@
<ul class="players">
{% for player in players %}
<li class="player-item" data-level="{{ player.id }}"
data-entity="{{ player.to_json() }}">
<div class="head sort">
<i class="fa fa-display icon-left"></i>
</div>
<div class="infos">
<div class="type">
{% set icon = enum_operating_system.get_fa_icon(player.operating_system) %}
{% set color = enum_operating_system.get_color_icon(player.operating_system) %}
<i class="fa {{ icon }} {{ color }}"></i>
</div>
<div class="title" title="{{ player.name|default(l.common_empty) }}">
{{ truncate(player.name|default(l.common_empty), 30, '...') }}
</div>
</div>
<div class="body">
<span>{{ l.common_reachable_at }}: </span>{{ player.host }}
</div>
<div class="tail">
<a href="javascript:void(0);" class="item-delete node-player-group-unassign-player btn btn-naked" data-route="{{ url_for('fleet_node_player_group_unassign_player', node_player_id=player.id) }}">
<i class="fa fa-close"></i>
</a>
</div>
</li>
{% else %}
<div class="inner-empty empty-flag">
<i class="fa fa-display"></i>
</div>
{% endfor %}
</ul>

View File

@ -21,9 +21,9 @@
{% block page %}
<div class="top-content">
{# <h1>#}
{# {{ l.fleet_node_player_page_title }}#}
{# </h1>#}
{# <h1>#}
{# {{ l.fleet_node_player_page_title }}#}
{# </h1>#}
<div class="top-actions">
{{ HOOK(H_FLEET_NODE_PLAYER_TOOLBAR_ACTIONS_START) }}
@ -36,14 +36,18 @@
{{ l.common_new_folder }}
</button>
<div class="explr-selection-actions">
<button type="button" class="explr-item-edit explr-selection-entity btn-info" data-entity-route="{{ url_for('fleet_node_player_edit', node_player_id='!c!') }}">
<div class="explr-selection-actions">
<button type="button" class="explr-item-edit explr-selection-entity btn-info"
data-entity-route="{{ url_for('fleet_node_player_edit', node_player_id='!c!') }}">
<i class="fa fa-eye"></i>
</button>
<button type="button" class="explr-item-rename explr-selection-entity explr-selection-folder btn-info">
<i class="fa fa-pencil"></i>
</button>
<button type="button" class="explr-item-delete explr-selection-entity explr-selection-folder btn-danger-alt" data-folder-route="{{ url_for('fleet_node_player_folder_delete') }}" data-entity-route="{{ url_for('fleet_node_player_delete') }}">
<button type="button"
class="explr-item-delete explr-selection-entity explr-selection-folder btn-danger-alt"
data-folder-route="{{ url_for('fleet_node_player_folder_delete') }}"
data-entity-route="{{ url_for('fleet_node_player_delete') }}">
<i class="fa fa-trash-alt"></i>
</button>
</div>
@ -68,44 +72,16 @@
<div class="bottom-content">
<div class="page-panel left-panel explr-explorer">
{% macro render_folder(folder) %}
{% set node_player_children = node_players[folder.id]|default([]) %}
{% set has_children = folder.children or node_player_children %}
<li class="icon-folder li-explr-folder li-explr-folder-{{ folder.id if folder.id else 0 }}">
<a href="{{ url_for('fleet_node_player_cd') }}?path={{ folder.path }}" class="{% if folder.path == working_folder_path %}active{% endif %}">
{{ folder.name }}
</a>
{% if has_children %}
<ul>
{% for child in folder.children %}
{{ render_folder(child) }}
{% endfor %}
{% for node_player in node_player_children %}
{% set icon = enum_operating_system.get_fa_icon(node_player.operating_system) %}
{% set color = node_player.operating_system.value %}
<li class="explr-item">
<i class="fa {{ icon }} {{ color }}"></i>
<a href="{{ url_for('fleet_node_player_edit', node_player_id=node_player.id) }}">
{{ node_player.name }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endmacro %}
<ul class="explr hidden" id="tree" data-working-folder-id="{{ working_folder.id }}">
{{ render_folder(folders_tree) }}
</ul>
{% with use_href=True %}
{% include 'fleet/node-players/component/explr-sidebar.jinja.html' %}
{% endwith %}
</div>
<form id="folder-move-form" action="{{ url_for('fleet_node_player_folder_move') }}" class="hidden" method="POST">
<input type="hidden" name="entity_id" />
<input type="hidden" name="new_folder_id" />
<input type="hidden" name="is_folder" />
<form id="folder-move-form" action="{{ url_for('fleet_node_player_folder_move') }}" class="hidden"
method="POST">
<input type="hidden" name="entity_id"/>
<input type="hidden" name="new_folder_id"/>
<input type="hidden" name="is_folder"/>
</form>
<div class="page-content">
@ -141,15 +117,17 @@
<a href="javascript:void(0);">
<i class="fa fa-folder"></i>
<form action="{{ url_for('fleet_node_player_folder_add') }}" method="POST">
<input type="text" name="name" autocomplete="off" />
<input type="text" name="name" autocomplete="off"/>
</form>
</a>
</li>
{% set parent_path = '/'.join(working_folder_path.rstrip('/').split('/')[:-1]) %}
{% if parent_path %}
<li class="previous-folder droppable" data-path="{{ parent_path }}" data-id="{{ working_folder.parent_id }}" data-folder="1">
<a href="{{ url_for('fleet_node_player_cd', path=parent_path) }}" class="explr-link explr-item-selectable explr-item-folder">
<li class="previous-folder droppable" data-path="{{ parent_path }}"
data-id="{{ working_folder.parent_id }}" data-folder="1">
<a href="{{ url_for('fleet_node_player_cd', path=parent_path) }}"
class="explr-link explr-item-selectable explr-item-folder">
<i class="fa fa-folder"></i>
..
</a>
@ -158,29 +136,40 @@
{% for folder in working_folder_children %}
{% set folder_path = working_folder_path ~ '/' ~ folder.name %}
<li class="draggable droppable" data-path="{{ folder_path }}" data-id="{{ folder.id }}" data-folder="1">
<a href="{{ url_for('fleet_node_player_cd', path=folder_path) }}" class="explr-link explr-item-selectable explr-item-actionable explr-item-folder">
<li class="draggable droppable" data-path="{{ folder_path }}" data-id="{{ folder.id }}"
data-folder="1">
<a href="{{ url_for('fleet_node_player_cd', path=folder_path) }}"
class="explr-link explr-item-selectable explr-item-actionable explr-item-folder">
<i class="fa fa-folder"></i>
<span>{{ truncate(folder.name, 25, '...') }}</span>
<form action="{{ url_for('fleet_node_player_folder_rename') }}" method="POST">
<input type="text" name="name" value="{{ folder.name }}" autocomplete="off" />
<input type="hidden" name="id" value="{{ folder.id }}" />
<input type="text" name="name" value="{{ folder.name }}" autocomplete="off"/>
<input type="hidden" name="id" value="{{ folder.id }}"/>
</form>
</a>
</li>
{% endfor %}
{% for node_player in node_players[working_folder.id|default(None)]|default([]) %}
{% for node_player in foldered_node_players[working_folder.id|default(None)]|default([]) %}
{% set icon = enum_operating_system.get_fa_icon(node_player.operating_system) %}
{% set color = node_player.operating_system.value %}
<li class="draggable" data-path="{{ working_folder_path }}" data-id="{{ node_player.id }}" data-folder="0" data-entity-json="{{ node_player.to_json() }}">
<a href="{{ url_for('fleet_node_player_edit', node_player_id=node_player.id) }}" class="explr-link explr-item-selectable explr-item-actionable explr-item-entity" data-callback="nodePlayerEdit">
<i class="fa {{ icon }} {{ color }}"></i>
{% set group_label = groups[node_player.group_id]|default(l.common_empty) %}
<li class="draggable" data-path="{{ working_folder_path }}" data-id="{{ node_player.id }}"
data-folder="0" data-entity-json="{{ node_player.to_json({"group_label": group_label}) }}">
<a href="{{ url_for('fleet_node_player_edit', node_player_id=node_player.id) }}"
class="explr-link explr-item-selectable explr-item-actionable explr-item-entity"
data-callback="nodePlayerEdit">
<i class="fa {{ icon }} {{ color }} main"></i>
{% if node_player.group_id %}
<sub>
<i class="fa fa-layer-group"></i>
</sub>
{% endif %}
<span>{{ truncate(node_player.name, 25, '...') }}</span>
<form action="{{ url_for('fleet_node_player_rename') }}" method="POST">
<input type="text" name="name" value="{{ node_player.name }}" autocomplete="off" />
<input type="hidden" name="id" value="{{ node_player.id }}" />
<input type="text" name="name" value="{{ node_player.name }}" autocomplete="off"/>
<input type="hidden" name="id" value="{{ node_player.id }}"/>
</form>
</a>
</li>

View File

@ -17,7 +17,9 @@
<i class="fa fa-get-pocket operating-system-icon"></i>
<select name="operating_system" id="node-player-add-operating-system" class="operating-system-select">
{% for os in enum_operating_system %}
<option value="{{ os.value }}" data-icon="{{ enum_operating_system.get_fa_icon(os) }}" data-color="{{ os.value }}">
{% set icon = enum_operating_system.get_fa_icon(os) %}
{% set color = enum_operating_system.get_color_icon(os) %}
<option value="{{ os.value }}" data-icon="{{ icon }}" data-color="{{ color }}">
{{ t(os) }}
</option>
{% endfor %}

View File

@ -13,12 +13,26 @@
</div>
</div>
<div class="form-group form-group-for-group-id hidden">
<label for="node-player-edit-group-id">{{ l.fleet_node_player_form_label_group_id }}</label>
<div class="widget">
<input name="name" type="text" id="node-player-edit-group-label" class="input-naked" disabled="disabled" />
<input name="name" type="text" id="node-player-edit-group-id" required="required" class="hidden" disabled="disabled" />
<a href="javascript:void(0);" class="btn btn-neutral group-edit-link" data-route="{{ url_for('fleet_node_player_group_list', player_group_id='__id__') }}">
<i class="fa-solid fa-layer-group"></i>
</a>
</div>
</div>
<div class="form-group tab-select">
<div class="widget">
<i class="fa fa-get-pocket operating-system-icon"></i>
<select name="operating_system" id="node-player-edit-operating-system" class="operating-system-select">
{% for os in enum_operating_system %}
<option value="{{ os.value }}" data-icon="{{ enum_operating_system.get_fa_icon(os) }}" data-color="{{ os.value }}">
{% set icon = enum_operating_system.get_fa_icon(os) %}
{% set color = enum_operating_system.get_color_icon(os) %}
<option value="{{ os.value }}" data-icon="{{ icon }}" data-color="{{ color }}">
{{ t(os) }}
</option>
{% endfor %}

View File

@ -1,37 +1,40 @@
<div class="horizontal">
<div class="players-holder vertical">
<h3>
{{ l.playlist_panel_content_management }}
{{ l.fleet_node_player_group_page_player_management }}
</h3>
<p>
{{ l.playlist_panel_content_management_desc }}
{{ l.fleet_node_player_group_page_player_management_desc }}
</p>
{# {% with slides=slides %}#}
{# {% include 'slideshow/slides/component/table.jinja.html' %}#}
{# {% endwith %}#}
{##}
{# <div class="actions actions-right">#}
{# <button type="button" class="btn btn-info slide-add">#}
{# <i class="fa fa-plus"></i> {{ l.slideshow_slide_form_add_title }}#}
{# </button>#}
{# <a href="{{ url_for('slideshow_content_list') }}" class="btn btn-neutral" target="_blank">#}
{# <i class="fa fa-upload"></i>#}
{# </a>#}
{# </div>#}
{% with players=players %}
{% include 'fleet/node-players/component/table.jinja.html' %}
{% endwith %}
<div class="actions actions-right">
<button type="button" class="btn btn-info node-player-group-player-assign" data-route="{{ url_for('fleet_node_player_group_assign_player', player_group_id=current_player_group.id, player_id='__id__') }}">
<i class="fa fa-plus"></i> {{ l.fleet_node_player_group_assign_player }}
</button>
<a href="{{ url_for('fleet_node_player_list') }}" class="btn btn-neutral" target="_blank">
<i class="fa fa-display main"></i>
<sub>
<i class="fa fa-download"></i>
</sub>
</a>
</div>
</div>
<div class="node-player-group-holder vertical">
{# <h3>#}
{# {{ l.playlist_panel_about_playlist }}#}
{# </h3>#}
<h3>
{{ l.fleet_node_player_group_page_about }}
</h3>
<div class="form-holder">
<form class="form" action="{{ url_for('fleet_node_player_group_save') }}" method="POST">
<input type="hidden" name="id" id="node-player-group-edit-id" value="{{ current_player_group.id }}"/>
<div class="form-group">
<label for="node-player-group-edit-name">{{ l.playlist_form_label_name }}</label>
<label for="node-player-group-edit-name">{{ l.fleet_node_player_group_form_label_name }}</label>
<div class="widget">
<input type="text" name="name" id="node-player-group-edit-name" required="required" value="{{ current_player_group.name }}"/>
</div>
@ -40,7 +43,7 @@
<div class="form-group">
<label for="node-player-group-edit-playlist-id">{{ l.fleet_node_player_group_form_label_playlist_id }}</label>
<div class="widget">
<select name="playlist_id" id="node-player-group-edit-playlist-id">
<select name="playlist_id" id="node-player-group-edit-playlist-id" class="input-naked">
{% for playlist_id, playlist_name in playlists.items() %}
<option value="{{ playlist_id }}" {% if current_player_group.playlist_id == playlist_id %}selected="selected"{% endif %}>
{{ playlist_name }}
@ -61,6 +64,7 @@
</div>
</form>
</div>
<div class="preview-holder">
{% set preview_url = request.scheme ~ '://' ~ request.headers.get('host') ~ url_for('player_use', playlist_slug_or_id=current_player_group.playlist_id) %}

View File

@ -1,77 +1,3 @@
{#<table class="{{ tclass }}-node-players">#}
{# <thead>#}
{# <tr>#}
{# <th>{{ l.fleet_node_player_group_panel_th_name }}</th>#}
{# {% if AUTH_ENABLED %}#}
{# <th class="tac">#}
{# <i class="fa fa-user"></i>#}
{# </th>#}
{# {% endif %}#}
{# {% if PLAYLIST_ENABLED %}#}
{# <th class="tac">{{ l.fleet_node_player_group_panel_th_playlist }}</th>#}
{# {% endif %}#}
{# <th class="tac">{{ l.fleet_node_player_group_panel_th_activity }}</th>#}
{# </tr>#}
{# </thead>#}
{# <tbody>#}
{# <tr class="empty-tr {% if node_player_groups|length != 0 %}hidden{% endif %}">#}
{# <td colspan="4">#}
{# {{ l.fleet_node_player_group_panel_empty|replace(#}
{# '%link%',#}
{# ('<a href="javascript:void(0);" class="item-add node-player-group-add">'~l.fleet_node_player_group_button_add~'</a>')|safe#}
{# ) }}#}
{# </td>#}
{# </tr>#}
{##}
{# {% for node_player_group in node_player_groups %}#}
{# <tr class="node-player-group-item" data-level="{{ node_player_group.id }}" data-entity="{{ node_player_group.to_json({"created_by": track_created(node_player_group).username, "updated_by": track_updated(node_player_group).username}) }}">#}
{# <td class="infos">#}
{# <div class="inner">#}
{# {% if node_player_group.id %}#}
{# <div class="badge"><i class="fa fa-key icon-left"></i> {{ node_player_group.id }}</div>#}
{# {% else %}#}
{# <div class="badge"><i class="fa fa-lock"></i></div>#}
{# {% endif %}#}
{##}
{# <i class="fa fa-layer-group icon-left"></i>#}
{# {{ node_player_group.name }}#}
{# </div>#}
{# </td>#}
{# {% if AUTH_ENABLED %}#}
{# <td class="tac">#}
{# {% if node_player_group.id %}#}
{# {% set creator = track_created(node_player_group) %}#}
{# {% if creator.username %}#}
{# <a href="javascript:void(0);" class="badge item-utrack node-player-group-utrack {% if not creator.enabled %}anonymous{% endif %}">#}
{# {{ creator.username }}#}
{# </a>#}
{# {% endif %}#}
{# {% endif %}#}
{# </td>#}
{# {% endif %}#}
{# <td class="tac">#}
{# {% if node_player_group.playlist_id and playlists[node_player_group.playlist_id] %}#}
{# {{ playlists[node_player_group.playlist_id] }}#}
{# {% else %}#}
{# {{ l.common_default_playlist }}#}
{# {% endif %}#}
{# </td>#}
{# <td class="actions tac">#}
{# {% if node_player_group.id %}#}
{# <a href="javascript:void(0);" class="item-edit node-player-group-edit">#}
{# <i class="fa fa-pencil"></i>#}
{# </a>#}
{# <a href="javascript:void(0);" class="item-delete node-player-group-delete">#}
{# <i class="fa fa-trash"></i>#}
{# </a>#}
{# {% endif %}#}
{# </td>#}
{# </tr>#}
{# {% endfor %}#}
{# </tbody>#}
{#</table>#}
<div class="tiles node-player-groups">
<div class="tiles-inner">
{% for node_player_group in node_player_groups %}

View File

@ -14,7 +14,7 @@
<div class="form-group">
<label for="node-player-group-add-playlist-id">{{ l.fleet_node_player_group_form_label_playlist_id }}</label>
<div class="widget">
<select name="playlist_id" id="node-player-group-add-playlist-id">
<select name="playlist_id" id="node-player-group-add-playlist-id" class="input-naked">
{% for playlist_id, playlist_name in playlists.items() %}
<option value="{{ playlist_id }}">{{ playlist_name }}</option>
{% endfor %}

View File

@ -3,7 +3,8 @@
{% set has_children = folder.children or content_children %}
<li class="icon-folder li-explr-folder li-explr-folder-{{ folder.id }}">
<a href="{% if use_href %}{{ url_for('slideshow_content_cd') }}?path={{ folder.path }}{% else %}javascript:void(0);{% endif %}"
<a href="{% if use_href %}{{ url_for('slideshow_content_cd') }}?path=
{{ folder.path }}{% else %}javascript:void(0);{% endif %}"
class="{% if folder.path == working_folder_path %}active{% endif %} {{ 'explr-pick-folder' if not use_href }}">
{{ folder.name }}
</a>
@ -14,11 +15,20 @@
{{ render_folder(child) }}
{% endfor %}
{% for content in content_children %}
{% set slides = slides_with_content[content.id]|default([]) %}
{% set icon = enum_content_type.get_fa_icon(content.type) %}
{% set color = enum_content_type.get_color_icon(content.type) %}
<li class="explr-item">
<i class="fa {{ icon }} {{ color }}"></i>
<a href="{% if use_href %}{{ url_for('slideshow_content_edit', content_id=content.id) }}{% else %}javascript:void(0);{% endif %}" class="{{ 'explr-pick-element' if not use_href }}" data-json="{{ json_dumps(content.to_dict()) }}">
{% if slides|length > 0 %}
<sub>
<i class="fa fa-play"></i>
</sub>
{% endif %}
<a href="
{% if use_href %}{{ url_for('slideshow_content_edit', content_id=content.id) }}{% else %}javascript:void(0);{% endif %}"
class="{{ 'explr-pick-element' if not use_href }}"
data-json="{{ json_dumps(content.to_dict()) }}">
{{ content.name }}
</a>
</li>

View File

@ -22,9 +22,9 @@
{% block page %}
<div class="top-content">
{# <h1>#}
{# {{ l.slideshow_content_page_title }}#}
{# </h1>#}
{# <h1>#}
{# {{ l.slideshow_content_page_title }}#}
{# </h1>#}
<div class="top-actions">
{{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START) }}
@ -46,7 +46,8 @@
<button type="button" class="explr-item-rename explr-selection-entity explr-selection-folder btn-info">
<i class="fa fa-pencil"></i>
</button>
<button type="button" class="explr-item-delete explr-selection-entity explr-selection-folder btn-danger-alt"
<button type="button"
class="explr-item-delete explr-selection-entity explr-selection-folder btn-danger-alt"
data-folder-route="{{ url_for('slideshow_content_folder_delete') }}"
data-entity-route="{{ url_for('slideshow_content_delete') }}">
<i class="fa fa-trash-alt"></i>

View File

@ -3,7 +3,7 @@
{% set content = contents[slide.content_id] %}
<li class="slide-item {{ 'disabled' if not slide.enabled }}" data-level="{{ slide.id }}"
data-entity="{{ slide.to_json({"content": content}) }}">
<div class="sort">
<div class="head sort">
<a href="javascript:void(0);" class="item-sort slide-sort">
<i class="fa fa-bars icon-left"></i>
</a>