Compare commits
2 commits
408de46e73
...
086946b357
| Author | SHA1 | Date | |
|---|---|---|---|
| 086946b357 | |||
| 00bc5bc607 |
8 changed files with 134 additions and 91 deletions
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"app_title": "Deja-vu",
|
||||
"clone_app_path": "C:/Program Files/Some Clone Tool/app.exe",
|
||||
"conemu_path": "C:/Program Files/ConEmu/ConEmu64.exe",
|
||||
"keybindings": {
|
||||
"Home": {
|
||||
"<q>": "Quit",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,11 @@ pub enum Action {
|
|||
SelectRight(Option<usize>, Option<usize>), // indicies for right info pane
|
||||
UpdateDiskList(Vec<Disk>),
|
||||
UpdateFooter(String),
|
||||
UpdateLeft(String, Vec<String>, Vec<String>, bool), // (title, labels, items, select_one)
|
||||
UpdateLeft(String, Vec<String>, Vec<String>, usize), // (title, labels, items, select_num)
|
||||
// NOTE: select_num should be set to 0, 1, or 2.
|
||||
// 0: For repeating selections
|
||||
// 1: For a single choice
|
||||
// 2: For two selections (obviously)
|
||||
UpdateRight(Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>), // (labels, start_index, items) - items before start are always shown
|
||||
// Screens
|
||||
DismissPopup,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ pub struct Left {
|
|||
config: Config,
|
||||
labels: Vec<String>,
|
||||
list: StatefulList<String>,
|
||||
select_one: bool,
|
||||
select_num: usize,
|
||||
selections: Vec<Option<usize>>,
|
||||
selections_saved: Vec<Option<usize>>,
|
||||
title_text: String,
|
||||
|
|
@ -39,7 +39,7 @@ pub struct Left {
|
|||
impl Left {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
select_one: false,
|
||||
select_num: 0,
|
||||
labels: vec![String::from("one"), String::from("two")],
|
||||
selections: vec![None, None],
|
||||
selections_saved: vec![None, None],
|
||||
|
|
@ -75,12 +75,14 @@ impl Component for Left {
|
|||
Action::KeyUp => self.list.previous(),
|
||||
Action::KeyDown => self.list.next(),
|
||||
Action::Process => {
|
||||
if let Some(command_tx) = self.command_tx.clone() {
|
||||
if self.select_num == 0 {
|
||||
// Selections aren't being used so this is a no-op
|
||||
} else if let Some(command_tx) = self.command_tx.clone() {
|
||||
match (self.selections[0], self.selections[1]) {
|
||||
(None, None) => {
|
||||
// Making first selection
|
||||
command_tx.send(Action::Select(self.list.selected(), None))?;
|
||||
if self.select_one {
|
||||
if self.select_num == 1 {
|
||||
// Confirm selection
|
||||
command_tx.send(Action::NextScreen)?;
|
||||
}
|
||||
|
|
@ -115,14 +117,14 @@ impl Component for Left {
|
|||
self.selections[0] = None;
|
||||
self.selections[1] = None;
|
||||
}
|
||||
Action::UpdateLeft(title, labels, items, select_one) => {
|
||||
Action::UpdateLeft(title, labels, items, select_num) => {
|
||||
self.title_text = title;
|
||||
self.labels = labels
|
||||
.iter()
|
||||
.map(|label| format!(" ~{}~", label.to_lowercase()))
|
||||
.collect();
|
||||
self.list.set_items(items);
|
||||
self.select_one = select_one;
|
||||
self.select_num = select_num;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -136,7 +138,7 @@ impl Component for Left {
|
|||
.areas(area);
|
||||
|
||||
// Title
|
||||
let title_text = if self.selections[1].is_some() || self.select_one {
|
||||
let title_text = if self.selections[1].is_some() || self.select_num == 1 {
|
||||
"Confirm Selections"
|
||||
} else {
|
||||
self.title_text.as_str()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ use ratatui::{
|
|||
widgets::{Block, Borders, Padding, Paragraph, Wrap},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tracing::info;
|
||||
|
||||
use super::{state::StatefulList, Component};
|
||||
use crate::{action::Action, config::Config, line::DVLine};
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ pub struct Config {
|
|||
pub app_title: String,
|
||||
#[serde(default)]
|
||||
pub clone_app_path: PathBuf,
|
||||
#[serde(default)]
|
||||
pub conemu_path: PathBuf,
|
||||
#[serde(default, flatten)]
|
||||
pub config: AppConfig,
|
||||
#[serde(default)]
|
||||
|
|
@ -76,6 +78,10 @@ impl Config {
|
|||
"clone_app_path",
|
||||
String::from("C:\\Program Files\\Some Clone Tool\\app.exe"),
|
||||
)?
|
||||
.set_default(
|
||||
"conemu_path",
|
||||
String::from("C:\\Program Files\\ConEmu\\ConEmu64.exe"),
|
||||
)?
|
||||
.set_default("config_dir", config_dir.to_str().unwrap())?
|
||||
.set_default("data_dir", data_dir.to_str().unwrap())?;
|
||||
|
||||
|
|
|
|||
|
|
@ -433,9 +433,9 @@ impl App {
|
|||
self.set_mode(new_mode)?;
|
||||
self.action_tx
|
||||
.send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?;
|
||||
let (title, labels, items, select_one) = build_left_items(self, self.cur_mode);
|
||||
let (title, labels, items, select_num) = build_left_items(self, self.cur_mode);
|
||||
self.action_tx
|
||||
.send(Action::UpdateLeft(title, labels, items, select_one))?;
|
||||
.send(Action::UpdateLeft(title, labels, items, select_num))?;
|
||||
let (labels, start, items) = build_right_items(self, self.cur_mode);
|
||||
self.action_tx
|
||||
.send(Action::UpdateRight(labels, start, items))?;
|
||||
|
|
@ -577,24 +577,26 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec<String>, Vec<String>, bool) {
|
||||
fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec<String>, Vec<String>, usize) {
|
||||
let select_num: usize;
|
||||
let title: String;
|
||||
let mut items = Vec::new();
|
||||
let mut labels: Vec<String> = Vec::new();
|
||||
let mut select_one = false;
|
||||
match cur_mode {
|
||||
Mode::Home => {
|
||||
select_num = 0;
|
||||
title = String::from("Home");
|
||||
}
|
||||
Mode::InstallDrivers => {
|
||||
select_num = 1;
|
||||
title = String::from("Install Drivers");
|
||||
select_one = true;
|
||||
app.clone
|
||||
.driver_list
|
||||
.iter()
|
||||
.for_each(|driver| items.push(driver.to_string()));
|
||||
}
|
||||
Mode::SelectDisks => {
|
||||
select_num = 2;
|
||||
title = String::from("Select Source and Destination Disks");
|
||||
labels.push(String::from("source"));
|
||||
labels.push(String::from("dest"));
|
||||
|
|
@ -604,18 +606,21 @@ fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec<String>, Vec<Stri
|
|||
.for_each(|disk| items.push(disk.description.to_string()));
|
||||
}
|
||||
Mode::SelectTableType => {
|
||||
select_num = 1;
|
||||
title = String::from("Select Partition Table Type");
|
||||
select_one = true;
|
||||
items.push(format!("{}", PartitionTableType::Guid));
|
||||
items.push(format!("{}", PartitionTableType::Legacy));
|
||||
}
|
||||
Mode::Confirm => {
|
||||
select_num = 0;
|
||||
title = String::from("Confirm Selections");
|
||||
}
|
||||
Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => {
|
||||
select_num = 0;
|
||||
title = String::from("Processing");
|
||||
}
|
||||
Mode::SelectParts => {
|
||||
select_num = 2;
|
||||
title = String::from("Select Boot and OS Partitions");
|
||||
labels.push(String::from("boot"));
|
||||
labels.push(String::from("os"));
|
||||
|
|
@ -628,11 +633,14 @@ fn build_left_items(app: &App, cur_mode: Mode) -> (String, Vec<String>, Vec<Stri
|
|||
}
|
||||
}
|
||||
}
|
||||
Mode::Done | Mode::Failed => title = String::from("Done"),
|
||||
Mode::Done | Mode::Failed => {
|
||||
select_num = 0;
|
||||
title = String::from("Done");
|
||||
}
|
||||
// Invalid states
|
||||
Mode::PEMenu => panic!("This shouldn't happen?"),
|
||||
};
|
||||
(title, labels, items, select_one)
|
||||
(title, labels, items, select_num)
|
||||
}
|
||||
|
||||
fn build_right_items(app: &App, cur_mode: Mode) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {
|
||||
|
|
|
|||
|
|
@ -1,17 +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/>.
|
||||
|
||||
con_emu = 'X:\Program Files\ConEmu\ConEmu64.exe'
|
||||
tools = []
|
||||
|
|
@ -43,41 +43,7 @@ use serde::Deserialize;
|
|||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, info};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Menu {
|
||||
con_emu: String,
|
||||
tools: Vec<Tool>,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
#[must_use]
|
||||
pub fn load() -> Menu {
|
||||
// Main config
|
||||
let exe_path = env::current_exe().expect("Failed to find main executable");
|
||||
let contents = fs::read_to_string(exe_path.with_file_name("pe-menu.toml"))
|
||||
.expect("Failed to load config file");
|
||||
let mut menu: Menu = toml::from_str(&contents).expect("Failed to parse config file");
|
||||
|
||||
// Tools
|
||||
let tool_config_path = exe_path.parent().unwrap().join("menu_entries");
|
||||
let mut entries: Vec<PathBuf> = std::fs::read_dir(tool_config_path)
|
||||
.expect("Failed to find any tool configs")
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
entries.sort();
|
||||
entries.iter().for_each(|entry| {
|
||||
let contents = fs::read_to_string(&entry).expect("Failed to read tool config file");
|
||||
let tool: Tool = toml::from_str(&contents).expect("Failed to parse tool config file");
|
||||
menu.tools.push(tool);
|
||||
});
|
||||
|
||||
// Done
|
||||
menu
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
pub struct Tool {
|
||||
name: String,
|
||||
command: String,
|
||||
|
|
@ -99,17 +65,18 @@ pub struct App {
|
|||
should_suspend: bool,
|
||||
tick_rate: f64,
|
||||
// App
|
||||
list: StatefulList<usize>,
|
||||
menu: Menu,
|
||||
list: StatefulList<Tool>,
|
||||
mode: Mode,
|
||||
selections: Vec<Option<usize>>,
|
||||
tasks: Tasks,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
|
||||
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
||||
let disk_list_arc = Arc::new(Mutex::new(Vec::new()));
|
||||
let mut tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
|
||||
let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
|
||||
let mut list = StatefulList::default();
|
||||
list.set_items(load_tools());
|
||||
Ok(Self {
|
||||
// TUI
|
||||
action_rx,
|
||||
|
|
@ -129,10 +96,9 @@ impl App {
|
|||
should_suspend: false,
|
||||
tick_rate,
|
||||
// App
|
||||
list: StatefulList::default(),
|
||||
menu: Menu::load(),
|
||||
list,
|
||||
mode: Mode::default(),
|
||||
selections: vec![None, None],
|
||||
tasks,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -229,17 +195,40 @@ impl App {
|
|||
Action::Suspend => self.should_suspend = true,
|
||||
Action::Resume => self.should_suspend = false,
|
||||
Action::ClearScreen => tui.terminal.clear()?,
|
||||
Action::KeyUp => self.list.previous(),
|
||||
Action::KeyDown => self.list.next(),
|
||||
Action::KeyUp => {
|
||||
self.list.previous();
|
||||
if let Some(tool) = self.list.get_selected() {
|
||||
if tool.separator {
|
||||
// Skip over separator
|
||||
self.list.previous();
|
||||
if let Some(index) = self.list.selected() {
|
||||
self.action_tx.send(Action::Highlight(index))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::KeyDown => {
|
||||
self.list.next();
|
||||
if let Some(tool) = self.list.get_selected() {
|
||||
if tool.separator {
|
||||
// Skip over separator
|
||||
self.list.next();
|
||||
if let Some(index) = self.list.selected() {
|
||||
self.action_tx.send(Action::Highlight(index))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::Error(ref msg) => {
|
||||
self.action_tx
|
||||
.send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?;
|
||||
self.action_tx.send(Action::SetMode(Mode::Failed))?;
|
||||
}
|
||||
Action::Process => {
|
||||
if let Some(index) = self.list.selected() {
|
||||
//TODO: Run selected tool?
|
||||
info!("Run tool at index: {index}");
|
||||
// Run selected tool
|
||||
if let Some(tool) = self.list.get_selected() {
|
||||
info!("Run tool: {:?}", &tool);
|
||||
self.tasks.add(build_command(&self, &tool));
|
||||
}
|
||||
}
|
||||
Action::Resize(w, h) => self.handle_resize(tui, w, h)?,
|
||||
|
|
@ -248,16 +237,18 @@ impl App {
|
|||
self.mode = mode;
|
||||
self.action_tx
|
||||
.send(Action::UpdateFooter(String::from("(Enter) to select")))?;
|
||||
let (title, labels, items, select_one) = build_left_items(self);
|
||||
let (title, labels, items, select_num) = build_left_items(self);
|
||||
self.action_tx
|
||||
.send(Action::UpdateLeft(title, labels, items, select_one))?;
|
||||
.send(Action::UpdateLeft(title, labels, items, select_num))?;
|
||||
let (labels, start, items) = build_right_items(self);
|
||||
self.action_tx
|
||||
.send(Action::UpdateRight(labels, start, items))?;
|
||||
self.action_tx.send(Action::Select(None, None))?;
|
||||
}
|
||||
Action::UpdateFooter(_)
|
||||
| Action::UpdateLeft(_, _, _, _)
|
||||
| Action::UpdateRight(_, _, _) => {
|
||||
| Action::UpdateRight(_, _, _)
|
||||
| Action::Highlight(_) => {
|
||||
info!("Processing Action: {:?}", action);
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -352,23 +343,53 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
|||
chunks
|
||||
}
|
||||
|
||||
fn build_left_items(app: &App) -> (String, Vec<String>, Vec<String>, bool) {
|
||||
pub fn build_command(app: &App, tool: &Tool) -> Task {
|
||||
let cmd_path: PathBuf;
|
||||
let mut cmd_args: Vec<String> = Vec::new();
|
||||
let start_index: usize;
|
||||
if tool.use_conemu {
|
||||
cmd_path = app.config.conemu_path.clone();
|
||||
cmd_args.push(String::from("-new_console:n"));
|
||||
cmd_args.push(tool.command.clone());
|
||||
start_index = 1;
|
||||
} else {
|
||||
cmd_path = PathBuf::from(tool.command.clone());
|
||||
start_index = 0;
|
||||
}
|
||||
if let Some(args) = &tool.args {
|
||||
if args.len() > start_index {
|
||||
args[start_index..].iter().for_each(|a| {
|
||||
cmd_args.push(a.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
Task::Command(cmd_path, cmd_args)
|
||||
}
|
||||
|
||||
fn build_left_items(app: &App) -> (String, Vec<String>, Vec<String>, usize) {
|
||||
let title = String::from("Tools");
|
||||
let labels = vec![String::new(), String::new()];
|
||||
let items = app
|
||||
.menu
|
||||
.tools
|
||||
.list
|
||||
.items
|
||||
.iter()
|
||||
.map(|tool| tool.name.clone())
|
||||
.map(|tool| {
|
||||
if tool.separator {
|
||||
String::from("──────────────")
|
||||
} else {
|
||||
tool.name.clone()
|
||||
}
|
||||
})
|
||||
// ─
|
||||
.collect();
|
||||
(title, labels, items, false)
|
||||
(title, labels, items, 0)
|
||||
}
|
||||
|
||||
fn build_right_items(app: &App) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {
|
||||
let labels: Vec<Vec<DVLine>> = Vec::new();
|
||||
let items = app
|
||||
.menu
|
||||
.tools
|
||||
.list
|
||||
.items
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
vec![
|
||||
|
|
@ -390,3 +411,22 @@ fn build_right_items(app: &App) -> (Vec<Vec<DVLine>>, usize, Vec<Vec<DVLine>>) {
|
|||
let start_index = 0;
|
||||
(labels, start_index, items)
|
||||
}
|
||||
|
||||
pub fn load_tools() -> Vec<Tool> {
|
||||
let exe_path = env::current_exe().expect("Failed to find main executable");
|
||||
let tool_config_path = exe_path.parent().unwrap().join("menu_entries");
|
||||
let mut entries: Vec<PathBuf> = std::fs::read_dir(tool_config_path)
|
||||
.expect("Failed to find any tool configs")
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
entries.sort();
|
||||
entries
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let contents = fs::read_to_string(&entry).expect("Failed to read tool config file");
|
||||
let tool: Tool = toml::from_str(&contents).expect("Failed to parse tool config file");
|
||||
tool
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue