Export to CSV
This commit is contained in:
parent
09d99e27a0
commit
52baa03d79
|
@ -77,6 +77,27 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086"
|
||||||
|
dependencies = [
|
||||||
|
"csv-core",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv-core"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
|
@ -533,6 +554,8 @@ dependencies = [
|
||||||
name = "plate-tool"
|
name = "plate-tool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"csv",
|
||||||
|
"js-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
|
@ -9,13 +9,17 @@ edition = "2021"
|
||||||
yew = { version = "0.20.0", features = ["csr"] }
|
yew = { version = "0.20.0", features = ["csr"] }
|
||||||
yewdux = "0.9"
|
yewdux = "0.9"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
web-sys = { version = "0.3", features = ["FormData", "HtmlFormElement", "HtmlDialogElement"] }
|
web-sys = { version = "0.3", features = ["FormData", "HtmlFormElement",
|
||||||
|
"HtmlDialogElement", "Blob", "Url", "Window",
|
||||||
|
"HtmlAnchorElement"] }
|
||||||
|
js-sys = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
wasm-logger = "0.2"
|
wasm-logger = "0.2"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
uuid = { version = "1.3", features = ["v4", "fast-rng", "macro-diagnostics", "js", "serde"] }
|
uuid = { version = "1.3", features = ["v4", "fast-rng", "macro-diagnostics", "js", "serde"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
csv = "1.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.0"
|
wasm-bindgen-test = "0.3.0"
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
@forward "plates";
|
@forward "plates";
|
||||||
@forward "tree";
|
@forward "tree";
|
||||||
@forward "transfer_menu";
|
@forward "transfer_menu";
|
||||||
|
@forward "upper_menu";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
div.main_container {
|
div.main_container {
|
||||||
height: 95vh;
|
height: 97vh;
|
||||||
width: 98vw;
|
width: 98vw;
|
||||||
margin-top: 2.5vh;
|
margin-top: 2.5vh;
|
||||||
margin-left: 1vw;
|
margin-left: 1vw;
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
@use "sass:color";
|
||||||
|
@use "../variables" as *;
|
||||||
|
|
||||||
|
div.upper_menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
|
||||||
|
$menu-height: min(2.5vh, 25px);
|
||||||
|
height: $menu-height;
|
||||||
|
padding-left: 1vw;
|
||||||
|
|
||||||
|
div.dropdown {
|
||||||
|
margin-right: 2px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: $menu-height;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
outline: 1px solid $color-dark;
|
||||||
|
|
||||||
|
button {
|
||||||
|
vertical-align: top;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: calc($menu-height*0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
*:first-child {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover * {
|
||||||
|
visibility: visible;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yewdux::prelude::*;
|
use yewdux::prelude::*;
|
||||||
|
use wasm_bindgen::{JsValue, JsCast};
|
||||||
|
use web_sys::{Blob, Url, HtmlAnchorElement};
|
||||||
|
use js_sys::Array;
|
||||||
|
|
||||||
use super::new_plate_dialog::NewPlateDialog;
|
use super::new_plate_dialog::NewPlateDialog;
|
||||||
use super::plates::plate_container::PlateContainer;
|
use super::plates::plate_container::PlateContainer;
|
||||||
|
@ -9,6 +12,7 @@ use super::transfer_menu::TransferMenu;
|
||||||
use super::tree::Tree;
|
use super::tree::Tree;
|
||||||
|
|
||||||
use crate::data::plate_instances::PlateInstance;
|
use crate::data::plate_instances::PlateInstance;
|
||||||
|
use crate::data::csv::state_to_csv;
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
pub fn MainWindow() -> Html {
|
pub fn MainWindow() -> Html {
|
||||||
|
@ -50,7 +54,36 @@ pub fn MainWindow() -> Html {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let save_button_callback = {
|
||||||
|
let main_state = main_state.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
if let Ok(csv) = state_to_csv(&main_state) {
|
||||||
|
let csv: &str = &csv;
|
||||||
|
let blob = Blob::new_with_str_sequence(
|
||||||
|
&Array::from_iter(std::iter::once(JsValue::from_str(csv))));
|
||||||
|
if let Ok(blob) = blob {
|
||||||
|
let url = Url::create_object_url_with_blob(&blob).expect("We have a blob, why not URL?");
|
||||||
|
// Beneath is the cool hack to download files
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let anchor = document.create_element("a").unwrap()
|
||||||
|
.dyn_into::<HtmlAnchorElement>().unwrap();
|
||||||
|
anchor.set_download("transfers.csv");
|
||||||
|
anchor.set_href(&url);
|
||||||
|
anchor.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
|
<>
|
||||||
|
<div class="upper_menu">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button>{"File"}</button>
|
||||||
|
<button onclick={save_button_callback}>{"Save"}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="main_container">
|
<div class="main_container">
|
||||||
<Tree open_new_plate_callback={open_new_plate_dialog_callback}/>
|
<Tree open_new_plate_callback={open_new_plate_dialog_callback}/>
|
||||||
<TransferMenu />
|
<TransferMenu />
|
||||||
|
@ -60,5 +93,6 @@ pub fn MainWindow() -> Html {
|
||||||
<NewPlateDialog close_callback={new_plate_dialog_callback}/>
|
<NewPlateDialog close_callback={new_plate_dialog_callback}/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -365,7 +365,7 @@ fn letters_to_num(letters: &str) -> Option<u8> {
|
||||||
}
|
}
|
||||||
return Some(num);
|
return Some(num);
|
||||||
}
|
}
|
||||||
fn num_to_letters(num: u8) -> Option<String> {
|
pub fn num_to_letters(num: u8) -> Option<String> {
|
||||||
if num == 0 {
|
if num == 0 {
|
||||||
return None;
|
return None;
|
||||||
} // Otherwise, we will not return none!
|
} // Otherwise, we will not return none!
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
use crate::data::transfer::Transfer;
|
||||||
|
use crate::components::transfer_menu::num_to_letters;
|
||||||
|
use crate::components::states::MainState;
|
||||||
|
|
||||||
|
use std::{error::Error};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
struct TransferRecord {
|
||||||
|
#[serde(rename = "Source Plate Barcode")]
|
||||||
|
source_plate: String,
|
||||||
|
#[serde(rename = "Source Well")]
|
||||||
|
source_well: String,
|
||||||
|
#[serde(rename = "Destination Plate Barcode")]
|
||||||
|
destination_plate: String,
|
||||||
|
#[serde(rename = "Destination Well")]
|
||||||
|
destination_well: String,
|
||||||
|
#[serde(rename = "Volume")]
|
||||||
|
volume: f64,
|
||||||
|
#[serde(rename = "Concentration")]
|
||||||
|
concentration: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_to_csv(state: &MainState) -> Result<String, Box<dyn Error>> {
|
||||||
|
let mut records: Vec<TransferRecord> = Vec::new();
|
||||||
|
for transfer in &state.transfers {
|
||||||
|
let src_barcode = state.source_plates.iter().find(|spi| spi.get_uuid() == transfer.source_id)
|
||||||
|
.ok_or("Found unpurged transfer")?;
|
||||||
|
let dest_barcode = state.destination_plates.iter().find(|dpi| dpi.get_uuid() == transfer.dest_id)
|
||||||
|
.ok_or("Found unpurged transfer")?;
|
||||||
|
records.append(&mut transfer_to_records(transfer, &src_barcode.name, &dest_barcode.name))
|
||||||
|
}
|
||||||
|
return records_to_csv(records)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_to_records(
|
||||||
|
tr: &Transfer,
|
||||||
|
src_barcode: &str,
|
||||||
|
dest_barcode: &str,
|
||||||
|
) -> Vec<TransferRecord> {
|
||||||
|
let source_wells = tr.transfer_region.get_source_wells();
|
||||||
|
let map = tr.transfer_region.calculate_map();
|
||||||
|
|
||||||
|
let mut records: Vec<TransferRecord> = vec![];
|
||||||
|
|
||||||
|
for s_well in source_wells {
|
||||||
|
let dest_wells = map(s_well);
|
||||||
|
if let Some(dest_wells) = dest_wells {
|
||||||
|
for d_well in dest_wells {
|
||||||
|
records.push(TransferRecord {
|
||||||
|
source_plate: src_barcode.to_string(),
|
||||||
|
source_well: format!("{}{}", num_to_letters(s_well.0).unwrap(), s_well.1),
|
||||||
|
destination_plate: dest_barcode.to_string(),
|
||||||
|
destination_well: format!("{}{}", num_to_letters(d_well.0).unwrap(), d_well.1),
|
||||||
|
volume: 2.5, // Default value since not yet implemented
|
||||||
|
concentration: None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
fn records_to_csv(trs: Vec<TransferRecord>) -> Result<String, Box<dyn Error>> {
|
||||||
|
let mut wtr = csv::WriterBuilder::new().from_writer(vec![]);
|
||||||
|
for record in trs {
|
||||||
|
wtr.serialize(record)?
|
||||||
|
}
|
||||||
|
let data = String::from_utf8(wtr.into_inner()?)?;
|
||||||
|
return Ok(data)
|
||||||
|
}
|
|
@ -2,3 +2,4 @@ pub mod plate;
|
||||||
pub mod plate_instances;
|
pub mod plate_instances;
|
||||||
pub mod transfer;
|
pub mod transfer;
|
||||||
pub mod transfer_region;
|
pub mod transfer_region;
|
||||||
|
pub mod csv;
|
||||||
|
|
Loading…
Reference in New Issue