Added logs. MPRIS, UI changes
This commit is contained in:
+5
-1
@@ -180,11 +180,15 @@ pub fn spawn_sso_exchange(form: &mut LoginForm, runtime: &Runtime, code: String)
|
||||
fn login_event(result: Result<auth::AuthSession, client::ApiError>) -> AppEvent {
|
||||
match result {
|
||||
Ok(session) => {
|
||||
tracing::info!(user = %session.user.name, server = %session.server_base_url, "signed in");
|
||||
if let Err(err) = auth::save_session(&session) {
|
||||
tracing::warn!(%err, "failed to persist credentials");
|
||||
}
|
||||
AppEvent::LoginSucceeded(Box::new(session))
|
||||
}
|
||||
Err(err) => AppEvent::LoginFailed(err.to_string()),
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, "login failed");
|
||||
AppEvent::LoginFailed(err.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -702,6 +702,7 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
|
||||
global.artists.extend(page.items);
|
||||
}
|
||||
AppEvent::ArtistsLoaded(Err(message)) => {
|
||||
tracing::warn!(%message, "artists page load failed");
|
||||
state.global.loading = false;
|
||||
state.global.error = Some(message.clone());
|
||||
state.status_message = Some(message);
|
||||
@@ -709,14 +710,20 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
|
||||
AppEvent::ArtistViewLoaded { id, result } => {
|
||||
let entry = match result {
|
||||
Ok(detail) => state::Loadable::Ready(detail),
|
||||
Err(message) => state::Loadable::Failed(message),
|
||||
Err(message) => {
|
||||
tracing::warn!(artist = id, %message, "artist view load failed");
|
||||
state::Loadable::Failed(message)
|
||||
}
|
||||
};
|
||||
state.artist_views.insert(id, entry);
|
||||
}
|
||||
AppEvent::ReleaseViewLoaded { id, result } => {
|
||||
let entry = match result {
|
||||
Ok(detail) => state::Loadable::Ready(detail),
|
||||
Err(message) => state::Loadable::Failed(message),
|
||||
Err(message) => {
|
||||
tracing::warn!(release = id, %message, "release view load failed");
|
||||
state::Loadable::Failed(message)
|
||||
}
|
||||
};
|
||||
state.release_views.insert(id, entry);
|
||||
}
|
||||
@@ -770,6 +777,7 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
|
||||
push_state_now(state, runtime);
|
||||
}
|
||||
AppEvent::Player(player::PlayerEvent::Failed(message)) => {
|
||||
tracing::error!(%message, "playback failed");
|
||||
state.player.playing = false;
|
||||
state.player.paused = false;
|
||||
state.status_message = Some(message);
|
||||
|
||||
+41
-1
@@ -167,6 +167,36 @@ pub struct PlaylistsTab {
|
||||
pub opened: Option<OpenedPlaylist>,
|
||||
}
|
||||
|
||||
/// Severity steps for the Logs tab filter, cycled with the view-toggle key.
|
||||
pub const LOG_LEVELS: [tracing::Level; 5] = [
|
||||
tracing::Level::ERROR,
|
||||
tracing::Level::WARN,
|
||||
tracing::Level::INFO,
|
||||
tracing::Level::DEBUG,
|
||||
tracing::Level::TRACE,
|
||||
];
|
||||
|
||||
/// The Logs tab: a live view over the in-memory ring buffer.
|
||||
#[derive(Debug)]
|
||||
pub struct LogsTab {
|
||||
/// Index into LOG_LEVELS; entries more verbose than this are hidden.
|
||||
pub level_index: usize,
|
||||
/// Stick to the newest entries as they arrive.
|
||||
pub follow: bool,
|
||||
/// When not following: how many (filtered) entries back from the end.
|
||||
pub scroll_from_end: usize,
|
||||
}
|
||||
|
||||
impl Default for LogsTab {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
level_index: 2,
|
||||
follow: true,
|
||||
scroll_from_end: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Command line (`:`), vim-style. Lives on the Main screen status bar.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Cmdline {
|
||||
@@ -271,10 +301,17 @@ pub enum Tab {
|
||||
Playlists,
|
||||
Queue,
|
||||
Devices,
|
||||
Logs,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub const ALL: [Tab; 4] = [Tab::Global, Tab::Playlists, Tab::Queue, Tab::Devices];
|
||||
pub const ALL: [Tab; 5] = [
|
||||
Tab::Global,
|
||||
Tab::Playlists,
|
||||
Tab::Queue,
|
||||
Tab::Devices,
|
||||
Tab::Logs,
|
||||
];
|
||||
|
||||
pub fn title(self) -> &'static str {
|
||||
match self {
|
||||
@@ -282,6 +319,7 @@ impl Tab {
|
||||
Tab::Playlists => "Playlists",
|
||||
Tab::Queue => "Queue",
|
||||
Tab::Devices => "Devices",
|
||||
Tab::Logs => "Logs",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +345,7 @@ impl Tab {
|
||||
Tab::Playlists => KeyContext::Playlists,
|
||||
Tab::Queue => KeyContext::Queue,
|
||||
Tab::Devices => KeyContext::Devices,
|
||||
Tab::Logs => KeyContext::Logs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,6 +438,7 @@ pub struct AppState {
|
||||
/// Liked track ids, for the ♥ markers everywhere tracks are shown.
|
||||
pub likes: std::collections::HashSet<i64>,
|
||||
pub likes_loaded: bool,
|
||||
pub logs: LogsTab,
|
||||
pub cmdline: Cmdline,
|
||||
pub search: SearchState,
|
||||
/// Shared image cache keyed by `art::cache_key(url, w, h)`; reused by
|
||||
|
||||
+43
-4
@@ -142,8 +142,16 @@ pub fn update(state: &mut AppState, action: Action) -> Option<Effect> {
|
||||
None
|
||||
}
|
||||
Action::ToggleViewMode => {
|
||||
if state.active_tab == Tab::Global {
|
||||
state.global.view = state.global.view.toggle();
|
||||
match state.active_tab {
|
||||
Tab::Global => state.global.view = state.global.view.toggle(),
|
||||
// On the Logs tab the same key cycles the severity filter.
|
||||
Tab::Logs => {
|
||||
state.logs.level_index =
|
||||
(state.logs.level_index + 1) % super::state::LOG_LEVELS.len();
|
||||
state.logs.scroll_from_end = 0;
|
||||
state.logs.follow = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -204,7 +212,7 @@ pub fn selected_track(state: &AppState) -> Option<TrackItem> {
|
||||
.queue
|
||||
.get(state.player.queue_pos)
|
||||
.cloned(),
|
||||
Tab::Devices => None,
|
||||
Tab::Devices | Tab::Logs => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +405,9 @@ fn viewport_lines() -> isize {
|
||||
/// lines.
|
||||
fn page_step(state: &AppState) -> isize {
|
||||
let lines = viewport_lines();
|
||||
if state.active_tab != Tab::Global {
|
||||
return lines;
|
||||
}
|
||||
let tile_rows = (lines / TILE_HEIGHT as isize).max(1);
|
||||
match state.global.stack.last() {
|
||||
None => match state.global.view {
|
||||
@@ -419,6 +430,20 @@ fn page_step(state: &AppState) -> isize {
|
||||
}
|
||||
|
||||
fn move_selection(state: &mut AppState, dx: isize, dy: isize) {
|
||||
if state.active_tab == Tab::Logs {
|
||||
let total = crate::config::logging::buffer().map_or(0, |b| b.len());
|
||||
let logs = &mut state.logs;
|
||||
if dy < 0 {
|
||||
logs.follow = false;
|
||||
logs.scroll_from_end = (logs.scroll_from_end + dy.unsigned_abs()).min(total);
|
||||
} else if dy > 0 {
|
||||
logs.scroll_from_end = logs.scroll_from_end.saturating_sub(dy as usize);
|
||||
if logs.scroll_from_end == 0 {
|
||||
logs.follow = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if state.active_tab == Tab::Playlists {
|
||||
let len = playlists_view_len(state);
|
||||
if len == 0 {
|
||||
@@ -565,6 +590,16 @@ fn current_view_len(state: &AppState) -> usize {
|
||||
}
|
||||
|
||||
fn jump_selection(state: &mut AppState, first: bool) {
|
||||
if state.active_tab == Tab::Logs {
|
||||
if first {
|
||||
state.logs.follow = false;
|
||||
state.logs.scroll_from_end = crate::config::logging::buffer().map_or(0, |b| b.len());
|
||||
} else {
|
||||
state.logs.follow = true;
|
||||
state.logs.scroll_from_end = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if state.active_tab != Tab::Global && state.active_tab != Tab::Playlists {
|
||||
return not_yet(state, "Navigation in this view");
|
||||
}
|
||||
@@ -737,6 +772,10 @@ fn reset_tab(state: &mut AppState, tab: Tab) {
|
||||
state.global.stack.clear();
|
||||
}
|
||||
Tab::Playlists => state.playlists.opened = None,
|
||||
Tab::Logs => {
|
||||
state.logs.follow = true;
|
||||
state.logs.scroll_from_end = 0;
|
||||
}
|
||||
Tab::Queue | Tab::Devices => {}
|
||||
}
|
||||
}
|
||||
@@ -797,7 +836,7 @@ mod tests {
|
||||
fn tab_cycling_wraps() {
|
||||
let mut state = AppState::default();
|
||||
update(&mut state, Action::PrevTab);
|
||||
assert_eq!(state.active_tab, Tab::Devices);
|
||||
assert_eq!(state.active_tab, Tab::Logs);
|
||||
update(&mut state, Action::NextTab);
|
||||
assert_eq!(state.active_tab, Tab::Global);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user