Export to CSV

This commit is contained in:
Emilia Allison 2023-06-05 14:25:47 -04:00
parent 09d99e27a0
commit 52baa03d79
Signed by: emilia
GPG Key ID: 7A3F8997BFE894E0
9 changed files with 182 additions and 3 deletions

23
Cargo.lock generated
View File

@ -77,6 +77,27 @@ dependencies = [
"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]]
name = "darling"
version = "0.13.4"
@ -533,6 +554,8 @@ dependencies = [
name = "plate-tool"
version = "0.1.0"
dependencies = [
"csv",
"js-sys",
"lazy_static",
"log",
"regex",

View File

@ -9,13 +9,17 @@ edition = "2021"
yew = { version = "0.20.0", features = ["csr"] }
yewdux = "0.9"
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"
wasm-logger = "0.2"
regex = "1"
lazy_static = "1.4"
uuid = { version = "1.3", features = ["v4", "fast-rng", "macro-diagnostics", "js", "serde"] }
serde = { version = "1.0", features = ["derive"] }
csv = "1.2"
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View File

@ -3,3 +3,4 @@
@forward "plates";
@forward "tree";
@forward "transfer_menu";
@forward "upper_menu";

View File

@ -1,5 +1,5 @@
div.main_container {
height: 95vh;
height: 97vh;
width: 98vw;
margin-top: 2.5vh;
margin-left: 1vw;

View File

@ -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;
}
}
}

View File

@ -1,6 +1,9 @@
#![allow(non_snake_case)]
use yew::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::plates::plate_container::PlateContainer;
@ -9,6 +12,7 @@ use super::transfer_menu::TransferMenu;
use super::tree::Tree;
use crate::data::plate_instances::PlateInstance;
use crate::data::csv::state_to_csv;
#[function_component]
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! {
<>
<div class="upper_menu">
<div class="dropdown">
<button>{"File"}</button>
<button onclick={save_button_callback}>{"Save"}</button>
</div>
</div>
<div class="main_container">
<Tree open_new_plate_callback={open_new_plate_dialog_callback}/>
<TransferMenu />
@ -60,5 +93,6 @@ pub fn MainWindow() -> Html {
<NewPlateDialog close_callback={new_plate_dialog_callback}/>
}
</div>
</>
}
}

View File

@ -365,7 +365,7 @@ fn letters_to_num(letters: &str) -> Option<u8> {
}
return Some(num);
}
fn num_to_letters(num: u8) -> Option<String> {
pub fn num_to_letters(num: u8) -> Option<String> {
if num == 0 {
return None;
} // Otherwise, we will not return none!

70
src/data/csv.rs Normal file
View File

@ -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)
}

View File

@ -2,3 +2,4 @@ pub mod plate;
pub mod plate_instances;
pub mod transfer;
pub mod transfer_region;
pub mod csv;