Compare commits

...

3 Commits

Author SHA1 Message Date
20f33f5694 ui 2026-03-08 19:47:40 +01:00
989e0aef91 ui 2026-03-08 19:11:02 +01:00
54ddc9851c uds ipc working 2026-03-08 19:07:37 +01:00
6 changed files with 170 additions and 24 deletions

View File

@ -112,4 +112,10 @@ impl Database {
Ok(entries) Ok(entries)
} }
pub fn delete_entry_by_content(&self, content: &str) -> Result<(), Box<dyn Error>> {
self.conn
.execute("DELETE FROM history WHERE content = ?1", [content])?;
Ok(())
}
} }

View File

@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex};
pub enum IpcRequest { pub enum IpcRequest {
GetHistory { limit: usize }, GetHistory { limit: usize },
SetClipboard { content: String }, SetClipboard { content: String },
DeleteEntry { content: String },
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -90,6 +91,29 @@ pub fn start_server(db: Arc<Mutex<Database>>, socket_path: &Path) {
} }
} }
} }
IpcRequest::DeleteEntry { content } => {
{
let db_lock = db_clone.lock().unwrap();
let _ = db_lock.delete_entry_by_content(&content);
}
if content.ends_with(".jpg") || content.ends_with(".png") {
if let Some(proj_dirs) =
directories::ProjectDirs::from("com", "zefad", "rklipd")
{
let img_path =
proj_dirs.data_dir().join("images").join(&content);
if img_path.exists() {
if let Err(e) = std::fs::remove_file(&img_path) {
eprintln!("Error while deleting image: {}", e);
} else {
println!("Image deleted : {}", content);
}
}
}
}
}
} }
} }
} }

View File

@ -8,6 +8,7 @@ pub enum Mode {
Normal, Normal,
Command, Command,
Search, Search,
ConfirmDelete,
} }
pub struct App { pub struct App {

View File

@ -6,6 +6,7 @@ use std::os::unix::net::UnixStream;
pub enum IpcRequest { pub enum IpcRequest {
GetHistory { limit: usize }, GetHistory { limit: usize },
SetClipboard { content: String }, SetClipboard { content: String },
DeleteEntry { content: String },
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -49,3 +50,16 @@ pub fn set_clipboard(content: String) {
} }
} }
} }
pub fn delete_entry(content: String) {
if let Some(base_dir) = directories::ProjectDirs::from("com", "zefad", "rklipd") {
let socket_path = base_dir.data_dir().join("rklip.sock");
if let Ok(mut stream) = UnixStream::connect(&socket_path) {
let req = IpcRequest::DeleteEntry { content };
if let Ok(req_json) = serde_json::to_string(&req) {
let _ = stream.write_all(req_json.as_bytes());
let _ = stream.shutdown(std::net::Shutdown::Write);
}
}
}
}

View File

@ -60,7 +60,7 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App)
} }
KeyCode::Char('d') => { KeyCode::Char('d') => {
if last_key_was_d { if last_key_was_d {
app.delete_selected(); app.mode = Mode::ConfirmDelete;
last_key_was_d = false; last_key_was_d = false;
} else { } else {
last_key_was_d = true; last_key_was_d = true;
@ -150,6 +150,19 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App)
} }
_ => {} _ => {}
}, },
Mode::ConfirmDelete => match key.code {
KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
if let Some(selected) = app.get_selected_item() {
crate::ipc::delete_entry(selected.clone());
app.delete_selected();
}
app.mode = Mode::Normal;
}
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
app.mode = Mode::Normal;
}
_ => {}
},
} }
} }
} else { } else {

134
src/ui.rs
View File

@ -1,15 +1,10 @@
use std::{io::Write, os::unix::net::UnixStream}; use crate::app::{App, Mode};
use crate::{
app::{App, Mode},
ipc::IpcRequest,
};
use ratatui::{ use ratatui::{
Frame, Frame,
layout::{Constraint, Direction, Layout}, layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, List, ListItem, Padding, Paragraph}, widgets::{Block, BorderType, Borders, List, ListItem, Padding, Paragraph},
}; };
use ratatui_image::StatefulImage; use ratatui_image::StatefulImage;
@ -21,32 +16,64 @@ pub fn render(f: &mut Frame, app: &mut App) {
let content_chunks = Layout::default() let content_chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .constraints([Constraint::Length(45), Constraint::Min(0)])
.split(main_chunks[0]); .split(main_chunks[0]);
let items: Vec<ListItem> = app let items: Vec<ListItem> = app
.filtered_items .filtered_items
.iter() .iter()
.map(|i| ListItem::new(i.as_str())) .map(|i| {
if i.ends_with(".jpg") || i.ends_with(".png") {
ListItem::new(Line::from(Span::styled(
format!("🖼️ {}", i),
Style::default()
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD),
)))
} else {
ListItem::new(Line::from(format!(" {}", i)))
}
})
.collect(); .collect();
let list = List::new(items) let list = List::new(items)
.block(Block::default().borders(Borders::ALL).title(" History ")) .block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(Color::DarkGray))
.title(Span::styled(
" History ",
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
))
.title_alignment(Alignment::Center),
)
.highlight_style( .highlight_style(
Style::default() Style::default()
.bg(Color::DarkGray) .bg(Color::Rgb(40, 44, 52))
.fg(Color::White)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
) )
.highlight_symbol(">> "); .highlight_symbol(">> ");
f.render_stateful_widget(list, content_chunks[0], &mut app.list_state); f.render_stateful_widget(list, content_chunks[0], &mut app.list_state);
let right_panel_block = Block::default() let right_panel_block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.title(" Prev ") .border_type(BorderType::Rounded)
.border_style(Style::default().fg(Color::DarkGray))
.title(Span::styled(
" Previsualisation ",
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
))
.title_alignment(Alignment::Center)
.padding(Padding::uniform(1)); .padding(Padding::uniform(1));
let inner_right_area = right_panel_block.inner(content_chunks[1]); let inner_right_area = right_panel_block.inner(content_chunks[1]);
f.render_widget(right_panel_block, content_chunks[1]); f.render_widget(right_panel_block, content_chunks[1]);
if let Some(state) = &mut app.current_image { if let Some(state) = &mut app.current_image {
@ -59,21 +86,82 @@ pub fn render(f: &mut Frame, app: &mut App) {
f.render_widget(preview_paragraph, inner_right_area); f.render_widget(preview_paragraph, inner_right_area);
} }
let bottom_text = match app.mode { let current_color = match app.mode {
Mode::Normal => Color::Green,
Mode::ConfirmDelete => Color::Red,
Mode::Command => Color::Yellow,
Mode::Search => Color::Cyan,
};
let bottom_block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(current_color));
let inner_bottom_area = bottom_block.inner(main_chunks[1]);
f.render_widget(bottom_block, main_chunks[1]);
let bottom_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Min(0), Constraint::Length(15)])
.split(inner_bottom_area);
let mode_text = match app.mode {
Mode::Normal => Line::from(vec![Span::styled( Mode::Normal => Line::from(vec![Span::styled(
"-- NORMAL --", " NORMAL ",
Style::default().fg(Color::Green), Style::default()
.bg(Color::Green)
.fg(Color::Black)
.add_modifier(Modifier::BOLD),
)]),
Mode::ConfirmDelete => Line::from(vec![Span::styled(
" Delete ? (y/n) ",
Style::default()
.bg(Color::Red)
.fg(Color::White)
.add_modifier(Modifier::BOLD),
)]), )]),
Mode::Command => Line::from(vec![ Mode::Command => Line::from(vec![
Span::styled(":", Style::default().fg(Color::Yellow)), Span::styled(
Span::raw(&app.input_buffer), " COMMAND ",
Style::default()
.bg(Color::Yellow)
.fg(Color::Black)
.add_modifier(Modifier::BOLD),
),
Span::raw(format!(" :{}", app.input_buffer)),
]), ]),
Mode::Search => Line::from(vec![ Mode::Search => Line::from(vec![
Span::styled("/", Style::default().fg(Color::Cyan)), Span::styled(
Span::raw(&app.input_buffer), " SEARCH ",
Style::default()
.bg(Color::Cyan)
.fg(Color::Black)
.add_modifier(Modifier::BOLD),
),
Span::raw(format!(" /{}", app.input_buffer)),
]), ]),
}; };
let bottom_bar = Paragraph::new(bottom_text).block(Block::default().borders(Borders::ALL)); f.render_widget(
f.render_widget(bottom_bar, main_chunks[1]); Paragraph::new(mode_text).block(Block::default().padding(Padding::horizontal(1))),
bottom_chunks[0],
);
let total = app.filtered_items.len();
let current = if total == 0 {
0
} else {
app.list_state.selected().unwrap_or(0) + 1
};
let stats_text = Line::from(vec![Span::styled(
format!("{}/{} ", current, total),
Style::default()
.fg(Color::DarkGray)
.add_modifier(Modifier::BOLD),
)])
.alignment(Alignment::Right);
f.render_widget(Paragraph::new(stats_text), bottom_chunks[1]);
} }