diff --git a/CHANGELOG.md b/CHANGELOG.md index 93c3e53..dbf2c28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## 2.2.2 - 2026-08-01 + +### Fixed + +- The theme bootstrap now resolves saved `auto` to an actual dark or light value on first paint, so explicit dark mode no longer flashes white while the page loads. +- The shared app header is now sticky so the top navigation stays visible while scrolling. + ## 2.2.1 - 2026-08-01 ### Fixed diff --git a/docs/schema.md b/docs/schema.md index cd7ec33..58cc6be 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -101,6 +101,11 @@ Primary keys are `id` unless noted otherwise. Timestamps are stored as `created_ - `d_screens` - screen records and playlist/player assignment. - `d_onboarding_devices` - device-to-screen bindings and onboarded client names. +## Announcements + +- `d_announcements` - announcement content and display metadata. +- `d_announcement_screens` - announcement-to-screen assignments. + ### `d_players` - `device_id`, `public_base_url`, `internal_base_url`, `last_seen_at`, `created_at`, `modified_at` @@ -123,6 +128,19 @@ Primary keys are `id` unless noted otherwise. Timestamps are stored as `created_ - Foreign key: - `screen_id` -> `d_screens.id` with `ON DELETE SET NULL` +### `d_announcements` + +- `id`, `message`, `short_label`, `announcement_type`, `color_key`, `icon_key`, `duration_seconds`, `expires_at`, `created_at`, `created_by`, `modified_at`, `modified_by` +- `announcement_type` defaults to `lower-third`. + +### `d_announcement_screens` + +- `announcement_id`, `screen_id`, `created_at`, `created_by`, `modified_at`, `modified_by` +- Foreign keys: + - `announcement_id` -> `d_announcements.id` with `ON DELETE CASCADE` + - `screen_id` -> `d_screens.id` with `ON DELETE CASCADE` +- Composite primary key: `(announcement_id, screen_id)` + ## Onboarding - The onboarding flow uses `d_onboarding_devices` to bind a device to a screen and persist the client name. @@ -166,6 +184,240 @@ Primary keys are `id` unless noted otherwise. Timestamps are stored as `created_ ```mermaid erDiagram + A_USERS { + INT id + VARCHAR name + VARCHAR username + CHAR password_hash + VARCHAR password_salt + INT password_iterations + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + A_ROLES { + INT id + VARCHAR role_key + VARCHAR name + TEXT description + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + A_PERMISSIONS { + INT id + VARCHAR permission_key + VARCHAR name + VARCHAR section_name + TEXT description + TIMESTAMP created_at + TIMESTAMP modified_at + } + A_ROLE_PERMISSIONS { + INT role_id + INT permission_id + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + A_USER_ROLES { + INT user_id + INT role_id + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + A_SESSIONS { + CHAR session_hash + INT user_id + DATETIME expires_at + TIMESTAMP created_at + INT created_by + TIMESTAMP last_used_at + INT modified_by + } + C_CANVAS_SIZES { + INT id + VARCHAR name + INT width + INT height + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + C_PLAYLISTS { + INT id + VARCHAR name + TINYINT fade_between_slides + TINYINT skip_unavailable_rtmp + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + C_TEMPLATES { + INT id + VARCHAR name + INT canvas_size_id + VARCHAR background_image_path + VARCHAR background_color + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + C_TEMPLATE_REGIONS { + INT id + INT template_id + VARCHAR region_key + VARCHAR region_type + VARCHAR label + VARCHAR font_family + VARCHAR lock_ratio + INT x + INT y + INT width + INT height + INT z_index + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + C_SLIDES { + INT id + VARCHAR title + INT template_id + JSON content_json + VARCHAR thumbnail_path + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + C_PLAYLIST_SLIDES { + INT id + INT playlist_id + INT slide_id + INT position + DECIMAL duration_seconds + TINYINT use_video_duration + VARCHAR schedule_mode + DATETIME schedule_start_datetime + DATETIME schedule_end_datetime + TIME schedule_start_time + TIME schedule_end_time + JSON schedule_days_json + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + D_PLAYERS { + INT device_id + VARCHAR public_base_url + VARCHAR internal_base_url + TIMESTAMP last_seen_at + TIMESTAMP created_at + TIMESTAMP modified_at + } + D_SCREENS { + INT id + VARCHAR name + VARCHAR slug + INT playlist_id + INT player_id + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + D_ONBOARDING_DEVICES { + VARCHAR device_id + VARCHAR client_name + INT screen_id + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + D_ANNOUNCEMENTS { + INT id + TEXT message + VARCHAR short_label + VARCHAR announcement_type + VARCHAR color_key + VARCHAR icon_key + INT duration_seconds + TIMESTAMP expires_at + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + D_ANNOUNCEMENT_SCREENS { + INT announcement_id + INT screen_id + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + I_RSS_FEEDS { + INT id + VARCHAR name + VARCHAR feed_url + INT update_interval_value + VARCHAR update_interval_unit + INT item_limit + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + I_RSS_FEED_ITEMS { + INT id + INT rss_feed_id + INT position + MEDIUMTEXT item_json + TIMESTAMP created_at + INT created_by + TIMESTAMP modified_at + INT modified_by + } + I_API_SOURCES { + INT id + VARCHAR name + VARCHAR api_url + INT update_interval_value + VARCHAR update_interval_unit + TIMESTAMP last_pulled_at + MEDIUMTEXT last_pull_error + INT last_response_status + VARCHAR last_response_content_type + MEDIUMTEXT last_response_json + } + O_BACKGROUND_TASKS { + BIGINT id + VARCHAR task_key + VARCHAR task_type + VARCHAR title + VARCHAR category + VARCHAR status + MEDIUMTEXT payload_json + MEDIUMTEXT metadata_json + INT attempts + TIMESTAMP created_at + TIMESTAMP started_at + TIMESTAMP finished_at + MEDIUMTEXT error_message + } + A_USERS ||--o{ A_USER_ROLES : has A_ROLES ||--o{ A_USER_ROLES : assigned_to A_ROLES ||--o{ A_ROLE_PERMISSIONS : has @@ -181,9 +433,8 @@ erDiagram D_PLAYERS ||--o{ D_SCREENS : assigned_to C_PLAYLISTS ||--o{ D_SCREENS : uses D_SCREENS ||--o{ D_ONBOARDING_DEVICES : binds + D_ANNOUNCEMENTS ||--o{ D_ANNOUNCEMENT_SCREENS : targets + D_SCREENS ||--o{ D_ANNOUNCEMENT_SCREENS : receives I_RSS_FEEDS ||--o{ I_RSS_FEED_ITEMS : caches - O_BACKGROUND_TASKS { - BIGINT id - } ``` \ No newline at end of file diff --git a/package.json b/package.json index 683b972..50df656 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse-signage", - "version": "2.2.1", + "version": "2.2.2", "private": false, "description": "Pulse Signage application with MySQL and media storage", "repository": { diff --git a/src/web/public/css/theme-custom.css b/src/web/public/css/theme-custom.css index f114386..8ad0e36 100644 --- a/src/web/public/css/theme-custom.css +++ b/src/web/public/css/theme-custom.css @@ -123,6 +123,11 @@ --bs-navbar-nav-link-padding-x: 10px; } +.app-header { + top: 0; + z-index: 1030; +} + .app-sidebar { display: flex; flex-direction: column; diff --git a/src/web/public/js/theme-init.js b/src/web/public/js/theme-init.js index 371fe67..35e8e53 100644 --- a/src/web/public/js/theme-init.js +++ b/src/web/public/js/theme-init.js @@ -2,7 +2,7 @@ (function () { var storageKey = 'lte-theme'; - var theme = 'auto'; + var theme = 'dark'; function getPreferredTheme() { if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { @@ -15,10 +15,10 @@ try { var storedTheme = window.localStorage.getItem(storageKey); if (storedTheme === 'dark' || storedTheme === 'light' || storedTheme === 'auto') { - theme = storedTheme; + theme = storedTheme === 'auto' ? getPreferredTheme() : storedTheme; } } catch (error) { - theme = 'auto'; + theme = 'dark'; } document.documentElement.dataset.bsTheme = theme; diff --git a/src/web/views/shared/frame-layout.hbs b/src/web/views/shared/frame-layout.hbs index 0e9997d..45c3cf6 100644 --- a/src/web/views/shared/frame-layout.hbs +++ b/src/web/views/shared/frame-layout.hbs @@ -22,7 +22,7 @@