This commit is contained in:
parent
3a6b9713f9
commit
265f8d8dc3
5 changed files with 154 additions and 44 deletions
1
thecockpit/.gitignore
vendored
1
thecockpit/.gitignore
vendored
|
@ -4,3 +4,4 @@ Cargo.lock
|
||||||
bin/
|
bin/
|
||||||
pkg/
|
pkg/
|
||||||
wasm-pack.log
|
wasm-pack.log
|
||||||
|
.env
|
||||||
|
|
|
@ -17,10 +17,12 @@ path = "src/main.rs"
|
||||||
required-features = ["ratatui/default"]
|
required-features = ["ratatui/default"]
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
tokio = { version = "1.47.0", features = ["rt", "macros"] }
|
||||||
ratatui = { version = "0.29.0", default-features = false }
|
ratatui = { version = "0.29.0", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
wasm-bindgen = "0.2.84"
|
wasm-bindgen = "0.2.84"
|
||||||
|
wasm-bindgen-futures = "0.4.50"
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
@ -37,9 +39,13 @@ wasm-bindgen-test = "0.3.34"
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
dotenvy_macro = "0.15.7"
|
||||||
|
futures-util = "0.3.31"
|
||||||
getrandom = { version = "0.3.3", features = ["wasm_js"] }
|
getrandom = { version = "0.3.3", features = ["wasm_js"] }
|
||||||
heapless = "0.8.0"
|
heapless = "0.8.0"
|
||||||
rand = { version = "0.9.2", default-features = false, features = ["os_rng", "std"] }
|
rand = { version = "0.9.2", default-features = false, features = ["os_rng", "std"] }
|
||||||
|
reqwest = { version = "0.12.22", features = ["json"] }
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
tachyonfx = { version = "0.16.0", default-features = false, features = ["web-time"] }
|
tachyonfx = { version = "0.16.0", default-features = false, features = ["web-time"] }
|
||||||
web-time = "1.1.0"
|
web-time = "1.1.0"
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
future::Future,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
mpsc::{self, Receiver, Sender},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::{rngs::OsRng, seq::IndexedRandom, TryRngCore};
|
use rand::{rngs::OsRng, seq::IndexedRandom, TryRngCore};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use ratzilla::ratatui;
|
use ratzilla::ratatui;
|
||||||
|
|
||||||
|
use dotenvy_macro::dotenv;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Style, Stylize},
|
style::{Color, Style, Stylize},
|
||||||
widgets::{Block, Borders, Paragraph, Tabs},
|
widgets::{Block, Borders, Paragraph, Tabs},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
use tachyonfx::{fx, Effect, Interpolation, Shader};
|
use tachyonfx::{fx, Effect, Interpolation, Shader};
|
||||||
use web_time::Instant;
|
use web_time::Instant;
|
||||||
|
|
||||||
|
@ -61,12 +67,43 @@ impl Shader for SelectedTab {
|
||||||
fn filter(&mut self, _filter: tachyonfx::CellFilter) {}
|
fn filter(&mut self, _filter: tachyonfx::CellFilter) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub trait AppExecutor {
|
||||||
|
fn execute<T: 'static>(&self, future: impl Future<Output = T> + 'static, sender: Sender<T>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pending<T> {
|
||||||
|
rx: Receiver<T>,
|
||||||
|
resolved: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Pending<T> {
|
||||||
|
pub fn new(rx: Receiver<T>) -> Self {
|
||||||
|
Self { rx, resolved: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolved(&mut self) -> &Option<T> {
|
||||||
|
if self.resolved.is_some() {
|
||||||
|
return &self.resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.resolved = self.rx.try_recv().ok();
|
||||||
|
&self.resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WeatherInfo {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App<E: AppExecutor> {
|
||||||
tabs: [&'static str; 3],
|
tabs: [&'static str; 3],
|
||||||
transition_instant: Instant,
|
transition_instant: Instant,
|
||||||
selected_tab: SelectedTab,
|
selected_tab: SelectedTab,
|
||||||
password_locker: PasswordLocker,
|
password_locker: PasswordLocker,
|
||||||
current_effect: Effect,
|
current_effect: Effect,
|
||||||
|
weather: Pending<WeatherInfo>,
|
||||||
|
executor: E,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SPLASH: &str = r#"
|
const SPLASH: &str = r#"
|
||||||
|
@ -109,14 +146,27 @@ const TABS: [&'static str; 10] = [
|
||||||
"Goobers",
|
"Goobers",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl App {
|
impl<E: AppExecutor> App<E> {
|
||||||
pub fn new() -> Self {
|
pub fn new(executor: E) -> Self {
|
||||||
|
let (weather_tx, weather_rx) = mpsc::channel();
|
||||||
|
executor.execute(async move {
|
||||||
|
let weather_info: WeatherInfo = reqwest::get(concat!("https://api.openweathermap.org/data/2.5/weather?lat=40.7660851712019&lon=-111.89066476757807&appid=", dotenv!("OPENWEATHERMAP_API_KEY")))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
weather_info
|
||||||
|
},
|
||||||
|
weather_tx);
|
||||||
Self {
|
Self {
|
||||||
tabs: TABS.choose_multiple_array(&mut OsRng.unwrap_err()).unwrap(),
|
tabs: TABS.choose_multiple_array(&mut OsRng.unwrap_err()).unwrap(),
|
||||||
transition_instant: Instant::now(),
|
transition_instant: Instant::now(),
|
||||||
selected_tab: SelectedTab::default(),
|
selected_tab: SelectedTab::default(),
|
||||||
password_locker: PasswordLocker::default(),
|
password_locker: PasswordLocker::default(),
|
||||||
current_effect: fx::fade_from_fg(Color::Black, (0, Interpolation::CircIn)),
|
current_effect: fx::fade_from_fg(Color::Black, (0, Interpolation::CircIn)),
|
||||||
|
weather: Pending::new(weather_rx),
|
||||||
|
executor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,13 +212,32 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_first_tab(&mut self, frame: &mut Frame, layout: Rect) {
|
pub fn draw_first_tab(&mut self, frame: &mut Frame, layout: Rect) {
|
||||||
|
let layout = Layout::new(
|
||||||
|
Direction::Horizontal,
|
||||||
|
[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)],
|
||||||
|
)
|
||||||
|
.split(layout);
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new(
|
||||||
|
self.weather
|
||||||
|
.resolved()
|
||||||
|
.as_ref()
|
||||||
|
.map(|weather| weather.name.as_str())
|
||||||
|
.unwrap_or("Loading"),
|
||||||
|
)
|
||||||
|
.alignment(ratatui::layout::Alignment::Center)
|
||||||
|
.fg(Color::Rgb(226, 190, 89))
|
||||||
|
.bg(Color::Black)
|
||||||
|
.block(Block::new().title("Weather").borders(Borders::all())),
|
||||||
|
layout[0],
|
||||||
|
);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(SPLASH)
|
Paragraph::new(SPLASH)
|
||||||
.alignment(ratatui::layout::Alignment::Center)
|
.alignment(ratatui::layout::Alignment::Center)
|
||||||
.fg(Color::Rgb(226, 190, 89))
|
.fg(Color::Rgb(226, 190, 89))
|
||||||
.bg(Color::Black)
|
.bg(Color::Black)
|
||||||
.block(Block::new().borders(Borders::all())),
|
.block(Block::new().borders(Borders::all())),
|
||||||
layout,
|
layout[1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,63 @@
|
||||||
use std::time::Duration;
|
use std::{future::Future, time::Duration};
|
||||||
|
|
||||||
|
use futures_util::FutureExt;
|
||||||
use ratatui::crossterm::event;
|
use ratatui::crossterm::event;
|
||||||
use thecockpit::app::App;
|
use thecockpit::app::{App, AppExecutor};
|
||||||
|
|
||||||
|
struct TokioExecutor;
|
||||||
|
|
||||||
|
impl AppExecutor for TokioExecutor {
|
||||||
|
fn execute<T: 'static>(
|
||||||
|
&self,
|
||||||
|
future: impl Future<Output = T> + 'static,
|
||||||
|
sender: std::sync::mpsc::Sender<T>,
|
||||||
|
) {
|
||||||
|
tokio::task::spawn_local(future.map(move |output| sender.send(output).unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut terminal = ratatui::init();
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
let mut app = App::new();
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
loop {
|
let local = tokio::task::LocalSet::new();
|
||||||
terminal.draw(|frame| app.draw(frame)).unwrap();
|
|
||||||
|
|
||||||
if event::poll(Duration::from_secs(0)).unwrap() {
|
local.spawn_local(async {
|
||||||
match event::read().unwrap() {
|
let mut terminal = ratatui::init();
|
||||||
event::Event::Key(event::KeyEvent {
|
let mut app = App::new(TokioExecutor);
|
||||||
code: event::KeyCode::Char('q'),
|
|
||||||
..
|
loop {
|
||||||
}) => break,
|
terminal.draw(|frame| app.draw(frame)).unwrap();
|
||||||
event::Event::Key(event::KeyEvent {
|
|
||||||
code: event::KeyCode::Left,
|
if event::poll(Duration::from_secs(0)).unwrap() {
|
||||||
..
|
match event::read().unwrap() {
|
||||||
}) => app.prev_tab(),
|
event::Event::Key(event::KeyEvent {
|
||||||
event::Event::Key(event::KeyEvent {
|
code: event::KeyCode::Char('q'),
|
||||||
code: event::KeyCode::Right,
|
..
|
||||||
..
|
}) => break,
|
||||||
}) => app.next_tab(),
|
event::Event::Key(event::KeyEvent {
|
||||||
event::Event::Key(event::KeyEvent {
|
code: event::KeyCode::Left,
|
||||||
code: event::KeyCode::Char(c),
|
..
|
||||||
..
|
}) => app.prev_tab(),
|
||||||
}) => app.add_char_to_number_guess(c),
|
event::Event::Key(event::KeyEvent {
|
||||||
_ => {}
|
code: event::KeyCode::Right,
|
||||||
|
..
|
||||||
|
}) => app.next_tab(),
|
||||||
|
event::Event::Key(event::KeyEvent {
|
||||||
|
code: event::KeyCode::Char(c),
|
||||||
|
..
|
||||||
|
}) => app.add_char_to_number_guess(c),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ratatui::restore();
|
tokio::task::yield_now().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
ratatui::restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
rt.block_on(local);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,36 @@
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, future::Future, rc::Rc};
|
||||||
|
|
||||||
|
use futures_util::FutureExt;
|
||||||
use ratzilla::{
|
use ratzilla::{
|
||||||
backend::canvas::CanvasBackendOptions,
|
backend::canvas::CanvasBackendOptions, event::KeyCode, ratatui::Terminal, CanvasBackend,
|
||||||
event::KeyCode,
|
WebRenderer,
|
||||||
ratatui::{
|
|
||||||
widgets::{Block, Borders},
|
|
||||||
Terminal,
|
|
||||||
},
|
|
||||||
web_sys, CanvasBackend, WebRenderer,
|
|
||||||
};
|
};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::{App, AppExecutor};
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn alert(s: &str);
|
fn alert(s: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WasmBindgenExecutor;
|
||||||
|
|
||||||
|
impl AppExecutor for WasmBindgenExecutor {
|
||||||
|
fn execute<T: 'static>(
|
||||||
|
&self,
|
||||||
|
future: impl Future<Output = T> + 'static,
|
||||||
|
sender: std::sync::mpsc::Sender<T>,
|
||||||
|
) {
|
||||||
|
wasm_bindgen_futures::spawn_local(future.map(move |output| sender.send(output).unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn run(grid_id: &str) {
|
pub fn run(grid_id: &str) {
|
||||||
console_error_panic_hook::set_once();
|
utils::set_panic_hook();
|
||||||
|
|
||||||
let backend = CanvasBackend::new_with_options(
|
let backend = CanvasBackend::new_with_options(
|
||||||
CanvasBackendOptions::new()
|
CanvasBackendOptions::new()
|
||||||
|
@ -32,7 +40,7 @@ pub fn run(grid_id: &str) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let terminal = Terminal::new(backend).unwrap();
|
let terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
let app = Rc::new(RefCell::new(App::new()));
|
let app = Rc::new(RefCell::new(App::new(WasmBindgenExecutor)));
|
||||||
|
|
||||||
terminal.on_key_event({
|
terminal.on_key_event({
|
||||||
let app = Rc::clone(&app);
|
let app = Rc::clone(&app);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue