diff --git a/Cargo.lock b/Cargo.lock index e921acb..492180e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,25 @@ version = 3 [[package]] -name = "aho-corasick" -version = "1.0.1" +name = "addr2line" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -25,13 +40,13 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", ] [[package]] @@ -46,6 +61,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bincode" version = "1.3.3" @@ -63,9 +93,24 @@ checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -85,9 +130,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ "csv-core", "itoa", @@ -97,18 +142,18 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] [[package]] name = "darling" -version = "0.13.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8" dependencies = [ "darling_core", "darling_macro", @@ -116,27 +161,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] @@ -147,18 +192,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -170,9 +215,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -180,44 +225,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -233,9 +278,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "js-sys", @@ -245,10 +290,16 @@ dependencies = [ ] [[package]] -name = "gloo" -version = "0.8.0" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gloo" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d" dependencies = [ "gloo-console", "gloo-dialogs", @@ -310,9 +361,9 @@ dependencies = [ [[package]] name = "gloo-history" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd451019e0b7a2b8a7a7b23e74916601abf1135c54664e57ff71dcc26dfcdeb7" +checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" dependencies = [ "gloo-events", "gloo-utils", @@ -326,14 +377,15 @@ dependencies = [ [[package]] name = "gloo-net" -version = "0.2.6" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" dependencies = [ "futures-channel", "futures-core", "futures-sink", "gloo-utils", + "http", "js-sys", "pin-project", "serde", @@ -381,9 +433,9 @@ dependencies = [ [[package]] name = "gloo-utils" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" dependencies = [ "js-sys", "serde", @@ -417,11 +469,19 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ - "libc", + "bytes", + "fnv", + "itoa", ] [[package]] @@ -432,9 +492,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "implicit-clone" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7" +checksum = "cfd6201e7c30ccb24773cac7efa6fec1e06189d414b7439ce756a481c8bfbf53" dependencies = [ "indexmap", ] @@ -451,15 +511,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -472,72 +532,87 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] -name = "once_cell" -version = "1.17.1" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -557,7 +632,22 @@ dependencies = [ ] [[package]] -name = "plate-tool" +name = "plate-tool-lib" +version = "0.1.0" +dependencies = [ + "csv", + "getrandom", + "lazy_static", + "log", + "rand", + "regex", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "plate-tool-web" version = "0.2.0" dependencies = [ "csv", @@ -565,6 +655,7 @@ dependencies = [ "js-sys", "lazy_static", "log", + "plate-tool-lib", "rand", "regex", "serde", @@ -620,9 +711,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -646,9 +737,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.27" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -685,9 +776,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -696,21 +799,27 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scoped-tls" @@ -720,18 +829,18 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde-wasm-bindgen" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" dependencies = [ "js-sys", "serde", @@ -740,20 +849,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -774,9 +883,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -800,9 +909,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -811,33 +920,32 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", ] [[package]] name = "tokio" -version = "1.28.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ - "autocfg", + "backtrace", "pin-project-lite", - "windows-sys", ] [[package]] @@ -853,11 +961,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -865,35 +972,35 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "uuid" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "atomic", "getrandom", @@ -905,13 +1012,13 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49e7f3f3db8040a100710a11932239fd30697115e2ba4107080d8252939845e" +checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", ] [[package]] @@ -928,9 +1035,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -938,24 +1045,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -965,9 +1072,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -975,28 +1082,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "wasm-bindgen-test" -version = "0.3.36" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502" +checksum = "143ddeb4f833e2ed0d252e618986e18bfc7b0e52f2d28d77d05b2f045dd8eb61" dependencies = [ "console_error_panic_hook", "js-sys", @@ -1008,12 +1115,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.36" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3" +checksum = "a5211b7550606857312bba1d978a8ec75692eae187becc5e680444fffc5e6f89" dependencies = [ "proc-macro2", "quote", + "syn 2.0.48", ] [[package]] @@ -1029,80 +1137,14 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "yew" version = "0.20.0" @@ -1145,9 +1187,9 @@ dependencies = [ [[package]] name = "yewdux" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653ba356bc60d1804c28ec6cc8ddac2741c686bde2a65074d07faba735914464" +checksum = "3c528544a814a0beb30059c8340eb78c88160141db788112b51a5a08098cef2d" dependencies = [ "anymap", "async-trait", @@ -1164,13 +1206,13 @@ dependencies = [ [[package]] name = "yewdux-macros" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bcd923aceaa85cb4affad8657cc36e3d6b6932740e711574182f7817492739" +checksum = "d22b1832d3e3eaa61a5c2ecd40affa876507457180d1e599143368c1c3317c2d" dependencies = [ "darling", "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", ] diff --git a/Cargo.toml b/Cargo.toml index cc87d26..d040592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,3 @@ -[package] -name = "plate-tool" -version = "0.2.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -yew = { version = "0.20.0", features = ["csr"] } -yewdux = "0.9" -wasm-bindgen = "0.2" -web-sys = { version = "0.3", features = ["FormData", "HtmlFormElement", - "HtmlDialogElement", "Blob", "Url", "Window", - "HtmlAnchorElement", "ReadableStream", "HtmlSelectElement", "HtmlOptionElement", "HtmlButtonElement", - "FileReader"] } -js-sys = "0.3" -log = "0.4" -wasm-logger = "0.2" -regex = "1" -lazy_static = "1.4" -uuid = { version = "1.6", features = ["v7", "fast-rng", "macro-diagnostics", "js", "serde"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -csv = "1.2" -getrandom = { version = "0.2", features = ["js"] } -rand = { version = "0.8", features = ["small_rng"] } - -[dev-dependencies] -wasm-bindgen-test = "0.3.0" +[workspace] +members = ["plate-tool-web", "plate-tool-lib"] +resolver = "2" diff --git a/README.md b/README.md index 544a896..af35af1 100644 --- a/README.md +++ b/README.md @@ -78,13 +78,6 @@ To add a new plate, click the "New Plate" button: Keep in mind that this will overwrite any work you currently have open, so you may wish to export first (see above). - #### Import Transfer from CSV (Using a picklist as a transfer) - If you have a CSV generated by another tool (or plate-tool), - you can import it as a single transfer. - To do so, mouse over the "File" tab, then "Import", and finally "Import Transfer from CSV". - When creating transfers via this method, the transfer cannot be edited. - This is useful if you have a pre-existing picklist that you would like to visualize in plate-tool. - _Note 1_: JSON files are plaintext! By default there is little whitespace (this makes comprehending them a challenge) but if we pass it through a "JSON Beautifier" (enter this into your search engine of choice) @@ -99,6 +92,18 @@ To add a new plate, click the "New Plate" button: that this application does not "phone home". Your data is stored locally (unless you choose to export it and distribute it yourself). + #### Import Transfer from CSV (Using a picklist as a transfer) + If you have a CSV generated by another tool (or plate-tool), + you can import it as a single transfer. + To do so, mouse over the "File" tab, then "Import", and finally "Import Transfer from CSV". + When creating transfers via this method, the transfer cannot be edited. + This is useful if you have a pre-existing picklist that you would like to visualize in plate-tool. + + _Note_: If you try to use this feature and no plates are available to select, + there was likely an issue parsing your picklist. + Your browser's console may have guidance as to why parsing failed; + plate-tool was probably expecting a different name for a column than was in your file. + ### Other Neat Features #### Taking Pictures of Plates @@ -130,7 +135,7 @@ To add a new plate, click the "New Plate" button: Plate tool is hosted [here](https://ilia.moe/cool-stuff/plate-tool/) for your convenience. However, you're absolutely welcome to host your own instance (even locally). Here's how: -(_Note:_ If you run Windows you're probably best off doing the following in WSL2) +(_Note:_ ~~If you run Windows you're probably best off doing the following in WSL2~~ You're absolutely fine to install rustup in Powershell, and the subsequent steps should be very similar but likely with different filepaths.) 1. Make sure you have a working Rust toolchain 1. Installing `rustup` is the easiest way to do this. See [their website](https://rustup.rs/), @@ -140,7 +145,7 @@ Here's how: 2. Install [trunk](https://trunkrs.dev/) - Run `cargo install --locked trunk` 3. Clone this repository using git -4. Enter the project directory and run `trunk serve` +4. Enter the plate-tool-web directory and run `trunk serve` - You may need to check where `cargo` is installing binaries by default. For me, they're at `~/.cargo/bin`. If trunk is not automatically placed in your path, you would then run `/your/path/to/.cargo/bin/trunk serve`. - You can instead run `trunk build --release` for a more performant binary. diff --git a/assets/scss/default_theme/components/_plate_container.scss b/assets/scss/default_theme/components/_plate_container.scss deleted file mode 100644 index cff857f..0000000 --- a/assets/scss/default_theme/components/_plate_container.scss +++ /dev/null @@ -1,18 +0,0 @@ -@use "sass:color"; -@use "../variables" as *; - -div.plate_container { - display: flex; - flex-direction: column; - justify-content: space-evenly; - align-items: center; - - border: 2px solid $color-dark; - grid-column: right / right; - grid-row: upper / 3; - - h2 { - margin-bottom: 1%; - text-align: center; - } -} diff --git a/plate-tool-lib/.gitignore b/plate-tool-lib/.gitignore new file mode 100644 index 0000000..4f96631 --- /dev/null +++ b/plate-tool-lib/.gitignore @@ -0,0 +1,2 @@ +/target +/dist diff --git a/plate-tool-lib/Cargo.lock b/plate-tool-lib/Cargo.lock new file mode 100644 index 0000000..5c974a4 --- /dev/null +++ b/plate-tool-lib/Cargo.lock @@ -0,0 +1,339 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "plate-tool-lib" +version = "0.1.0" +dependencies = [ + "csv", + "getrandom", + "log", + "rand", + "regex", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "atomic", + "getrandom", + "rand", + "serde", + "uuid-macro-internal", + "wasm-bindgen", +] + +[[package]] +name = "uuid-macro-internal" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" diff --git a/plate-tool-lib/Cargo.toml b/plate-tool-lib/Cargo.toml new file mode 100644 index 0000000..05f1b09 --- /dev/null +++ b/plate-tool-lib/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "plate-tool-lib" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4" +regex = "1" +uuid = { version = "1.6", features = ["v7", "fast-rng", "macro-diagnostics", "js", "serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +csv = "1.2" +getrandom = { version = "0.2", features = ["js"] } +rand = { version = "0.8", features = ["small_rng"] } +lazy_static = "1.4" diff --git a/plate-tool-lib/src/csv/auto.rs b/plate-tool-lib/src/csv/auto.rs new file mode 100644 index 0000000..e330b20 --- /dev/null +++ b/plate-tool-lib/src/csv/auto.rs @@ -0,0 +1,173 @@ +use super::{string_well_to_pt, TransferRecord, read_csv}; +use crate::plate::{PlateFormat, PlateType}; +use crate::plate_instances::PlateInstance; +use crate::transfer::Transfer; +use crate::transfer_region::{CustomRegion, Region, TransferRegion}; + +use std::collections::HashSet; + +pub struct AutoOutput { + pub sources: Vec, + pub destinations: Vec, + pub transfers: Vec, +} + +struct UniquePlates { + sources: Vec, + destinations: Vec, +} + +const W96_COL_MAX: u8 = 12; +const W96_ROW_MAX: u8 = 8; +const W384_COL_MAX: u8 = 24; +const W384_ROW_MAX: u8 = 16; +const W1536_COL_MAX: u8 = 48; +const W1536_ROW_MAX: u8 = 32; + +pub fn auto(records: &[TransferRecord]) -> AutoOutput { + let unique_plates = find_unique_plates(records); + let transfers = get_transfer_for_all_pairs(records, &unique_plates); + + AutoOutput { sources: unique_plates.sources, destinations: unique_plates.destinations, transfers } +} + +pub fn read_csv_auto(data: &str) -> AutoOutput { + let transfer_records = read_csv(data); + auto(&transfer_records) +} + +fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates { + let mut source_names: HashSet<&str> = HashSet::new(); + let mut destination_names: HashSet<&str> = HashSet::new(); + + for record in records { + source_names.insert(&record.source_plate); + destination_names.insert(&record.destination_plate); + } + + let mut sources: Vec = Vec::with_capacity(source_names.len()); + for source_name in source_names { + let filtered_records: Vec<&TransferRecord> = records + .iter() + .filter(|x| x.source_plate == source_name) + .collect(); + let format_guess = guess_plate_size(&filtered_records, PlateType::Source); + sources.push(PlateInstance::new( + PlateType::Source, + format_guess, + source_name.to_string(), + )) + } + + let mut destinations: Vec = Vec::with_capacity(destination_names.len()); + for destination_name in destination_names { + let filtered_records: Vec<&TransferRecord> = records + .iter() + .filter(|x| x.destination_plate == destination_name) + .collect(); + let format_guess = guess_plate_size(&filtered_records, PlateType::Destination); + destinations.push(PlateInstance::new( + PlateType::Destination, + format_guess, + destination_name.to_string(), + )) + } + + UniquePlates { + sources, + destinations, + } +} + +fn guess_plate_size(plate_filtered_records: &[&TransferRecord], which: PlateType) -> PlateFormat { + let mut guess = PlateFormat::W96; // Never guess smaller than 96 + for record in plate_filtered_records { + if let Some((row, col)) = string_well_to_pt(match which { + PlateType::Source => &record.source_well, + PlateType::Destination => &record.destination_well, + }) { + if row > W1536_ROW_MAX || col > W1536_COL_MAX { + return PlateFormat::W3456; + } else if row > W384_ROW_MAX || col > W384_COL_MAX { + guess = PlateFormat::W1536; + } else if row > W96_ROW_MAX || col > W96_COL_MAX { + guess = PlateFormat::W384; + } + } + } + guess +} + +fn get_transfer_for_all_pairs( + records: &[TransferRecord], + unique_plates: &UniquePlates, +) -> Vec { + let mut transfers: Vec = Vec::new(); + for source_instance in &unique_plates.sources { + for destination_instance in &unique_plates.destinations { + if let Some(transfer) = + get_transfer_for_pair(records, source_instance, destination_instance) + { + transfers.push(transfer); + } + } + } + + transfers +} + +fn get_transfer_for_pair( + records: &[TransferRecord], + source: &PlateInstance, + destination: &PlateInstance, +) -> Option { + let source_name: &str = &source.name; + let destination_name: &str = &destination.name; + + let mut filtered_records = records + .iter() + .filter(|x| x.source_plate == source_name && x.destination_plate == destination_name) + .peekable(); + + if filtered_records.peek().is_none() { + return None; + } + + let mut source_wells: HashSet<(u8, u8)> = HashSet::new(); + let mut destination_wells: HashSet<(u8, u8)> = HashSet::new(); + for record in filtered_records { + let source_point_opt = string_well_to_pt(&record.source_well); + let destination_point_opt = string_well_to_pt(&record.destination_well); + + if source_point_opt.and(destination_point_opt).is_some() { + let source_point = source_point_opt.unwrap(); + let destination_point = destination_point_opt.unwrap(); + + source_wells.insert(source_point); + destination_wells.insert(destination_point); + } + } + let source_wells_vec: Vec<(u8, u8)> = source_wells.into_iter().collect(); + let destination_wells_vec: Vec<(u8, u8)> = destination_wells.into_iter().collect(); + + let custom_region: Region = + Region::Custom(CustomRegion::new(source_wells_vec, destination_wells_vec)); + + let transfer_region = TransferRegion { + source_plate: source.plate, + dest_plate: destination.plate, + interleave_source: (1, 1), + interleave_dest: (1, 1), + source_region: custom_region.clone(), + dest_region: custom_region, + }; + + let transfer_name = format!("{} to {}", source.name, destination.name); + + Some(Transfer::new( + source.clone(), + destination.clone(), + transfer_region, + transfer_name, + )) +} diff --git a/plate-tool-lib/src/csv/conversion.rs b/plate-tool-lib/src/csv/conversion.rs new file mode 100644 index 0000000..ba10250 --- /dev/null +++ b/plate-tool-lib/src/csv/conversion.rs @@ -0,0 +1,80 @@ +use crate::transfer::Transfer; +use crate::util::*; + +use super::TransferRecord; +use lazy_static::lazy_static; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use std::error::Error; + +pub fn transfer_to_records( + tr: &Transfer, + src_barcode: &str, + dest_barcode: &str, +) -> Vec { + let source_wells = tr.transfer_region.get_source_wells(); + let map = tr.transfer_region.calculate_map(); + + let mut records: Vec = 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: tr.volume, + concentration: None, + }) + } + } + } + records +} + +pub fn records_to_csv(trs: Vec) -> Result> { + let mut wtr = csv::WriterBuilder::new().from_writer(vec![]); + for record in trs { + wtr.serialize(record)? + } + let data = String::from_utf8(wtr.into_inner()?)?; + Ok(data) +} + +pub fn string_well_to_pt(input: &str) -> Option<(u8, u8)> { + lazy_static! { + static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap(); + } + if let Some(c1) = REGEX.captures(input) { + if let (Some(row), Some(col)) = (letters_to_num(&c1[1]), c1[2].parse::().ok()) { + Some((row, col)) + } else { + None + } + } else { + None + } +} + +pub fn read_csv(data: &str) -> Vec { + let (header, data) = data.split_at(data.find('\n').unwrap()); + let modified: String = header.to_lowercase() + data; + + let mut rdr = csv::Reader::from_reader(modified.as_bytes()); + let mut records: Vec = Vec::new(); + for record in rdr.deserialize::() { + match record { + Ok(r) => { + //log::debug!("{:?}", r); + records.push(r); + } + Err(e) => { + log::debug!("{:?}", e); + } + } + } + records +} diff --git a/plate-tool-lib/src/csv/mod.rs b/plate-tool-lib/src/csv/mod.rs new file mode 100644 index 0000000..d72fe26 --- /dev/null +++ b/plate-tool-lib/src/csv/mod.rs @@ -0,0 +1,9 @@ +mod transfer_record; +mod conversion; +mod auto; + +pub use transfer_record::volume_default; +pub use transfer_record::TransferRecord; +pub use conversion::*; + +pub use auto::{auto, read_csv_auto}; diff --git a/plate-tool-lib/src/csv/transfer_record.rs b/plate-tool-lib/src/csv/transfer_record.rs new file mode 100644 index 0000000..ac54c87 --- /dev/null +++ b/plate-tool-lib/src/csv/transfer_record.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; + +use crate::transfer::Transfer; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TransferRecord { + #[serde(rename = "Source Plate", alias = "source plate", alias = "src plate")] + pub source_plate: String, + #[serde(rename = "Source Well", alias = "source well", alias = "src well")] + pub source_well: String, + #[serde( + rename = "Dest Plate", + alias = "dest plate", + alias = "destination plate" + )] + pub destination_plate: String, + #[serde( + rename = "Destination Well", + alias = "destination well", + alias = "dest well" + )] + pub destination_well: String, + #[serde(rename = "Transfer Volume", alias = "transfer volume")] + #[serde(default = "volume_default")] + pub volume: f32, + #[serde(rename = "Concentration", alias = "concentration")] + pub concentration: Option, +} + +pub fn volume_default() -> f32 { + Transfer::default().volume +} + diff --git a/src/data/mod.rs b/plate-tool-lib/src/lib.rs similarity index 87% rename from src/data/mod.rs rename to plate-tool-lib/src/lib.rs index bc5afaa..a85a4bb 100644 --- a/src/data/mod.rs +++ b/plate-tool-lib/src/lib.rs @@ -3,3 +3,4 @@ pub mod plate; pub mod plate_instances; pub mod transfer; pub mod transfer_region; +pub mod util; diff --git a/src/data/plate.rs b/plate-tool-lib/src/plate.rs similarity index 74% rename from src/data/plate.rs rename to plate-tool-lib/src/plate.rs index 2331aea..0c9040e 100644 --- a/src/data/plate.rs +++ b/plate-tool-lib/src/plate.rs @@ -60,6 +60,23 @@ impl std::fmt::Display for PlateFormat { } } } +impl TryFrom<&str> for PlateFormat { + type Error = (); + fn try_from(value: &str) -> Result { + let lower = value.to_lowercase(); + match lower.trim() { + "w6" | "6" => Ok(PlateFormat::W6), + "w12" | "12" => Ok(PlateFormat::W12), + "w24" | "24" => Ok(PlateFormat::W24), + "w48" | "48" => Ok(PlateFormat::W48), + "w96" | "96" => Ok(PlateFormat::W96), + "w384" | "384" => Ok(PlateFormat::W384), + "w1536" | "1536" => Ok(PlateFormat::W1536), + "w3456" | "3456" => Ok(PlateFormat::W3456), + _ => Err(()) + } + } +} impl PlateFormat { pub fn size(&self) -> (u8, u8) { diff --git a/src/data/plate_instances.rs b/plate-tool-lib/src/plate_instances.rs similarity index 70% rename from src/data/plate_instances.rs rename to plate-tool-lib/src/plate_instances.rs index f444c18..3ee4c70 100644 --- a/src/data/plate_instances.rs +++ b/plate-tool-lib/src/plate_instances.rs @@ -2,7 +2,7 @@ use super::plate::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[derive(PartialEq, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)] pub struct PlateInstance { pub plate: Plate, #[serde(rename = "id_v7")] @@ -30,6 +30,10 @@ impl PlateInstance { pub fn change_name(&mut self, new_name: String) { self.name = new_name; } + + pub fn change_format(&mut self, new_format: &PlateFormat) { + self.plate.plate_format = *new_format; + } } impl From for PlateInstance { @@ -41,3 +45,10 @@ impl From for PlateInstance { } } } + +impl From<&PlateInstance> for String { + fn from(value: &PlateInstance) -> Self { + // Could have other formatting here + format!("{}, {}", value.name, value.plate.plate_format) + } +} diff --git a/src/data/transfer.rs b/plate-tool-lib/src/transfer.rs similarity index 97% rename from src/data/transfer.rs rename to plate-tool-lib/src/transfer.rs index b27868b..d9e7edd 100644 --- a/src/data/transfer.rs +++ b/plate-tool-lib/src/transfer.rs @@ -26,7 +26,7 @@ impl Default for Transfer { name: "New Transfer".to_string(), id: Default::default(), transfer_region: Default::default(), - volume: 2.5f32, + volume: default_volume(), } } } diff --git a/src/data/transfer_region.rs b/plate-tool-lib/src/transfer_region.rs similarity index 94% rename from src/data/transfer_region.rs rename to plate-tool-lib/src/transfer_region.rs index 35ee1dd..ced6b13 100644 --- a/src/data/transfer_region.rs +++ b/plate-tool-lib/src/transfer_region.rs @@ -1,7 +1,5 @@ use serde::{Deserialize, Serialize}; -use crate::components::transfer_menu::RegionDisplay; - use super::plate::Plate; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] @@ -10,6 +8,12 @@ pub struct CustomRegion { dest: Vec<(u8, u8)>, } +impl CustomRegion { + pub fn new(src: Vec<(u8, u8)>, dest: Vec<(u8, u8)>) -> Self { + CustomRegion { src, dest } + } +} + #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub enum Region { Rect((u8, u8), (u8, u8)), @@ -32,8 +36,11 @@ impl TryFrom for ((u8, u8), (u8, u8)) { } } } + +type Corner = (u8, u8); +type Rectangle = (Corner, Corner); impl Region { - pub fn new_custom(transfers: &Vec<((u8, u8), (u8, u8))>) -> Self { + pub fn new_custom(transfers: &Vec) -> Self { let mut src_pts: Vec<(u8, u8)> = Vec::with_capacity(transfers.len()); let mut dest_pts: Vec<(u8, u8)> = Vec::with_capacity(transfers.len()); @@ -77,7 +84,7 @@ impl TransferRegion { match &self.source_region { Region::Rect(c1, c2) => { let mut wells = Vec::<(u8, u8)>::new(); - let (ul, br) = standardize_rectangle(&c1, &c2); + let (ul, br) = standardize_rectangle(c1, c2); let (interleave_i, interleave_j) = self.interleave_source; // NOTE: This will panic if either is 0! // We'll reassign these values (still not mutable) just in case. @@ -174,8 +181,8 @@ impl TransferRegion { Region::Rect(c1, c2) => { Box::new(move |(i, j)| { if source_wells.contains(&(i, j)) { - let possible_destination_wells = create_dense_rectangle(&c1, &c2); - let (d_ul, d_br) = standardize_rectangle(&c1, &c2); + let possible_destination_wells = create_dense_rectangle(c1, c2); + let (d_ul, d_br) = standardize_rectangle(c1, c2); let (s_ul, s_br) = standardize_rectangle(&source_corners.0, &source_corners.1); let s_dims = ( @@ -186,7 +193,7 @@ impl TransferRegion { d_br.0.checked_sub(d_ul.0).unwrap() + 1, d_br.1.checked_sub(d_ul.1).unwrap() + 1, ); - let N_s = ( + let number_used_src_wells = ( // Number of used source wells (s_dims.0 + il_source.0.unsigned_abs() - 1) .div_euclid(il_source.0.unsigned_abs()), @@ -197,14 +204,14 @@ impl TransferRegion { // How many times can we replicate? (1..) .position(|n| { - n * N_s.0 * il_dest.0.unsigned_abs() - il_dest.0.unsigned_abs() + n * number_used_src_wells.0 * il_dest.0.unsigned_abs() - il_dest.0.unsigned_abs() + 1 > d_dims.0 }) .unwrap() as u8, (1..) .position(|n| { - n * N_s.1 * il_dest.1.unsigned_abs() - il_dest.1.unsigned_abs() + n * number_used_src_wells.1 * il_dest.1.unsigned_abs() - il_dest.1.unsigned_abs() + 1 > d_dims.1 }) @@ -222,26 +229,26 @@ impl TransferRegion { .into_iter() .filter(|(x, _)| { x.checked_sub(d_ul.0).unwrap() - % (N_s.0 * il_dest.0.unsigned_abs()) // Counter along x + % (number_used_src_wells.0 * il_dest.0.unsigned_abs()) // Counter along x == (il_dest.0.unsigned_abs() *i) - % (N_s.0 * il_dest.0.unsigned_abs()) + % (number_used_src_wells.0 * il_dest.0.unsigned_abs()) }) .filter(|(_, y)| { y.checked_sub(d_ul.1).unwrap() - % (N_s.1 * il_dest.1.unsigned_abs()) // Counter along u + % (number_used_src_wells.1 * il_dest.1.unsigned_abs()) // Counter along u == (il_dest.1.unsigned_abs() *j) - % (N_s.1 * il_dest.1.unsigned_abs()) + % (number_used_src_wells.1 * il_dest.1.unsigned_abs()) }) .filter(|(x, y)| { // How many times have we replicated? < How many are we allowed // to replicate? x.checked_sub(d_ul.0) .unwrap() - .div_euclid(N_s.0 * il_dest.0.unsigned_abs()) + .div_euclid(number_used_src_wells.0 * il_dest.0.unsigned_abs()) < count.0 && y.checked_sub(d_ul.1) .unwrap() - .div_euclid(N_s.1 * il_dest.1.unsigned_abs()) + .div_euclid(number_used_src_wells.1 * il_dest.1.unsigned_abs()) < count.1 }) .collect(), diff --git a/plate-tool-lib/src/util.rs b/plate-tool-lib/src/util.rs new file mode 100644 index 0000000..477cbd2 --- /dev/null +++ b/plate-tool-lib/src/util.rs @@ -0,0 +1,70 @@ +pub fn letters_to_num(letters: &str) -> Option { + let mut num: u8 = 0; + for (i, letter) in letters.to_ascii_uppercase().chars().rev().enumerate() { + log::debug!("{}, {}", i, letter); + let n = letter as u8; + if !(65..=90).contains(&n) { + return None; + } + num = num.checked_add((26_i32.pow(i as u32) * (n as i32 - 64)).try_into().ok()?)?; + } + Some(num) +} +pub fn num_to_letters(num: u8) -> Option { + 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). + 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 { + text.push((64 + digit1) as char) + } + text.push((64 + digit2) as char); + + Some(text.to_string()) +} + +#[cfg(test)] +mod tests { + use super::{letters_to_num, num_to_letters}; + + #[test] + fn test_letters_to_num() { + assert_eq!(letters_to_num("D"), Some(4)); + assert_eq!(letters_to_num("d"), None); + assert_eq!(letters_to_num("AD"), Some(26 + 4)); + assert_eq!(letters_to_num("CG"), Some(3 * 26 + 7)); + } + + #[test] + 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] + fn test_l2n_and_n2l() { + 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()) + ); + for i in 1..=255 { + assert_eq!(letters_to_num(&num_to_letters(i).unwrap()), Some(i)); + } + } + +} diff --git a/plate-tool-web/.gitignore b/plate-tool-web/.gitignore new file mode 100644 index 0000000..4f96631 --- /dev/null +++ b/plate-tool-web/.gitignore @@ -0,0 +1,2 @@ +/target +/dist diff --git a/plate-tool-web/Cargo.toml b/plate-tool-web/Cargo.toml new file mode 100644 index 0000000..1f6cb70 --- /dev/null +++ b/plate-tool-web/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "plate-tool-web" +version = "0.2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +plate-tool-lib = { path = "../plate-tool-lib" } +yew = { version = "0.20.0", features = ["csr"] } +yewdux = "0.9" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["FormData", "HtmlFormElement", + "HtmlDialogElement", "Blob", "Url", "Window", + "HtmlAnchorElement", "ReadableStream", "HtmlSelectElement", "HtmlOptionElement", "HtmlButtonElement", + "FileReader", "HtmlCollection"] } +js-sys = "0.3" +log = "0.4" +wasm-logger = "0.2" +regex = "1" +lazy_static = "1.4" +uuid = { version = "1.6", features = ["v7", "fast-rng", "macro-diagnostics", "js", "serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +csv = "1.2" +getrandom = { version = "0.2", features = ["js"] } +rand = { version = "0.8", features = ["small_rng"] } + +[dev-dependencies] +wasm-bindgen-test = "0.3.0" diff --git a/assets/fonts/Inconsolata.ttf b/plate-tool-web/assets/fonts/Inconsolata.ttf similarity index 100% rename from assets/fonts/Inconsolata.ttf rename to plate-tool-web/assets/fonts/Inconsolata.ttf diff --git a/assets/fonts/Jost.ttf b/plate-tool-web/assets/fonts/Jost.ttf similarity index 100% rename from assets/fonts/Jost.ttf rename to plate-tool-web/assets/fonts/Jost.ttf diff --git a/assets/js/html2canvas.js b/plate-tool-web/assets/js/html2canvas.js similarity index 100% rename from assets/js/html2canvas.js rename to plate-tool-web/assets/js/html2canvas.js diff --git a/assets/js/screenshot_utility.js b/plate-tool-web/assets/js/screenshot_utility.js similarity index 100% rename from assets/js/screenshot_utility.js rename to plate-tool-web/assets/js/screenshot_utility.js diff --git a/assets/scss/default_theme/_variables.scss b/plate-tool-web/assets/scss/default_theme/_variables.scss similarity index 100% rename from assets/scss/default_theme/_variables.scss rename to plate-tool-web/assets/scss/default_theme/_variables.scss diff --git a/assets/scss/default_theme/base.scss b/plate-tool-web/assets/scss/default_theme/base.scss similarity index 100% rename from assets/scss/default_theme/base.scss rename to plate-tool-web/assets/scss/default_theme/base.scss diff --git a/assets/scss/default_theme/components/_dialog.scss b/plate-tool-web/assets/scss/default_theme/components/_dialog.scss similarity index 75% rename from assets/scss/default_theme/components/_dialog.scss rename to plate-tool-web/assets/scss/default_theme/components/_dialog.scss index 0a03929..6aad48d 100644 --- a/assets/scss/default_theme/components/_dialog.scss +++ b/plate-tool-web/assets/scss/default_theme/components/_dialog.scss @@ -35,3 +35,16 @@ dialog > form[method="dialog"] { } } } + +.close_button { + color: red; + position: absolute; + top: 5%; + right: 2%; + background: rgba(0%,0%,0%,10%); + + &:hover { + color: rgb(0%,100%,100%); + background: rgba(0%,0%,0%,80%); + } +} diff --git a/assets/scss/default_theme/components/_index.scss b/plate-tool-web/assets/scss/default_theme/components/_index.scss similarity index 100% rename from assets/scss/default_theme/components/_index.scss rename to plate-tool-web/assets/scss/default_theme/components/_index.scss diff --git a/assets/scss/default_theme/components/_main_window.scss b/plate-tool-web/assets/scss/default_theme/components/_main_window.scss similarity index 100% rename from assets/scss/default_theme/components/_main_window.scss rename to plate-tool-web/assets/scss/default_theme/components/_main_window.scss diff --git a/plate-tool-web/assets/scss/default_theme/components/_plate_container.scss b/plate-tool-web/assets/scss/default_theme/components/_plate_container.scss new file mode 100644 index 0000000..182bc24 --- /dev/null +++ b/plate-tool-web/assets/scss/default_theme/components/_plate_container.scss @@ -0,0 +1,41 @@ +@use "sass:color"; +@use "../variables" as *; + +div.plate_container { + display: flex; + flex-direction: column; + justify-content: space-evenly; + align-items: center; + + border: 2px solid $color-dark; + grid-column: right / right; + grid-row: upper / 3; + + h2 { + margin-bottom: 1%; + text-align: center; + } + + &>div { + display: grid; + grid-template-rows: auto auto; + grid-template-rows: auto auto; + + &>h2:nth-of-type(1) { + grid-column: 2; + grid-row: 1; + } + + &>h2:nth-of-type(2) { + grid-column: 1; + grid-row: 2; + writing-mode: vertical-rl; + transform: rotate(-180deg); + } + + &>div { + grid-column:2; + grid-row: 2; + } + } +} diff --git a/assets/scss/default_theme/components/_plates.scss b/plate-tool-web/assets/scss/default_theme/components/_plates.scss similarity index 100% rename from assets/scss/default_theme/components/_plates.scss rename to plate-tool-web/assets/scss/default_theme/components/_plates.scss diff --git a/assets/scss/default_theme/components/_transfer_menu.scss b/plate-tool-web/assets/scss/default_theme/components/_transfer_menu.scss similarity index 100% rename from assets/scss/default_theme/components/_transfer_menu.scss rename to plate-tool-web/assets/scss/default_theme/components/_transfer_menu.scss diff --git a/assets/scss/default_theme/components/_tree.scss b/plate-tool-web/assets/scss/default_theme/components/_tree.scss similarity index 100% rename from assets/scss/default_theme/components/_tree.scss rename to plate-tool-web/assets/scss/default_theme/components/_tree.scss diff --git a/assets/scss/default_theme/components/_upper_menu.scss b/plate-tool-web/assets/scss/default_theme/components/_upper_menu.scss similarity index 100% rename from assets/scss/default_theme/components/_upper_menu.scss rename to plate-tool-web/assets/scss/default_theme/components/_upper_menu.scss diff --git a/assets/scss/default_theme/main.scss b/plate-tool-web/assets/scss/default_theme/main.scss similarity index 100% rename from assets/scss/default_theme/main.scss rename to plate-tool-web/assets/scss/default_theme/main.scss diff --git a/assets/scss/index.scss b/plate-tool-web/assets/scss/index.scss similarity index 100% rename from assets/scss/index.scss rename to plate-tool-web/assets/scss/index.scss diff --git a/index.html b/plate-tool-web/index.html similarity index 100% rename from index.html rename to plate-tool-web/index.html diff --git a/src/components/callbacks/main_window_callbacks.rs b/plate-tool-web/src/components/callbacks/import_csv_callbacks.rs similarity index 58% rename from src/components/callbacks/main_window_callbacks.rs rename to plate-tool-web/src/components/callbacks/import_csv_callbacks.rs index 268407b..a7f15b8 100644 --- a/src/components/callbacks/main_window_callbacks.rs +++ b/plate-tool-web/src/components/callbacks/import_csv_callbacks.rs @@ -1,122 +1,23 @@ -#![allow(non_snake_case)] use std::collections::HashSet; -use js_sys::Array; -use lazy_static::lazy_static; -use regex::Regex; -use wasm_bindgen::{prelude::*, JsCast, JsValue}; +use wasm_bindgen::{prelude::*, JsCast}; use web_sys::{ - Blob, FileReader, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement, - HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url, + FileReader, HtmlButtonElement, HtmlDialogElement, HtmlElement, HtmlFormElement, + HtmlInputElement, HtmlOptionElement, HtmlSelectElement, }; use yew::prelude::*; use yewdux::prelude::*; -use crate::components::states::{CurrentTransfer, MainState}; -use crate::components::transfer_menu::letters_to_num; +use crate::components::states::MainState; -use crate::data::transfer::Transfer; -use crate::data::transfer_region::{Region, TransferRegion}; +use plate_tool_lib::transfer::Transfer; +use plate_tool_lib::transfer_region::{Region, TransferRegion}; -use crate::data::csv::{state_to_csv, TransferRecord}; +use plate_tool_lib::csv::{auto, string_well_to_pt, TransferRecord}; -type NoParamsCallback = Box ()>; +use super::main_window_callbacks::create_close_button; -pub fn toggle_in_transfer_hashes_callback( - main_dispatch: Dispatch, -) -> Callback { - let main_dispatch = main_dispatch.clone(); - Callback::from(move |_| { - main_dispatch.reduce_mut(|state| { - state.preferences.in_transfer_hashes ^= true; - }) - }) -} - -pub fn new_plate_dialog_callback( - new_plate_dialog_is_open: UseStateHandle, -) -> NoParamsCallback { - let new_plate_dialog_is_open = new_plate_dialog_is_open.clone(); - Box::new(move |_| { - new_plate_dialog_is_open.set(false); - }) -} - -pub fn open_new_plate_dialog_callback( - new_plate_dialog_is_open: UseStateHandle, -) -> NoParamsCallback { - let new_plate_dialog_is_open = new_plate_dialog_is_open.clone(); - Box::new(move |_| { - new_plate_dialog_is_open.set(true); - }) -} - -pub fn new_button_callback( - main_dispatch: Dispatch, - ct_dispatch: Dispatch, -) -> Callback { - Callback::from(move |_| { - let window = web_sys::window().unwrap(); - let confirm = - window.confirm_with_message("This will reset all plates and transfers. Proceed?"); - if let Ok(confirm) = confirm { - if confirm { - main_dispatch.set(MainState::default()); - ct_dispatch.set(CurrentTransfer::default()); - } - } - }) -} - -fn save_str(data: &str, name: &str) { - let blob = - Blob::new_with_str_sequence(&Array::from_iter(std::iter::once(JsValue::from_str(data)))); - 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::() - .unwrap(); - anchor.set_download(name); - anchor.set_href(&url); - anchor.click(); - } -} - -pub fn export_csv_button_callback(main_state: std::rc::Rc) -> Callback { - Callback::from(move |_| { - if main_state.transfers.is_empty() { - web_sys::window() - .unwrap() - .alert_with_message("No transfers to export.") - .unwrap(); - return; - } - web_sys::window().unwrap().alert_with_message("CSV export is currently not importable. Export as JSON if you'd like to back up your work!").unwrap(); - if let Ok(csv) = state_to_csv(&main_state) { - save_str(&csv, "transfers.csv"); - } - }) -} - -pub fn export_json_button_callback(main_state: std::rc::Rc) -> Callback { - Callback::from(move |_| { - if let Ok(json) = serde_json::to_string(&main_state) { - save_str(&json, "plate-tool-state.json"); - } else { - web_sys::window() - .unwrap() - .alert_with_message("Failed to export.") - .unwrap(); - } - }) -} - -pub fn input_json_input_callback( +pub fn import_transfer_csv_input_callback( main_dispatch: Dispatch, modal: HtmlDialogElement, ) -> Closure { @@ -133,16 +34,7 @@ pub fn input_json_input_callback( let main_dispatch = main_dispatch.clone(); // Clone to satisfy FnMut // trait let modal = modal.clone(); - let onload = Closure::::new(move |_: Event| { - if let Some(value) = &fr1.result().ok().and_then(|v| v.as_string()) { - let ms = serde_json::from_str::(value); - match ms { - Ok(ms) => main_dispatch.set(ms), - Err(e) => log::debug!("{:?}", e), - }; - modal.close(); - } - }); + let onload = import_transfer_csv_onload_callback(main_dispatch, fr1, modal); fr.set_onload(Some(onload.as_ref().unchecked_ref())); onload.forget(); // Magic (don't touch) } @@ -151,7 +43,7 @@ pub fn input_json_input_callback( }) } -pub fn import_json_button_callback(main_dispatch: Dispatch) -> Callback { +pub fn import_transfer_csv_callback(main_dispatch: Dispatch) -> Callback { Callback::from(move |_| { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); @@ -169,7 +61,10 @@ pub fn import_json_button_callback(main_dispatch: Dispatch) -> Callba }) }; modal.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); + + let close_button = create_close_button(&onclose_callback); onclose_callback.forget(); + modal.append_child(&close_button).unwrap(); let form = document .create_element("form") @@ -182,13 +77,13 @@ pub fn import_json_button_callback(main_dispatch: Dispatch) -> Callba .dyn_into::() .unwrap(); input.set_type("file"); - input.set_accept(".json"); + input.set_accept(".csv"); form.append_child(&input).unwrap(); let input_callback = { let main_dispatch = main_dispatch.clone(); let modal = modal.clone(); - input_json_input_callback(main_dispatch, modal) + import_transfer_csv_input_callback(main_dispatch, modal) }; input.set_onchange(Some(input_callback.as_ref().unchecked_ref())); input_callback.forget(); // Magic straight from the docs, don't touch :( @@ -199,83 +94,6 @@ pub fn import_json_button_callback(main_dispatch: Dispatch) -> Callba }) } -pub fn import_transfer_csv_submit_callback( - main_dispatch: Dispatch, - from_source: HtmlSelectElement, - to_source: HtmlSelectElement, - from_dest: HtmlSelectElement, - to_dest: HtmlSelectElement, - records: Vec, -) -> Closure { - Closure::::new(move |_: Event| { - let from_source = from_source.value(); - let to_source = to_source.value(); - let from_dest = from_dest.value(); - let to_dest = to_dest.value(); - - lazy_static! { - static ref REGEX: Regex = Regex::new(r"([A-Z]+)(\d+)").unwrap(); - } - let records: Vec<((u8, u8), (u8, u8))> = records - .iter() - .filter(|record| record.source_plate == from_source) - .filter(|record| record.destination_plate == from_dest) - .map(|record| { - let c1 = REGEX.captures(&record.source_well).unwrap(); - let c2 = REGEX.captures(&record.destination_well).unwrap(); - log::debug!("{} {}", &record.source_well, &record.destination_well); - log::debug!("{},{} {},{}", &c1[1], &c1[2], &c2[1], &c2[2]); - - ( - ( - letters_to_num(&c1[1]).unwrap(), - c1[2].parse::().unwrap(), - ), - ( - letters_to_num(&c2[1]).unwrap(), - c2[2].parse::().unwrap(), - ), - ) - }) - .collect(); - - let spi = main_dispatch - .get() - .source_plates - .iter() - .find(|src| src.name == to_source) - .unwrap() - .clone(); - let dpi = main_dispatch - .get() - .destination_plates - .iter() - .find(|dest| dest.name == to_dest) - .unwrap() - .clone(); - - let custom_region = Region::new_custom(&records); - let transfer_region = TransferRegion { - source_region: custom_region.clone(), - dest_region: custom_region, - interleave_source: (1, 1), - interleave_dest: (1, 1), - source_plate: spi.plate, - dest_plate: dpi.plate, - }; - - let transfer = Transfer::new(spi, dpi, transfer_region, "Custom Transfer".to_string()); - main_dispatch.reduce_mut(|state| { - state.transfers.push(transfer); - state.selected_transfer = state - .transfers - .last() - .expect("An element should have just been added") - .get_uuid(); - }); - }) -} - pub fn import_transfer_csv_onload_callback( main_dispatch: Dispatch, file_reader: FileReader, @@ -283,19 +101,7 @@ pub fn import_transfer_csv_onload_callback( ) -> Closure { Closure::::new(move |_: Event| { if let Some(value) = &file_reader.result().ok().and_then(|v| v.as_string()) { - let mut rdr = csv::Reader::from_reader(value.as_bytes()); - let mut records = Vec::new(); - for record in rdr.deserialize::() { - match record { - Ok(r) => { - //log::debug!("{:?}", r); - records.push(r); - } - Err(e) => { - log::debug!("{:?}", e); - } - } - } + let records = plate_tool_lib::csv::read_csv(value); let mut sources: HashSet = HashSet::new(); let mut destinations: HashSet = HashSet::new(); @@ -306,6 +112,17 @@ pub fn import_transfer_csv_onload_callback( let window = web_sys::window().unwrap(); let document = window.document().unwrap(); + + let auto_button = document + .create_element("button") + .unwrap() + .dyn_into::() + .unwrap(); + auto_button.set_inner_text("Auto"); + let auto_button_callback = auto_callback(main_dispatch.clone(), &records); + auto_button.set_onclick(Some(auto_button_callback.as_ref().unchecked_ref())); + auto_button_callback.forget(); + let form = document .create_element("form") .unwrap() @@ -402,32 +219,87 @@ pub fn import_transfer_csv_onload_callback( form.append_child(&to_dest).unwrap(); modal.append_child(&submit).unwrap(); modal.append_child(&form).unwrap(); + modal.append_child(&auto_button).unwrap(); } }) } -pub fn import_transfer_csv_input_callback( +pub fn import_transfer_csv_submit_callback( main_dispatch: Dispatch, - modal: HtmlDialogElement, + from_source: HtmlSelectElement, + to_source: HtmlSelectElement, + from_dest: HtmlSelectElement, + to_dest: HtmlSelectElement, + records: Vec, ) -> Closure { - Closure::::new(move |e: Event| { - if let Some(input) = e.current_target() { - let input = input - .dyn_into::() - .expect("We know this is an input."); - if let Some(files) = input.files() { - if let Some(file) = files.get(0) { - let fr = web_sys::FileReader::new().unwrap(); - fr.read_as_text(&file).unwrap(); - let fr1 = fr.clone(); // Clone to avoid outliving closure - let main_dispatch = main_dispatch.clone(); // Clone to satisfy FnMut - // trait - let modal = modal.clone(); - let onload = import_transfer_csv_onload_callback(main_dispatch, fr1, modal); - fr.set_onload(Some(onload.as_ref().unchecked_ref())); - onload.forget(); // Magic (don't touch) - } - } - } + Closure::::new(move |_: Event| { + let from_source = from_source.value(); + let to_source = to_source.value(); + let from_dest = from_dest.value(); + let to_dest = to_dest.value(); + + let records: Vec<((u8, u8), (u8, u8))> = records + .iter() + .filter(|record| record.source_plate == from_source) + .filter(|record| record.destination_plate == from_dest) + .map(|record| { + ( + string_well_to_pt(&record.source_well).unwrap(), + string_well_to_pt(&record.destination_well).unwrap(), + ) + }) + .collect(); + + let spi = main_dispatch + .get() + .source_plates + .iter() + .find(|src| src.name == to_source) + .unwrap() + .clone(); + let dpi = main_dispatch + .get() + .destination_plates + .iter() + .find(|dest| dest.name == to_dest) + .unwrap() + .clone(); + + let custom_region = Region::new_custom(&records); + let transfer_region = TransferRegion { + source_region: custom_region.clone(), + dest_region: custom_region, + interleave_source: (1, 1), + interleave_dest: (1, 1), + source_plate: spi.plate, + dest_plate: dpi.plate, + }; + + let transfer = Transfer::new(spi, dpi, transfer_region, "Custom Transfer".to_string()); + main_dispatch.reduce_mut(|state| { + state.transfers.push(transfer); + state.selected_transfer = state + .transfers + .last() + .expect("An element should have just been added") + .get_uuid(); + }); + }) +} + +fn auto_callback( + main_dispatch: Dispatch, + records: &[TransferRecord], +) -> Closure { + let records = Vec::from(records); + Closure::::new(move |_| { + let res = auto(&records); + main_dispatch.reduce_mut(|state| { + state.source_plates.extend(res.sources.into_iter()); + state + .destination_plates + .extend(res.destinations.into_iter()); + state.transfers.extend(res.transfers.into_iter()); + }); }) } diff --git a/plate-tool-web/src/components/callbacks/main_window_callbacks.rs b/plate-tool-web/src/components/callbacks/main_window_callbacks.rs new file mode 100644 index 0000000..73b262c --- /dev/null +++ b/plate-tool-web/src/components/callbacks/main_window_callbacks.rs @@ -0,0 +1,221 @@ +#![allow(non_snake_case)] + + +use js_sys::Array; + + +use wasm_bindgen::{prelude::*, JsCast, JsValue}; +use web_sys::{ + Blob, HtmlAnchorElement, HtmlDialogElement, HtmlElement, + HtmlFormElement, HtmlInputElement, Url, +}; +use yew::prelude::*; +use yewdux::prelude::*; + +use crate::components::states::{CurrentTransfer, MainState}; + +use crate::state_to_csv; + +type NoParamsCallback = Box; + +pub fn create_close_button(close: &Closure) -> HtmlElement { + let document = web_sys::window().unwrap().document().unwrap(); + let close_button = document + .create_element("button") + .unwrap() + .dyn_into::() + .unwrap(); + close_button.set_text_content(Some("✖")); + close_button.set_class_name("close_button"); + close_button.set_onclick(Some(close.as_ref().unchecked_ref())); + + close_button +} + +pub fn toggle_in_transfer_hashes_callback( + main_dispatch: Dispatch, +) -> Callback { + let main_dispatch = main_dispatch.clone(); + Callback::from(move |_| { + main_dispatch.reduce_mut(|state| { + state.preferences.in_transfer_hashes ^= true; + }) + }) +} + +pub fn new_plate_dialog_callback( + new_plate_dialog_is_open: UseStateHandle, +) -> NoParamsCallback { + let new_plate_dialog_is_open = new_plate_dialog_is_open.clone(); + Box::new(move |_| { + new_plate_dialog_is_open.set(false); + }) +} + +pub fn open_new_plate_dialog_callback( + new_plate_dialog_is_open: UseStateHandle, +) -> NoParamsCallback { + let new_plate_dialog_is_open = new_plate_dialog_is_open.clone(); + Box::new(move |_| { + new_plate_dialog_is_open.set(true); + }) +} + +pub fn new_button_callback( + main_dispatch: Dispatch, + ct_dispatch: Dispatch, +) -> Callback { + Callback::from(move |_| { + let window = web_sys::window().unwrap(); + let confirm = + window.confirm_with_message("This will reset all plates and transfers. Proceed?"); + if let Ok(confirm) = confirm { + if confirm { + main_dispatch.set(MainState::default()); + ct_dispatch.set(CurrentTransfer::default()); + } + } + }) +} + +fn save_str(data: &str, name: &str) { + let blob = + Blob::new_with_str_sequence(&Array::from_iter(std::iter::once(JsValue::from_str(data)))); + 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::() + .unwrap(); + anchor.set_download(name); + anchor.set_href(&url); + anchor.click(); + } +} + +pub fn export_csv_button_callback(main_state: std::rc::Rc) -> Callback { + Callback::from(move |_| { + if main_state.transfers.is_empty() { + web_sys::window() + .unwrap() + .alert_with_message("No transfers to export.") + .unwrap(); + return; + } + web_sys::window().unwrap().alert_with_message("CSV export is currently not importable. Export as JSON if you'd like to back up your work!").unwrap(); + if let Ok(csv) = state_to_csv(&main_state) { + save_str(&csv, "transfers.csv"); + } + }) +} + +pub fn export_json_button_callback(main_state: std::rc::Rc) -> Callback { + Callback::from(move |_| { + if let Ok(json) = serde_json::to_string(&main_state) { + save_str(&json, "plate-tool-state.json"); + } else { + web_sys::window() + .unwrap() + .alert_with_message("Failed to export.") + .unwrap(); + } + }) +} + +pub fn input_json_input_callback( + main_dispatch: Dispatch, + modal: HtmlDialogElement, +) -> Closure { + Closure::::new(move |e: Event| { + if let Some(input) = e.current_target() { + let input = input + .dyn_into::() + .expect("We know this is an input."); + if let Some(files) = input.files() { + if let Some(file) = files.get(0) { + let fr = web_sys::FileReader::new().unwrap(); + fr.read_as_text(&file).unwrap(); + let fr1 = fr.clone(); // Clone to avoid outliving closure + let main_dispatch = main_dispatch.clone(); // Clone to satisfy FnMut + // trait + let modal = modal.clone(); + let onload = Closure::::new(move |_: Event| { + if let Some(value) = &fr1.result().ok().and_then(|v| v.as_string()) { + let ms = serde_json::from_str::(value); + match ms { + Ok(ms) => main_dispatch.set(ms), + Err(e) => log::debug!("{:?}", e), + }; + modal.close(); + } + }); + fr.set_onload(Some(onload.as_ref().unchecked_ref())); + onload.forget(); // Magic (don't touch) + } + } + } + }) +} + +pub fn import_json_button_callback(main_dispatch: Dispatch) -> Callback { + Callback::from(move |_| { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + let modal = document + .create_element("dialog") + .unwrap() + .dyn_into::() + .unwrap(); + modal.set_text_content(Some("Import File:")); + let onclose_callback = { + let modal = modal.clone(); + Closure::::new(move |_: Event| { + modal.remove(); + }) + }; + modal.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); + + let close_button = create_close_button(&onclose_callback); + onclose_callback.forget(); + modal.append_child(&close_button).unwrap(); + + let form = document + .create_element("form") + .unwrap() + .dyn_into::() + .unwrap(); + let input = document + .create_element("input") + .unwrap() + .dyn_into::() + .unwrap(); + input.set_type("file"); + input.set_accept(".json"); + form.append_child(&input).unwrap(); + + let input_callback = { + let main_dispatch = main_dispatch.clone(); + let modal = modal.clone(); + input_json_input_callback(main_dispatch, modal) + }; + input.set_onchange(Some(input_callback.as_ref().unchecked_ref())); + input_callback.forget(); // Magic straight from the docs, don't touch :( + + modal.append_child(&form).unwrap(); + body.append_child(&modal).unwrap(); + modal.show_modal().unwrap(); + }) +} + +pub use super::import_csv_callbacks::import_transfer_csv_callback; + +pub use super::import_csv_callbacks::import_transfer_csv_submit_callback; + +pub use super::import_csv_callbacks::import_transfer_csv_onload_callback; + +pub use super::import_csv_callbacks::import_transfer_csv_input_callback; diff --git a/src/components/callbacks/mod.rs b/plate-tool-web/src/components/callbacks/mod.rs similarity index 82% rename from src/components/callbacks/mod.rs rename to plate-tool-web/src/components/callbacks/mod.rs index 175182a..daec33c 100644 --- a/src/components/callbacks/mod.rs +++ b/plate-tool-web/src/components/callbacks/mod.rs @@ -2,3 +2,4 @@ pub mod main_window_callbacks; pub mod new_plate_dialog_callbacks; pub mod transfer_menu_callbacks; pub mod tree_callbacks; +mod import_csv_callbacks; diff --git a/src/components/callbacks/new_plate_dialog_callbacks.rs b/plate-tool-web/src/components/callbacks/new_plate_dialog_callbacks.rs similarity index 96% rename from src/components/callbacks/new_plate_dialog_callbacks.rs rename to plate-tool-web/src/components/callbacks/new_plate_dialog_callbacks.rs index 71cb8ec..5bb7b09 100644 --- a/src/components/callbacks/new_plate_dialog_callbacks.rs +++ b/plate-tool-web/src/components/callbacks/new_plate_dialog_callbacks.rs @@ -5,8 +5,8 @@ use wasm_bindgen::JsCast; use web_sys::{EventTarget, FormData, HtmlFormElement}; use crate::components::states::MainState; -use crate::data::plate::*; -use crate::data::plate_instances::PlateInstance; +use plate_tool_lib::plate::*; +use plate_tool_lib::plate_instances::PlateInstance; pub fn new_plate_callback( dispatch: Dispatch, diff --git a/src/components/callbacks/transfer_menu_callbacks.rs b/plate-tool-web/src/components/callbacks/transfer_menu_callbacks.rs similarity index 99% rename from src/components/callbacks/transfer_menu_callbacks.rs rename to plate-tool-web/src/components/callbacks/transfer_menu_callbacks.rs index 2803ac1..ae8d005 100644 --- a/src/components/callbacks/transfer_menu_callbacks.rs +++ b/plate-tool-web/src/components/callbacks/transfer_menu_callbacks.rs @@ -6,7 +6,7 @@ use yew::prelude::*; use yewdux::prelude::*; use crate::components::transfer_menu::RegionDisplay; -use crate::data::{transfer::Transfer, transfer_region::Region}; +use plate_tool_lib::{transfer::Transfer, transfer_region::Region}; use crate::components::states::{CurrentTransfer, MainState}; diff --git a/src/components/callbacks/tree_callbacks.rs b/plate-tool-web/src/components/callbacks/tree_callbacks.rs similarity index 74% rename from src/components/callbacks/tree_callbacks.rs rename to plate-tool-web/src/components/callbacks/tree_callbacks.rs index 0bb32ce..f005fab 100644 --- a/src/components/callbacks/tree_callbacks.rs +++ b/plate-tool-web/src/components/callbacks/tree_callbacks.rs @@ -1,14 +1,14 @@ use std::rc::Rc; use uuid::Uuid; use wasm_bindgen::JsCast; -use web_sys::{EventTarget, HtmlElement}; +use web_sys::{EventTarget, HtmlElement, HtmlInputElement, HtmlSelectElement, HtmlOptionElement}; use yew::prelude::*; use yewdux::prelude::*; use crate::components::states::{CurrentTransfer, MainState}; -use crate::data::transfer_region::Region; +use plate_tool_lib::{transfer_region::Region, plate::PlateFormat}; -type NoParamsCallback = Box ()>; +type NoParamsCallback = Box; pub fn open_plate_info_callback( plate_menu_id: UseStateHandle>, @@ -45,6 +45,38 @@ pub fn plate_info_delete_callback( }) } +pub fn rename_onchange(id: Uuid, main_dispatch: Dispatch) -> Callback { + Callback::from(move |e: Event| { + log::debug!("Changed name"); + let input = e + .target() + .expect("Event must have target") + .dyn_into::() + .unwrap(); + main_dispatch.reduce_mut(|state| state.rename_plate(id, &input.value())) + }) +} + +pub fn format_onchange(id: Uuid, main_dispatch: Dispatch) -> Callback { + Callback::from(move |e: Event| { + log::debug!("Changing plate format"); + let new_format: Option = e.target() + .expect("Event must have target") + .dyn_into::() + .unwrap() + .selected_options() + .get_with_index(0) + .map(|el| { + el.dyn_into::().unwrap() + }) + .map(|opt_el| opt_el.value()) + .and_then(|value| PlateFormat::try_from(value.as_str()).ok()); + if let Some(format) = new_format { + main_dispatch.reduce_mut(|state| state.change_format(id, &format)); + } + }) +} + pub fn source_plate_select_callback( main_dispatch: Dispatch, ct_dispatch: Dispatch, diff --git a/src/components/main_window.rs b/plate-tool-web/src/components/main_window.rs similarity index 60% rename from src/components/main_window.rs rename to plate-tool-web/src/components/main_window.rs index e72f035..1b6175f 100644 --- a/src/components/main_window.rs +++ b/plate-tool-web/src/components/main_window.rs @@ -1,8 +1,4 @@ #![allow(non_snake_case)] - -use js_sys::Array; -use wasm_bindgen::{prelude::*, JsCast, JsValue}; -use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url}; use yew::prelude::*; use yewdux::prelude::*; @@ -12,7 +8,7 @@ use crate::components::states::{CurrentTransfer, MainState}; use crate::components::transfer_menu::TransferMenu; use crate::components::tree::Tree; -use crate::data::plate_instances::PlateInstance; +use plate_tool_lib::plate_instances::PlateInstance; use crate::components::callbacks::main_window_callbacks; @@ -72,53 +68,8 @@ pub fn MainWindow() -> Html { main_window_callbacks::import_json_button_callback(main_dispatch) }; - let import_transfer_csv_callback = { - Callback::from(move |_| { - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); - let modal = document - .create_element("dialog") - .unwrap() - .dyn_into::() - .unwrap(); - modal.set_text_content(Some("Import File:")); - let onclose_callback = { - let modal = modal.clone(); - Closure::::new(move |_: Event| { - modal.remove(); - }) - }; - modal.set_onclose(Some(onclose_callback.as_ref().unchecked_ref())); - onclose_callback.forget(); - - let form = document - .create_element("form") - .unwrap() - .dyn_into::() - .unwrap(); - let input = document - .create_element("input") - .unwrap() - .dyn_into::() - .unwrap(); - input.set_type("file"); - input.set_accept(".csv"); - form.append_child(&input).unwrap(); - - let input_callback = { - let main_dispatch = main_dispatch.clone(); - let modal = modal.clone(); - main_window_callbacks::import_transfer_csv_input_callback(main_dispatch, modal) - }; - input.set_onchange(Some(input_callback.as_ref().unchecked_ref())); - input_callback.forget(); // Magic straight from the docs, don't touch :( - - modal.append_child(&form).unwrap(); - body.append_child(&modal).unwrap(); - modal.show_modal().unwrap(); - }) - }; + let import_transfer_csv_callback = + main_window_callbacks::import_transfer_csv_callback(main_dispatch.clone()); html! { <> @@ -163,22 +114,3 @@ pub fn MainWindow() -> Html { } } - -fn save_str(data: &str, name: &str) { - let blob = - Blob::new_with_str_sequence(&Array::from_iter(std::iter::once(JsValue::from_str(data)))); - 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::() - .unwrap(); - anchor.set_download(name); - anchor.set_href(&url); - anchor.click(); - } -} diff --git a/src/components/mod.rs b/plate-tool-web/src/components/mod.rs similarity index 100% rename from src/components/mod.rs rename to plate-tool-web/src/components/mod.rs diff --git a/src/components/new_plate_dialog.rs b/plate-tool-web/src/components/new_plate_dialog.rs similarity index 90% rename from src/components/new_plate_dialog.rs rename to plate-tool-web/src/components/new_plate_dialog.rs index 31fee6c..f50c2d3 100644 --- a/src/components/new_plate_dialog.rs +++ b/plate-tool-web/src/components/new_plate_dialog.rs @@ -4,7 +4,6 @@ use yewdux::prelude::*; use web_sys::HtmlDialogElement; use crate::components::states::MainState; -use crate::data::plate_instances::PlateInstance; use crate::components::callbacks::new_plate_dialog_callbacks; @@ -69,10 +68,3 @@ pub fn NewPlateDialog(props: &NewPlateDialogProps) -> Html { } } - -impl From<&PlateInstance> for String { - fn from(value: &PlateInstance) -> Self { - // Could have other formatting here - format!("{}, {}", value.name, value.plate.plate_format) - } -} diff --git a/plate-tool-web/src/components/plates/mod.rs b/plate-tool-web/src/components/plates/mod.rs new file mode 100644 index 0000000..0b506d5 --- /dev/null +++ b/plate-tool-web/src/components/plates/mod.rs @@ -0,0 +1,5 @@ +pub mod plate_container; +mod util; +mod plate_callbacks; +mod plate_data; +mod plate; diff --git a/src/components/plates/source_plate.rs b/plate-tool-web/src/components/plates/plate.rs similarity index 66% rename from src/components/plates/source_plate.rs rename to plate-tool-web/src/components/plates/plate.rs index 8faac98..938e963 100644 --- a/src/components/plates/source_plate.rs +++ b/plate-tool-web/src/components/plates/plate.rs @@ -5,66 +5,70 @@ use yew::prelude::*; use yewdux::prelude::*; use crate::components::states::{CurrentTransfer, MainState}; -use crate::data::plate_instances::PlateInstance; -use crate::data::transfer::Transfer; -use crate::data::transfer_region::Region; +use plate_tool_lib::plate::PlateType; +use plate_tool_lib::transfer::Transfer; +use plate_tool_lib::transfer_region::Region; // Color Palette for the Source Plates, can be changed here use crate::components::plates::util::Palettes; const PALETTE: super::util::ColorPalette = Palettes::RAINBOW; -use super::super::transfer_menu::{num_to_letters, RegionDisplay}; +use plate_tool_lib::util::num_to_letters; -#[derive(PartialEq, Properties)] -pub struct SourcePlateProps { - pub source_plate: PlateInstance, - pub destination_plate: PlateInstance, - pub cell_height: f64, -} +use super::plate_data::*; +use super::plate_callbacks; #[function_component] -pub fn SourcePlate(props: &SourcePlateProps) -> Html { +pub fn Plate(props: &PlateProps) -> Html { let (main_state, _) = use_store::(); let (ct_state, ct_dispatch) = use_store::(); - let m_start_handle: UseStateHandle> = use_state_eq(|| None); - let m_end_handle: UseStateHandle> = use_state_eq(|| None); - let m_stat_handle: UseStateHandle = use_state_eq(|| false); + let m_start_handle: MStartHandle = use_state_eq(|| None); + let m_end_handle: MEndHandle = use_state_eq(|| None); + let m_stat_handle: MStatHandle = use_state_eq(|| false); if !(*m_stat_handle) { - let (pt1, pt2) = match ct_state.transfer.transfer_region.source_region { + let region = match props.ptype { + PlateType::Source => ct_state.transfer.transfer_region.source_region.clone(), + PlateType::Destination => ct_state.transfer.transfer_region.dest_region.clone(), + }; + let (pt1, pt2) = match region { Region::Point((x, y)) => ((x, y), (x, y)), Region::Rect(c1, c2) => (c1, c2), - Region::Custom(_) => ((0,0), (0,0)), + Region::Custom(_) => ((0, 0), (0, 0)), }; m_start_handle.set(Some(pt1)); m_end_handle.set(Some(pt2)); } - let transfer_map = { - let ts = main_state - .transfers - .iter() - .filter(|t| t.source_id == props.source_plate.get_uuid()); + let tooltip_map = { + let transfers = main_state.transfers.iter().filter(|t| match props.ptype { + PlateType::Source => t.source_id == props.source_plate.get_uuid(), + PlateType::Destination => t.dest_id == props.destination_plate.get_uuid(), + }); let mut tooltip_map: HashMap<(u8, u8), Vec<&Transfer>> = HashMap::new(); - for t in ts { - let sws = t.transfer_region.get_source_wells(); - for sw in sws { - if let Some(val) = tooltip_map.get_mut(&sw) { - val.push(t); + for transfer in transfers { + let wells = match props.ptype { + PlateType::Source => transfer.transfer_region.get_source_wells(), + PlateType::Destination => transfer.transfer_region.get_destination_wells(), + }; + for well in wells { + if let Some(val) = tooltip_map.get_mut(&well) { + val.push(transfer); } else { - tooltip_map.insert(sw, vec![t]); + tooltip_map.insert(well, vec![transfer]); } } } tooltip_map }; - let source_wells = ct_state.transfer.transfer_region.get_source_wells(); + let wells = match props.ptype { + PlateType::Source => ct_state.transfer.transfer_region.get_source_wells(), + PlateType::Destination => ct_state.transfer.transfer_region.get_destination_wells(), + }; let ordered_ids: Vec = { - let mut ids: Vec = main_state.transfers.clone().iter() - .map(|x| x.id) - .collect(); + let mut ids: Vec = main_state.transfers.clone().iter().map(|x| x.id).collect(); ids.sort_unstable(); ids }; @@ -73,37 +77,13 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html { let m_start_handle = m_start_handle.clone(); let m_end_handle = m_end_handle.clone(); let m_stat_handle = m_stat_handle.clone(); - - Callback::from(move |(i, j, t)| match t { - MouseEventType::Mousedown => { - m_start_handle.set(Some((i, j))); - m_end_handle.set(Some((i, j))); - m_stat_handle.set(true); - } - MouseEventType::Mouseenter => { - if *m_stat_handle { - m_end_handle.set(Some((i, j))); - } - } - }) + plate_callbacks::mouse_callback(m_start_handle, m_end_handle, m_stat_handle) }; let mouseup_callback = { let m_start_handle = m_start_handle.clone(); let m_end_handle = m_end_handle.clone(); - - Callback::from(move |_: MouseEvent| { - m_stat_handle.set(false); - if let Some(ul) = *m_start_handle { - if let Some(br) = *m_end_handle { - if let Ok(rd) = RegionDisplay::try_from((ul.0, ul.1, br.0, br.1)) { - ct_dispatch.reduce_mut(|state| { - state.transfer.transfer_region.source_region = Region::from(&rd); - }); - } - } - } - }) + plate_callbacks::mouseup_callback(m_start_handle, m_end_handle, m_stat_handle, ct_dispatch) }; let mouseleave_callback = Callback::clone(&mouseup_callback); @@ -112,8 +92,20 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html { let _ = js_sys::eval("copy_screenshot_src()"); }); + let width = match props.ptype { + PlateType::Source => props.source_plate.plate.size().1, + PlateType::Destination => props.destination_plate.plate.size().1, + }; + let height = match props.ptype { + PlateType::Source => props.source_plate.plate.size().0, + PlateType::Destination => props.destination_plate.plate.size().0, + }; + let pformat = match props.ptype { + PlateType::Source => props.source_plate.plate.plate_format, + PlateType::Destination => props.destination_plate.plate.plate_format, + }; let column_header = { - let headers = (1..=props.source_plate.plate.size().1) + let headers = (1..=width) .map(|j| { html! { {format!("{:0>2}", j)} @@ -122,22 +114,23 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html { .collect::(); html! {{ headers }} }; - let rows = (1..=props.source_plate.plate.size().0) + let rows = + (1..=height) .map(|i| { let row_header = html! {{num_to_letters(i)}}; - let row = (1..=props.source_plate.plate.size().1) + let row = (1..=width) .map(|j| { html! { - >().join(", ")))} /> } @@ -153,8 +146,11 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html { html! {
+ class={classes!{match props.ptype { + PlateType::Source => "source_plate", + PlateType::Destination => "dest_plate", + }, + "W".to_owned()+&pformat.to_string()}}> Html { } #[derive(PartialEq, Properties)] -pub struct SourcePlateCellProps { +pub struct PlateCellProps { i: u8, j: u8, selected: bool, @@ -180,14 +176,9 @@ pub struct SourcePlateCellProps { cell_height: f64, title: Option, } -#[derive(Debug)] -pub enum MouseEventType { - Mousedown, - Mouseenter, -} #[function_component] -fn SourcePlateCell(props: &SourcePlateCellProps) -> Html { +fn PlateCell(props: &PlateCellProps) -> Html { let selected_class = match props.selected { true => Some("current_select"), false => None, diff --git a/plate-tool-web/src/components/plates/plate_callbacks.rs b/plate-tool-web/src/components/plates/plate_callbacks.rs new file mode 100644 index 0000000..089fcbf --- /dev/null +++ b/plate-tool-web/src/components/plates/plate_callbacks.rs @@ -0,0 +1,50 @@ +use yew::prelude::*; +use yewdux::prelude::*; + +use crate::components::states::CurrentTransfer; +use plate_tool_lib::transfer_region::Region; + +// Color Palette for the Source Plates, can be changed here + +use super::super::transfer_menu::RegionDisplay; + +use super::plate_data::*; + +pub fn mouse_callback( + m_start_handle: MStartHandle, + m_end_handle: MEndHandle, + m_stat_handle: MStatHandle, +) -> Callback<(u8, u8, MouseEventType)> { + Callback::from(move |(i, j, t)| match t { + MouseEventType::Mousedown => { + m_start_handle.set(Some((i, j))); + m_end_handle.set(Some((i, j))); + m_stat_handle.set(true); + } + MouseEventType::Mouseenter => { + if *m_stat_handle { + m_end_handle.set(Some((i, j))); + } + } + }) +} + +pub fn mouseup_callback( + m_start_handle: MStartHandle, + m_end_handle: MEndHandle, + m_stat_handle: MStatHandle, + ct_dispatch: Dispatch, +) -> Callback { + Callback::from(move |_: MouseEvent| { + m_stat_handle.set(false); + if let Some(ul) = *m_start_handle { + if let Some(br) = *m_end_handle { + if let Ok(rd) = RegionDisplay::try_from((ul.0, ul.1, br.0, br.1)) { + ct_dispatch.reduce_mut(|state| { + state.transfer.transfer_region.source_region = Region::from(&rd); + }); + } + } + } + }) +} diff --git a/src/components/plates/plate_container.rs b/plate-tool-web/src/components/plates/plate_container.rs similarity index 76% rename from src/components/plates/plate_container.rs rename to plate-tool-web/src/components/plates/plate_container.rs index e07ca16..27232f9 100644 --- a/src/components/plates/plate_container.rs +++ b/plate-tool-web/src/components/plates/plate_container.rs @@ -3,10 +3,12 @@ use wasm_bindgen::prelude::Closure; use wasm_bindgen::JsCast; use yew::prelude::*; -use crate::data::plate_instances::PlateInstance; +use plate_tool_lib::plate::PlateType; +use plate_tool_lib::plate_instances::PlateInstance; -use super::destination_plate::DestinationPlate; -use super::source_plate::SourcePlate; +// use super::destination_plate::DestinationPlate; +// use super::source_plate::SourcePlate; +use super::plate::Plate; #[derive(Properties, PartialEq)] pub struct PlateContainerProps { @@ -53,13 +55,15 @@ pub fn PlateContainer(props: &PlateContainerProps) -> Html { if let Some(dpi) = props.destination_dims.clone() {

{spi.name.clone()}

- +

{"Source"}

+

{dpi.name.clone()}

- +

{"Destination"}

+
} else {

{"No Destination Plate Selected"}

diff --git a/plate-tool-web/src/components/plates/plate_data.rs b/plate-tool-web/src/components/plates/plate_data.rs new file mode 100644 index 0000000..fdd63e2 --- /dev/null +++ b/plate-tool-web/src/components/plates/plate_data.rs @@ -0,0 +1,23 @@ + +use yew::prelude::*; + +use plate_tool_lib::plate_instances::PlateInstance; +use plate_tool_lib::plate::PlateType; + +#[derive(PartialEq, Properties)] +pub struct PlateProps { + pub source_plate: PlateInstance, + pub destination_plate: PlateInstance, + pub cell_height: f64, + pub ptype: PlateType, +} + +pub type MStartHandle = UseStateHandle>; +pub type MEndHandle = UseStateHandle>; +pub type MStatHandle = UseStateHandle; + +#[derive(Debug)] +pub enum MouseEventType { + Mousedown, + Mouseenter, +} diff --git a/src/components/plates/util.rs b/plate-tool-web/src/components/plates/util.rs similarity index 86% rename from src/components/plates/util.rs rename to plate-tool-web/src/components/plates/util.rs index 51de79b..5baf8ad 100644 --- a/src/components/plates/util.rs +++ b/plate-tool-web/src/components/plates/util.rs @@ -2,10 +2,6 @@ // https://iquilezles.org/articles/palettes/ // http://dev.thi.ng/gradients/ -use rand::prelude::*; -use rand::rngs::SmallRng; -use lazy_static::lazy_static; - #[derive(Clone, Copy, PartialEq, Debug)] pub struct ColorPalette { a: [f64; 3], @@ -41,17 +37,17 @@ impl ColorPalette { // self.get(r.gen_range(0.0..1.0f64)) // } - pub fn get_ordered(&self, t: uuid::Uuid, ordered_uuids: &Vec) + pub fn get_ordered(&self, t: uuid::Uuid, ordered_uuids: &[uuid::Uuid]) -> [f64; 3] { let index = ordered_uuids.iter().position(|&x| x == t).expect("uuid must be in list of uuids") + 1; - return self.get(Self::space_evenly(index)) + self.get(Self::space_evenly(index)) } fn space_evenly(x: usize) -> f64 { let e: usize = (x.ilog2() + 1) as usize; - let d: usize = (2usize.pow(e as u32)) as usize; + let d: usize = 2usize.pow(e as u32); let n: usize = (2*x + 1) % d; - return (n as f64) / (d as f64); + (n as f64) / (d as f64) } } diff --git a/src/components/states.rs b/plate-tool-web/src/components/states.rs similarity index 84% rename from src/components/states.rs rename to plate-tool-web/src/components/states.rs index 51bea91..7e9b3a3 100644 --- a/src/components/states.rs +++ b/plate-tool-web/src/components/states.rs @@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use yewdux::{prelude::*, storage}; -use crate::data::plate::*; -use crate::data::plate_instances::PlateInstance; -use crate::data::transfer::Transfer; +use plate_tool_lib::plate::*; +use plate_tool_lib::plate_instances::PlateInstance; +use plate_tool_lib::transfer::Transfer; #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Store)] #[store(storage = "session")] @@ -108,4 +108,13 @@ impl MainState { self.destination_plates[index].change_name(new_name.to_string()); } } + + pub fn change_format(&mut self, id: Uuid, new_format: &PlateFormat) { + if let Some(index) = self.source_plates.iter().position(|spi| spi.get_uuid() == id) { + self.source_plates[index].change_format(new_format); + } + if let Some(index) = self.destination_plates.iter().position(|dpi| dpi.get_uuid() == id) { + self.destination_plates[index].change_format(new_format); + } + } } diff --git a/src/components/transfer_menu.rs b/plate-tool-web/src/components/transfer_menu.rs similarity index 82% rename from src/components/transfer_menu.rs rename to plate-tool-web/src/components/transfer_menu.rs index 28d355e..789f014 100644 --- a/src/components/transfer_menu.rs +++ b/plate-tool-web/src/components/transfer_menu.rs @@ -7,7 +7,8 @@ use yew::prelude::*; use yewdux::prelude::*; use crate::components::callbacks::transfer_menu_callbacks; -use crate::data::transfer_region::Region; +use plate_tool_lib::transfer_region::Region; +use plate_tool_lib::util::{letters_to_num, num_to_letters}; use super::states::{CurrentTransfer, MainState}; @@ -205,7 +206,7 @@ impl TryFrom<&str> for RegionDisplay { 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 let Some(captures) = REGION_REGEX.captures(value) { if captures.len() != 5 { return Err("Not enough capture groups"); } @@ -277,79 +278,12 @@ impl TryFrom<(u8, u8, u8, u8)> for RegionDisplay { }) } } -pub fn letters_to_num(letters: &str) -> Option { - let mut num: u8 = 0; - for (i, letter) in letters.to_ascii_uppercase().chars().rev().enumerate() { - log::debug!("{}, {}", i, letter); - let n = letter as u8; - if !(65..=90).contains(&n) { - return None; - } - num = num.checked_add((26_i32.pow(i as u32) * (n as i32 - 64)).try_into().ok()?)?; - } - Some(num) -} -pub fn num_to_letters(num: u8) -> Option { - 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). - 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 { - text.push((64 + digit1) as char) - } - text.push((64 + digit2) as char); - - Some(text.to_string()) -} #[cfg(test)] mod tests { use wasm_bindgen_test::*; - use super::{letters_to_num, num_to_letters, RegionDisplay}; - - #[test] - #[wasm_bindgen_test] - fn test_letters_to_num() { - assert_eq!(letters_to_num("D"), Some(4)); - assert_eq!(letters_to_num("d"), None); - assert_eq!(letters_to_num("AD"), Some(26 + 4)); - assert_eq!(letters_to_num("CG"), Some(3 * 26 + 7)); - } - - #[test] - #[wasm_bindgen_test] - 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] - #[wasm_bindgen_test] - fn test_l2n_and_n2l() { - 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()) - ); - for i in 1..=255 { - assert_eq!(letters_to_num(&num_to_letters(i).unwrap()), Some(i)); - } - } + use super::*; #[test] #[wasm_bindgen_test] diff --git a/src/components/tree.rs b/plate-tool-web/src/components/tree.rs similarity index 87% rename from src/components/tree.rs rename to plate-tool-web/src/components/tree.rs index 82e3766..92fda96 100644 --- a/src/components/tree.rs +++ b/plate-tool-web/src/components/tree.rs @@ -1,13 +1,14 @@ #![allow(non_snake_case)] +use plate_tool_lib::plate::PlateFormat; use uuid::Uuid; use wasm_bindgen::JsCast; use web_sys::{HtmlDialogElement, HtmlInputElement}; use yew::prelude::*; use yewdux::prelude::*; -use crate::components::states::{CurrentTransfer, MainState}; use crate::components::callbacks::tree_callbacks; +use crate::components::states::{CurrentTransfer, MainState}; #[derive(PartialEq, Properties)] pub struct TreeProps { @@ -157,23 +158,32 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html { Some(plate) => plate.name.clone(), None => "Not Found".to_string(), }; + let plate_format = plate.map(|p| p.plate.plate_format); + let plate_formats = vec![ + PlateFormat::W6, + PlateFormat::W12, + PlateFormat::W24, + PlateFormat::W48, + PlateFormat::W96, + PlateFormat::W384, + PlateFormat::W1536, + PlateFormat::W3456, + ]; + let plate_format_options = plate_formats.iter().map(|v| { + let selected = Some(v) == plate_format.as_ref(); + html!{ + + } + }); + let onclose = { let dialog_close_callback = props.dialog_close_callback.clone(); move |_| dialog_close_callback.emit(()) }; - let rename_onchange = { - let id = props.id; - Callback::from(move |e: Event| { - log::debug!("Changed name"); - let input = e - .target() - .expect("Event must have target") - .dyn_into::() - .unwrap(); - main_dispatch.reduce_mut(|state| state.rename_plate(id, &input.value())) - }) - }; + let rename_onchange = tree_callbacks::rename_onchange(props.id, main_dispatch.clone()); + + let format_onchange = tree_callbacks::format_onchange(props.id, main_dispatch.clone()); let delete_onclick = { let delete_button_callback = props.delete_button_callback.clone(); @@ -204,6 +214,11 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {

{"Plate Info"}

{"Name: "}

+ if let Some(_) = plate_format { + + }
} - }) - .collect::(); - html! {} - }; - let rows = (1..=props.destination_plate.plate.size().0) - .map(|i| { - let row_header = html! {}; - let row = (1..=props.destination_plate.plate.size().1).map(|j| { - html! { - >().join(", ")))} - /> - } - }).collect::(); - html! { - - { row_header }{ row } - - } - }) - .collect::(); - - html! { -
-
{format!("{:0>2}", j)}
{ headers }
{num_to_letters(i)}
- { column_header }{ rows } -
-
- } -} - -#[derive(Debug)] -pub enum MouseEventType { - Mousedown, - Mouseenter, -} - -#[derive(Properties, PartialEq)] -pub struct DestPlateCellProps { - pub i: u8, - pub j: u8, - pub selected: bool, - pub mouse: Callback<(u8, u8, MouseEventType)>, - pub in_transfer: Option, - color: Option<[f64; 3]>, - cell_height: f64, - title: Option, -} - -#[function_component] -fn DestPlateCell(props: &DestPlateCellProps) -> Html { - let selected_class = match props.selected { - true => Some("current_select"), - false => None, - }; - let in_transfer_class = match props.in_transfer { - Some(true) => Some("in_transfer"), - _ => None, - }; - let color = props.color.unwrap_or([255.0, 255.0, 255.0]); - let mouse = Callback::clone(&props.mouse); - let mouse2 = Callback::clone(&props.mouse); - let (i, j) = (props.i, props.j); - - html! { - -
- - } -} diff --git a/src/components/plates/mod.rs b/src/components/plates/mod.rs deleted file mode 100644 index fe8968e..0000000 --- a/src/components/plates/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod destination_plate; -pub mod plate_container; -pub mod source_plate; -mod util; diff --git a/src/data/csv.rs b/src/data/csv.rs deleted file mode 100644 index ec3e7b3..0000000 --- a/src/data/csv.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::components::states::MainState; -use crate::components::transfer_menu::num_to_letters; -use crate::data::transfer::Transfer; - -use serde::{Serialize, Deserialize}; -use std::error::Error; - -#[derive(Serialize, Deserialize, Debug)] -pub struct TransferRecord { - #[serde(rename = "Source Plate")] - pub source_plate: String, - #[serde(rename = "Source Well")] - pub source_well: String, - #[serde(rename = "Dest Plate")] - pub destination_plate: String, - #[serde(rename = "Destination Well")] - pub destination_well: String, - #[serde(rename = "Transfer Volume")] - pub volume: f32, - #[serde(rename = "Concentration")] - pub concentration: Option, -} - -pub fn state_to_csv(state: &MainState) -> Result> { - let mut records: Vec = 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 { - let source_wells = tr.transfer_region.get_source_wells(); - let map = tr.transfer_region.calculate_map(); - - let mut records: Vec = 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: tr.volume, - concentration: None, - }) - } - } - } - records -} - -fn records_to_csv(trs: Vec) -> Result> { - let mut wtr = csv::WriterBuilder::new().from_writer(vec![]); - for record in trs { - wtr.serialize(record)? - } - let data = String::from_utf8(wtr.into_inner()?)?; - Ok(data) -}