Add username section
This commit is contained in:
parent
88dfe52b07
commit
dd4733c991
8 changed files with 206 additions and 21 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -689,7 +689,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2669,7 +2669,7 @@ dependencies = [
|
||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.3",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2933,6 +2933,16 @@ version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui-input"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "911e93158bf80bbc94bad533b2b16e3d711e1132d69a6a6980c3920a63422c19"
|
||||||
|
dependencies = [
|
||||||
|
"ratatui",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.18.0"
|
version = "1.18.0"
|
||||||
|
|
@ -3144,6 +3154,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"tui-input",
|
||||||
"vergen-gix",
|
"vergen-gix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
"xml",
|
"xml",
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,6 @@
|
||||||
"<Ctrl-z>": "Suspend"
|
"<Ctrl-z>": "Suspend"
|
||||||
},
|
},
|
||||||
"SetUserName": {
|
"SetUserName": {
|
||||||
"<Enter>": "Process",
|
|
||||||
"<Esc>": "PrevScreen",
|
"<Esc>": "PrevScreen",
|
||||||
"<Ctrl-d>": "Quit",
|
"<Ctrl-d>": "Quit",
|
||||||
"<Ctrl-c>": "Quit",
|
"<Ctrl-c>": "Quit",
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ pub enum Action {
|
||||||
Shutdown,
|
Shutdown,
|
||||||
// App (Win-Installer)
|
// App (Win-Installer)
|
||||||
FindWimNetwork,
|
FindWimNetwork,
|
||||||
|
SetUserName(String),
|
||||||
// Screens
|
// Screens
|
||||||
DismissPopup,
|
DismissPopup,
|
||||||
DisplayPopup(PopupType, String),
|
DisplayPopup(PopupType, String),
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
|
||||||
tempfile = "3.23.0"
|
tempfile = "3.23.0"
|
||||||
windows-sys = { version = "0.61.1", features = ["Win32_NetworkManagement_WNet"] }
|
windows-sys = { version = "0.61.1", features = ["Win32_NetworkManagement_WNet"] }
|
||||||
xml = "1.1.0"
|
xml = "1.1.0"
|
||||||
|
tui-input = "0.14.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ use ratatui::{
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::{components::wim_scan::WimScan, state::State};
|
use crate::{
|
||||||
|
components::{set_username::InputUsername, wim_scan::WimScan},
|
||||||
|
state::State,
|
||||||
|
wim::WimImage,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
// TUI
|
// TUI
|
||||||
|
|
@ -81,6 +85,7 @@ impl App {
|
||||||
Box::new(Left::new()),
|
Box::new(Left::new()),
|
||||||
Box::new(Right::new()),
|
Box::new(Right::new()),
|
||||||
Box::new(WimScan::new(wim_sources)),
|
Box::new(WimScan::new(wim_sources)),
|
||||||
|
Box::new(InputUsername::new()),
|
||||||
Box::new(Footer::new()),
|
Box::new(Footer::new()),
|
||||||
Box::new(popup::Popup::new()),
|
Box::new(popup::Popup::new()),
|
||||||
],
|
],
|
||||||
|
|
@ -356,6 +361,10 @@ impl App {
|
||||||
self.action_tx.send(build_right_items(self))?;
|
self.action_tx.send(build_right_items(self))?;
|
||||||
self.action_tx.send(Action::Select(None, None))?;
|
self.action_tx.send(Action::Select(None, None))?;
|
||||||
}
|
}
|
||||||
|
Action::SetUserName(ref name) => {
|
||||||
|
self.state.username = Some(name.clone());
|
||||||
|
self.action_tx.send(Action::NextScreen)?;
|
||||||
|
}
|
||||||
Action::TasksComplete => self.action_tx.send(Action::NextScreen)?,
|
Action::TasksComplete => self.action_tx.send(Action::NextScreen)?,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -376,10 +385,10 @@ impl App {
|
||||||
|
|
||||||
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
||||||
tui.draw(|frame| {
|
tui.draw(|frame| {
|
||||||
if let [header, _body, footer, center, left, right, popup] =
|
if let [header, _body, footer, center, left, right, username, popup] =
|
||||||
get_chunks(frame.area())[..]
|
get_chunks(frame.area())[..]
|
||||||
{
|
{
|
||||||
let component_areas = vec![header, center, left, right, footer, popup];
|
let component_areas = vec![header, center, left, right, username, footer, popup];
|
||||||
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
||||||
if let Err(err) = component.draw(frame, area) {
|
if let Err(err) = component.draw(frame, area) {
|
||||||
let _ = self
|
let _ = self
|
||||||
|
|
@ -464,7 +473,7 @@ fn build_left_items(app: &App) -> Action {
|
||||||
.for_each(|wim_file| items.push(wim_file.path.clone()));
|
.for_each(|wim_file| items.push(wim_file.path.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::SelectWinImage => {
|
Mode::SelectWinImage | Mode::SetUserName => {
|
||||||
select_type = SelectionType::One;
|
select_type = SelectionType::One;
|
||||||
title = String::from("Select Windows Image");
|
title = String::from("Select Windows Image");
|
||||||
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
||||||
|
|
@ -491,11 +500,6 @@ fn build_left_items(app: &App) -> Action {
|
||||||
items.push(format!("{}", PartitionTableType::Guid));
|
items.push(format!("{}", PartitionTableType::Guid));
|
||||||
items.push(format!("{}", PartitionTableType::Legacy));
|
items.push(format!("{}", PartitionTableType::Legacy));
|
||||||
}
|
}
|
||||||
Mode::SetUserName => {
|
|
||||||
select_type = SelectionType::Loop;
|
|
||||||
title = String::from("Customize");
|
|
||||||
// TODO: FIXME
|
|
||||||
}
|
|
||||||
Mode::Confirm => {
|
Mode::Confirm => {
|
||||||
select_type = SelectionType::Loop;
|
select_type = SelectionType::Loop;
|
||||||
title = String::from("Confirm Selections");
|
title = String::from("Confirm Selections");
|
||||||
|
|
@ -627,7 +631,8 @@ fn build_right_items(app: &App) -> Action {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::SelectWinImage => {
|
Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
|
||||||
|
info!("Building right items for: {:?}", &app.cur_mode);
|
||||||
let source;
|
let source;
|
||||||
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
||||||
&& let Some(index) = app.state.wim_file_index
|
&& let Some(index) = app.state.wim_file_index
|
||||||
|
|
@ -679,17 +684,45 @@ fn build_right_items(app: &App) -> Action {
|
||||||
line_colors: vec![Color::Blue],
|
line_colors: vec![Color::Blue],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
labels.push(label_dv_lines);
|
|
||||||
|
|
||||||
// WIM Info
|
// WIM Info
|
||||||
source.images.iter().for_each(|image| {
|
match app.cur_mode {
|
||||||
items.push(vec![DVLine {
|
Mode::SelectWinImage => {
|
||||||
line_parts: vec![format!("{image}")],
|
source.images.iter().for_each(|image| {
|
||||||
line_colors: vec![Color::Reset],
|
items.push(vec![DVLine {
|
||||||
}])
|
line_parts: vec![format!("{image}")],
|
||||||
});
|
line_colors: vec![Color::Reset],
|
||||||
|
}])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Mode::Confirm => {
|
||||||
|
if let Some(index) = app.state.wim_image_index
|
||||||
|
&& let Some(image) = source.images.get(index)
|
||||||
|
{
|
||||||
|
label_dv_lines.append(&mut vec![
|
||||||
|
DVLine {
|
||||||
|
line_parts: vec![format!("{image}")],
|
||||||
|
line_colors: vec![Color::Reset],
|
||||||
|
},
|
||||||
|
DVLine::blank(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if let Some(username) = &app.state.username {
|
||||||
|
label_dv_lines.append(&mut vec![DVLine {
|
||||||
|
line_parts: vec![String::from("Username: "), username.clone()],
|
||||||
|
line_colors: vec![Color::Green, Color::Reset],
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
items.push(vec![DVLine::blank()]);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done
|
||||||
|
info!("label_dv_lines: {:?}", &label_dv_lines);
|
||||||
|
labels.push(label_dv_lines);
|
||||||
}
|
}
|
||||||
Mode::SelectTableType | Mode::SetUserName | Mode::Confirm => {
|
Mode::SelectTableType => {
|
||||||
// Labels
|
// Labels
|
||||||
let dest_dv_line = DVLine {
|
let dest_dv_line = DVLine {
|
||||||
line_parts: vec![
|
line_parts: vec![
|
||||||
|
|
@ -777,6 +810,9 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
||||||
// Center
|
// Center
|
||||||
chunks.push(center);
|
chunks.push(center);
|
||||||
|
|
||||||
|
// Set username
|
||||||
|
chunks.push(centered_rect(60, 20, r));
|
||||||
|
|
||||||
// Popup
|
// Popup
|
||||||
chunks.push(centered_rect(60, 25, r));
|
chunks.push(centered_rect(60, 25, r));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,5 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
pub mod set_username;
|
||||||
pub mod wim_scan;
|
pub mod wim_scan;
|
||||||
|
|
|
||||||
135
win_installer/src/components/set_username.rs
Normal file
135
win_installer/src/components/set_username.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
// 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 color_eyre::Result;
|
||||||
|
use core::{action::Action, components::Component, config::Config, state::Mode, tui::Event};
|
||||||
|
use crossterm::event::KeyCode;
|
||||||
|
use ratatui::{
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Clear, Paragraph},
|
||||||
|
};
|
||||||
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
use tui_input::{Input, InputRequest};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct InputUsername {
|
||||||
|
command_tx: Option<UnboundedSender<Action>>,
|
||||||
|
config: Config,
|
||||||
|
input: Input,
|
||||||
|
mode: Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputUsername {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
input: Input::new(String::from("")),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for InputUsername {
|
||||||
|
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||||
|
self.command_tx = Some(tx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||||
|
self.config = config;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>> {
|
||||||
|
if self.mode != Mode::SetUserName {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let action = match event {
|
||||||
|
Some(Event::Key(key_event)) => match key_event.code {
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
self.input.handle(InputRequest::DeletePrevChar);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
let ok_chars: Vec<char> = vec![' ', '-', '_'];
|
||||||
|
if c.is_ascii_alphanumeric() || ok_chars.contains(&c) {
|
||||||
|
self.input.handle(InputRequest::InsertChar(c));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let username = self.input.value();
|
||||||
|
Some(Action::SetUserName(String::from(username)))
|
||||||
|
}
|
||||||
|
KeyCode::Esc => Some(Action::SetMode(Mode::Home)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Some(Event::Mouse(_)) => None,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
Ok(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
|
match action {
|
||||||
|
Action::SetMode(mode) => self.mode = mode.clone(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||||
|
if self.mode != Mode::SetUserName {
|
||||||
|
// Bail early
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set areas
|
||||||
|
let [_, center_area, _] = Layout::horizontal([
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Min(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
])
|
||||||
|
.areas(area);
|
||||||
|
let [_, input_area, _] = Layout::vertical([
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(1),
|
||||||
|
])
|
||||||
|
.areas(center_area);
|
||||||
|
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
|
let outer_block = Block::bordered().cyan().bold();
|
||||||
|
frame.render_widget(outer_block, area);
|
||||||
|
|
||||||
|
// Input Box
|
||||||
|
let width = input_area.width.max(3) - 3; // keep 2 for borders and 1 for cursor
|
||||||
|
let scroll = self.input.visual_scroll(width as usize);
|
||||||
|
let input = Paragraph::new(self.input.value())
|
||||||
|
.scroll((0, scroll as u16))
|
||||||
|
.white()
|
||||||
|
.block(Block::new().borders(Borders::ALL).title("Enter Username"));
|
||||||
|
frame.render_widget(input, input_area);
|
||||||
|
|
||||||
|
// Ratatui hides the cursor unless it's explicitly set. Position the cursor past the
|
||||||
|
// end of the input text and one line down from the border to the input line
|
||||||
|
let x = self.input.visual_cursor().max(scroll) - scroll + 1;
|
||||||
|
frame.set_cursor_position((input_area.x + x as u16, input_area.y + 1));
|
||||||
|
|
||||||
|
// Done
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,7 @@ pub struct State {
|
||||||
pub driver: Option<drivers::Driver>,
|
pub driver: Option<drivers::Driver>,
|
||||||
pub driver_list: Vec<drivers::Driver>,
|
pub driver_list: Vec<drivers::Driver>,
|
||||||
pub table_type: Option<PartitionTableType>,
|
pub table_type: Option<PartitionTableType>,
|
||||||
|
pub username: Option<String>,
|
||||||
pub wim_file_index: Option<usize>,
|
pub wim_file_index: Option<usize>,
|
||||||
pub wim_image_index: Option<usize>,
|
pub wim_image_index: Option<usize>,
|
||||||
pub wim_sources: Arc<Mutex<WimSources>>,
|
pub wim_sources: Arc<Mutex<WimSources>>,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue