Skip to content

Commit

Permalink
Show profile contents when profile list is selected
Browse files Browse the repository at this point in the history
Closes #26
  • Loading branch information
LucasPickering committed Dec 21, 2023
1 parent cf7743c commit 4ddb106
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 166 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased] - ReleaseDate

### Changed

- Show profile contents while in the profile list ([#26](https://github.com/LucasPickering/slumber/issues/26))

## [0.11.0] - 2023-12-20

### Added
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
tui::{
input::Action,
view::{
component::{root::Root, Component},
component::{Component, Root},
draw::{Draw, DrawContext},
event::{Event, EventHandler, Update, UpdateContext},
state::Notification,
Expand Down
1 change: 1 addition & 0 deletions src/tui/view/common/template_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use std::{
/// changed globally.
#[derive(Debug)]
pub enum TemplatePreview {
// TODO key state on preview_templates
/// Template previewing is disabled, just show the raw text
Disabled { template: Template },
/// Template previewing is enabled, render the template
Expand Down
17 changes: 10 additions & 7 deletions src/tui/view/component.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
//! Specific single-use components

pub mod help;
pub mod misc;
pub mod primary;
pub mod request;
pub mod response;
pub mod root;
pub mod settings;
mod help;
mod misc;
mod primary;
mod profile;
mod profile_list;
mod recipe_list;
mod request;
mod response;
mod root;
mod settings;

pub use root::Root;

Expand Down
199 changes: 41 additions & 158 deletions src/tui/view/component/primary.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
//! Components for the "primary" view, which is the paned request/response view

use crate::{
collection::{
Profile, ProfileId, RequestCollection, RequestRecipe, RequestRecipeId,
},
collection::{Profile, RequestCollection, RequestRecipe},
tui::{
context::TuiContext,
input::Action,
message::Message,
view::{
common::{list::List, Pane},
component::{
help::HelpModal,
misc::EmptyActionsModal,
profile::{ProfilePane, ProfilePaneProps},
profile_list::{ProfileListPane, ProfileListPaneProps},
recipe_list::{RecipeListPane, RecipeListPaneProps},
request::{RequestPane, RequestPaneProps},
response::{ResponsePane, ResponsePaneProps},
settings::SettingsModal,
},
draw::{Draw, DrawContext, Generate},
draw::{Draw, DrawContext},
event::{Event, EventHandler, Update, UpdateContext},
state::{
persistence::{Persistable, Persistent, PersistentKey},
select::{Dynamic, Fixed, SelectState},
persistence::{Persistent, PersistentKey},
select::{Fixed, SelectState},
RequestState,
},
util::layout,
Expand All @@ -48,6 +48,8 @@ pub struct PrimaryView {
#[debug(skip)]
recipe_list_pane: Component<RecipeListPane>,
#[debug(skip)]
profile_pane: Component<ProfilePane>,
#[debug(skip)]
request_pane: Component<RequestPane>,
#[debug(skip)]
response_pane: Component<ResponsePane>,
Expand Down Expand Up @@ -112,6 +114,7 @@ impl PrimaryView {

profile_list_pane,
recipe_list_pane,
profile_pane: Default::default(),
request_pane: Default::default(),
response_pane: Default::default(),
}
Expand All @@ -120,13 +123,13 @@ impl PrimaryView {
/// Which recipe in the recipe list is selected? `None` iff the list is
/// empty.
pub fn selected_recipe(&self) -> Option<&RequestRecipe> {
self.recipe_list_pane.recipes.selected()
self.recipe_list_pane.recipes().selected()
}

/// Which profile in the list is selected? `None` iff the list is empty.
/// Exposing inner state is hacky but it's an easy shortcut
pub fn selected_profile(&self) -> Option<&Profile> {
self.profile_list_pane.profiles.selected()
self.profile_list_pane.profiles().selected()
}

fn toggle_fullscreen(&mut self, mode: FullscreenMode) {
Expand Down Expand Up @@ -288,7 +291,7 @@ impl<'a> Draw<PrimaryViewProps<'a>> for PrimaryView {
// Make profile list as small as possible, with a max
// size
Constraint::Max(
self.profile_list_pane.profiles.len().clamp(1, 16)
self.profile_list_pane.profiles().len().clamp(1, 16)
as u16
+ 2, // Account for top/bottom border
),
Expand All @@ -307,31 +310,46 @@ impl<'a> Draw<PrimaryViewProps<'a>> for PrimaryView {
let panes = &self.selected_pane;
self.profile_list_pane.draw(
context,
ListPaneProps {
ProfileListPaneProps {
is_selected: panes
.is_selected(&PrimaryPane::ProfileList),
},
profiles_area,
);
self.recipe_list_pane.draw(
context,
ListPaneProps {
RecipeListPaneProps {
is_selected: panes
.is_selected(&PrimaryPane::RecipeList),
},
recipes_area,
);
self.request_pane.draw(
context,
RequestPaneProps {
is_selected: panes.is_selected(&PrimaryPane::Request),
selected_recipe: self.selected_recipe(),
selected_profile_id: self
.selected_profile()
.map(|profile| &profile.id),
},
request_area,
);

// If profile list is selected, show the profile contents.
// Otherwise show the recipe pane
if let (PrimaryPane::ProfileList, Some(profile)) =
(self.selected_pane.selected(), self.selected_profile())
{
self.profile_pane.draw(
context,
ProfilePaneProps { profile },
request_area,
)
} else {
self.request_pane.draw(
context,
RequestPaneProps {
is_selected: panes
.is_selected(&PrimaryPane::Request),
selected_recipe: self.selected_recipe(),
selected_profile_id: self
.selected_profile()
.map(|profile| &profile.id),
},
request_area,
);
}

self.response_pane.draw(
context,
ResponsePaneProps {
Expand Down Expand Up @@ -367,138 +385,3 @@ impl<'a> Draw<PrimaryViewProps<'a>> for PrimaryView {
}
}
}

#[derive(Debug)]
struct ProfileListPane {
profiles: Component<Persistent<SelectState<Dynamic, Profile>>>,
}

struct ListPaneProps {
is_selected: bool,
}

impl ProfileListPane {
pub fn new(profiles: Vec<Profile>) -> Self {
// Loaded request depends on the profile, so refresh on change
fn on_select(context: &mut UpdateContext, _: &Profile) {
context.queue_event(Event::HttpLoadRequest);
}

Self {
profiles: Persistent::new(
PersistentKey::ProfileId,
SelectState::new(profiles).on_select(on_select),
)
.into(),
}
}
}

impl EventHandler for ProfileListPane {
fn children(&mut self) -> Vec<Component<&mut dyn EventHandler>> {
vec![self.profiles.as_child()]
}
}

impl Draw<ListPaneProps> for ProfileListPane {
fn draw(
&self,
context: &mut DrawContext,
props: ListPaneProps,
area: Rect,
) {
self.profiles.set_area(area); // Needed for tracking cursor events
let title = PrimaryPane::ProfileList.to_string();
let list = List {
block: Some(Pane {
title: &title,
is_focused: props.is_selected,
}),
list: &self.profiles,
};
context.frame.render_stateful_widget(
list.generate(),
area,
&mut self.profiles.state_mut(),
)
}
}

/// Persist profile by ID
impl Persistable for Profile {
type Persisted = ProfileId;

fn get_persistent(&self) -> &Self::Persisted {
&self.id
}
}

#[derive(Debug)]
struct RecipeListPane {
recipes: Component<Persistent<SelectState<Dynamic, RequestRecipe>>>,
}

impl RecipeListPane {
pub fn new(recipes: Vec<RequestRecipe>) -> Self {
// When highlighting a new recipe, load it from the repo
fn on_select(context: &mut UpdateContext, _: &RequestRecipe) {
context.queue_event(Event::HttpLoadRequest);
}

// Trigger a request on submit
fn on_submit(context: &mut UpdateContext, _: &RequestRecipe) {
// Parent has to be responsible for actually sending the request
// because it also needs access to the profile list state
context.queue_event(Event::HttpSendRequest);
}

Self {
recipes: Persistent::new(
PersistentKey::RecipeId,
SelectState::new(recipes)
.on_select(on_select)
.on_submit(on_submit),
)
.into(),
}
}
}

impl EventHandler for RecipeListPane {
fn children(&mut self) -> Vec<Component<&mut dyn EventHandler>> {
vec![self.recipes.as_child()]
}
}

impl Draw<ListPaneProps> for RecipeListPane {
fn draw(
&self,
context: &mut DrawContext,
props: ListPaneProps,
area: Rect,
) {
self.recipes.set_area(area); // Needed for tracking cursor events
let title = PrimaryPane::RecipeList.to_string();
let list = List {
block: Some(Pane {
title: &title,
is_focused: props.is_selected,
}),
list: &self.recipes,
};
context.frame.render_stateful_widget(
list.generate(),
area,
&mut self.recipes.state_mut(),
)
}
}

/// Persist recipe by ID
impl Persistable for RequestRecipe {
type Persisted = RequestRecipeId;

fn get_persistent(&self) -> &Self::Persisted {
&self.id
}
}
Loading

0 comments on commit 4ddb106

Please sign in to comment.