···
10
-
button, column, container, horizontal_rule, pane_grid, row, scrollable, text, text_input,
11
+
use iced::widget::{button, column, container, pane_grid, row, text, text_input};
use iced::{Length, Padding, Theme};
···
panes: pane_grid::State<PaneContent>,
65
-
selected_channel: Option<usize>,
66
-
selected_tracks: Vec<usize>,
65
+
channels_view_state: channels_view::State,
66
+
tracks_view_state: tracks_view::State,
modifiers: iced::keyboard::Modifiers,
···
106
-
selected_channel: None,
107
-
selected_tracks: Vec::new(),
106
+
channels_view_state: channels_view::State::new(),
107
+
tracks_view_state: tracks_view::State::new(),
modifiers: iced::keyboard::Modifiers::default(),
palette: Palette::default(),
···
118
-
ChannelSelected(usize),
119
-
TrackClicked(usize),
118
+
ChannelsView(channels_view::Message),
119
+
TracksView(tracks_view::Message),
ModifiersChanged(iced::keyboard::Modifiers),
SearchChanged(pane_grid::Pane, String),
···
ViewEditor(view_editor::Message),
130
-
fn matches_query(text: &str, query: &str) -> bool {
131
-
if query.is_empty() {
134
-
text.to_lowercase().contains(&query.to_lowercase())
fn update(state: &mut State, message: Message) {
139
-
Message::ChannelSelected(idx) => {
140
-
state.selected_channel = Some(idx);
142
-
if let Some(channel) = state.channels.get(idx) {
143
-
let view = view::View::channel_tracks(&channel.slug, &channel.name);
144
-
let view_id = view.id.clone();
146
-
if !state.views.iter().any(|v| v.id == view_id) {
147
-
state.views.push(view);
132
+
Message::ChannelsView(msg) => {
133
+
let action = state.channels_view_state.update(msg, &state.channels);
135
+
channels_view::Action::ShowChannelTracks(slug, name) => {
136
+
let view = view::View::channel_tracks(&slug, &name);
137
+
let view_id = view.id.clone();
139
+
if !state.views.iter().any(|v| v.id == view_id) {
140
+
state.views.push(view);
150
-
if let Some((_pane_id, content)) = state.panes.iter_mut().find(|(_, c)| {
151
-
c.current_view_id()
152
-
.map(|id| id != "all-channels")
155
-
content.push_view(view_id);
143
+
if let Some((_pane_id, content)) = state.panes.iter_mut().find(|(_, c)| {
144
+
c.current_view_id()
145
+
.map(|id| id != "all-channels")
148
+
content.push_view(view_id);
151
+
channels_view::Action::None => {}
159
-
Message::TrackClicked(index) => {
160
-
let ctrl = state.modifiers.command() || state.modifiers.control();
161
-
let shift = state.modifiers.shift();
164
-
if state.selected_tracks.contains(&index) {
165
-
state.selected_tracks.retain(|&i| i != index);
167
-
state.selected_tracks.push(index);
170
-
if let Some(&last) = state.selected_tracks.last() {
171
-
let start = last.min(index);
172
-
let end = last.max(index);
173
-
state.selected_tracks.clear();
174
-
state.selected_tracks.extend(start..=end);
176
-
state.selected_tracks = vec![index];
179
-
state.selected_tracks = vec![index];
154
+
Message::TracksView(msg) => {
155
+
let _action = state.tracks_view_state.update(msg);
Message::ModifiersChanged(modifiers) => {
state.modifiers = modifiers;
159
+
state.tracks_view_state.modifiers = modifiers;
Message::SearchChanged(pane, query) => {
if let Some(content) = state.panes.get(pane) {
···
let result_count = match view.source {
428
-
view::DataSource::Channels => filtered_channels(state, view).len(),
429
-
view::DataSource::Tracks => filtered_tracks(state, view).len(),
404
+
view::DataSource::Channels => channels_view::filtered_channels(&state.channels, view).len(),
405
+
view::DataSource::Tracks => tracks_view::filtered_tracks(&state.tracks, view).len(),
let search_placeholder = match view.source {
···
let view_content = match view.source {
461
-
view::DataSource::Channels => render_channels_view(view, state, palette),
462
-
view::DataSource::Tracks => render_tracks_view(view, state, palette),
437
+
view::DataSource::Channels => state
438
+
.channels_view_state
439
+
.view(&state.channels, view, &state.tracks, palette)
440
+
.map(Message::ChannelsView),
441
+
view::DataSource::Tracks => state
443
+
.view(&state.tracks, view, palette)
444
+
.map(Message::TracksView),
column![tabs, search_bar, view_content]
···
471
-
fn filtered_channels<'a>(state: &'a State, view: &view::View) -> Vec<(usize, &'a Channel)> {
476
-
.filter(|(_idx, channel)| {
477
-
if let Some(ref slug) = view.filter.slug {
478
-
if &channel.slug != slug {
483
-
if let Some(min) = view.filter.track_count_min {
484
-
if channel.track_count < min {
489
-
if let Some(max) = view.filter.track_count_max {
490
-
if channel.track_count > max {
495
-
if !view.filter.search_query.is_empty()
496
-
&& !matches_query(&channel.name, &view.filter.search_query)
497
-
&& !matches_query(&channel.slug, &view.filter.search_query)
507
-
fn render_channels_view<'a>(
508
-
view: &'a view::View,
510
-
palette: &'a Palette,
511
-
) -> iced::Element<'a, Message> {
512
-
let mut sorted_channels = filtered_channels(state, view);
514
-
match view.sort.field {
515
-
view::SortField::Name => {
516
-
sorted_channels.sort_by(|(_, a), (_, b)| a.name.cmp(&b.name));
518
-
view::SortField::TrackCount => {
519
-
sorted_channels.sort_by_key(|(_, ch)| ch.track_count);
521
-
view::SortField::LatestTrack => {
522
-
sorted_channels.sort_by_key(|(_, channel)| {
526
-
.filter(|t| t.slug == channel.slug)
527
-
.map(|t| &t.created_at)
530
-
.unwrap_or_default()
533
-
view::SortField::CreatedAt => {}
536
-
if matches!(view.sort.direction, view::SortDirection::Desc) {
537
-
sorted_channels.reverse();
540
-
let list = sorted_channels
542
-
.fold(column![].spacing(0), |col, (idx, channel)| {
543
-
let is_selected = state.selected_channel == Some(*idx);
544
-
let channel_info = format!("{} ({})", channel.name, channel.track_count);
546
-
let item = container(text(channel_info).size(14))
548
-
.width(Length::Fill)
549
-
.style(move |_theme: &Theme| {
552
-
background: Some(iced::Background::Color(palette.gray3)),
553
-
..Default::default()
556
-
container::Style::default()
560
-
let button = iced::widget::button(item)
561
-
.on_press(Message::ChannelSelected(*idx))
563
-
.style(|theme: &Theme, _status| iced::widget::button::Style {
565
-
text_color: theme.palette().text,
566
-
border: iced::Border::default(),
567
-
shadow: iced::Shadow::default(),
570
-
let col = col.push(button);
572
-
horizontal_rule(1).style(move |_theme: &Theme| iced::widget::rule::Style {
573
-
color: palette.gray3,
575
-
radius: 0.0.into(),
576
-
fill_mode: iced::widget::rule::FillMode::Full,
581
-
let content = scrollable(list);
584
-
.width(Length::Fill)
585
-
.height(Length::Fill)
586
-
.style(move |_theme: &Theme| container::Style {
587
-
background: Some(iced::Background::Color(palette.gray4)),
588
-
..Default::default()
593
-
fn filtered_tracks<'a>(state: &'a State, view: &view::View) -> Vec<(usize, &'a Track)> {
598
-
.filter(|(_idx, track)| {
599
-
if let Some(ref slug) = view.filter.slug {
600
-
if &track.slug != slug {
605
-
if !view.filter.search_query.is_empty()
606
-
&& !matches_query(&track.title, &view.filter.search_query)
607
-
&& !matches_query(&track.description, &view.filter.search_query)
608
-
&& !matches_query(&track.slug, &view.filter.search_query)
618
-
fn render_tracks_view<'a>(
619
-
view: &'a view::View,
621
-
palette: &'a Palette,
622
-
) -> iced::Element<'a, Message> {
623
-
let mut sorted_tracks = filtered_tracks(state, view);
625
-
match view.sort.field {
626
-
view::SortField::Name => {
627
-
sorted_tracks.sort_by(|(_, a), (_, b)| a.title.cmp(&b.title));
629
-
view::SortField::CreatedAt => {
630
-
sorted_tracks.sort_by(|(_, a), (_, b)| a.created_at.cmp(&b.created_at));
632
-
view::SortField::TrackCount | view::SortField::LatestTrack => {}
635
-
if matches!(view.sort.direction, view::SortDirection::Desc) {
636
-
sorted_tracks.reverse();
639
-
sorted_tracks.truncate(5000);
641
-
let list = sorted_tracks
643
-
.fold(column![].spacing(0), |col, (index, track)| {
644
-
let is_selected = state.selected_tracks.contains(index);
646
-
let slug_part = text(format!("@{}", track.slug))
648
-
.color(palette.red);
650
-
let title_part = text(format!(" {}", track.title))
653
-
let track_content = if track.description.is_empty() {
654
-
row![slug_part, title_part].spacing(0)
656
-
let desc_part = text(format!(" {}", track.description))
658
-
.color(palette.text_dim);
659
-
row![slug_part, title_part, desc_part].spacing(0)
662
-
let item = container(track_content)
664
-
.width(Length::Fill)
665
-
.style(move |_theme: &Theme| {
668
-
background: Some(iced::Background::Color(palette.gray3)),
669
-
..Default::default()
672
-
container::Style::default()
676
-
let button = iced::widget::button(item)
677
-
.on_press(Message::TrackClicked(*index))
679
-
.style(|theme: &Theme, _status| iced::widget::button::Style {
681
-
text_color: theme.palette().text,
682
-
border: iced::Border::default(),
683
-
shadow: iced::Shadow::default(),
686
-
let col = col.push(button);
688
-
horizontal_rule(1).style(move |_theme: &Theme| iced::widget::rule::Style {
689
-
color: palette.gray3,
691
-
radius: 0.0.into(),
692
-
fill_mode: iced::widget::rule::FillMode::Full,
697
-
let content = scrollable(list);
700
-
.width(Length::Fill)
701
-
.height(Length::Fill)