mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 10:04:12 +02:00
Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d03092aa16 | |||
| 6d3b67e823 |
13 changed files with 198 additions and 81 deletions
41
Cargo.lock
generated
41
Cargo.lock
generated
|
|
@ -1027,6 +1027,7 @@ dependencies = [
|
|||
"main_error",
|
||||
"matchit",
|
||||
"osc94",
|
||||
"rar-stream",
|
||||
"ratatui",
|
||||
"rayon",
|
||||
"regex",
|
||||
|
|
@ -1039,7 +1040,6 @@ dependencies = [
|
|||
"tikv-jemallocator",
|
||||
"time",
|
||||
"tinystr",
|
||||
"unrar",
|
||||
"xz2",
|
||||
"zip",
|
||||
]
|
||||
|
|
@ -1438,6 +1438,16 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rar-stream"
|
||||
version = "5.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bad41fc4447843b83b5e858b778dedcbbaf116e2bd3d7e21ab19ed096a942ef"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.30.0"
|
||||
|
|
@ -2113,29 +2123,6 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
|
||||
|
||||
[[package]]
|
||||
name = "unrar"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ec61343a630d2b50d13216dea5125e157d3fc180a7d3f447d22fe146b648fc"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"regex",
|
||||
"unrar_sys",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unrar_sys"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b77675b883cfbe6bf41e6b7a5cd6008e0a83ba497de3d96e41a064bbeead765"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
|
|
@ -2360,12 +2347,6 @@ dependencies = [
|
|||
"wezterm-dynamic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ xz2 = "0.1.7"
|
|||
bzip2-rs = "0.1.2"
|
||||
ruzstd = "0.8.2"
|
||||
sevenz-rust2 = "0.20.2"
|
||||
rar-stream = "5.7.1"
|
||||
dialoguer = "0.12.0"
|
||||
indicatif = { version = "0.18.3", features = ["rayon"] }
|
||||
csv = "1.4.0"
|
||||
|
|
@ -39,8 +40,6 @@ matchit = "0.9.0"
|
|||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
tikv-jemallocator = "0.6.1"
|
||||
|
||||
[target.'cfg(all(unix, not(target_env = "musl")))'.dependencies]
|
||||
unrar = "0.5.8"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub enum LogLevel {
|
|||
Info = 1,
|
||||
Warn = 2,
|
||||
Error = 3,
|
||||
Exception,
|
||||
Exception = 4,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
|
|
|||
29
src/app.rs
29
src/app.rs
|
|
@ -2,6 +2,7 @@ use crate::grouping::{GroupingResult, UniqueGrouping};
|
|||
use crate::logfile::{LogFile, LogLine, LogLineNumber};
|
||||
use crate::logs::ParsedLogs;
|
||||
use crate::timegraph::{SparkLine, TimeGraph};
|
||||
use logsmash_data::LogLevel;
|
||||
use regex::{escape, Regex, RegexBuilder};
|
||||
use serde_json::Error as JsonError;
|
||||
use std::cell::OnceCell;
|
||||
|
|
@ -83,10 +84,10 @@ impl<'logs> LineSet<'logs> {
|
|||
}
|
||||
|
||||
pub fn matches(&self, filter: &Filter) -> bool {
|
||||
let line = self.lines[0];
|
||||
if filter.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let line = self.lines[0];
|
||||
if line.request_id == filter.filter {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -98,15 +99,23 @@ impl<'logs> LineSet<'logs> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct Filter {
|
||||
filter: String,
|
||||
regexes: Vec<Regex>,
|
||||
pub levels: [bool; 6],
|
||||
}
|
||||
|
||||
impl Default for Filter {
|
||||
fn default() -> Self {
|
||||
EMPTY_FILTER.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub static EMPTY_FILTER: Filter = Filter {
|
||||
filter: String::new(),
|
||||
regexes: Vec::new(),
|
||||
levels: [true; 6],
|
||||
};
|
||||
|
||||
impl Filter {
|
||||
|
|
@ -122,18 +131,24 @@ impl Filter {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new(filter: String) -> Self {
|
||||
let regexes = Self::build_regex(&filter);
|
||||
Filter { filter, regexes }
|
||||
pub fn level(&self, level: LogLevel) -> bool {
|
||||
self.levels[level as i64 as usize]
|
||||
}
|
||||
|
||||
pub fn toggle_level(&mut self, level: LogLevel) {
|
||||
self.levels[level as i64 as usize] = !self.levels[level as i64 as usize];
|
||||
}
|
||||
|
||||
pub fn parts(&self) -> impl Iterator<Item = &Regex> {
|
||||
self.regexes.iter()
|
||||
}
|
||||
|
||||
pub fn all_levels(&self) -> bool {
|
||||
self.levels.iter().all(|level| *level)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.filter.is_empty()
|
||||
self.filter.is_empty() && self.levels.iter().all(|level| *level)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, c: char) {
|
||||
|
|
|
|||
|
|
@ -27,9 +27,8 @@ pub enum ReadError {
|
|||
Zstd(#[from] FrameDecoderError),
|
||||
#[error(transparent)]
|
||||
SevenZip(#[from] sevenz_rust2::Error),
|
||||
#[cfg(all(unix, not(target_env = "musl")))]
|
||||
#[error(transparent)]
|
||||
Rar(#[from] unrar::error::UnrarError),
|
||||
Rar(#[from] rar_stream::error::RarError),
|
||||
#[error("archive contains no files")]
|
||||
NoFiles,
|
||||
#[error("log file contained non-utf8 characters: {0:#}")]
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ impl<'logs> LogGrouping<'logs> {
|
|||
if filter.is_empty() {
|
||||
return true;
|
||||
}
|
||||
if !filter.all_levels() && !self.lines.lines.iter().any(|line| filter.level(line.level)) {
|
||||
return false;
|
||||
}
|
||||
match &self.result {
|
||||
Some(result) => result.matches(filter),
|
||||
_ => true,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ impl<'a> GroupingResult<'a> for UniqueGrouping<'a> {
|
|||
if filter.is_empty() {
|
||||
return true;
|
||||
}
|
||||
if !filter.level(self.line.level) {
|
||||
return false;
|
||||
}
|
||||
filter.parts().all(|filter_part| {
|
||||
if let Some(ex) = self.line.exception.as_ref() {
|
||||
if filter_part.is_match(&ex.message) || filter_part.is_match(&ex.exception) {
|
||||
|
|
|
|||
|
|
@ -1,51 +1,50 @@
|
|||
use crate::error::ReadError;
|
||||
use crate::logfile::archive::{Archive, ArchiveEntry};
|
||||
use rar_stream::MemoryArchive;
|
||||
use std::borrow::Cow;
|
||||
use std::fs::read;
|
||||
|
||||
pub struct RarArchive {
|
||||
path: String,
|
||||
archive: MemoryArchive,
|
||||
}
|
||||
|
||||
impl RarArchive {
|
||||
pub fn new(path: &str) -> Result<Self, ReadError> {
|
||||
Ok(RarArchive { path: path.into() })
|
||||
let data = read(path)?;
|
||||
Ok(RarArchive {
|
||||
archive: MemoryArchive::from_vec(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RarEntry {
|
||||
archive: String,
|
||||
pub struct RarEntry<'a> {
|
||||
archive: &'a MemoryArchive,
|
||||
index: usize,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl ArchiveEntry for RarEntry {
|
||||
impl ArchiveEntry for RarEntry<'_> {
|
||||
fn name<'a>(&'a self) -> Cow<'a, str> {
|
||||
self.name.as_str().into()
|
||||
}
|
||||
|
||||
fn extract(self) -> Result<Vec<u8>, ReadError> {
|
||||
let mut archive = unrar::Archive::new(&self.archive).open_for_processing()?;
|
||||
while let Some(header) = archive.read_header()? {
|
||||
if header.entry().filename.to_string_lossy() == self.name {
|
||||
return Ok(header.read()?.0);
|
||||
}
|
||||
archive = header.skip()?;
|
||||
}
|
||||
Err(ReadError::NoFiles)
|
||||
Ok(self.archive.extract(self.index)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Archive for RarArchive {
|
||||
type Entry<'a> = RarEntry;
|
||||
type Entry<'a> = RarEntry<'a>;
|
||||
|
||||
fn entries(&mut self) -> impl Iterator<Item = Self::Entry<'_>> {
|
||||
unrar::Archive::new(&self.path)
|
||||
.open_for_listing()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.map(|header| RarEntry {
|
||||
archive: self.path.clone(),
|
||||
name: header.filename.to_string_lossy().into(),
|
||||
self.archive
|
||||
.entries_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, entry)| !entry.is_directory)
|
||||
.map(|(index, entry)| RarEntry {
|
||||
archive: &self.archive,
|
||||
index,
|
||||
name: entry.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,13 +55,13 @@ pub fn footer<'a>(app: &App, params: FooterParams<'a>) -> Table<'a> {
|
|||
|
||||
fn help(page: UiPage) -> &'static str {
|
||||
match page {
|
||||
UiPage::GroupList => "«Q» Exit - «Enter» Select - «F» Filter - «E» Show parse errors",
|
||||
UiPage::Group => "«Q» Exit - «Enter» Select - «F» Filter - «G» Group By - «Esc» Back",
|
||||
UiPage::GroupList => "«Q» Exit - «Enter» Select - «F» Filter - «L» Toggle Level - «E» Show parse errors",
|
||||
UiPage::Group => "«Q» Exit - «Enter» Select - «F» Filter - «L» Toggle Level - «G» Group By - «Esc» Back",
|
||||
UiPage::DistinctLogs => {
|
||||
"«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines - «G» Group By - «R» Show logs for request"
|
||||
"«Q» Exit - «F» Filter - «L» Toggle Level - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines - «G» Group By - «R» Show logs for request"
|
||||
}
|
||||
UiPage::ByRequest => {
|
||||
"«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines"
|
||||
"«Q» Exit - «F» Filter - «L» Toggle Level - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines"
|
||||
}
|
||||
UiPage::Log => {
|
||||
"«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line - «R» Show logs for request"
|
||||
|
|
|
|||
|
|
@ -19,11 +19,12 @@ pub enum UiEvent {
|
|||
Copy,
|
||||
CopyAll,
|
||||
EnterFilterMode,
|
||||
ClearFilter,
|
||||
Text(char),
|
||||
PopText(PopMode),
|
||||
ByRequest,
|
||||
GroupBy,
|
||||
ToggleLevelFilterMode,
|
||||
Toggle,
|
||||
}
|
||||
|
||||
pub enum PopMode {
|
||||
|
|
@ -61,7 +62,7 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEv
|
|||
Some(UiEvent::EnterFilterMode)
|
||||
}
|
||||
|
||||
(Mode::FilterInput, KeyCode::Esc) => Some(UiEvent::ClearFilter),
|
||||
(Mode::FilterInput, KeyCode::Esc) => Some(UiEvent::Back),
|
||||
(Mode::FilterInput, KeyCode::F(4)) => Some(UiEvent::Back),
|
||||
(Mode::FilterInput, KeyCode::Backspace) => {
|
||||
Some(UiEvent::PopText(PopMode::Character))
|
||||
|
|
@ -72,6 +73,9 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEv
|
|||
Some(UiEvent::PopText(PopMode::Word))
|
||||
}
|
||||
(Mode::FilterInput, KeyCode::Char(c)) => Some(UiEvent::Text(c)),
|
||||
|
||||
(_, KeyCode::Char(' ')) => Some(UiEvent::Toggle),
|
||||
(_, KeyCode::Char('l')) => Some(UiEvent::ToggleLevelFilterMode),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use ratatui::buffer::Buffer;
|
|||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::StatefulWidget;
|
||||
use ratatui::widgets::{Block, HighlightSpacing, List, ListItem, ListState};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub struct SelectList<'a> {
|
||||
list: List<'a>,
|
||||
|
|
@ -37,6 +38,51 @@ impl StatefulWidget for SelectList<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ToggleList<'a> {
|
||||
list: List<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ToggleList<'a> {
|
||||
pub fn new<T, S>(items: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = (S, bool)>,
|
||||
S: Display,
|
||||
{
|
||||
fn symbol(state: bool) -> &'static str {
|
||||
if state {
|
||||
"☑"
|
||||
} else {
|
||||
"☐"
|
||||
}
|
||||
}
|
||||
|
||||
ToggleList {
|
||||
list: List::new(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|(text, state)| format!("{} {text}", symbol(state))),
|
||||
)
|
||||
.highlight_style(TABLE_SELECTED_STYLE)
|
||||
.highlight_spacing(HighlightSpacing::Always),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn block(self, block: Block<'a>) -> Self {
|
||||
ToggleList {
|
||||
list: self.list.block(block),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidget for ToggleList<'_> {
|
||||
type State = SizedListState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
StatefulWidget::render(self.list, area, buf, &mut state.list_state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct SizedListState {
|
||||
list_state: ListState,
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ use crate::ui::footer::footer;
|
|||
use crate::ui::grouping_list::grouping_list;
|
||||
use crate::ui::histogram::UiHistogram;
|
||||
use crate::ui::input::handle_events;
|
||||
use crate::ui::list::SelectList;
|
||||
use crate::ui::list::{SelectList, ToggleList};
|
||||
use crate::ui::single_group::single_group;
|
||||
use crate::ui::single_log::single_log;
|
||||
use crate::ui::state::{
|
||||
DistinctLogsState, ErrorLinesState, ErrorState, GroupByMenuState, GroupListState, GroupState,
|
||||
LogState, UiState,
|
||||
LevelsMenuState, LogState, UiState,
|
||||
};
|
||||
use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||
use ratatui::crossterm::terminal::{
|
||||
|
|
@ -189,6 +189,32 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
|||
frame.render_widget(Clear, area);
|
||||
frame.render_stateful_widget(popup, area, list_state);
|
||||
}
|
||||
UiState::LevelsMenu(LevelsMenuState {
|
||||
previous,
|
||||
list_state,
|
||||
levels,
|
||||
..
|
||||
}) => {
|
||||
ui(frame, app, previous);
|
||||
let options = [
|
||||
("Debug", levels[0]),
|
||||
("Info", levels[1]),
|
||||
("Warn", levels[2]),
|
||||
("Error", levels[3]),
|
||||
("Exception", levels[4]),
|
||||
("Unknown", levels[5]),
|
||||
];
|
||||
let area = center(
|
||||
frame.area(),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Length(options.len() as u16 + 2), // top and bottom border + content
|
||||
);
|
||||
let block = Block::bordered().title("Levels");
|
||||
|
||||
let popup = ToggleList::new(options).block(block);
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_stateful_widget(popup, area, list_state);
|
||||
}
|
||||
UiState::Distinct(DistinctLogsState {
|
||||
lines,
|
||||
table_state,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ pub enum UiState<'a> {
|
|||
GroupList(GroupListState<'a>),
|
||||
Group(GroupState<'a>),
|
||||
GroupByMenu(GroupByMenuState<'a>),
|
||||
LevelsMenu(LevelsMenuState<'a>),
|
||||
Distinct(DistinctLogsState<'a>),
|
||||
Log(LogState<'a>),
|
||||
Errors(ErrorLinesState<'a>),
|
||||
|
|
@ -36,6 +37,7 @@ impl<'a> UiState<'a> {
|
|||
..
|
||||
})
|
||||
| UiState::GroupByMenu(_)
|
||||
| UiState::LevelsMenu(_)
|
||||
| UiState::Distinct(DistinctLogsState { .. })
|
||||
| UiState::Log(LogState { .. })
|
||||
| UiState::Errors(ErrorLinesState { .. })
|
||||
|
|
@ -213,6 +215,24 @@ impl<'a> GroupState<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct LevelsMenuState<'a> {
|
||||
pub previous: Box<UiState<'a>>,
|
||||
pub list_state: SizedListState,
|
||||
pub levels: [bool; 6],
|
||||
}
|
||||
|
||||
impl PartialEq for LevelsMenuState<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.list_state == other.list_state
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LevelsMenuState<'a> {
|
||||
fn selected(&self) -> usize {
|
||||
self.list_state.selected()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for GroupState<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.result.result == other.result.result
|
||||
|
|
@ -379,6 +399,7 @@ impl<'a> UiState<'a> {
|
|||
UiState::Quit | UiState::GroupList(_) => UiPage::GroupList,
|
||||
UiState::Group(_) => UiPage::Group,
|
||||
UiState::GroupByMenu(_) => UiPage::Group, // todo
|
||||
UiState::LevelsMenu(_) => UiPage::Group, // todo
|
||||
UiState::Distinct(DistinctLogsState {
|
||||
grouping: DistinctLogGrouping::Message,
|
||||
..
|
||||
|
|
@ -455,6 +476,7 @@ impl<'a> UiState<'a> {
|
|||
fn list_state_mut(&mut self) -> Option<&mut SizedListState> {
|
||||
match self {
|
||||
UiState::GroupByMenu(state) => Some(&mut state.list_state),
|
||||
UiState::LevelsMenu(state) => Some(&mut state.list_state),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -511,6 +533,7 @@ impl<'a> UiState<'a> {
|
|||
UiState::Group(_) => UI_HEADER_SIZE + 1,
|
||||
UiState::Distinct(_) => UI_HEADER_SIZE + 1,
|
||||
UiState::GroupByMenu(_) => 0,
|
||||
UiState::LevelsMenu(_) => 0,
|
||||
UiState::Log(_) => 0,
|
||||
UiState::Errors(_) => 0,
|
||||
UiState::Error(_) => 0,
|
||||
|
|
@ -602,9 +625,6 @@ impl<'a> UiState<'a> {
|
|||
state.filter.pop();
|
||||
(true, state.into())
|
||||
}
|
||||
(UiState::GroupByMenu(GroupByMenuState { previous, .. }), UiEvent::ClearFilter) => {
|
||||
(true, *previous)
|
||||
}
|
||||
(UiState::Distinct(state), UiEvent::Select) => {
|
||||
let selected = state.selected();
|
||||
(true, state.enter(selected, app))
|
||||
|
|
@ -657,7 +677,7 @@ impl<'a> UiState<'a> {
|
|||
copy_osc(raw);
|
||||
(false, UiState::Errors(state))
|
||||
}
|
||||
(mut ui, UiEvent::EnterFilterMode) if ui.mode() != Mode::FilterInput => {
|
||||
(mut ui, UiEvent::EnterFilterMode) if ui.mode() == Mode::Normal => {
|
||||
ui.set_mode(Mode::FilterInput);
|
||||
(true, ui)
|
||||
}
|
||||
|
|
@ -676,12 +696,34 @@ impl<'a> UiState<'a> {
|
|||
}
|
||||
(true, ui)
|
||||
}
|
||||
(mut ui, UiEvent::ClearFilter) if ui.mode() != Mode::Normal => {
|
||||
if let Some(filter) = ui.filter_mut() {
|
||||
filter.clear();
|
||||
(
|
||||
ui @ (UiState::GroupList(_) | UiState::Group(_) | UiState::Distinct(_)),
|
||||
UiEvent::ToggleLevelFilterMode,
|
||||
) if ui.mode() == Mode::Normal => {
|
||||
let levels = ui.filter().map(|filter| filter.levels).unwrap_or([true; 6]);
|
||||
let state = LevelsMenuState {
|
||||
previous: Box::new(ui),
|
||||
list_state: SizedListState::new(levels.len()),
|
||||
levels,
|
||||
};
|
||||
(true, UiState::LevelsMenu(state))
|
||||
}
|
||||
(UiState::LevelsMenu(mut state), UiEvent::Toggle) => {
|
||||
state.levels[state.selected()] = !state.levels[state.selected()];
|
||||
(true, UiState::LevelsMenu(state))
|
||||
}
|
||||
(
|
||||
UiState::LevelsMenu(LevelsMenuState {
|
||||
mut previous,
|
||||
levels,
|
||||
..
|
||||
}),
|
||||
UiEvent::Back | UiEvent::ToggleLevelFilterMode | UiEvent::Select,
|
||||
) => {
|
||||
if let Some(filter) = previous.filter_mut() {
|
||||
filter.levels = levels;
|
||||
}
|
||||
ui.set_mode(Mode::Normal);
|
||||
(true, ui)
|
||||
(true, *previous)
|
||||
}
|
||||
(
|
||||
mut ui @ UiState::GroupList(GroupListState {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue