Compare commits
3 Commits
dcc863c451
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 20f33f5694 | |||
| 989e0aef91 | |||
| 54ddc9851c |
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ pub enum Mode {
|
|||||||
Normal,
|
Normal,
|
||||||
Command,
|
Command,
|
||||||
Search,
|
Search,
|
||||||
|
ConfirmDelete,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|||||||
14
src/ipc.rs
14
src/ipc.rs
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
15
src/main.rs
15
src/main.rs
@ -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
134
src/ui.rs
@ -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]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user