fix app layouts: remove style tags from content fragments, use component classes

This commit is contained in:
2026-01-20 17:40:38 +01:00
parent 4b47611902
commit 82bc0c78fe
25 changed files with 5350 additions and 537 deletions

View File

@@ -0,0 +1,388 @@
-- music.lua - Music player functionality
-- Handles playback, playlists, library, and now playing
local music_doc = nil
-- Player state
local player_state = {
is_playing = false,
is_shuffled = false,
repeat_mode = "off", -- off, all, one
current_time = 0,
duration = 234, -- 3:54
volume = 80
}
-- Current track
local current_track = {
id = "1",
title = "Midnight City",
artist = "M83",
album = "Hurry Up, We're Dreaming",
duration = 234,
art_color = "#667eea"
}
-- Playlists
local playlists = {
{id = "liked", name = "Liked Songs", count = 127, color = "#dc2626"},
{id = "daily1", name = "Daily Mix 1", count = 50, color = "#667eea"},
{id = "release", name = "Release Radar", count = 30, color = "#16a34a"},
{id = "chill", name = "Chill Vibes", count = 45, color = "#f093fb"},
{id = "workout", name = "Workout Mix", count = 35, color = "#2563eb"},
{id = "focus", name = "Focus Flow", count = 40, color = "#4facfe"}
}
-- Recently played
local recently_played = {
{id = "pop", name = "Pop Hits", type = "Playlist", color = "#43e97b"},
{id = "electronic", name = "Electronic", type = "Playlist", color = "#fa709a"},
{id = "jazz", name = "Jazz Classics", type = "Playlist", color = "#667eea"},
{id = "rock", name = "Rock Legends", type = "Playlist", color = "#f093fb"}
}
-- Song queue
local queue = {
{id = "1", title = "Midnight City", artist = "M83", duration = 234},
{id = "2", title = "Intro", artist = "The xx", duration = 128},
{id = "3", title = "Retrograde", artist = "James Blake", duration = 233},
{id = "4", title = "Tame Impala", artist = "The Less I Know The Better", duration = 218},
{id = "5", title = "Redbone", artist = "Childish Gambino", duration = 327}
}
local current_queue_index = 1
local timer_id = nil
-- Initialize music app
function initMusic(doc)
print("[Music] Initializing...")
music_doc = doc
updateNowPlaying()
updateMiniPlayer()
renderPlaylists()
renderRecentlyPlayed()
end
-- Format time (seconds to mm:ss)
local function formatTime(seconds)
local mins = math.floor(seconds / 60)
local secs = seconds % 60
return string.format("%d:%02d", mins, secs)
end
-- Update now playing display
function updateNowPlaying()
if not music_doc then return end
local title = music_doc:GetElementById("now-playing-title")
local artist = music_doc:GetElementById("now-playing-artist")
if title then
title.inner_rml = current_track.title
end
if artist then
artist.inner_rml = current_track.artist
end
end
-- Update mini player
function updateMiniPlayer()
if not music_doc then return end
local title = music_doc:GetElementById("mini-player-title")
local artist = music_doc:GetElementById("mini-player-artist")
local art = music_doc:GetElementById("mini-player-art")
local play_btn = music_doc:GetElementById("mini-play-btn")
if title then
title.inner_rml = current_track.title
end
if artist then
artist.inner_rml = current_track.artist
end
if art then
art.style["background-color"] = current_track.art_color
art.inner_rml = current_track.title:sub(1,1):upper()
end
if play_btn then
local icon = player_state.is_playing and "pause.tga" or "play.tga"
play_btn.inner_rml = [[<img src="../../icons/]] .. icon .. [[" style="width: 28px; height: 28px;"/>]]
end
end
-- Toggle play/pause
function togglePlay()
player_state.is_playing = not player_state.is_playing
print("[Music] " .. (player_state.is_playing and "Playing" or "Paused"))
if player_state.is_playing then
startPlaybackTimer()
else
stopPlaybackTimer()
end
updateMiniPlayer()
updatePlayButton()
end
-- Start playback timer
function startPlaybackTimer()
if setInterval then
timer_id = setInterval(function()
if player_state.is_playing then
player_state.current_time = player_state.current_time + 1
if player_state.current_time >= current_track.duration then
nextTrack()
end
updateProgress()
end
end, 1000)
end
end
-- Stop playback timer
function stopPlaybackTimer()
if timer_id and clearInterval then
clearInterval(timer_id)
timer_id = nil
end
end
-- Update play button
function updatePlayButton()
if not music_doc then return end
local btn = music_doc:GetElementById("play-btn")
if btn then
local icon = player_state.is_playing and "pause.tga" or "play.tga"
btn.inner_rml = [[<img src="../../icons/]] .. icon .. [[" style="width: 48px; height: 48px;"/>]]
end
end
-- Update progress display
function updateProgress()
if not music_doc then return end
local current = music_doc:GetElementById("current-time")
local total = music_doc:GetElementById("total-time")
local progress = music_doc:GetElementById("progress-bar")
if current then
current.inner_rml = formatTime(player_state.current_time)
end
if total then
total.inner_rml = formatTime(current_track.duration)
end
if progress then
local percent = (player_state.current_time / current_track.duration) * 100
progress.style.width = percent .. "%"
end
end
-- Next track
function nextTrack()
print("[Music] Next track")
if player_state.is_shuffled then
current_queue_index = math.random(1, #queue)
else
current_queue_index = current_queue_index + 1
if current_queue_index > #queue then
if player_state.repeat_mode == "all" then
current_queue_index = 1
else
current_queue_index = #queue
player_state.is_playing = false
stopPlaybackTimer()
end
end
end
loadTrack(queue[current_queue_index])
end
-- Previous track
function previousTrack()
print("[Music] Previous track")
if player_state.current_time > 3 then
-- Restart current track
player_state.current_time = 0
else
current_queue_index = current_queue_index - 1
if current_queue_index < 1 then
current_queue_index = 1
end
loadTrack(queue[current_queue_index])
end
updateProgress()
end
-- Load a track
function loadTrack(track)
current_track.id = track.id
current_track.title = track.title
current_track.artist = track.artist
current_track.duration = track.duration
player_state.current_time = 0
updateNowPlaying()
updateMiniPlayer()
updateProgress()
print("[Music] Now playing: " .. track.title .. " - " .. track.artist)
if showToast then
showToast("Now playing: " .. track.title)
end
end
-- Toggle shuffle
function toggleShuffle()
player_state.is_shuffled = not player_state.is_shuffled
print("[Music] Shuffle: " .. tostring(player_state.is_shuffled))
local btn = music_doc:GetElementById("shuffle-btn")
if btn then
if player_state.is_shuffled then
btn:SetClass("active", true)
else
btn:SetClass("active", false)
end
end
if showToast then
showToast(player_state.is_shuffled and "Shuffle on" or "Shuffle off")
end
end
-- Toggle repeat
function toggleRepeat()
if player_state.repeat_mode == "off" then
player_state.repeat_mode = "all"
elseif player_state.repeat_mode == "all" then
player_state.repeat_mode = "one"
else
player_state.repeat_mode = "off"
end
print("[Music] Repeat: " .. player_state.repeat_mode)
local btn = music_doc:GetElementById("repeat-btn")
if btn then
if player_state.repeat_mode ~= "off" then
btn:SetClass("active", true)
else
btn:SetClass("active", false)
end
end
if showToast then
local msg = "Repeat: " .. player_state.repeat_mode
showToast(msg)
end
end
-- Toggle like
function toggleLike()
print("[Music] Toggle like")
if showToast then
showToast("Added to Liked Songs")
end
end
-- Render playlists
function renderPlaylists()
if not music_doc then return end
local container = music_doc:GetElementById("quick-access")
if not container then return end
local html = ""
for i, pl in ipairs(playlists) do
if i <= 6 then
local initial = pl.name:sub(1,1):upper()
html = html .. [[
<div class="quick-card" onclick="openPlaylist(']] .. pl.id .. [[')">
<div class="quick-card-art" style="background-color: ]] .. pl.color .. [[;">]] .. initial .. [[</div>
<span class="quick-card-title">]] .. pl.name .. [[</span>
</div>
]]
end
end
container.inner_rml = html
end
-- Render recently played
function renderRecentlyPlayed()
if not music_doc then return end
local container = music_doc:GetElementById("recent-row")
if not container then return end
local html = ""
for _, item in ipairs(recently_played) do
local initial = item.name:sub(1,1):upper()
html = html .. [[
<div class="recent-item" onclick="openPlaylist(']] .. item.id .. [[')">
<div class="recent-art" style="background-color: ]] .. item.color .. [[;">]] .. initial .. [[</div>
<div class="recent-title">]] .. item.name .. [[</div>
<div class="recent-subtitle">]] .. item.type .. [[</div>
</div>
]]
end
container.inner_rml = html
end
-- Open playlist
function openPlaylist(playlist_id)
print("[Music] Opening playlist: " .. playlist_id)
if navigateTo then
navigateTo("playlist_" .. playlist_id)
else
if showToast then
showToast("Playlist: " .. playlist_id)
end
end
end
-- Open now playing
function openNowPlaying()
print("[Music] Opening now playing...")
if navigateTo then
navigateTo("now_playing")
end
end
-- Open search
function openSearch()
print("[Music] Opening search...")
if navigateTo then
navigateTo("music_search")
else
if showToast then
showToast("Search music")
end
end
end
-- Open library
function openLibrary()
print("[Music] Opening library...")
if navigateTo then
navigateTo("music_library")
else
if showToast then
showToast("Your library")
end
end
end
-- Seek to position (0-1)
function seekTo(position)
player_state.current_time = math.floor(position * current_track.duration)
updateProgress()
end

View File

@@ -6,20 +6,30 @@
<link type="text/rcss" href="../../ui/layout.rcss"/>
<script src="../../scripts/navigation.lua"></script>
<script src="../../scripts/layout.lua"></script>
<script src="music.lua"></script>
<title>Music</title>
<style>
.music-content {
flex: 1;
overflow: auto;
padding-bottom: 140px;
}
.mini-player {
position: absolute;
bottom: 56px;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #282828;
border-top-width: 1px;
border-top-color: #333333;
border-top: 1px solid #333333;
cursor: pointer;
}
.mini-player:hover {
background-color: #333333;
}
.mini-player-art {
@@ -68,6 +78,7 @@
.mini-control-btn img {
width: 28px;
height: 28px;
pointer-events: none;
}
.section-header {
@@ -104,10 +115,12 @@
.recent-item {
min-width: 120px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
}
.recent-item:hover {
opacity: 0.9;
background-color: rgba(255, 255, 255, 0.05);
}
.recent-art {
@@ -214,11 +227,14 @@
}
.music-bottom-nav {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
height: 56px;
background-color: #1E1E1E;
border-top-width: 1px;
border-top-color: #282828;
border-top: 1px solid #282828;
}
.nav-item {
@@ -243,12 +259,29 @@
width: 28px;
height: 28px;
margin-bottom: 4px;
pointer-events: none;
}
.nav-item span {
font-size: 14px;
}
/* Progress bar for mini player */
.progress-container {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background-color: #404040;
}
.progress-bar {
width: 0%;
height: 100%;
background-color: #1DB954;
}
.bg-gradient-1 { background-color: #667eea; }
.bg-gradient-2 { background-color: #f093fb; }
.bg-gradient-3 { background-color: #4facfe; }
@@ -260,7 +293,7 @@
.bg-solid-blue { background-color: #2563eb; }
</style>
</head>
<body class="app-screen" onload="initLayout(document)">
<body class="app-screen" onload="initLayout(document); initMusic(document)">
<!-- System Status Bar -->
<div class="system-status-bar">
<span id="status-time" class="system-status-time">12:30</span>
@@ -278,7 +311,7 @@
</div>
<span class="app-bar-title">Music</span>
<div class="app-bar-actions">
<div class="app-bar-action">
<div class="app-bar-action" onclick="openSearch()">
<img src="../../icons/search.tga"/>
</div>
</div>
@@ -292,28 +325,28 @@
</div>
<!-- Quick Access Grid -->
<div class="quick-access">
<div class="quick-card">
<div class="quick-access" id="quick-access">
<div class="quick-card" onclick="openPlaylist('liked')">
<div class="quick-card-art bg-solid-red">L</div>
<span class="quick-card-title">Liked Songs</span>
</div>
<div class="quick-card">
<div class="quick-card" onclick="openPlaylist('daily1')">
<div class="quick-card-art bg-gradient-1">D</div>
<span class="quick-card-title">Daily Mix 1</span>
</div>
<div class="quick-card">
<div class="quick-card" onclick="openPlaylist('release')">
<div class="quick-card-art bg-solid-green">R</div>
<span class="quick-card-title">Release Radar</span>
</div>
<div class="quick-card">
<div class="quick-card" onclick="openPlaylist('chill')">
<div class="quick-card-art bg-gradient-2">C</div>
<span class="quick-card-title">Chill Vibes</span>
</div>
<div class="quick-card">
<div class="quick-card" onclick="openPlaylist('workout')">
<div class="quick-card-art bg-solid-blue">W</div>
<span class="quick-card-title">Workout Mix</span>
</div>
<div class="quick-card">
<div class="quick-card" onclick="openPlaylist('focus')">
<div class="quick-card-art bg-gradient-3">F</div>
<span class="quick-card-title">Focus Flow</span>
</div>
@@ -325,23 +358,23 @@
<span class="section-action">SEE ALL</span>
</div>
<div class="recent-row">
<div class="recent-item">
<div class="recent-row" id="recent-row">
<div class="recent-item" onclick="openPlaylist('pop')">
<div class="recent-art bg-gradient-4">P</div>
<div class="recent-title">Pop Hits</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item">
<div class="recent-item" onclick="openPlaylist('electronic')">
<div class="recent-art bg-gradient-5">E</div>
<div class="recent-title">Electronic</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item">
<div class="recent-item" onclick="openPlaylist('jazz')">
<div class="recent-art bg-gradient-1">J</div>
<div class="recent-title">Jazz Classics</div>
<div class="recent-subtitle">Playlist</div>
</div>
<div class="recent-item">
<div class="recent-item" onclick="openPlaylist('rock')">
<div class="recent-art bg-gradient-2">R</div>
<div class="recent-title">Rock Legends</div>
<div class="recent-subtitle">Playlist</div>
@@ -354,7 +387,7 @@
<span class="section-action">SEE ALL</span>
</div>
<div class="playlist-item">
<div class="playlist-item" onclick="openPlaylist('daily1')">
<div class="playlist-art bg-gradient-3">1</div>
<div class="playlist-info">
<div class="playlist-title">Daily Mix 1</div>
@@ -362,7 +395,7 @@
</div>
</div>
<div class="playlist-item">
<div class="playlist-item" onclick="openPlaylist('daily2')">
<div class="playlist-art bg-gradient-4">2</div>
<div class="playlist-info">
<div class="playlist-title">Daily Mix 2</div>
@@ -370,7 +403,7 @@
</div>
</div>
<div class="playlist-item">
<div class="playlist-item" onclick="openPlaylist('discover')">
<div class="playlist-art bg-gradient-5">D</div>
<div class="playlist-info">
<div class="playlist-title">Discover Weekly</div>
@@ -380,17 +413,20 @@
</div>
<!-- Mini Player -->
<div class="mini-player">
<div class="mini-player-art">M</div>
<div class="mini-player" onclick="openNowPlaying()">
<div class="progress-container">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div class="mini-player-art" id="mini-player-art">M</div>
<div class="mini-player-info">
<div class="mini-player-title">Midnight City</div>
<div class="mini-player-artist">M83</div>
<div class="mini-player-title" id="mini-player-title">Midnight City</div>
<div class="mini-player-artist" id="mini-player-artist">M83</div>
</div>
<div style="display: flex; gap: 4px;">
<div class="mini-control-btn">
<div class="mini-control-btn" onclick="toggleLike(); event.stopPropagation();">
<img src="../../icons/heart.tga"/>
</div>
<div class="mini-control-btn">
<div class="mini-control-btn" id="mini-play-btn" onclick="togglePlay(); event.stopPropagation();">
<img src="../../icons/play.tga"/>
</div>
</div>
@@ -402,11 +438,11 @@
<img src="../../icons/home.tga"/>
<span>Home</span>
</div>
<div class="nav-item">
<div class="nav-item" onclick="openSearch()">
<img src="../../icons/search.tga"/>
<span>Search</span>
</div>
<div class="nav-item">
<div class="nav-item" onclick="openLibrary()">
<img src="../../icons/library.tga"/>
<span>Library</span>
</div>