2023-05-21 16:45:12 +00:00
|
|
|
#![allow(non_snake_case)]
|
2023-05-22 17:25:16 +00:00
|
|
|
|
2023-06-01 17:04:03 +00:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use regex::Regex;
|
|
|
|
use serde::{Deserialize, Serialize};
|
2023-06-07 20:14:19 +00:00
|
|
|
use uuid::Uuid;
|
2023-05-22 17:25:16 +00:00
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
use web_sys::{EventTarget, HtmlInputElement};
|
2023-06-01 17:04:03 +00:00
|
|
|
use yew::prelude::*;
|
|
|
|
use yewdux::prelude::*;
|
2023-05-21 16:45:12 +00:00
|
|
|
|
2023-06-01 17:04:03 +00:00
|
|
|
use crate::data::{transfer::Transfer, transfer_region::Region};
|
2023-05-24 22:39:38 +00:00
|
|
|
|
2023-06-01 17:04:03 +00:00
|
|
|
use super::states::{CurrentTransfer, MainState};
|
2023-05-22 17:25:16 +00:00
|
|
|
|
2023-05-22 15:26:08 +00:00
|
|
|
#[function_component]
|
|
|
|
pub fn TransferMenu() -> Html {
|
2023-05-27 17:12:58 +00:00
|
|
|
let (main_state, main_dispatch) = use_store::<MainState>();
|
2023-05-24 22:39:38 +00:00
|
|
|
let (ct_state, ct_dispatch) = use_store::<CurrentTransfer>();
|
2023-05-22 17:25:16 +00:00
|
|
|
|
2023-06-01 17:04:03 +00:00
|
|
|
let on_name_change = {
|
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
|
|
|
|
|
|
|
Callback::from(move |e: Event| {
|
|
|
|
let target: Option<EventTarget> = e.target();
|
|
|
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
|
|
if let Some(input) = input {
|
2023-06-08 15:14:50 +00:00
|
|
|
if input.value() == "" {
|
2023-06-08 15:57:03 +00:00
|
|
|
return;
|
2023-06-08 15:14:50 +00:00
|
|
|
} // We do not want empty inputs!
|
2023-06-01 17:04:03 +00:00
|
|
|
ct_dispatch.reduce_mut(|state| {
|
2023-06-08 15:57:03 +00:00
|
|
|
state.transfer.name = input.value();
|
2023-06-01 17:04:03 +00:00
|
|
|
});
|
|
|
|
}
|
2023-06-07 20:14:19 +00:00
|
|
|
})
|
|
|
|
};
|
2023-06-01 17:04:03 +00:00
|
|
|
|
2023-05-22 17:25:16 +00:00
|
|
|
let on_src_region_change = {
|
2023-05-24 22:39:38 +00:00
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
2023-05-22 17:25:16 +00:00
|
|
|
|
|
|
|
Callback::from(move |e: Event| {
|
|
|
|
let target: Option<EventTarget> = e.target();
|
|
|
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
|
|
if let Some(input) = input {
|
2023-06-08 15:00:14 +00:00
|
|
|
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
|
2023-05-24 22:39:38 +00:00
|
|
|
ct_dispatch.reduce_mut(|state| {
|
2023-06-01 17:04:03 +00:00
|
|
|
state.transfer.transfer_region.source_region = Region::from(&rd);
|
2023-05-24 22:39:38 +00:00
|
|
|
});
|
2023-05-22 18:07:50 +00:00
|
|
|
input.set_custom_validity("");
|
|
|
|
} else {
|
|
|
|
input.set_custom_validity("Invalid region.")
|
2023-05-22 17:25:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
2023-05-22 17:45:48 +00:00
|
|
|
let on_dest_region_change = {
|
2023-05-24 22:39:38 +00:00
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
2023-05-22 17:45:48 +00:00
|
|
|
|
|
|
|
Callback::from(move |e: Event| {
|
|
|
|
let target: Option<EventTarget> = e.target();
|
|
|
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
|
|
if let Some(input) = input {
|
2023-06-08 15:00:14 +00:00
|
|
|
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
|
2023-05-24 22:39:38 +00:00
|
|
|
ct_dispatch.reduce_mut(|state| {
|
2023-06-01 17:04:03 +00:00
|
|
|
state.transfer.transfer_region.dest_region = Region::from(&rd);
|
2023-05-24 22:39:38 +00:00
|
|
|
});
|
2023-05-22 18:07:50 +00:00
|
|
|
input.set_custom_validity("");
|
|
|
|
} else {
|
|
|
|
input.set_custom_validity("Invalid region.")
|
2023-05-22 17:45:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
2023-06-01 17:04:03 +00:00
|
|
|
|
2023-05-25 16:29:17 +00:00
|
|
|
let on_source_interleave_x_change = {
|
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
|
|
|
|
|
|
|
Callback::from(move |e: Event| {
|
|
|
|
let target: Option<EventTarget> = e.target();
|
|
|
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
|
|
if let Some(input) = input {
|
|
|
|
if let Ok(num) = input.value().parse::<i8>() {
|
|
|
|
ct_dispatch.reduce_mut(|state| {
|
2023-06-01 17:04:03 +00:00
|
|
|
state.transfer.transfer_region.interleave_source =
|
|
|
|
(num, state.transfer.transfer_region.interleave_source.1);
|
2023-05-25 16:29:17 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
|
|
|
let on_source_interleave_y_change = {
|
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
|
|
|
|
|
|
|
Callback::from(move |e: Event| {
|
|
|
|
let target: Option<EventTarget> = e.target();
|
|
|
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
|
|
if let Some(input) = input {
|
|
|
|
if let Ok(num) = input.value().parse::<i8>() {
|
|
|
|
ct_dispatch.reduce_mut(|state| {
|
2023-06-01 17:04:03 +00:00
|
|
|
state.transfer.transfer_region.interleave_source =
|
|
|
|
(state.transfer.transfer_region.interleave_source.0, num);
|
2023-05-25 16:29:17 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
2023-05-24 22:39:38 +00:00
|
|
|
let on_dest_interleave_x_change = {
|
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
|
|
|
|
|
|
|
Callback::from(move |e: Event| {
|
|
|
|
let target: Option<EventTarget> = e.target();
|
|
|
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
|
|
if let Some(input) = input {
|
|
|
|
if let Ok(num) = input.value().parse::<i8>() {
|
2023-05-25 15:43:01 +00:00
|
|
|
ct_dispatch.reduce_mut(|state| {
|
2023-06-01 17:04:03 +00:00
|
|
|
state.transfer.transfer_region.interleave_dest =
|
|
|
|
(num, state.transfer.transfer_region.interleave_dest.1);
|
2023-05-25 15:43:01 +00:00
|
|
|
});
|
2023-05-24 22:39:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
|
|
|
let on_dest_interleave_y_change = {
|
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
|
|
|
|
|
|
|
Callback::from(move |e: Event| {
|
|
|
|
let target: Option<EventTarget> = e.target();
|
|
|
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
|
|
if let Some(input) = input {
|
|
|
|
if let Ok(num) = input.value().parse::<i8>() {
|
2023-05-25 15:43:01 +00:00
|
|
|
ct_dispatch.reduce_mut(|state| {
|
2023-06-01 17:04:03 +00:00
|
|
|
state.transfer.transfer_region.interleave_dest =
|
|
|
|
(state.transfer.transfer_region.interleave_dest.0, num);
|
2023-05-25 15:43:01 +00:00
|
|
|
});
|
2023-05-24 22:39:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
2023-06-01 17:04:03 +00:00
|
|
|
|
2023-06-07 21:08:43 +00:00
|
|
|
let on_volume_change = {
|
|
|
|
let ct_dispatch = ct_dispatch.clone();
|
2023-06-07 21:17:40 +00:00
|
|
|
|
2023-06-07 21:08:43 +00:00
|
|
|
Callback::from(move |e: Event| {
|
2023-06-07 21:17:40 +00:00
|
|
|
let input = e
|
|
|
|
.target()
|
|
|
|
.expect("Event must have target")
|
|
|
|
.dyn_into::<HtmlInputElement>()
|
|
|
|
.expect("Must have been emitted by input");
|
2023-06-07 21:08:43 +00:00
|
|
|
if let Ok(num) = input.value().parse::<f32>() {
|
|
|
|
ct_dispatch.reduce_mut(|state| {
|
|
|
|
state.transfer.volume = num;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
2023-06-01 17:04:03 +00:00
|
|
|
let new_transfer_button_callback = {
|
|
|
|
let main_dispatch = main_dispatch.clone();
|
|
|
|
let main_state = main_state.clone();
|
|
|
|
|
|
|
|
Callback::from(move |_: MouseEvent| {
|
|
|
|
main_dispatch.reduce_mut(|state| {
|
|
|
|
state.selected_transfer = Uuid::nil();
|
|
|
|
});
|
|
|
|
ct_dispatch.reduce_mut(|state| {
|
|
|
|
state.transfer = Transfer::default();
|
|
|
|
state.transfer.source_id = main_state.selected_source_plate;
|
|
|
|
state.transfer.dest_id = main_state.selected_dest_plate;
|
|
|
|
});
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
|
|
|
let save_transfer_button_callback = {
|
|
|
|
let main_dispatch = main_dispatch.clone();
|
|
|
|
let main_state = main_state.clone();
|
|
|
|
let ct_state = ct_state.clone();
|
2023-06-13 15:10:54 +00:00
|
|
|
let new_transfer_button_callback = new_transfer_button_callback.clone();
|
2023-06-01 17:04:03 +00:00
|
|
|
|
2023-06-13 15:10:54 +00:00
|
|
|
Callback::from(move |e: MouseEvent| {
|
2023-06-01 17:04:03 +00:00
|
|
|
log::debug!("Button pressed");
|
|
|
|
if main_state.selected_transfer.is_nil() {
|
|
|
|
if let Some(spi) = main_state
|
|
|
|
.source_plates
|
|
|
|
.iter()
|
|
|
|
.find(|spi| spi.get_uuid() == main_state.selected_source_plate)
|
|
|
|
{
|
|
|
|
if let Some(dpi) = main_state
|
|
|
|
.destination_plates
|
|
|
|
.iter()
|
|
|
|
.find(|dpi| dpi.get_uuid() == main_state.selected_dest_plate)
|
|
|
|
{
|
|
|
|
let new_transfer = Transfer::new(
|
|
|
|
spi.clone(),
|
|
|
|
dpi.clone(),
|
|
|
|
ct_state.transfer.transfer_region,
|
2023-06-07 20:14:19 +00:00
|
|
|
ct_state.transfer.name.clone(),
|
2023-06-01 17:04:03 +00:00
|
|
|
);
|
2023-06-01 17:51:25 +00:00
|
|
|
main_dispatch.reduce_mut(|state| {
|
|
|
|
state.transfers.push(new_transfer);
|
2023-06-07 20:14:19 +00:00
|
|
|
state.selected_transfer = state
|
|
|
|
.transfers
|
|
|
|
.last()
|
|
|
|
.expect("An element should have just been added")
|
|
|
|
.get_uuid();
|
2023-06-01 17:51:25 +00:00
|
|
|
});
|
2023-06-13 15:10:54 +00:00
|
|
|
new_transfer_button_callback.emit(e); // If we just made a new transfer,
|
|
|
|
// then we should make another on
|
|
|
|
// save.
|
2023-06-01 17:04:03 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-08 15:57:03 +00:00
|
|
|
} else if let Some(index) = main_state
|
|
|
|
.transfers
|
|
|
|
.iter()
|
|
|
|
.position(|t| t.get_uuid() == main_state.selected_transfer)
|
|
|
|
{
|
|
|
|
main_dispatch.reduce_mut(|state| {
|
|
|
|
state.transfers[index] = ct_state.transfer.clone();
|
|
|
|
});
|
2023-06-01 17:04:03 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
|
|
|
let delete_transfer_button_callback = {
|
2023-05-27 17:12:58 +00:00
|
|
|
let ct_state = ct_state.clone();
|
2023-06-01 17:04:03 +00:00
|
|
|
let new_callback = new_transfer_button_callback.clone();
|
2023-05-27 17:12:58 +00:00
|
|
|
|
|
|
|
Callback::from(move |e: MouseEvent| {
|
|
|
|
if main_state.selected_transfer.is_nil() {
|
2023-06-08 15:57:03 +00:00
|
|
|
// Maybe reset transfer?
|
|
|
|
} else if let Some(index) = main_state
|
|
|
|
.transfers
|
|
|
|
.iter()
|
|
|
|
.position(|t| t.get_uuid() == ct_state.transfer.get_uuid())
|
|
|
|
{
|
|
|
|
main_dispatch.reduce_mut(|state| {
|
|
|
|
state.transfers.remove(index);
|
|
|
|
state.selected_transfer = Uuid::nil();
|
|
|
|
});
|
|
|
|
new_callback.emit(e); // We need a new transfer now
|
2023-05-27 17:12:58 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
2023-05-22 17:25:16 +00:00
|
|
|
|
2023-05-22 15:26:08 +00:00
|
|
|
html! {
|
|
|
|
<div class="transfer_menu">
|
|
|
|
<form>
|
2023-06-01 17:04:03 +00:00
|
|
|
<div>
|
2023-06-02 20:26:20 +00:00
|
|
|
<label for="name"><h3>{"Name:"}</h3></label>
|
2023-06-01 17:04:03 +00:00
|
|
|
<input type="text" name="name"
|
|
|
|
onchange={on_name_change}
|
|
|
|
value={ct_state.transfer.name.clone()}/>
|
|
|
|
</div>
|
2023-05-22 17:55:23 +00:00
|
|
|
<div>
|
2023-06-02 20:26:20 +00:00
|
|
|
<label for="src_region"><h3>{"Source Region:"}</h3></label>
|
2023-05-22 17:25:16 +00:00
|
|
|
<input type="text" name="src_region"
|
2023-05-25 16:07:21 +00:00
|
|
|
onchange={on_src_region_change}
|
2023-06-01 17:04:03 +00:00
|
|
|
value={RegionDisplay::from(&ct_state.transfer.transfer_region.source_region).text}/>
|
2023-05-22 17:55:23 +00:00
|
|
|
</div>
|
|
|
|
<div>
|
2023-06-02 20:26:20 +00:00
|
|
|
<label for="dest_region"><h3>{"Destination Region:"}</h3></label>
|
2023-05-22 17:45:48 +00:00
|
|
|
<input type="text" name="dest_region"
|
2023-05-25 16:07:21 +00:00
|
|
|
onchange={on_dest_region_change}
|
2023-06-01 17:04:03 +00:00
|
|
|
value={RegionDisplay::from(&ct_state.transfer.transfer_region.dest_region).text}/>
|
2023-05-22 17:55:23 +00:00
|
|
|
</div>
|
|
|
|
<div>
|
2023-06-02 20:26:20 +00:00
|
|
|
<h3>{"Source Interleave "}</h3>
|
2023-05-25 16:29:17 +00:00
|
|
|
<label for="source_interleave_x">{"Row:"}</label>
|
|
|
|
<input type="number" name="source_interleave_x"
|
|
|
|
onchange={on_source_interleave_x_change}
|
2023-06-01 17:04:03 +00:00
|
|
|
value={ct_state.transfer.transfer_region.interleave_source.0.to_string()}/>
|
2023-05-25 16:29:17 +00:00
|
|
|
<label for="source_interleave_y">{"Col:"}</label>
|
|
|
|
<input type="number" name="source_interleave_y"
|
|
|
|
onchange={on_source_interleave_y_change}
|
2023-06-01 17:04:03 +00:00
|
|
|
value={ct_state.transfer.transfer_region.interleave_source.1.to_string()}/>
|
2023-05-25 16:29:17 +00:00
|
|
|
</div>
|
|
|
|
<div>
|
2023-06-02 20:26:20 +00:00
|
|
|
<h3>{"Destination Interleave "}</h3>
|
2023-05-25 16:29:17 +00:00
|
|
|
<label for="dest_interleave_x">{"Row:"}</label>
|
2023-05-24 22:39:38 +00:00
|
|
|
<input type="number" name="dest_interleave_x"
|
2023-06-01 17:04:03 +00:00
|
|
|
onchange={on_dest_interleave_x_change}
|
|
|
|
value={ct_state.transfer.transfer_region.interleave_dest.0.to_string()}/>
|
2023-05-25 16:29:17 +00:00
|
|
|
<label for="dest_interleave_y">{"Col:"}</label>
|
2023-05-24 22:39:38 +00:00
|
|
|
<input type="number" name="dest_interleave_y"
|
2023-06-01 17:04:03 +00:00
|
|
|
onchange={on_dest_interleave_y_change}
|
|
|
|
value={ct_state.transfer.transfer_region.interleave_dest.1.to_string()}/>
|
2023-05-22 17:55:23 +00:00
|
|
|
</div>
|
2023-06-07 21:08:43 +00:00
|
|
|
<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>
|
2023-06-02 20:26:20 +00:00
|
|
|
<div id="controls">
|
2023-06-01 17:04:03 +00:00
|
|
|
<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"} />
|
2023-06-02 20:26:20 +00:00
|
|
|
</div>
|
2023-05-22 15:26:08 +00:00
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
}
|
2023-05-21 16:45:12 +00:00
|
|
|
}
|
2023-05-21 22:05:46 +00:00
|
|
|
|
2023-05-23 00:48:03 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)]
|
2023-05-22 17:25:16 +00:00
|
|
|
pub struct RegionDisplay {
|
|
|
|
pub text: String,
|
|
|
|
pub col_start: u8,
|
|
|
|
pub row_start: u8,
|
|
|
|
pub col_end: u8,
|
|
|
|
pub row_end: u8,
|
2023-05-21 22:05:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2023-06-01 17:04:03 +00:00
|
|
|
if captures.len() != 5 {
|
|
|
|
return Err("Not enough capture groups");
|
|
|
|
}
|
2023-05-22 00:14:55 +00:00
|
|
|
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")?;
|
2023-06-01 17:04:03 +00:00
|
|
|
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"))?;
|
2023-06-08 15:57:03 +00:00
|
|
|
Ok(RegionDisplay {
|
2023-05-21 22:05:46 +00:00
|
|
|
text: value,
|
2023-05-22 00:14:55 +00:00
|
|
|
col_start,
|
2023-05-21 22:05:46 +00:00
|
|
|
row_start,
|
2023-05-22 00:14:55 +00:00
|
|
|
col_end,
|
2023-05-21 22:05:46 +00:00
|
|
|
row_end,
|
2023-06-08 15:57:03 +00:00
|
|
|
})
|
2023-05-21 22:05:46 +00:00
|
|
|
} else {
|
2023-06-08 15:57:03 +00:00
|
|
|
Err("Regex match failed")
|
2023-05-21 22:05:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-25 16:07:21 +00:00
|
|
|
impl From<&Region> for RegionDisplay {
|
|
|
|
fn from(value: &Region) -> Self {
|
|
|
|
match *value {
|
2023-06-01 17:04:03 +00:00
|
|
|
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(),
|
2023-05-25 16:07:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-24 22:39:38 +00:00
|
|
|
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 {
|
2023-06-01 17:04:03 +00:00
|
|
|
Region::Rect(
|
|
|
|
(value.col_start, value.row_start),
|
|
|
|
(value.col_end, value.row_end),
|
|
|
|
)
|
2023-05-24 22:39:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-01 17:04:03 +00:00
|
|
|
impl TryFrom<(u8, u8, u8, u8)> for RegionDisplay {
|
|
|
|
type Error = &'static str;
|
2023-05-22 00:10:13 +00:00
|
|
|
|
2023-06-01 17:04:03 +00:00
|
|
|
fn try_from(value: (u8, u8, u8, u8)) -> Result<Self, Self::Error> {
|
2023-05-22 00:14:55 +00:00
|
|
|
// (Column Start, Row Start, Column End, Row End)
|
2023-05-22 00:10:13 +00:00
|
|
|
// This can only possibly fail if one of the coordinates is zero...
|
2023-05-22 00:14:55 +00:00
|
|
|
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,
|
|
|
|
})
|
2023-05-22 00:10:13 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-21 22:05:46 +00:00
|
|
|
fn letters_to_num(letters: &str) -> Option<u8> {
|
|
|
|
let mut num: u8 = 0;
|
|
|
|
for (i, letter) in letters.chars().rev().enumerate() {
|
|
|
|
let n = letter as u8;
|
2023-06-08 15:57:03 +00:00
|
|
|
if !(65..=90).contains(&n) {
|
2023-06-01 17:04:03 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
num = num.checked_add((26_i32.pow(i as u32) * (n as i32 - 64)).try_into().ok()?)?;
|
2023-05-21 22:05:46 +00:00
|
|
|
}
|
2023-06-08 15:57:03 +00:00
|
|
|
Some(num)
|
2023-05-21 22:05:46 +00:00
|
|
|
}
|
2023-06-05 18:25:47 +00:00
|
|
|
pub fn num_to_letters(num: u8) -> Option<String> {
|
2023-06-01 17:04:03 +00:00
|
|
|
if num == 0 {
|
|
|
|
return None;
|
|
|
|
} // Otherwise, we will not return none!
|
|
|
|
// As another note, we can't represent higher than "IV" anyway;
|
|
|
|
// thus there's no reason for a loop (26^n with n>1 will NOT occur).
|
2023-05-22 00:10:13 +00:00
|
|
|
let mut text = "".to_string();
|
|
|
|
let mut digit1 = num.div_euclid(26u8);
|
|
|
|
let mut digit2 = num.rem_euclid(26u8);
|
|
|
|
if digit1 > 0 && digit2 == 0u8 {
|
|
|
|
digit1 -= 1;
|
|
|
|
digit2 = 26;
|
|
|
|
}
|
|
|
|
if digit1 != 0 {
|
2023-06-01 17:04:03 +00:00
|
|
|
text.push((64 + digit1) as char)
|
2023-05-22 00:10:13 +00:00
|
|
|
}
|
2023-06-01 17:04:03 +00:00
|
|
|
text.push((64 + digit2) as char);
|
2023-05-22 00:10:13 +00:00
|
|
|
|
2023-06-08 15:57:03 +00:00
|
|
|
Some(text.to_string())
|
2023-05-22 00:10:13 +00:00
|
|
|
}
|
2023-05-21 22:05:46 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-05-26 20:20:52 +00:00
|
|
|
use wasm_bindgen_test::*;
|
|
|
|
|
2023-05-22 00:10:13 +00:00
|
|
|
use super::{letters_to_num, num_to_letters, RegionDisplay};
|
2023-05-21 22:05:46 +00:00
|
|
|
|
|
|
|
#[test]
|
2023-05-26 20:20:52 +00:00
|
|
|
#[wasm_bindgen_test]
|
2023-05-21 22:05:46 +00:00
|
|
|
fn test_letters_to_num() {
|
|
|
|
assert_eq!(letters_to_num("D"), Some(4));
|
|
|
|
assert_eq!(letters_to_num("d"), None);
|
2023-06-01 17:04:03 +00:00
|
|
|
assert_eq!(letters_to_num("AD"), Some(26 + 4));
|
|
|
|
assert_eq!(letters_to_num("CG"), Some(3 * 26 + 7));
|
2023-05-21 22:05:46 +00:00
|
|
|
}
|
|
|
|
|
2023-05-22 00:10:13 +00:00
|
|
|
#[test]
|
2023-05-26 20:20:52 +00:00
|
|
|
#[wasm_bindgen_test]
|
2023-05-22 00:10:13 +00:00
|
|
|
fn test_num_to_letters() {
|
|
|
|
println!("27 is {:?}", num_to_letters(27));
|
|
|
|
assert_eq!(num_to_letters(1), Some("A".to_string()));
|
|
|
|
assert_eq!(num_to_letters(26), Some("Z".to_string()));
|
|
|
|
assert_eq!(num_to_letters(27), Some("AA".to_string()));
|
|
|
|
assert_eq!(num_to_letters(111), Some("DG".to_string()));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-05-26 20:20:52 +00:00
|
|
|
#[wasm_bindgen_test]
|
2023-05-22 00:10:13 +00:00
|
|
|
fn test_l2n_and_n2l() {
|
2023-06-01 17:04:03 +00:00
|
|
|
assert_eq!(
|
|
|
|
num_to_letters(letters_to_num("A").unwrap()),
|
|
|
|
Some("A".to_string())
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
num_to_letters(letters_to_num("BJ").unwrap()),
|
|
|
|
Some("BJ".to_string())
|
|
|
|
);
|
2023-05-22 00:10:13 +00:00
|
|
|
for i in 1..=255 {
|
|
|
|
assert_eq!(letters_to_num(&num_to_letters(i as u8).unwrap()), Some(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-21 22:05:46 +00:00
|
|
|
#[test]
|
2023-05-26 20:20:52 +00:00
|
|
|
#[wasm_bindgen_test]
|
2023-05-21 22:05:46 +00:00
|
|
|
fn test_try_from_string_for_regiondisplay() {
|
|
|
|
let desired = RegionDisplay {
|
|
|
|
text: "A1:E5".to_string(),
|
|
|
|
row_start: 1,
|
|
|
|
row_end: 5,
|
|
|
|
col_start: 1,
|
2023-06-01 17:04:03 +00:00
|
|
|
col_end: 5,
|
2023-05-21 22:05:46 +00:00
|
|
|
};
|
|
|
|
assert_eq!(desired, "A1:E5".to_string().try_into().unwrap());
|
|
|
|
}
|
|
|
|
}
|