mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-22 04:39:48 -04:00
feat: UI styling and loading files
This commit is contained in:
parent
63d8c47c1f
commit
43956ac0eb
3
examples/test.csv
Normal file
3
examples/test.csv
Normal file
@ -0,0 +1,3 @@
|
||||
pi,3^5,"ref(0,0)",-(1/0)
|
||||
12%5,"pow(3,5)",0/NaN,"""Apollo"""
|
||||
A1+A2,"if(true , sqrt(25),round(if(false,1.1,2.5)))",D2+1969,
|
|
42
src/main.rs
42
src/main.rs
@ -1,17 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||
use ratatui::{
|
||||
self,
|
||||
layout::{Constraint, Layout},
|
||||
widgets::{Table, Tabs},
|
||||
Frame,
|
||||
};
|
||||
use sheet::{Address, CellValue, Tbl};
|
||||
use ratatui;
|
||||
|
||||
mod sheet;
|
||||
mod ui;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
@ -20,9 +14,9 @@ pub struct Args {
|
||||
workbook: PathBuf,
|
||||
}
|
||||
|
||||
fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> {
|
||||
fn run(terminal: &mut ratatui::DefaultTerminal, name: PathBuf) -> std::io::Result<()> {
|
||||
loop {
|
||||
terminal.draw(|frame| draw(frame))?;
|
||||
terminal.draw(|frame| ui::draw(frame, &name))?;
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
return Ok(());
|
||||
@ -31,36 +25,12 @@ fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_default_table<'a>() -> Table<'a> {
|
||||
let mut tbl = Tbl::new();
|
||||
tbl.update_entry(Address::new(5, 5), CellValue::text("5,5"))
|
||||
.context("Failed updating entry at 5,5")
|
||||
.unwrap();
|
||||
tbl.update_entry(Address::new(10, 10), CellValue::float(10.10))
|
||||
.context("Failed updating entry at 10,10")
|
||||
.unwrap();
|
||||
tbl.update_entry(Address::new(0, 0), CellValue::other("0.0"))
|
||||
.context("Failed updating entry at 0,0")
|
||||
.unwrap();
|
||||
tbl.into()
|
||||
}
|
||||
|
||||
fn draw(frame: &mut Frame) {
|
||||
use Constraint::{Min, Percentage};
|
||||
let table = generate_default_table();
|
||||
let tabs = Tabs::new(vec!["sheet1"]).select(0);
|
||||
let rects = Layout::vertical([Min(1), Percentage(90)]).split(frame.area());
|
||||
|
||||
frame.render_widget(tabs, rects[0]);
|
||||
frame.render_widget(table, rects[1]);
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let _ = Args::parse();
|
||||
let args = Args::parse();
|
||||
|
||||
let mut terminal = ratatui::init();
|
||||
terminal.clear()?;
|
||||
let app_result = run(&mut terminal);
|
||||
let app_result = run(&mut terminal, args.workbook);
|
||||
ratatui::restore();
|
||||
app_result
|
||||
}
|
||||
|
0
src/mod.rs
Normal file
0
src/mod.rs
Normal file
@ -8,7 +8,6 @@
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use csvx;
|
||||
use ratatui::widgets::{Cell, Row, Table};
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
@ -16,7 +15,7 @@ pub enum CellValue {
|
||||
Text(String),
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
Other(String),
|
||||
Formula(String),
|
||||
}
|
||||
|
||||
impl CellValue {
|
||||
@ -25,7 +24,7 @@ impl CellValue {
|
||||
CellValue::Text(v) => format!("\"{}\"", v),
|
||||
CellValue::Float(v) => format!("{}", v),
|
||||
CellValue::Integer(v) => format!("{}", v),
|
||||
CellValue::Other(v) => format!("{}", v),
|
||||
CellValue::Formula(v) => format!("{}", v),
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,8 +32,8 @@ impl CellValue {
|
||||
CellValue::Text(Into::<String>::into(value))
|
||||
}
|
||||
|
||||
pub fn other<S: Into<String>>(value: S) -> CellValue {
|
||||
CellValue::Other(Into::<String>::into(value))
|
||||
pub fn formula<S: Into<String>>(value: S) -> CellValue {
|
||||
CellValue::Formula(Into::<String>::into(value))
|
||||
}
|
||||
|
||||
pub fn float(value: f64) -> CellValue {
|
||||
@ -61,14 +60,12 @@ impl Address {
|
||||
|
||||
/// A single table of addressable computable values.
|
||||
pub struct Tbl {
|
||||
csv: csvx::Table,
|
||||
pub csv: csvx::Table,
|
||||
}
|
||||
|
||||
impl Tbl {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
csv: csvx::Table::new("").unwrap(),
|
||||
}
|
||||
Self::from_str("").unwrap()
|
||||
}
|
||||
|
||||
pub fn dimensions(&self) -> (usize, usize) {
|
||||
@ -108,20 +105,5 @@ impl Tbl {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> From<Tbl> for Table<'t> {
|
||||
fn from(value: Tbl) -> Self {
|
||||
let rows: Vec<Row> = value
|
||||
.csv
|
||||
.get_calculated_table()
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let cells = r.iter().map(|v| Cell::new(format!("{}", v)));
|
||||
Row::new(cells)
|
||||
})
|
||||
.collect();
|
||||
Table::default().rows(rows)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
131
src/ui/mod.rs
Normal file
131
src/ui/mod.rs
Normal file
@ -0,0 +1,131 @@
|
||||
//! Ui rendering logic
|
||||
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
|
||||
use super::sheet::{Address, CellValue, Tbl};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use ratatui::{
|
||||
self,
|
||||
layout::{Constraint, Flex, Layout},
|
||||
style::{Color, Stylize},
|
||||
text::Text,
|
||||
widgets::{Block, Cell, Row, Table, Tabs, Widget},
|
||||
Frame,
|
||||
};
|
||||
|
||||
pub struct Workspace {
|
||||
name: String,
|
||||
tbl: Tbl,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn new<S: Into<String>>(tbl: Tbl, name: S) -> Self {
|
||||
Self {
|
||||
tbl,
|
||||
name: name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(path: &PathBuf) -> Result<Self> {
|
||||
let mut f = File::open(path)?;
|
||||
let mut buf = Vec::new();
|
||||
let _ = f.read_to_end(&mut buf)?;
|
||||
let input = String::from_utf8(buf)
|
||||
.context(format!("Error reading file: {:?}", path))?;
|
||||
let tbl = Tbl::from_str(input)?;
|
||||
Ok(Workspace::new(tbl, path.file_name().map(|p| p.to_string_lossy().to_string()).unwrap_or_else(|| "Unknown".to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Workspace {
|
||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
use Constraint::{Min, Percentage};
|
||||
let rects = Layout::vertical([Min(1), Percentage(90)]).split(area);
|
||||
let table = Table::from(&self.tbl);
|
||||
let tabs = Tabs::new(vec![self.name.clone()]).select(0);
|
||||
|
||||
tabs.render(rects[0], buf);
|
||||
table.render(rects[1], buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_default_table<'a>() -> Tbl {
|
||||
let mut tbl = Tbl::new();
|
||||
tbl.update_entry(Address::new(3, 3), CellValue::text("3,3"))
|
||||
.context("Failed updating entry at 5,5")
|
||||
.expect("Unexpected fail to update entry");
|
||||
tbl.update_entry(Address::new(6, 6), CellValue::float(6.6))
|
||||
.context("Failed updating entry at 10,10")
|
||||
.expect("Unexpected fail to update entry");
|
||||
tbl.update_entry(Address::new(0, 0), CellValue::formula("0.0"))
|
||||
.context("Failed updating entry at 0,0")
|
||||
.expect("Unexpected fail to update entry");
|
||||
tbl.update_entry(Address::new(1, 0), CellValue::formula("1.0"))
|
||||
.context("Failed updating entry at 0,0")
|
||||
.expect("Unexpected fail to update entry");
|
||||
tbl.update_entry(Address::new(2, 0), CellValue::formula("2.0"))
|
||||
.context("Failed updating entry at 0,0")
|
||||
.expect("Unexpected fail to update entry");
|
||||
tbl
|
||||
}
|
||||
|
||||
const COLNAMES: [&'static str; 27] = [
|
||||
"", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
|
||||
"S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
];
|
||||
|
||||
impl<'t> From<&Tbl> for Table<'t> {
|
||||
fn from(value: &Tbl) -> Self {
|
||||
let (_, cols) = value.dimensions();
|
||||
let rows: Vec<Row> = value
|
||||
.csv
|
||||
.get_calculated_table()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, r)| {
|
||||
let cells = vec![Cell::new(format!("{}", i))]
|
||||
.into_iter()
|
||||
.chain(r.iter().map(|v| {
|
||||
let content = format!("{}", v);
|
||||
Cell::new(Text::raw(content))
|
||||
.bg(if i % 2 == 0 {
|
||||
Color::Rgb(57, 61, 71)
|
||||
} else {
|
||||
Color::Rgb(165, 169, 160)
|
||||
})
|
||||
.fg(if i % 2 == 0 {
|
||||
Color::White
|
||||
} else {
|
||||
Color::Rgb(31,32,34)
|
||||
})
|
||||
.underlined()
|
||||
.bold()
|
||||
}));
|
||||
Row::new(cells)
|
||||
})
|
||||
.collect();
|
||||
// TODO(zaphar): Handle the double letter column names
|
||||
let header: Vec<Cell> = (0..=cols).map(|i| Cell::new(COLNAMES[i % 26])).collect();
|
||||
let mut constraints: Vec<Constraint> = Vec::new();
|
||||
constraints.push(Constraint::Max(5));
|
||||
for _ in 0..cols {
|
||||
constraints.push(Constraint::Min(5));
|
||||
}
|
||||
Table::new(rows, constraints)
|
||||
.block(Block::bordered())
|
||||
.header(Row::new(header).underlined())
|
||||
.column_spacing(1)
|
||||
.flex(Flex::SpaceAround)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(frame: &mut Frame, name: &PathBuf) {
|
||||
let table = generate_default_table();
|
||||
let ws = Workspace::load(name).unwrap();
|
||||
|
||||
frame.render_widget(ws, frame.area());
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user