This commit is contained in:
2025-06-26 17:53:13 +03:00
parent 97f31f95f4
commit 303d743f7c
4 changed files with 305 additions and 4 deletions
+1
View File
@@ -0,0 +1 @@
# json editor
+5 -2
View File
@@ -1,3 +1,5 @@
use std::collections::HashMap;
pub enum CurrentScreen {
Main,
Editing,
@@ -31,6 +33,7 @@ impl App {
pub fn save_key_value(&mut self) {
self.pairs
.insert(self.key_input.clone(), self.value_input.clone());
self.key_input = String::new();
self.value_input = String::new();
self.currently_editing = None;
@@ -43,11 +46,11 @@ impl App {
CurrentlyEditing::Value => self.currently_editing = Some(CurrentlyEditing::Key),
};
} else {
self.currently_editing = Some(CurrentlyEditing::Key)
self.currently_editing = Some(CurrentlyEditing::Key);
}
}
pub fn print_json(&self) -> serde::Result<()> {
pub fn print_json(&self) -> serde_json::Result<()> {
let output = serde_json::to_string(&self.pairs)?;
println!("{}", output);
Ok(())
+127 -2
View File
@@ -1,3 +1,128 @@
fn main() {
println!("Hello, world!");
use std::{error::Error, io};
use ratatui::{
Terminal,
backend::{Backend, CrosstermBackend},
crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
},
};
mod app;
mod ui;
use crate::{
app::{App, CurrentScreen, CurrentlyEditing},
ui::ui,
};
fn main() -> Result<(), Box<dyn Error>> {
enable_raw_mode()?;
let mut stderr = io::stderr();
execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stderr);
let mut terminal = Terminal::new(backend)?;
let mut app = App::new();
let res = run_app(&mut terminal, &mut app);
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Ok(do_print) = res {
if do_print {
app.print_json()?;
}
} else if let Err(err) = res {
println!("{err:?}");
}
Ok(())
}
fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<bool> {
loop {
terminal.draw(|f| ui(f, app))?;
if let Event::Key(key) = event::read()? {
if key.kind == event::KeyEventKind::Release {
continue;
}
match app.current_screen {
CurrentScreen::Main => match key.code {
KeyCode::Char('e') => {
app.current_screen = CurrentScreen::Editing;
app.currently_editing = Some(CurrentlyEditing::Key);
}
KeyCode::Char('q') => {
app.current_screen = CurrentScreen::Exiting;
}
_ => {}
},
CurrentScreen::Exiting => match key.code {
KeyCode::Char('y') => {
return Ok(true);
}
KeyCode::Char('n') | KeyCode::Char('q') => {
return Ok(false);
}
_ => {}
},
CurrentScreen::Editing if key.kind == KeyEventKind::Press => match key.code {
KeyCode::Enter => {
if let Some(editing) = &app.currently_editing {
match editing {
CurrentlyEditing::Key => {
app.currently_editing = Some(CurrentlyEditing::Value);
}
CurrentlyEditing::Value => {
app.save_key_value();
app.current_screen = CurrentScreen::Main;
}
}
}
}
KeyCode::Backspace => {
if let Some(editing) = &app.currently_editing {
match editing {
CurrentlyEditing::Key => {
app.key_input.pop();
}
CurrentlyEditing::Value => {
app.value_input.pop();
}
}
}
}
KeyCode::Esc => {
app.current_screen = CurrentScreen::Main;
app.currently_editing = None;
}
KeyCode::Tab => {
app.toggle_editing();
}
KeyCode::Char(value) => {
if let Some(editing) = &app.currently_editing {
match editing {
CurrentlyEditing::Key => {
app.key_input.push(value);
}
CurrentlyEditing::Value => {
app.value_input.push(value);
}
}
}
}
_ => {}
},
_ => {}
}
}
}
}
+172
View File
@@ -0,0 +1,172 @@
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
text::{Line, Span, Text},
widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap},
};
use crate::app::{App, CurrentScreen, CurrentlyEditing};
pub fn ui(frame: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(1),
Constraint::Length(3),
])
.split(frame.area());
let title_block = Block::default()
.borders(Borders::ALL)
.style(Style::default());
let title = Paragraph::new(Text::styled(
"Create New Json",
Style::default().fg(Color::Green),
))
.block(title_block);
frame.render_widget(title, chunks[0]);
let mut list_items = Vec::<ListItem>::new();
for key in app.pairs.keys() {
list_items.push(ListItem::new(Line::from(Span::styled(
format!("{: <25} : {}", key, app.pairs.get(key).unwrap()),
Style::default().fg(Color::Yellow),
))));
}
let list = List::new(list_items);
frame.render_widget(list, chunks[1]);
let current_navigation_text = vec![
match app.current_screen {
CurrentScreen::Main => Span::styled("Normal Mode", Style::default().fg(Color::Green)),
CurrentScreen::Editing => {
Span::styled("Editing Mode", Style::default().fg(Color::Yellow))
}
CurrentScreen::Exiting => Span::styled("Exiting", Style::default().fg(Color::LightRed)),
}
.to_owned(),
Span::styled(" | ", Style::default().fg(Color::White)),
{
if let Some(editing) = &app.currently_editing {
match editing {
CurrentlyEditing::Key => {
Span::styled("Editing Json Key", Style::default().fg(Color::Green))
}
CurrentlyEditing::Value => {
Span::styled("Editing Json Value", Style::default().fg(Color::LightGreen))
}
}
} else {
Span::styled("Not Editing Anything", Style::default().fg(Color::DarkGray))
}
},
];
let mode_footer = Paragraph::new(Line::from(current_navigation_text))
.block(Block::default().borders(Borders::ALL));
let current_keys_hint = {
match app.current_screen {
CurrentScreen::Main => Span::styled(
"(q) to quit / (e) to make new pair",
Style::default().fg(Color::Red),
),
CurrentScreen::Editing => Span::styled(
"(ESC) to cancel/(Tab) to switch boxes/enter to complete",
Style::default().fg(Color::Red),
),
CurrentScreen::Exiting => Span::styled(
"(q) to quit / (e) to make new pair",
Style::default().fg(Color::Red),
),
}
};
let key_notes_footer =
Paragraph::new(Line::from(current_keys_hint)).block(Block::default().borders(Borders::ALL));
let footer_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(chunks[2]);
frame.render_widget(mode_footer, footer_chunks[0]);
frame.render_widget(key_notes_footer, footer_chunks[1]);
if let Some(editing) = &app.currently_editing {
let popup_block = Block::default()
.title("Enter a new key-value pair")
.borders(Borders::NONE)
.style(Style::default().bg(Color::DarkGray));
let area = centered_rect(60, 25, frame.area());
frame.render_widget(popup_block, area);
let popup_chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(1)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(area);
let mut key_block = Block::default().title("Key").borders(Borders::ALL);
let mut value_block = Block::default().title("Value").borders(Borders::ALL);
let active_style = Style::default().bg(Color::LightYellow).fg(Color::Black);
match editing {
CurrentlyEditing::Key => key_block = key_block.style(active_style),
CurrentlyEditing::Value => value_block = value_block.style(active_style),
};
let key_text = Paragraph::new(app.key_input.clone()).block(key_block);
frame.render_widget(key_text, popup_chunks[0]);
let value_text = Paragraph::new(app.value_input.clone()).block(value_block);
frame.render_widget(value_text, popup_chunks[1]);
}
if let CurrentScreen::Exiting = app.current_screen {
frame.render_widget(Clear, frame.area());
let popup_block = Block::default()
.title("Y/N")
.borders(Borders::NONE)
.style(Style::default().bg(Color::DarkGray));
let exit_text = Text::styled(
"Would you like to output the buffer as json? (y/n)",
Style::default().fg(Color::Red),
);
let exit_paragraph = Paragraph::new(exit_text)
.block(popup_block)
.wrap(Wrap { trim: false });
let area = centered_rect(60, 25, frame.area());
frame.render_widget(exit_paragraph, area);
}
}
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1]
}