Rust
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
use crate::azure::models::Application;
|
||||
use crate::state::AppState;
|
||||
use crate::ui::components::{
|
||||
show_error_message, show_loading_spinner, show_section_header, show_success_message, show_text,
|
||||
};
|
||||
use egui::{Context, ScrollArea, Ui};
|
||||
|
||||
pub fn show_app_list_view(ctx: &Context, state: &mut AppState, config: &crate::config::Config) -> Option<AppListAction> {
|
||||
let mut action = None;
|
||||
|
||||
// Handle keyboard navigation for app list
|
||||
// Check early, before text edits might consume the input
|
||||
let any_text_focused = ctx.memory(|m| m.focused().is_some());
|
||||
|
||||
if !any_text_focused {
|
||||
let filtered_apps = state.filtered_applications();
|
||||
|
||||
if !filtered_apps.is_empty() {
|
||||
let current_idx = state.selected_app.as_ref()
|
||||
.and_then(|sel| filtered_apps.iter().position(|a| a.id == sel.id));
|
||||
|
||||
ctx.input(|i| {
|
||||
let mut target_idx = None;
|
||||
|
||||
if i.key_pressed(egui::Key::ArrowDown) {
|
||||
target_idx = Some(current_idx.map_or(0, |idx| (idx + 1) % filtered_apps.len()));
|
||||
}
|
||||
|
||||
if i.key_pressed(egui::Key::ArrowUp) {
|
||||
target_idx = Some(current_idx.map_or(
|
||||
filtered_apps.len() - 1,
|
||||
|idx| if idx > 0 { idx - 1 } else { filtered_apps.len() - 1 }
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(idx) = target_idx {
|
||||
if let Some(app) = filtered_apps.get(idx) {
|
||||
action = Some(AppListAction::SelectApp(app.clone()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("top_panel")
|
||||
.frame(egui::Frame::none()
|
||||
.fill(egui::Color32::TRANSPARENT)
|
||||
.stroke(egui::Stroke::NONE)
|
||||
.inner_margin(egui::Margin::symmetric(20.0, 10.0)))
|
||||
.show_separator_line(false)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
show_section_header(ui, "App Registrations");
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Sign Out").clicked() {
|
||||
action = Some(AppListAction::SignOut);
|
||||
}
|
||||
|
||||
ui.add_space(10.0); // Replace separator with space
|
||||
|
||||
if ui.button("🔄 Refresh").clicked() {
|
||||
action = Some(AppListAction::Refresh);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if let Some(user) = &state.user_info {
|
||||
show_text(ui, &format!("Logged in as: {}", user.display_name));
|
||||
}
|
||||
});
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame::none()
|
||||
.fill(egui::Color32::TRANSPARENT)
|
||||
.inner_margin(egui::Margin {
|
||||
left: 20.0,
|
||||
right: 20.0,
|
||||
top: 20.0,
|
||||
bottom: 80.0, // Extra margin so cards don't show behind bottom panel
|
||||
}))
|
||||
.show(ctx, |ui| {
|
||||
// Show messages
|
||||
if let Some(error) = &state.error_message {
|
||||
show_error_message(ui, error);
|
||||
ui.add_space(10.0);
|
||||
}
|
||||
|
||||
if let Some(success) = &state.success_message {
|
||||
show_success_message(ui, success);
|
||||
ui.add_space(10.0);
|
||||
}
|
||||
|
||||
// Show loading spinner if loading
|
||||
if state.operations.load_applications.is_some() {
|
||||
show_loading_spinner(ui, "Loading applications...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Search box
|
||||
ui.horizontal(|ui| {
|
||||
show_text(ui, "Search:");
|
||||
ui.text_edit_singleline(&mut state.app_search_filter);
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
|
||||
let filtered_apps = state.filtered_applications();
|
||||
|
||||
if filtered_apps.is_empty() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
if state.applications.is_empty() {
|
||||
show_text(ui, "No app registrations found. Click Refresh to load.");
|
||||
} else {
|
||||
show_text(ui, "No matching applications found.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
show_text(ui, &format!("Found {} applications", filtered_apps.len()));
|
||||
ui.add_space(5.0);
|
||||
|
||||
ScrollArea::vertical()
|
||||
.auto_shrink([false, false])
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(ui.available_width() - 20.0); // Add margin for scrollbar
|
||||
for app in filtered_apps {
|
||||
show_app_card(ui, &app, &state.selected_app, &mut action, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::bottom("bottom_panel")
|
||||
.frame(egui::Frame::none()
|
||||
.fill(egui::Color32::TRANSPARENT)
|
||||
.stroke(egui::Stroke::NONE)
|
||||
.inner_margin(egui::Margin::symmetric(20.0, 15.0)))
|
||||
.show_separator_line(false)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let is_app_selected = state.selected_app.is_some();
|
||||
|
||||
if ui
|
||||
.add_enabled(is_app_selected, egui::Button::new("Create Secret").min_size(egui::vec2(120.0, 32.0)))
|
||||
.clicked()
|
||||
{
|
||||
action = Some(AppListAction::CreateSecret);
|
||||
}
|
||||
|
||||
if !is_app_selected {
|
||||
show_text(ui, "Select an app registration to create a secret");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
fn show_app_card(
|
||||
ui: &mut Ui,
|
||||
app: &Application,
|
||||
selected_app: &Option<Application>,
|
||||
action: &mut Option<AppListAction>,
|
||||
config: &crate::config::Config,
|
||||
) {
|
||||
let is_selected = selected_app
|
||||
.as_ref()
|
||||
.map(|selected| selected.id == app.id)
|
||||
.unwrap_or(false);
|
||||
|
||||
// Get card colors from config with fallback to defaults
|
||||
let card_colors = config.colors.as_ref()
|
||||
.map(|c| &c.cards.app_list);
|
||||
|
||||
let frame = if is_selected {
|
||||
let bg = card_colors.map(|c| c.selected_bg).unwrap_or([60, 60, 100, 180]);
|
||||
let border = card_colors.map(|c| c.selected_border).unwrap_or([100, 150, 255]);
|
||||
egui::Frame::none()
|
||||
.fill(egui::Color32::from_rgba_unmultiplied(bg[0], bg[1], bg[2], bg[3]))
|
||||
.inner_margin(10.0)
|
||||
.rounding(5.0)
|
||||
.stroke(egui::Stroke::new(2.0, egui::Color32::from_rgb(border[0], border[1], border[2])))
|
||||
} else {
|
||||
let bg = card_colors.map(|c| c.unselected_bg).unwrap_or([40, 40, 60, 150]);
|
||||
egui::Frame::none()
|
||||
.fill(egui::Color32::from_rgba_unmultiplied(bg[0], bg[1], bg[2], bg[3]))
|
||||
.inner_margin(10.0)
|
||||
.rounding(5.0)
|
||||
};
|
||||
|
||||
let response = frame.show(ui, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.strong(&app.display_name);
|
||||
ui.label(format!("App ID: {}", app.app_id));
|
||||
if let Some(created) = &app.created_date_time {
|
||||
ui.label(format!("Created: {}", created));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Make the entire card clickable
|
||||
if response.response.interact(egui::Sense::click()).clicked() && !is_selected {
|
||||
*action = Some(AppListAction::SelectApp(app.clone()));
|
||||
}
|
||||
|
||||
ui.add_space(5.0);
|
||||
}
|
||||
|
||||
pub enum AppListAction {
|
||||
Refresh,
|
||||
SelectApp(Application),
|
||||
CreateSecret,
|
||||
SignOut,
|
||||
}
|
||||
Reference in New Issue
Block a user