tui test and fix permissions

This commit is contained in:
tezlm 2024-02-16 18:14:26 -08:00
parent 820abbc089
commit 3f652d13a8
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
8 changed files with 367 additions and 203 deletions

234
Cargo.lock generated
View file

@ -359,6 +359,21 @@ dependencies = [
"thiserror",
]
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.0.83"
@ -526,6 +541,19 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642"
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"static_assertions",
]
[[package]]
name = "concurrent-queue"
version = "2.4.0"
@ -625,6 +653,31 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.4.2",
"crossterm_winapi",
"libc",
"mio",
"parking_lot 0.12.1",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crunchy"
version = "0.2.2"
@ -778,6 +831,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "equivalent"
version = "1.0.1"
@ -1072,7 +1131,7 @@ checksum = "5e87caa7459145f5e5f167bf34db4532901404c679e62339fb712a0e3ccf722a"
dependencies = [
"cosmic-text",
"etagere",
"lru",
"lru 0.11.1",
"wgpu",
]
@ -1408,6 +1467,12 @@ dependencies = [
"hashbrown 0.14.3",
]
[[package]]
name = "indoc"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
[[package]]
name = "instant"
version = "0.1.12"
@ -1431,6 +1496,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.10"
@ -1572,6 +1646,15 @@ dependencies = [
"hashbrown 0.14.3",
]
[[package]]
name = "lru"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22"
dependencies = [
"hashbrown 0.14.3",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@ -1985,6 +2068,12 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -2194,6 +2283,26 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "795915a3930a5d6bafd9053d37602fea3e61be2e5d4d788983a8ba9654c1c6f2"
[[package]]
name = "ratatui"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8"
dependencies = [
"bitflags 2.4.2",
"cassowary",
"compact_str",
"crossterm",
"indoc",
"itertools",
"lru 0.12.2",
"paste",
"stability",
"strum",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "raw-window-handle"
version = "0.5.2"
@ -2319,6 +2428,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "rustybuzz"
version = "0.8.0"
@ -2415,6 +2530,36 @@ dependencies = [
"digest",
]
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "2.2.0"
@ -2563,6 +2708,16 @@ dependencies = [
"der",
]
[[package]]
name = "stability"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -2587,6 +2742,28 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "strum"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.48",
]
[[package]]
name = "subtle"
version = "2.5.0"
@ -2781,6 +2958,28 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
[[package]]
name = "tui"
version = "0.1.0"
dependencies = [
"anyhow",
"crossterm",
"dag-resolve",
"dag-resolve-impls",
"ratatui",
"tui-term",
]
[[package]]
name = "tui-term"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a3a054e8a92d4b51f9c28e04712e18ef3721ea25552577e944ee9870cb7445b"
dependencies = [
"ratatui",
"vt100",
]
[[package]]
name = "twox-hash"
version = "1.6.3"
@ -2882,6 +3081,39 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vt100"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de"
dependencies = [
"itoa",
"log",
"unicode-width",
"vte",
]
[[package]]
name = "vte"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
dependencies = [
"arrayvec",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View file

@ -48,7 +48,7 @@ struct State<R: Resolver> {
impl<R: Resolver> State<R> {
fn create_event(&mut self, content: CoreContent<R::EventType>) -> Result<()> {
match self.room.create_event(content, &self.secret) {
match self.room.create_event(&mut *self.store, content, &self.secret) {
Ok(event) => {
self.db.execute(
"INSERT INTO _events (id, json) VALUES (?, ?)",
@ -62,7 +62,7 @@ impl<R: Resolver> State<R> {
}
fn append_event(&mut self, event: Event<R::EventType>) -> Result<()> {
match self.room.append_event(event) {
match self.room.append_event(&mut *self.store, event) {
Ok(event) => {
self.db.execute(
"INSERT INTO _events (id, json) VALUES (?, ?)",
@ -144,16 +144,20 @@ fn open(path: impl AsRef<Path>) -> Result<Opened> {
CoreContent::Create(c) => match c.resolver.as_str() {
"kv" => {
let mut room = Room::from_root(KVResolver, serde_json::from_str(&event_json)?)?;
let mut events = vec![];
while let Some(row) = rows.next()? {
let s: String = row.get(0)?;
let ev = serde_json::from_str(&s)?;
room.append_event(ev)?;
events.push(ev);
}
drop(event);
drop(rows);
drop(q);
let db = Rc::new(db);
let store = Database::from_conn(db.clone()).init()?;
let mut store = Database::from_conn(db.clone()).init()?;
for event in events {
room.append_event(&mut *store, event)?;
}
Ok(Opened::Kv(State {
db,
room,
@ -163,16 +167,20 @@ fn open(path: impl AsRef<Path>) -> Result<Opened> {
},
"forum-v0" => {
let mut room = Room::from_root(ForumResolver, serde_json::from_str(&event_json)?)?;
let mut events = vec![];
while let Some(row) = rows.next()? {
let s: String = row.get(0)?;
let ev = serde_json::from_str(&s)?;
room.append_event(ev)?;
events.push(ev);
}
drop(event);
drop(rows);
drop(q);
let db = Rc::new(db);
let store = Database::from_conn(db.clone()).init()?;
let mut store = Database::from_conn(db.clone()).init()?;
for event in events {
room.append_event(&mut *store, event)?;
}
Ok(Opened::Forum(State {
db,
room,

View file

@ -188,7 +188,7 @@ impl Resolver for ForumResolver {
]
}
CoreContent::Custom(ForumEventContent::Member { id, acl }) => {
let author = event.author().to_string().as_bytes().to_vec();
let author = id.to_string().as_bytes().to_vec();
let key: Vec<_> = b"member\xff".into_iter().copied().chain(author).collect();
let value = acl.to_str().as_bytes();
Command::put(key, value)

View file

@ -1,9 +1,8 @@
//! Store state in a sqlite database
use std::{collections::HashMap, path::Path, rc::Rc};
use std::{path::Path, rc::Rc};
use dag_resolve::proto::table::{self, Database as _};
use rusqlite::params_from_iter;
use thiserror::Error;
#[derive(Debug)]
@ -28,7 +27,7 @@ impl Database {
}
}
pub fn init(mut self) -> Result<Box<Self>, Error> {
pub fn init(self) -> Result<Box<Self>, Error> {
self.reset();
Ok(Box::new(self))
}
@ -64,16 +63,17 @@ impl table::Database for Database {
").unwrap();
}
fn query(&self, selector: table::Selector) -> Self::Query {
fn query(&self, _selector: table::Selector) -> Self::Query {
todo!()
}
fn query_reverse(&self, selector: table::Selector) -> Self::Query {
fn query_reverse(&self, _selector: table::Selector) -> Self::Query {
todo!()
}
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
todo!()
let mut s = self.connection.prepare("SELECT value FROM data WHERE key = ?").unwrap();
s.query_row([key.to_vec()], |f| f.get(0)).ok()
}
fn put(&self, key: &[u8], value: &[u8]) {
@ -81,190 +81,7 @@ impl table::Database for Database {
s.execute((key, value)).unwrap();
}
fn delete(&self, key: &[u8]) {
fn delete(&self, _key: &[u8]) {
todo!()
}
}
// impl table::Table for Table {
// type Err = Error;
// fn all(&self) -> Result<Vec<table::TableRow>, Self::Err> {
// let mut sql = String::from("SELECT * FROM '");
// sql.push_str(&self.name.replace('\'', "''"));
// sql.push('\'');
// let mut sql_query = self.connection.prepare(&sql).unwrap();
// let mut sql_rows = sql_query.query([]).unwrap();
// let mut rows = vec![];
// while let Some(sql_row) = sql_rows.next().unwrap() {
// let mut map = HashMap::new();
// for (column_name, column_type) in &self.config.columns {
// let val = match column_type {
// table::ColumnType::String => {
// table::ColumnValue::String(sql_row.get(column_name.as_str()).unwrap())
// }
// table::ColumnType::Text => todo!(),
// table::ColumnType::Integer => {
// table::ColumnValue::Integer(sql_row.get(column_name.as_str()).unwrap())
// }
// table::ColumnType::Float => {
// table::ColumnValue::Float(sql_row.get(column_name.as_str()).unwrap())
// }
// table::ColumnType::Actor => todo!(),
// table::ColumnType::Event => todo!(),
// };
// map.insert(column_name.to_string(), val);
// }
// rows.push(table::TableRow { values: map });
// }
// Ok(rows)
// }
// fn lookup(
// &self,
// column: &str,
// value: impl Into<table::ColumnValue>,
// ) -> Result<Vec<table::TableRow>, Self::Err> {
// let Some(column_config) = self.config.columns.get(column) else {
// panic!("does not exist");
// };
// let has_index = self.config.indexes.iter().any(|idx| {
// idx.column == column
// && matches!(
// idx.index_type,
// table::IndexType::Lookup | table::IndexType::LookupUnique
// )
// });
// if !has_index {
// panic!("no lookup index");
// }
// let mut sql = String::from("SELECT * FROM ");
// sql.push_str(&self.name.replace('\'', "''"));
// sql.push_str(" WHERE ");
// sql.push_str(&column.replace('\'', "''"));
// sql.push_str(" = ");
// match (column_config, value.into()) {
// (table::ColumnType::String, table::ColumnValue::String(s)) => {
// sql.push('\'');
// sql.push_str(&s.replace('\'', "''"));
// sql.push('\'');
// }
// (table::ColumnType::Integer, table::ColumnValue::Integer(i)) => {
// sql.push_str(&i.to_string());
// }
// (_, _) => todo!(),
// };
// let mut sql_query = self.connection.prepare(&sql).unwrap();
// let mut sql_rows = sql_query.query([]).unwrap();
// let mut rows = vec![];
// while let Some(sql_row) = sql_rows.next().unwrap() {
// let mut map = HashMap::new();
// for (column_name, column_type) in &self.config.columns {
// let val = match column_type {
// table::ColumnType::String => {
// table::ColumnValue::String(sql_row.get(column_name.as_str()).unwrap())
// }
// table::ColumnType::Text => todo!(),
// table::ColumnType::Integer => {
// table::ColumnValue::Integer(sql_row.get(column_name.as_str()).unwrap())
// }
// table::ColumnType::Float => {
// table::ColumnValue::Float(sql_row.get(column_name.as_str()).unwrap())
// }
// table::ColumnType::Event => todo!(),
// table::ColumnType::Actor => todo!(),
// };
// map.insert(column_name.to_string(), val);
// }
// rows.push(table::TableRow { values: map });
// }
// Ok(rows)
// }
// fn lookup_one(
// &self,
// column: &str,
// value: impl Into<table::ColumnValue>,
// ) -> Result<table::TableRow, Self::Err> {
// Ok(self
// .lookup(column, value)
// .unwrap()
// .into_iter()
// .next()
// .unwrap())
// }
// fn lookup_optional(
// &self,
// column: &str,
// value: impl Into<table::ColumnValue>,
// ) -> Result<Option<table::TableRow>, Self::Err> {
// Ok(self.lookup(column, value).unwrap().into_iter().next())
// }
// fn range(
// &self,
// _column: &str,
// _paginate: table::Paginate,
// _limit: u64,
// ) -> Result<Vec<table::TableRow>, Self::Err> {
// todo!()
// }
// fn search_text(
// &self,
// _column: &str,
// _search: &str,
// _limit: u64,
// _after: u64,
// ) -> Result<Vec<table::TableRow>, Self::Err> {
// todo!()
// }
// fn insert(&mut self, mut row: table::TableRow) -> Result<(), Self::Err> {
// let mut sql = String::from("INSERT INTO ");
// sql.push_str(&self.name.replace('\'', "''"));
// sql.push_str(" (");
// for (idx, name) in self.config.columns.keys().enumerate() {
// if idx != 0 {
// sql.push_str(", ");
// }
// sql.push_str(&name.replace('\'', "''"));
// }
// sql.push_str(") VALUES (");
// use rusqlite::types::Value;
// let mut params: Vec<Value> = vec![];
// for (idx, (name, column_type)) in self.config.columns.iter().enumerate() {
// if idx != 0 {
// sql.push_str(", ");
// }
// sql.push('?');
// match (column_type, row.values.remove(name).unwrap()) {
// (table::ColumnType::Text, table::ColumnValue::Text(t)) => {
// params.push(Value::Text(t.0))
// }
// (table::ColumnType::String, table::ColumnValue::String(s)) => {
// params.push(Value::Text(s))
// }
// (table::ColumnType::Integer, table::ColumnValue::Integer(i)) => {
// params.push(Value::Integer(i.try_into().unwrap()))
// }
// (table::ColumnType::Float, table::ColumnValue::Float(r)) => {
// params.push(Value::Real(r))
// }
// (table::ColumnType::Event, table::ColumnValue::Event(event_id)) => {
// params.push(Value::Text(event_id.to_string()))
// }
// (table::ColumnType::Actor, table::ColumnValue::Actor(actor_id)) => {
// params.push(Value::Text(actor_id.to_string()))
// }
// (column_type, column_value) => todo!("column_type={column_type:?} with value {column_value:?}"),
// };
// }
// sql.push(')');
// self.connection
// .execute(&sql, params_from_iter(params))
// .unwrap();
// Ok(())
// }
// }

View file

@ -3,11 +3,13 @@ use crate::{
event::EventId,
event::{CoreContent, CreateContent, Event, HashType, SignatureType},
proto,
resolver::{sort, Resolver, Command},
resolver::{sort, Command, Resolver, Verification},
Error, Result,
};
use std::fmt::Debug;
use super::table::Database;
#[derive(Debug)]
pub struct Room<R: Resolver> {
pub events: Vec<Event<R::EventType>>,
@ -81,8 +83,9 @@ impl<R: Resolver> Room<R> {
}
}
pub fn create_event(
pub fn create_event<D: Database>(
&mut self,
state: &mut D,
event_content: CoreContent<R::EventType>,
secret: &ActorSecret,
) -> Result<&Event<R::EventType>> {
@ -90,11 +93,16 @@ impl<R: Resolver> Room<R> {
.with_references(std::mem::take(&mut self.heads))
.then_hash()?
.and_sign()?;
self.append_event(event)
self.append_event(state, event)
}
pub fn append_event(&mut self, event: Event<R::EventType>) -> Result<&Event<R::EventType>> {
pub fn append_event<D: Database>(&mut self, state: &mut D, event: Event<R::EventType>) -> Result<&Event<R::EventType>> {
event.verify_room(self).expect("event failed verification");
match self.get_resolver().verify(state, &event) {
Verification::Valid => {},
Verification::Unauthorized => panic!("unauthorized"),
Verification::Invalid => panic!("invalid data"),
}
if self.events.iter().any(|p| p == &event) {
return Err(Error::AlreadyExists);
}

View file

@ -6,7 +6,7 @@
//! This is a work in progress; the goal is trying to find a good
//! effort/payoff ratio in the database schema definition.
mod old;
// mod old;
mod kv;
// pub use old::*;

14
crates/tui/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "tui"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.79"
crossterm = "0.27.0"
dag-resolve = { version = "0.1.0", path = "../proto" }
dag-resolve-impls = { version = "0.1.0", path = "../impls" }
ratatui = "0.26.1"
tui-term = "0.1.8"

85
crates/tui/src/main.rs Normal file
View file

@ -0,0 +1,85 @@
use std::{io::stdout, time::Duration};
use anyhow::Result;
use crossterm::{
event::{self, KeyCode, KeyEventKind},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use ratatui::{
backend::CrosstermBackend,
widgets::{Block, Paragraph},
Frame, Terminal,
};
fn main() -> Result<()> {
enable_raw_mode()?;
stdout().execute(EnterAlternateScreen)?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
let mut state = State {
view: View::Base
};
loop {
terminal.draw(|f| ui(&state, f))?;
if let Some(message) = handle_events(&state)? {
match message {
Message::Quit => break,
Message::View(v) => state.view = v,
}
}
}
stdout().execute(LeaveAlternateScreen)?;
disable_raw_mode()?;
Ok(())
}
#[derive(Debug)]
enum Message {
Quit,
View(View),
}
#[derive(Debug)]
struct State {
view: View,
}
#[derive(Debug)]
enum View {
Base,
Help,
}
fn handle_events(state: &State) -> Result<Option<Message>> {
if event::poll(Duration::from_millis(50))? {
match event::read()? {
event::Event::Key(key) if key.kind == KeyEventKind::Press => match (&state.view, key.code) {
(View::Base, KeyCode::Char('q') | KeyCode::Esc) => return Ok(Some(Message::Quit)),
(View::Base, KeyCode::Char('h' | '?')) => {
return Ok(Some(Message::View(View::Help)))
}
(View::Help, KeyCode::Char('q')) => return Ok(Some(Message::View(View::Base))),
(_, _) => Ok(None)
},
_ => Ok(None)
}
} else {
Ok(None)
}
}
fn ui(state: &State, f: &mut Frame) {
match state.view {
View::Base => {
let p = Paragraph::new("hello world!").block(Block::bordered().title("hello"));
f.render_widget(p, f.size())
}
View::Help => {
let p = Paragraph::new("help info").block(Block::bordered().title("help"));
f.render_widget(p, f.size())
}
}
}