plate-tool/plate-tool-web/src/components/transfer_menu.rs

301 lines
11 KiB
Rust

#![allow(non_snake_case)]
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use yew::prelude::*;
use yewdux::prelude::*;
use crate::components::callbacks::transfer_menu_callbacks;
use plate_tool_lib::transfer_region::Region;
use plate_tool_lib::util::{letters_to_num, num_to_letters};
use super::states::{CurrentTransfer, MainState};
#[function_component]
pub fn TransferMenu() -> Html {
let (main_state, main_dispatch) = use_store::<MainState>();
let (ct_state, ct_dispatch) = use_store::<CurrentTransfer>();
let on_name_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_name_change_callback(ct_dispatch)
};
let on_src_region_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_src_region_change_callback(ct_dispatch)
};
let on_dest_region_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_dest_region_change_callback(ct_dispatch)
};
let on_source_interleave_x_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_source_interleave_x_change_callback(ct_dispatch)
};
let on_source_interleave_y_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_source_interleave_y_change_callback(ct_dispatch)
};
let on_dest_interleave_x_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_dest_interleave_x_change_callback(ct_dispatch)
};
let on_dest_interleave_y_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_dest_interleave_y_change_callback(ct_dispatch)
};
let on_volume_change = {
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_volume_change_callback(ct_dispatch)
};
let new_transfer_button_callback = {
let main_dispatch = main_dispatch.clone();
let main_state = main_state.clone();
let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::new_transfer_button_callback_callback(
main_dispatch,
main_state,
ct_dispatch,
)
};
let save_transfer_button_callback = {
let main_dispatch = main_dispatch.clone();
let main_state = main_state.clone();
let ct_state = ct_state.clone();
let new_transfer_button_callback = new_transfer_button_callback.clone();
transfer_menu_callbacks::save_transfer_button_callback_callback(
main_dispatch,
main_state,
ct_state,
new_transfer_button_callback,
)
};
let delete_transfer_button_callback = {
let main_state = main_state.clone();
let main_dispatch = main_dispatch.clone();
let ct_state = ct_state.clone();
let new_transfer_button_callback = new_transfer_button_callback.clone();
transfer_menu_callbacks::delete_transfer_button_callback(
main_state,
main_dispatch,
ct_state,
new_transfer_button_callback,
)
};
html! {
<div class="transfer_menu">
<form>
<div>
<label for="name"><h3>{"Name:"}</h3></label>
<input type="text" name="name"
onchange={on_name_change}
value={ct_state.transfer.name.clone()}/>
</div>
// Anything below here is not rendered when a Custom transfer is selected
if !matches!(&ct_state.transfer.transfer_region.source_region, Region::Custom(_)) {
<div>
<label for="src_region"><h3>{"Source Region:"}</h3></label>
<input type="text" name="src_region"
onchange={on_src_region_change}
value={RegionDisplay::from(&ct_state.transfer.transfer_region.source_region).text}/>
</div>
<div>
<label for="dest_region"><h3>{"Destination Region:"}</h3></label>
<input type="text" name="dest_region"
onchange={on_dest_region_change}
value={RegionDisplay::from(&ct_state.transfer.transfer_region.dest_region).text}/>
</div>
<div>
<h3>{"Source Interleave "}</h3>
<label for="source_interleave_x">{"Row:"}</label>
<input type="number" name="source_interleave_x"
onchange={on_source_interleave_x_change}
value={ct_state.transfer.transfer_region.interleave_source.0.to_string()}/>
<label for="source_interleave_y">{"Col:"}</label>
<input type="number" name="source_interleave_y"
onchange={on_source_interleave_y_change}
value={ct_state.transfer.transfer_region.interleave_source.1.to_string()}/>
</div>
<div>
<h3>{"Destination Interleave "}</h3>
<label for="dest_interleave_x">{"Row:"}</label>
<input type="number" name="dest_interleave_x"
onchange={on_dest_interleave_x_change}
value={ct_state.transfer.transfer_region.interleave_dest.0.to_string()}/>
<label for="dest_interleave_y">{"Col:"}</label>
<input type="number" name="dest_interleave_y"
onchange={on_dest_interleave_y_change}
value={ct_state.transfer.transfer_region.interleave_dest.1.to_string()}/>
</div>
<div>
<label for="volume"><h3>{"Volume"}</h3></label>
<input type="number" name="volume" class="volume_input"
min="0" step="0.1"
onchange={on_volume_change}
value={ct_state.transfer.volume.to_string()}/>
</div>
}
<div id="controls">
<input type="button" name="new_transfer" onclick={new_transfer_button_callback}
value={"New"} />
<input type="button" name="save_transfer" onclick={save_transfer_button_callback}
value={"Save"} />
<input type="button" name="delete_transfer" onclick={delete_transfer_button_callback}
value={"Delete"} />
</div>
</form>
</div>
}
}
#[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)]
pub struct RegionDisplay {
pub text: String,
pub col_start: u8,
pub row_start: u8,
pub col_end: u8,
pub row_end: u8,
}
impl TryFrom<String> for RegionDisplay {
type Error = &'static str;
fn try_from(value: String) -> Result<Self, Self::Error> {
lazy_static! {
static ref REGION_REGEX: Regex = Regex::new(r"([A-Z]+)(\d+):([A-Z]+)(\d+)").unwrap();
}
if let Some(captures) = REGION_REGEX.captures(&value) {
if captures.len() != 5 {
return Err("Not enough capture groups");
}
let col_start = letters_to_num(&captures[1]).ok_or("Column start failed to parse")?;
let col_end = letters_to_num(&captures[3]).ok_or("Column end failed to parse")?;
let row_start: u8 = captures[2]
.parse::<u8>()
.or(Err("Row start failed to parse"))?;
let row_end: u8 = captures[4]
.parse::<u8>()
.or(Err("Row end failed to parse"))?;
Ok(RegionDisplay {
text: value,
col_start,
row_start,
col_end,
row_end,
})
} else {
Err("Regex match failed")
}
}
}
impl TryFrom<&str> for RegionDisplay {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
lazy_static! {
static ref REGION_REGEX: Regex = Regex::new(r"([A-Z]+)(\d+):([A-Z]+)(\d+)").unwrap();
}
if let Some(captures) = REGION_REGEX.captures(value) {
if captures.len() != 5 {
return Err("Not enough capture groups");
}
let col_start = letters_to_num(&captures[1]).ok_or("Column start failed to parse")?;
let col_end = letters_to_num(&captures[3]).ok_or("Column end failed to parse")?;
let row_start: u8 = captures[2]
.parse::<u8>()
.or(Err("Row start failed to parse"))?;
let row_end: u8 = captures[4]
.parse::<u8>()
.or(Err("Row end failed to parse"))?;
Ok(RegionDisplay {
text: value.to_string(),
col_start,
row_start,
col_end,
row_end,
})
} else {
Err("Regex match failed")
}
}
}
impl From<&Region> for RegionDisplay {
fn from(value: &Region) -> Self {
match *value {
Region::Point((col, row)) => {
RegionDisplay::try_from((col, row, col, row)).ok().unwrap()
}
Region::Rect(c1, c2) => RegionDisplay::try_from((c1.0, c1.1, c2.0, c2.1))
.ok()
.unwrap(),
Region::Custom(_) => RegionDisplay {
text: "CUSTOM".to_string(),
col_start: 0,
row_start: 0,
col_end: 0,
row_end: 0,
},
}
}
}
impl From<&RegionDisplay> for Region {
fn from(value: &RegionDisplay) -> Self {
if value.col_start == value.col_end && value.row_start == value.row_end {
Region::Point((value.col_start, value.row_start))
} else {
Region::Rect(
(value.col_start, value.row_start),
(value.col_end, value.row_end),
)
}
}
}
impl TryFrom<(u8, u8, u8, u8)> for RegionDisplay {
type Error = &'static str;
fn try_from(value: (u8, u8, u8, u8)) -> Result<Self, Self::Error> {
// (Column Start, Row Start, Column End, Row End)
// This can only possibly fail if one of the coordinates is zero...
let cs = num_to_letters(value.0).ok_or("Column start failed to parse")?;
let ce = num_to_letters(value.2).ok_or("Column end failed to parse")?;
Ok(RegionDisplay {
text: format!("{}{}:{}{}", cs, value.1, ce, value.3),
col_start: value.0,
row_start: value.1,
col_end: value.2,
row_end: value.3,
})
}
}
#[cfg(test)]
mod tests {
use wasm_bindgen_test::*;
use super::*;
#[test]
#[wasm_bindgen_test]
fn test_try_from_string_for_regiondisplay() {
let desired = RegionDisplay {
text: "A1:E5".to_string(),
row_start: 1,
row_end: 5,
col_start: 1,
col_end: 5,
};
assert_eq!(desired, "A1:E5".to_string().try_into().unwrap());
}
}