Compare commits
No commits in common. "e26a83299c6a8b3e3ba4528634f2c7e2cd290ebf" and "e029e02cc277ea5447ca143b9987cfc12d9102a4" have entirely different histories.
e26a83299c
...
e029e02cc2
37 changed files with 626 additions and 958 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,2 @@
|
||||||
/target
|
/target
|
||||||
/*/target
|
|
||||||
.data/*.log
|
.data/*.log
|
||||||
|
|
|
||||||
1317
Cargo.lock
generated
1317
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -14,6 +14,6 @@
|
||||||
# along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
# along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["core", "deja_vu", "pe_menu"]
|
members = ["deja_vu", "pe_menu"]
|
||||||
default-members = ["deja_vu", "pe_menu"]
|
description = "Clone/Install Windows, create/edit boot files, and troubleshoot boot issues."
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
# This file is part of Deja-vu.
|
|
||||||
#
|
|
||||||
# Deja-vu is free software: you can redistribute it and/or modify it
|
|
||||||
# under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Deja-vu is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
# See the GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
[package]
|
|
||||||
name = "core"
|
|
||||||
authors = ["2Shirt <2xShirt@gmail.com>"]
|
|
||||||
edition = "2021"
|
|
||||||
license = "GPL"
|
|
||||||
version = "0.2.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
better-panic = "0.3.0"
|
|
||||||
clap = { version = "4.4.5", features = [
|
|
||||||
"derive",
|
|
||||||
"cargo",
|
|
||||||
"wrap_help",
|
|
||||||
"unicode",
|
|
||||||
"string",
|
|
||||||
"unstable-styles",
|
|
||||||
] }
|
|
||||||
color-eyre = "0.6.3"
|
|
||||||
config = "0.15.6"
|
|
||||||
crossterm = { version = "0.28.1", features = ["serde", "event-stream"] }
|
|
||||||
derive_deref = "1.1.1"
|
|
||||||
directories = "6.0.0"
|
|
||||||
futures = "0.3.30"
|
|
||||||
human-panic = "2.0.1"
|
|
||||||
json5 = "0.4.1"
|
|
||||||
lazy_static = "1.5.0"
|
|
||||||
libc = "0.2.158"
|
|
||||||
once_cell = "1.20.2"
|
|
||||||
pretty_assertions = "1.4.0"
|
|
||||||
ratatui = { version = "0.29.0", features = ["serde", "macros"] }
|
|
||||||
raw-cpuid = "11.2.0"
|
|
||||||
regex = "1.11.1"
|
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
|
||||||
serde_json = "1.0.125"
|
|
||||||
signal-hook = "0.3.17"
|
|
||||||
strip-ansi-escapes = "0.2.0"
|
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
|
||||||
tempfile = "3.13.0"
|
|
||||||
tokio = { version = "1.43.0", features = ["full"] }
|
|
||||||
tokio-util = "0.7.11"
|
|
||||||
tracing = "0.1.41"
|
|
||||||
tracing-error = "0.2.0"
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
|
|
||||||
walkdir = "2.5.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
anyhow = "1.0.86"
|
|
||||||
vergen-gix = { version = "1.0.0", features = ["build", "cargo"] }
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// This file is part of Deja-vu.
|
|
||||||
//
|
|
||||||
// Deja-vu is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Deja-vu is distributed in the hope that it will be useful, but
|
|
||||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
// See the GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
use anyhow::Result;
|
|
||||||
use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder};
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let build = BuildBuilder::all_build()?;
|
|
||||||
let gix = GixBuilder::all_git()?;
|
|
||||||
let cargo = CargoBuilder::all_cargo()?;
|
|
||||||
Emitter::default()
|
|
||||||
.add_instructions(&build)?
|
|
||||||
.add_instructions(&gix)?
|
|
||||||
.add_instructions(&cargo)?
|
|
||||||
.emit()
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// This file is part of Deja-vu.
|
|
||||||
//
|
|
||||||
// Deja-vu is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Deja-vu is distributed in the hope that it will be useful, but
|
|
||||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
// See the GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
pub mod action;
|
|
||||||
pub mod cli;
|
|
||||||
pub mod components;
|
|
||||||
pub mod config;
|
|
||||||
pub mod errors;
|
|
||||||
pub mod line;
|
|
||||||
pub mod logging;
|
|
||||||
pub mod state;
|
|
||||||
pub mod system;
|
|
||||||
pub mod tasks;
|
|
||||||
pub mod tui;
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "deja-vu"
|
name = "deja-vu"
|
||||||
authors = ["2Shirt <2xShirt@gmail.com>"]
|
authors = ["2Shirt <2xShirt@gmail.com>"]
|
||||||
|
description = "Clone Windows."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL"
|
license = "GPL"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -23,8 +24,7 @@ version = "0.2.0"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
core = { path = "../core" }
|
better-panic = "0.3.0"
|
||||||
color-eyre = "0.6.3"
|
|
||||||
clap = { version = "4.4.5", features = [
|
clap = { version = "4.4.5", features = [
|
||||||
"derive",
|
"derive",
|
||||||
"cargo",
|
"cargo",
|
||||||
|
|
@ -33,14 +33,33 @@ clap = { version = "4.4.5", features = [
|
||||||
"string",
|
"string",
|
||||||
"unstable-styles",
|
"unstable-styles",
|
||||||
] }
|
] }
|
||||||
ratatui = { version = "0.29.0", features = ["serde", "macros"] }
|
color-eyre = "0.6.3"
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
config = "0.14.0"
|
||||||
|
crossterm = { version = "0.28.1", features = ["serde", "event-stream"] }
|
||||||
|
derive_deref = "1.1.1"
|
||||||
|
directories = "5.0.1"
|
||||||
|
futures = "0.3.30"
|
||||||
|
human-panic = "2.0.1"
|
||||||
|
json5 = "0.4.1"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
libc = "0.2.158"
|
||||||
|
once_cell = "1.20.2"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
|
ratatui = { version = "0.28.1", features = ["serde", "macros"] }
|
||||||
|
raw-cpuid = "11.2.0"
|
||||||
|
regex = "1.11.1"
|
||||||
|
serde = { version = "1.0.208", features = ["derive"] }
|
||||||
serde_json = "1.0.125"
|
serde_json = "1.0.125"
|
||||||
tokio = { version = "1.43.0", features = ["full"] }
|
signal-hook = "0.3.17"
|
||||||
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
|
tempfile = "3.13.0"
|
||||||
|
tokio = { version = "1.39.3", features = ["full"] }
|
||||||
tokio-util = "0.7.11"
|
tokio-util = "0.7.11"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.40"
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
|
||||||
|
walkdir = "2.5.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,23 @@
|
||||||
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
use core::{
|
use std::{
|
||||||
|
env,
|
||||||
|
iter::zip,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use color_eyre::Result;
|
||||||
|
use crossterm::event::KeyEvent;
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Constraint, Direction, Layout},
|
||||||
|
prelude::Rect,
|
||||||
|
style::Color,
|
||||||
|
};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
components::{
|
components::{
|
||||||
footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, state::StatefulList,
|
footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, state::StatefulList,
|
||||||
|
|
@ -30,21 +46,6 @@ use core::{
|
||||||
tasks::{Task, Tasks},
|
tasks::{Task, Tasks},
|
||||||
tui::{Event, Tui},
|
tui::{Event, Tui},
|
||||||
};
|
};
|
||||||
use std::{
|
|
||||||
env,
|
|
||||||
iter::zip,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use ratatui::{
|
|
||||||
crossterm::event::KeyEvent,
|
|
||||||
layout::{Constraint, Direction, Layout},
|
|
||||||
prelude::Rect,
|
|
||||||
style::Color,
|
|
||||||
};
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tracing::{debug, info};
|
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
// TUI
|
// TUI
|
||||||
|
|
@ -150,10 +151,6 @@ impl App {
|
||||||
if self.tasks.idle() {
|
if self.tasks.idle() {
|
||||||
self.tasks.add(Task::ScanDisks);
|
self.tasks.add(Task::ScanDisks);
|
||||||
}
|
}
|
||||||
self.action_tx.send(Action::DisplayPopup(
|
|
||||||
popup::Type::Info,
|
|
||||||
String::from("Scanning Disks..."),
|
|
||||||
))?;
|
|
||||||
}
|
}
|
||||||
Mode::PreClone => {
|
Mode::PreClone => {
|
||||||
self.action_tx.send(Action::DisplayPopup(
|
self.action_tx.send(Action::DisplayPopup(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use ratatui::{
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
use crate::{action::Action, config::Config};
|
use crate::{action::Action, config::Config, state::Mode};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Footer {
|
pub struct Footer {
|
||||||
|
|
@ -23,7 +23,7 @@ use strum::Display;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
use crate::{action::Action, config::Config};
|
use crate::{action::Action, config::Config, state::Mode};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
|
|
@ -37,6 +37,7 @@ pub enum Type {
|
||||||
pub struct Popup {
|
pub struct Popup {
|
||||||
command_tx: Option<UnboundedSender<Action>>,
|
command_tx: Option<UnboundedSender<Action>>,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
mode: Mode,
|
||||||
popup_type: Type,
|
popup_type: Type,
|
||||||
popup_text: String,
|
popup_text: String,
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +69,12 @@ impl Component for Popup {
|
||||||
self.popup_type = new_type;
|
self.popup_type = new_type;
|
||||||
self.popup_text = new_text;
|
self.popup_text = new_text;
|
||||||
}
|
}
|
||||||
|
Action::SetMode(mode) => {
|
||||||
|
if mode == Mode::ScanDisks {
|
||||||
|
self.popup_text = String::from("Scanning Disks...");
|
||||||
|
}
|
||||||
|
self.mode = mode;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
@ -28,7 +28,7 @@ use tracing::error;
|
||||||
|
|
||||||
use crate::{action::Action, state::Mode};
|
use crate::{action::Action, state::Mode};
|
||||||
|
|
||||||
const CONFIG: &str = include_str!("../../config/config.json5");
|
const CONFIG: &str = include_str!("../config/config.json5");
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
|
|
@ -52,15 +52,14 @@ pub struct Config {
|
||||||
pub styles: Styles,
|
pub styles: Styles,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static PROJECT_NAME: &'static str = "DEJA-VU";
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
//pub static ref PROJECT_NAME: String = env!("CARGO_PKG_NAME").to_uppercase().to_string();
|
pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
|
||||||
pub static ref DATA_FOLDER: Option<PathBuf> =
|
pub static ref DATA_FOLDER: Option<PathBuf> =
|
||||||
env::var(format!("{}_DATA", PROJECT_NAME))
|
env::var(format!("{}_DATA", PROJECT_NAME.clone()))
|
||||||
.ok()
|
.ok()
|
||||||
.map(PathBuf::from);
|
.map(PathBuf::from);
|
||||||
pub static ref CONFIG_FOLDER: Option<PathBuf> =
|
pub static ref CONFIG_FOLDER: Option<PathBuf> =
|
||||||
env::var(format!("{}_CONFIG", PROJECT_NAME))
|
env::var(format!("{}_CONFIG", PROJECT_NAME.clone()))
|
||||||
.ok()
|
.ok()
|
||||||
.map(PathBuf::from);
|
.map(PathBuf::from);
|
||||||
}
|
}
|
||||||
|
|
@ -143,8 +142,7 @@ pub fn get_config_dir() -> PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_directory() -> Option<ProjectDirs> {
|
fn project_directory() -> Option<ProjectDirs> {
|
||||||
ProjectDirs::from("com", "Deja-vu", "deja-vu")
|
ProjectDirs::from("com", "Deja-vu", env!("CARGO_PKG_NAME"))
|
||||||
//ProjectDirs::from("com", "Deja-vu", env!("CARGO_PKG_NAME"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deref, DerefMut)]
|
#[derive(Clone, Debug, Default, Deref, DerefMut)]
|
||||||
|
|
@ -20,9 +20,8 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref LOG_ENV: String = format!("{}_LOG_LEVEL", config::PROJECT_NAME);
|
pub static ref LOG_ENV: String = format!("{}_LOG_LEVEL", config::PROJECT_NAME.clone());
|
||||||
pub static ref LOG_FILE: String = format!("{}.log", config::PROJECT_NAME);
|
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
|
||||||
//pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
|
|
@ -14,19 +14,31 @@
|
||||||
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
// along with Deja-vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use cli::Cli;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use core;
|
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
|
||||||
|
mod action;
|
||||||
mod app;
|
mod app;
|
||||||
|
mod cli;
|
||||||
|
mod components;
|
||||||
|
mod config;
|
||||||
|
mod errors;
|
||||||
|
mod line;
|
||||||
|
mod logging;
|
||||||
|
mod state;
|
||||||
|
mod system;
|
||||||
|
mod tasks;
|
||||||
|
mod tests;
|
||||||
|
mod tui;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
core::errors::init()?;
|
crate::errors::init()?;
|
||||||
core::logging::init()?;
|
crate::logging::init()?;
|
||||||
|
|
||||||
let args = core::cli::Cli::parse();
|
let args = Cli::parse();
|
||||||
let mut app = App::new(args.tick_rate, args.frame_rate)?;
|
let mut app = App::new(args.tick_rate, args.frame_rate)?;
|
||||||
app.run().await?;
|
app.run().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,15 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pe-menu"
|
name = "pe-menu"
|
||||||
authors = ["2Shirt <2xShirt@gmail.com>"]
|
authors = ["2Shirt <2xShirt@gmail.com>"]
|
||||||
|
description = "Menu for WinPE."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL"
|
license = "GPL"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = { version = "0.28.1", features = ["event-stream"] }
|
crossterm = { version = "0.27.0", features = ["event-stream"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.26.0"
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
tokio = { version = "1.43.0", features = ["full"] }
|
tokio = { version = "1.35.1", features = ["full"] }
|
||||||
toml = "0.8.13"
|
toml = "0.8.13"
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ pub fn render(app: &mut App, frame: &mut Frame) {
|
||||||
Constraint::Min(1),
|
Constraint::Min(1),
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
])
|
])
|
||||||
.split(frame.area());
|
.split(frame.size());
|
||||||
|
|
||||||
// Title Block
|
// Title Block
|
||||||
let title_text = Span::styled("Deja-vu: PE Menu", Style::default().fg(Color::LightCyan));
|
let title_text = Span::styled("Deja-vu: PE Menu", Style::default().fg(Color::LightCyan));
|
||||||
|
|
@ -120,7 +120,7 @@ fn render_popup_pane(frame: &mut Frame, app: &mut App) {
|
||||||
.block(popup_block)
|
.block(popup_block)
|
||||||
.centered()
|
.centered()
|
||||||
.wrap(Wrap { trim: false });
|
.wrap(Wrap { trim: false });
|
||||||
let area = centered_rect(60, 25, frame.area());
|
let area = centered_rect(60, 25, frame.size());
|
||||||
frame.render_widget(Clear, area);
|
frame.render_widget(Clear, area);
|
||||||
frame.render_widget(scan_paragraph, area);
|
frame.render_widget(scan_paragraph, area);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue