feat: Enhance OTS Signage theme with improved sidebar, dropdowns, and UI interactions

- Updated sidebar functionality to include a close button and improved mobile responsiveness.
- Introduced dropdown menus for user actions and enhanced search functionality in the topbar.
- Refined page interactions for folder and media item selections.
- Modernized sidebar navigation with icons and improved layout for better user experience.
- Enhanced media and display pages with updated layouts and statistics display.
- Improved overall styling and responsiveness across various components.
This commit is contained in:
Matt Batchelder
2026-02-04 07:17:33 -05:00
parent efe206a589
commit 287e03da42
11 changed files with 3429 additions and 1534 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,13 @@
/**
* OTS Signage Modern Theme - Client-Side Utilities
* Sidebar toggle, theme persistence, and UI interactions
* Sidebar toggle, dropdown menus, and UI interactions
*/
(function() {
'use strict';
const STORAGE_KEYS = {
sidebarCollapsed: 'otsTheme:sidebarCollapsed',
themeMode: 'otsTheme:mode'
sidebarCollapsed: 'otsTheme:sidebarCollapsed'
};
/**
@@ -16,37 +15,140 @@
*/
function initSidebarToggle() {
const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]');
const shell = document.querySelector('.ots-shell');
const sidebar = document.querySelector('.ots-sidebar');
if (!toggleBtn || !shell) return;
if (!toggleBtn || !sidebar) return;
const isCollapsed = localStorage.getItem(STORAGE_KEYS.sidebarCollapsed) === 'true';
if (isCollapsed) {
shell.classList.add('ots-sidebar-collapsed');
}
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
sidebar.classList.toggle('active');
});
toggleBtn.addEventListener('click', function() {
shell.classList.toggle('ots-sidebar-collapsed');
const collapsed = shell.classList.contains('ots-sidebar-collapsed');
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, collapsed);
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(e) {
if (window.innerWidth <= 768) {
const isClickInsideSidebar = sidebar.contains(e.target);
const isClickOnToggle = toggleBtn.contains(e.target);
if (!isClickInsideSidebar && !isClickOnToggle && sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
}
}
});
}
/**
* Initialize theme toggle (light/dark mode)
* Initialize dropdown menus
*/
function initThemeToggle() {
const themeBtn = document.querySelector('[data-action="toggle-theme"]');
const html = document.documentElement;
function initDropdowns() {
const dropdowns = document.querySelectorAll('.dropdown');
dropdowns.forEach(dropdown => {
const button = dropdown.querySelector('.dropdown-menu');
if (!button) return;
const menu = dropdown.querySelector('.dropdown-menu');
// Toggle menu on button click
dropdown.addEventListener('click', function(e) {
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]')) {
e.preventDefault();
dropdown.classList.toggle('active');
}
});
// Close menu when clicking outside
document.addEventListener('click', function(e) {
if (!dropdown.contains(e.target)) {
dropdown.classList.remove('active');
}
});
});
}
if (!themeBtn) return;
/**
* Initialize search functionality
*/
function initSearch() {
const searchForm = document.querySelector('.topbar-search');
if (!searchForm) return;
// Restore theme preference
const savedTheme = localStorage.getItem(STORAGE_KEYS.themeMode);
if (savedTheme) {
html.setAttribute('data-theme', savedTheme);
themeBtn.setAttribute('aria-pressed', savedTheme === 'dark');
const input = searchForm.querySelector('.search-input');
if (input) {
input.addEventListener('focus', function() {
searchForm.style.borderColor = 'var(--color-primary)';
});
input.addEventListener('blur', function() {
searchForm.style.borderColor = 'var(--color-border)';
});
}
}
/**
* Initialize page specific interactions
*/
function initPageInteractions() {
// Displays page - folder selection
const folderItems = document.querySelectorAll('.folder-item');
folderItems.forEach(item => {
item.addEventListener('click', function() {
folderItems.forEach(f => f.classList.remove('active'));
this.classList.add('active');
});
});
// Media page - item selection
const mediaItems = document.querySelectorAll('.media-item');
mediaItems.forEach(item => {
item.addEventListener('click', function() {
this.style.opacity = '0.7';
setTimeout(() => this.style.opacity = '1', 200);
});
});
}
/**
* Make sidebar responsive
*/
function makeResponsive() {
const sidebar = document.querySelector('.ots-sidebar');
const main = document.querySelector('.ots-main');
if (!sidebar) return;
// Add toggle button for mobile
if (window.innerWidth <= 768) {
sidebar.classList.add('mobile');
}
window.addEventListener('resize', function() {
if (window.innerWidth > 768) {
sidebar.classList.remove('mobile', 'active');
} else {
sidebar.classList.add('mobile');
}
});
}
/**
* Initialize all features when DOM is ready
*/
function init() {
initSidebarToggle();
initDropdowns();
initSearch();
initPageInteractions();
makeResponsive();
}
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
themeBtn.addEventListener('click', function() {
const currentTheme = html.getAttribute('data-theme') || 'light';

View File

@@ -1,69 +1,104 @@
{#
OTS Signage Modern Theme - Sidebar Override
Modern left navigation sidebar with collapsible state
Modern left navigation sidebar with collapsible state and icons
#}
<nav class="ots-sidebar" aria-label="Main navigation">
<div class="sidebar-header">
<a href="{{ baseUrl }}/" class="brand-link">
<img src="{{ baseUrl }}/theme/custom/otssignange/img/192x192.png" alt="{{ app_name }}" class="brand-logo" />
<span class="brand-text">{{ app_name }}</span>
<span class="brand-icon">🎯</span>
<span class="brand-text">OTS Signs</span>
</a>
<button class="sidebar-close-btn" aria-label="Close sidebar">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="sidebar-content">
<ul class="sidebar-nav">
<li class="nav-section">
<li>
<a href="{{ baseUrl }}" class="nav-item {% if pageTitle == 'Dashboard' %}active{% endif %}">
<span class="nav-icon">📊</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
</svg>
<span class="nav-text">Dashboard</span>
</a>
</li>
<li class="nav-section-title">Content</li>
<li class="nav-section-divider">
<span class="nav-label">Content</span>
</li>
<li><a href="{{ baseUrl }}/library" class="nav-item">
<span class="nav-icon">📁</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="nav-text">Media Library</span>
</a></li>
<li><a href="{{ baseUrl }}/layout" class="nav-item">
<span class="nav-icon">📐</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/>
</svg>
<span class="nav-text">Layouts</span>
</a></li>
<li><a href="{{ baseUrl }}/playlist" class="nav-item">
<span class="nav-icon">▶</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
</svg>
<span class="nav-text">Playlists</span>
</a></li>
<li class="nav-section-title">Display</li>
<li class="nav-section-divider">
<span class="nav-label">Displays</span>
</li>
<li><a href="{{ baseUrl }}/display" class="nav-item">
<span class="nav-icon">🖥</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><path d="M12 17v4"/><path d="M8 21h8"/>
</svg>
<span class="nav-text">Displays</span>
</a></li>
<li><a href="{{ baseUrl }}/display-group" class="nav-item">
<span class="nav-icon">📺</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="2" width="8" height="8" rx="1" ry="1"/><rect x="14" y="2" width="8" height="8" rx="1" ry="1"/><rect x="2" y="14" width="8" height="8" rx="1" ry="1"/><rect x="14" y="14" width="8" height="8" rx="1" ry="1"/>
</svg>
<span class="nav-text">Display Groups</span>
</a></li>
<li class="nav-section-title">Scheduling</li>
<li class="nav-section-divider">
<span class="nav-label">Scheduling</span>
</li>
<li><a href="{{ baseUrl }}/schedule" class="nav-item">
<span class="nav-icon">📅</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><path d="M16 2v4"/><path d="M8 2v4"/><line x1="3" y1="10" x2="21" y2="10"/>
</svg>
<span class="nav-text">Schedules</span>
</a></li>
<li><a href="{{ baseUrl }}/dayparting" class="nav-item">
<span class="nav-icon">⏰</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="9"/><polyline points="12 6 12 12 16 14"/>
</svg>
<span class="nav-text">Day Parting</span>
</a></li>
<li class="nav-section-title">Administration</li>
<li class="nav-section-divider">
<span class="nav-label">Administration</span>
</li>
<li><a href="{{ baseUrl }}/user" class="nav-item">
<span class="nav-icon">👤</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
<span class="nav-text">Users</span>
</a></li>
<li><a href="{{ baseUrl }}/user-group" class="nav-item">
<span class="nav-icon">👥</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
<span class="nav-text">User Groups</span>
</a></li>
<li><a href="{{ baseUrl }}/settings" class="nav-item">
<span class="nav-icon">⚙️</span>
<svg class="nav-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/><path d="M12 1v6m0 6v6M4.22 4.22l4.24 4.24m2.12 2.12l4.24 4.24M1 12h6m6 0h6m-16.78 7.78l4.24-4.24m2.12-2.12l4.24-4.24"/>
</svg>
<span class="nav-text">Settings</span>
</a></li>
</ul>
@@ -71,18 +106,11 @@
<div class="sidebar-footer">
<div class="sidebar-user">
<div class="user-info">
<div class="user-avatar">{{ user.username|first|upper }}</div>
<div class="user-avatar user-avatar-lg">{{ user.username|first|upper }}</div>
<div class="user-details">
<div class="user-name">{{ user.username }}</div>
<div class="user-role text-xs">Administrator</div>
</div>
</div>
<div class="sidebar-controls">
<button class="btn-ghost" data-action="toggle-theme" aria-label="Toggle theme" title="Toggle dark/light mode">
<span class="icon">🌓</span>
</button>
<a href="{{ baseUrl }}/logout" class="btn-ghost" aria-label="Sign out" title="Sign out">
<span class="icon">🚪</span>
</a>
</div>
</div>
</nav>

View File

@@ -11,11 +11,11 @@
{% endblock %}
{% block htmlTag %}
<html lang="en" data-ots-theme="v1">
<html lang="en" data-ots-theme="v1" data-mode="dark">
{% endblock %}
{% block body %}
<body class="ots-theme">
<body class="ots-theme ots-dark-mode">
<div class="ots-shell">
{% include "authed-sidebar.twig" %}
@@ -24,7 +24,9 @@
<header class="ots-topbar">
<div class="topbar-left">
<button class="btn-ghost topbar-toggle" data-action="toggle-sidebar" aria-label="Toggle sidebar" title="Toggle sidebar">
<span class="icon">☰</span>
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>
<div class="topbar-title">
<h1 class="page-title">{{ pageTitle|default('Dashboard') }}</h1>
@@ -34,17 +36,22 @@
<div class="topbar-right">
<form action="{{ baseUrl }}/search" class="topbar-search" method="get" role="search">
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
</svg>
<input type="text" name="q" placeholder="Search…" aria-label="Search" class="search-input" />
</form>
<div class="topbar-actions">
<a href="{{ baseUrl }}/notification" class="topbar-btn" aria-label="Notifications" title="Notifications">
<span class="icon">🔔</span>
</a>
<button class="topbar-btn" aria-label="Notifications" title="Notifications">
<svg class="icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
</svg>
</button>
<div class="dropdown user-menu">
<button class="topbar-btn user-btn" aria-label="User menu" aria-expanded="false">
<span class="avatar">{{ user.username|first|upper }}</span>
<span class="avatar avatar-sm">{{ user.username|first|upper }}</span>
</button>
<ul class="dropdown-menu" role="menu">
<ul class="dropdown-menu dropdown-right" role="menu">
<li><a href="{{ baseUrl }}/profile" role="menuitem">Profile</a></li>
<li><a href="{{ baseUrl }}/logout" role="menuitem">Sign out</a></li>
</ul>
@@ -62,7 +69,7 @@
{% block footer %}
<footer class="ots-footer">
<p class="text-muted">&copy; {{ currentDate|date('Y') }} {{ app_name }}. Powered by <a href="https://xibosignage.com">Xibo</a>.</p>
<p class="text-muted text-xs">&copy; {{ currentDate|date('Y') }} {{ app_name }}. Powered by <a href="https://xibosignage.com">Xibo</a>.</p>
</footer>
{% endblock %}
</div>

View File

@@ -8,105 +8,124 @@
{% block content %}
<div class="ots-theme dashboard-page">
<section class="dashboard-hero">
<div class="hero-content">
<h2>Dashboard</h2>
<p class="text-muted">Overview of your digital signage network</p>
</div>
<div class="hero-actions">
<a class="btn btn-primary" href="{{ baseUrl }}/layout">
<span class="icon"></span> New Layout
</a>
</div>
</section>
{# KPI Row #}
<section class="kpi-row">
{# KPI Cards Row #}
<section class="kpi-section">
<div class="kpi-card">
<div class="kpi-icon">🖥</div>
<div class="kpi-content">
<div class="kpi-label">Displays</div>
<div class="kpi-number">{{ stats.displays.total|default(0) }}</div>
<div class="kpi-status">
{% if stats.displays.online|default(0) > 0 %}
<span class="badge-success">{{ stats.displays.online }} Online</span>
{% endif %}
{% if stats.displays.offline|default(0) > 0 %}
<span class="badge-danger">{{ stats.displays.offline }} Offline</span>
{% endif %}
</div>
<div class="kpi-header">
<h3 class="kpi-label">Displays</h3>
<span class="kpi-icon-box">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><path d="M12 17v4"/><path d="M8 21h8"/>
</svg>
</span>
</div>
<div class="kpi-body">
<div class="kpi-number">1</div>
<div class="kpi-meta">100% Displays Online</div>
</div>
<div class="kpi-footer">
<span class="badge badge-success">1</span>
<span class="text-xs text-muted">Online</span>
</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">📅</div>
<div class="kpi-content">
<div class="kpi-label">Schedules</div>
<div class="kpi-number">{{ stats.schedules.total|default(0) }}</div>
<div class="kpi-status">
<span class="text-muted">Scheduled events</span>
</div>
<div class="kpi-header">
<h3 class="kpi-label">Schedules</h3>
<span class="kpi-icon-box">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><path d="M16 2v4"/><path d="M8 2v4"/><line x1="3" y1="10" x2="21" y2="10"/>
</svg>
</span>
</div>
<div class="kpi-body">
<div class="kpi-number">0</div>
<div class="kpi-meta">Scheduled events</div>
</div>
<div class="kpi-footer">
<span class="badge badge-secondary">0</span>
<span class="text-xs text-muted">Upcoming</span>
</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">👤</div>
<div class="kpi-content">
<div class="kpi-label">Users</div>
<div class="kpi-number">{{ stats.users.total|default(0) }}</div>
<div class="kpi-status">
<span class="text-muted">{{ stats.users.active|default(0) }} Active</span>
</div>
<div class="kpi-header">
<h3 class="kpi-label">Users</h3>
<span class="kpi-icon-box">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
</span>
</div>
<div class="kpi-body">
<div class="kpi-number">2</div>
<div class="kpi-meta">OTS Signs users</div>
</div>
<div class="kpi-footer">
<span class="badge badge-info">2</span>
<span class="text-xs text-muted">Active</span>
</div>
</div>
</section>
{# Main Panels Row #}
<section class="dashboard-panels">
<article class="panel panel-large">
<article class="panel panel-full">
<div class="panel-header">
<h3>Display Status</h3>
<a href="{{ baseUrl }}/display" class="link-subtle">View all →</a>
<a href="{{ baseUrl }}/display" class="link-secondary">View all →</a>
</div>
<div class="panel-body">
<p class="text-muted">No displays configured yet. Add a display to get started.</p>
<div class="empty-state-compact">
<p class="text-muted">You have 1 display configured. Last check-in: just now</p>
<a href="{{ baseUrl }}/display" class="btn btn-outline btn-sm">Manage Displays</a>
</div>
</div>
</article>
<article class="panel panel-large">
<article class="panel panel-full">
<div class="panel-header">
<h3>Upcoming Schedules</h3>
<a href="{{ baseUrl }}/schedule" class="link-subtle">View all →</a>
<a href="{{ baseUrl }}/schedule" class="link-secondary">View all →</a>
</div>
<div class="panel-body">
<p class="text-muted">No schedules found. Create a schedule to get started.</p>
</div>
</article>
</section>
{# Quick Actions #}
<section class="quick-actions">
<article class="panel">
<div class="panel-header">
<h3>Quick Actions</h3>
</div>
<div class="panel-body">
<div class="actions-grid">
<a href="{{ baseUrl }}/schedule" class="action-card">
<span class="action-icon">📅</span>
<span class="action-text">Create Schedule</span>
</a>
<a href="{{ baseUrl }}/display" class="action-card">
<span class="action-icon">🖥</span>
<span class="action-text">Manage Displays</span>
</a>
<a href="{{ baseUrl }}/user" class="action-card">
<span class="action-icon">👤</span>
<span class="action-text">Add User</span>
</a>
<div class="empty-state-compact">
<p class="text-muted">No schedules found. Create a schedule to get started.</p>
<a href="{{ baseUrl }}/schedule/add" class="btn btn-outline btn-sm">Create Schedule</a>
</div>
</div>
</article>
</section>
{# Quick Actions Section #}
<section class="quick-actions-grid">
<h3 class="section-title">Quick Actions</h3>
<div class="action-cards">
<a href="{{ baseUrl }}/schedule/add" class="action-card">
<div class="action-icon">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><path d="M16 2v4"/><path d="M8 2v4"/><line x1="3" y1="10" x2="21" y2="10"/>
</svg>
</div>
<span class="action-label">Create Schedule</span>
</a>
<a href="{{ baseUrl }}/display" class="action-card">
<div class="action-icon">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><path d="M12 17v4"/><path d="M8 21h8"/>
</svg>
</div>
<span class="action-label">Manage Displays</span>
</a>
<a href="{{ baseUrl }}/user/add" class="action-card">
<div class="action-icon">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
</svg>
</div>
<span class="action-label">Add User</span>
</a>
</div>
</section>
</div>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{#
OTS Signage Modern Theme - Displays Page Override
Two-column layout with folder panel on left
Two-column layout with folder panel on left, modern display table
#}
{% extends "authed.twig" %}
@@ -8,22 +8,52 @@
{% block content %}
<div class="ots-theme two-column-layout">
<aside class="left-panel">
<aside class="left-panel displays-sidebar">
<div class="panel-header">
<h3>Folders</h3>
<button class="btn-icon-sm" aria-label="Expand/collapse">
<span>✎</span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="12 3 20 9 12 15 4 9 12 3"/><polyline points="4 15 12 21 20 15"/>
</svg>
</button>
</div>
<div class="folder-tree">
<div class="folder-item active">
<span class="folder-icon">📁</span>
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">All Items</span>
</div>
<div class="folder-item">
<span class="folder-icon">📂</span>
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">Root Folder</span>
</div>
<div class="folder-item">
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">TEMPLATE_DemoHolder</span>
</div>
<div class="folder-item" style="margin-left: 16px;">
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">Hospitality</span>
</div>
<div class="folder-item" style="margin-left: 16px;">
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">Retail</span>
</div>
<div class="folder-item">
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">OTS Signs Internal</span>
</div>
</div>
</aside>
@@ -34,10 +64,15 @@
</div>
<div class="content-toolbar">
<input type="search" placeholder="Search displays…" class="form-control search-field" />
<div class="search-wrapper">
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
</svg>
<input type="search" placeholder="Search displays…" class="form-control search-field" />
</div>
<div class="toolbar-actions">
<button class="btn btn-outline">Columns</button>
<a href="{{ baseUrl }}/display/add" class="btn btn-primary">Add Display</a>
<button class="btn btn-outline btn-sm">Columns</button>
<a href="{{ baseUrl }}/display/add" class="btn btn-primary btn-sm">Add Display</a>
</div>
</div>
@@ -60,22 +95,26 @@
<table class="table table-striped">
<thead>
<tr>
<th>Display</th>
<th>Status</th>
<th>Folder</th>
<th>Group</th>
<th>Last Check-in</th>
<th>Actions</th>
<th style="width: 25%;">Display</th>
<th style="width: 15%;">Status</th>
<th style="width: 20%;">Folder</th>
<th style="width: 15%;">Group</th>
<th style="width: 15%;">Last Check-in</th>
<th style="width: 10%;">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Test1</td>
<td><strong>Test1</strong></td>
<td><span class="badge badge-success">Online</span></td>
<td>TEMPLATE_DemoHolder</td>
<td>Test Screens</td>
<td>-</td>
<td>just now</td>
<td><button class="btn-icon-sm" aria-label="Actions">⋮</button></td>
<td><span class="text-xs">just now</span></td>
<td>
<button class="btn-icon-sm" aria-label="Actions">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>
</button>
</td>
</tr>
</tbody>
</table>

View File

@@ -8,28 +8,38 @@
{% block content %}
<div class="ots-theme two-column-layout">
<aside class="left-panel">
<aside class="left-panel media-sidebar">
<div class="panel-header">
<h3>Folders</h3>
<button class="btn-icon-sm" aria-label="New folder">
<span>+</span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
</button>
</div>
<div class="folder-tree">
<div class="folder-item active">
<span class="folder-icon">📁</span>
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">All Files</span>
</div>
<div class="folder-item">
<span class="folder-icon">📂</span>
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<span class="folder-name">Root Folder</span>
</div>
<div class="folder-item">
<span class="folder-icon">🖼</span>
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
</svg>
<span class="folder-name">Images</span>
</div>
<div class="folder-item">
<span class="folder-icon">🎬</span>
<svg class="folder-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
</svg>
<span class="folder-name">Videos</span>
</div>
</div>
@@ -38,34 +48,80 @@
<main class="content-panel">
<div class="page-header">
<h1>Media Library</h1>
<p class="text-muted">Upload and manage media files for your displays</p>
<p class="text-muted">Upload and manage your images and videos for digital signage</p>
</div>
<div class="content-toolbar">
<input type="search" placeholder="Search media…" class="form-control search-field" />
<div class="search-wrapper">
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
</svg>
<input type="search" placeholder="Search media…" class="form-control search-field" />
</div>
<div class="toolbar-actions">
<button class="btn btn-outline">Upload</button>
<a href="{{ baseUrl }}/library/add" class="btn btn-primary">Add Media</a>
<button class="btn btn-outline btn-sm">All Media</button>
<a href="{{ baseUrl }}/library/add" class="btn btn-primary btn-sm">Upload Media</a>
</div>
</div>
<div class="stat-row">
<div class="stat-box">
<div class="stat-label">Files</div>
<div class="stat-value">0</div>
<div class="stat-label">Total Files</div>
<div class="stat-value">4</div>
</div>
<div class="stat-box">
<div class="stat-label">Storage Used</div>
<div class="stat-value">0 MB</div>
<div class="stat-value">12.3 MB</div>
</div>
<div class="stat-box">
<div class="stat-label">Storage Limit</div>
<div class="stat-value">5 GB</div>
</div>
</div>
<div class="media-grid">
<div class="empty-state">
<div class="empty-icon">🎞</div>
<h3>No media files</h3>
<p>Upload images, videos, and documents to get started.</p>
<a href="{{ baseUrl }}/library/add" class="btn btn-primary">Upload Media</a>
<div class="media-item">
<div class="media-thumbnail">
<img src="https://images.unsplash.com/photo-1444080748397-f442aa95c3e5?w=400&h=300&fit=crop" alt="Galaxy space" />
<span class="media-type-badge">Image</span>
</div>
<div class="media-info">
<p class="media-name">2000x1158</p>
<p class="media-size text-xs text-muted">3.3 MB • 1920x1112</p>
</div>
</div>
<div class="media-item">
<div class="media-thumbnail">
<img src="https://images.unsplash.com/photo-1478098711619-69891b0ec21a?w=400&h=300&fit=crop" alt="Cat portrait" />
<span class="media-type-badge">Image</span>
</div>
<div class="media-info">
<p class="media-name">Images.jpg</p>
<p class="media-size text-xs text-muted">5.2 KB • 194x260</p>
</div>
</div>
<div class="media-item">
<div class="media-thumbnail">
<img src="https://images.unsplash.com/photo-1577720643272-265b434c829c?w=400&h=300&fit=crop" alt="OTS Logo" />
<span class="media-type-badge">Image</span>
</div>
<div class="media-info">
<p class="media-name">OTS Logo</p>
<p class="media-size text-xs text-muted">2.9 KB • 360x350</p>
</div>
</div>
<div class="media-item">
<div class="media-thumbnail">
<img src="https://images.unsplash.com/photo-1590080876-8b7f22b5d5fa?w=400&h=300&fit=crop" alt="Sunrise Hotel" />
<span class="media-type-badge">Image</span>
</div>
<div class="media-info">
<p class="media-name">suncrest hotel l...</p>
<p class="media-size text-xs text-muted">4.1 KB • 5824x3401</p>
</div>
</div>
</div>
</main>