Browse Source

Metric/imperial toggle

master
Stephen 3 weeks ago
parent
commit
9eafab1dcb
6 changed files with 215 additions and 98 deletions
  1. +9
    -1
      public/style.css
  2. +9
    -4
      src/components/available.rs
  3. +6
    -5
      src/components/cutlist.rs
  4. +77
    -49
      src/components/wood.rs
  5. +87
    -39
      src/lib.rs
  6. +27
    -0
      src/util.rs

+ 9
- 1
public/style.css View File

@ -2,6 +2,10 @@ input[type=number] {
width: 80px;
}
form {
display: inline-block;
}
.wood_in {
padding-left: 8px;
}
@ -22,7 +26,11 @@ input[type=number] {
}
.bottom-margin {
margin-bottom: 8px;
margin-bottom: 64px;
}
.top-margin {
margin-top: 64px;
}
.header {


+ 9
- 4
src/components/available.rs View File

@ -10,7 +10,6 @@ pub enum Msg {
#[derive(Debug, Clone, PartialEq, Properties)]
pub struct AvailableProperties {
// TODO need metric/imperial switch
pub metric: bool,
pub name: String, // TODO we should auto-generate the names based on the given measurements (?)
pub name_callback: Callback<String>,
@ -25,6 +24,12 @@ pub struct AvailableComponent {
props: AvailableProperties,
}
impl AvailableComponent {
fn cost_string(&self) -> String {
format!("{:.2}", self.props.cost as f64 / 100.0)
}
}
impl Component for AvailableComponent {
type Message = Msg;
type Properties = AvailableProperties;
@ -51,9 +56,8 @@ impl Component for AvailableComponent {
Msg::CostChange(s) => {
if let Ok(cost) = s.parse::<f64>() {
self.props.cost = (cost * 100.0) as u32;
self.props.cost = (cost * 100.0).round() as u32;
self.props.cost_callback.emit(self.props.cost);
change = true;
}
}
}
@ -74,6 +78,7 @@ impl Component for AvailableComponent {
html! {
<>
<WoodComponent
metric=self.props.metric
name=self.props.name.clone()
name_callback=self.link.callback(Msg::NameChange)
length=self.props.length
@ -85,7 +90,7 @@ impl Component for AvailableComponent {
</td>
<td>
{ "$" }
<input type="number" min="0" step="0.01" value={self.props.cost as f64 / 100.0}
<input type="number" min="0" step="0.01" value=self.cost_string()
oninput=self.link.callback(|e: InputData| Msg::CostChange(e.value)) />
</td>
</tr>


+ 6
- 5
src/components/cutlist.rs View File

@ -1,3 +1,4 @@
use crate::util;
use yew::prelude::*;
#[derive(Debug, Clone, PartialEq, Properties)]
@ -6,10 +7,10 @@ pub struct CutlistProperties {
pub cost: u32,
pub cuts: Vec<String>,
pub remaining: f64, // mm
pub metric: bool,
}
pub struct CutlistComponent {
link: ComponentLink<Self>,
props: CutlistProperties,
}
@ -17,8 +18,8 @@ impl Component for CutlistComponent {
type Message = ();
type Properties = CutlistProperties;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self { link, props }
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Self { props }
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
@ -42,8 +43,8 @@ impl Component for CutlistComponent {
{ "$" }
{ format!("{:.2}", self.props.cost as f64 / 100.0) }
<br />
{ format!("{:.2}", self.props.remaining) }
{ " mm leftover" }
{ util::format_dimension(self.props.remaining, self.props.metric) }
{ " leftover" }
</div>
<ul>
{


+ 77
- 49
src/components/wood.rs View File

@ -10,6 +10,7 @@ pub enum Msg {
#[derive(Debug, Clone, PartialEq, Properties)]
pub struct WoodProperties {
pub metric: bool,
pub length: f64, // mm
pub length_callback: Callback<f64>,
pub name: String,
@ -19,9 +20,24 @@ pub struct WoodProperties {
pub struct WoodComponent {
link: ComponentLink<Self>,
props: WoodProperties,
ft: f64,
inches: f64,
mm: f64,
}
impl WoodComponent {
fn get_mm(&self) -> f64 {
self.props.length
}
fn get_in(&self) -> f64 {
(self.props.length - (self.get_ft() as f64) * 304.8) / 25.4
}
fn get_ft(&self) -> u32 {
(self.props.length / 304.8 + 0.00001).floor() as u32
}
fn get_in_str(&self) -> String {
format!("{:.2}", self.get_in() + 0.0001)
}
}
impl Component for WoodComponent {
@ -29,13 +45,7 @@ impl Component for WoodComponent {
type Properties = WoodProperties;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
props,
ft: 0.0,
inches: 0.0,
mm: 0.0,
}
Self { link, props }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
@ -49,29 +59,28 @@ impl Component for WoodComponent {
}
Msg::FeetChange(s) => {
if let Ok(ft) = s.parse() {
self.ft = ft;
if let Ok(ft) = s.parse::<f64>() {
self.props.length = self.get_in() * 25.4 + ft * 304.8;
change = true;
}
}
Msg::InchChange(s) => {
if let Ok(inches) = s.parse() {
self.inches = inches;
if let Ok(inches) = s.parse::<f64>() {
self.props.length = inches * 25.4 + (self.get_ft() as f64) * 304.8;
change = true;
}
}
Msg::MmChange(s) => {
if let Ok(mm) = s.parse() {
self.mm = mm;
self.props.length = mm;
change = true;
}
}
}
if change {
self.props.length = self.mm + self.inches * 25.4 + self.ft * 304.8;
self.props.length_callback.emit(self.props.length);
}
@ -96,43 +105,62 @@ impl Component for WoodComponent {
{ "Name: " }
</td>
<td>
<form novalidate=true>
<input type="text" value={&self.props.name}
oninput=self.link.callback(|e: InputData| Msg::NameChange(e.value))
/>
</td>
</tr>
// feet
<tr>
<td>
{ "Feet: " }
</td>
<td>
<input type="number" min="0" step="1" value={self.ft}
oninput=self.link.callback(|e: InputData| Msg::FeetChange(e.value)) />
</td>
</tr>
//inches
<tr>
<td>
{ "Inches: " }
</td>
<td>
<input type="number" min="0" step="1" value={self.inches}
oninput=self.link.callback(|e: InputData| Msg::InchChange(e.value)) />
</td>
</tr>
// mm
<tr>
<td>
{ "Millimeters: " }
</td>
<td>
<input type="number" min="0" step="0.1" value={self.mm}
oninput=self.link.callback(|e: InputData| Msg::MmChange(e.value)) />
/>
</form>
</td>
</tr>
{
if self.props.metric {
html! {
// mm
<tr>
<td>
{ "Millimeters: " }
</td>
<td>
<form novalidate=true>
<input type="number" min="0" step="0.1" value={self.get_mm()}
oninput=self.link.callback(|e: InputData| Msg::MmChange(e.value)) />
</form>
</td>
</tr>
}
}
else {
html! {
<>
// feet
<tr>
<td>
{ "Feet: " }
</td>
<td>
<form novalidate=true>
<input type="number" min="0" step="1" value={self.get_ft()}
oninput=self.link.callback(|e: InputData| Msg::FeetChange(e.value)) />
</form>
</td>
</tr>
//inches
<tr>
<td>
{ "Inches: " }
</td>
<td>
<form novalidate=true>
<input type="number" min="0" step="1" value={self.get_in_str()}
oninput=self.link.callback(|e: InputData| Msg::InchChange(e.value)) />
</form>
</td>
</tr>
</>
}
}
}
</>
}
}


+ 87
- 39
src/lib.rs View File

@ -1,4 +1,4 @@
#![recursion_limit = "1024"]
#![recursion_limit = "10240"]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
@ -15,6 +15,8 @@ use components::wood::WoodComponent;
mod calculator;
use calculator::{find_best_cuts, CutRow, Req1D, Wood1D};
mod util;
struct AvailableRow {
name: String,
length: f64, // mm
@ -32,9 +34,9 @@ impl AvailableRow {
}
}
fn name(&self) -> String {
fn name(&self, metric: bool) -> String {
if self.name.is_empty() {
format!("[Unnamed] {:.2} mm", self.length)
format!("[Unnamed] {}", util::format_dimension(self.length, metric))
} else {
self.name.clone()
}
@ -58,9 +60,9 @@ impl RequiredRow {
}
}
fn name(&self) -> String {
fn name(&self, metric: bool) -> String {
if self.name.is_empty() {
format!("[Unnamed] {:.2} mm", self.length)
format!("[Unnamed] {}", util::format_dimension(self.length, metric))
} else {
self.name.clone()
}
@ -81,7 +83,8 @@ enum Msg {
DeleteRequired(usize),
ChangeBladeWidth(String),
ForceUpdate,
ChangeMetric,
ChangeImperial,
Calculate,
}
@ -99,12 +102,14 @@ struct Model {
impl Model {
// convert into metric/imperial depending on user's settings
fn get_blade_width_string(&self) -> String {
format!("{:.3}", if self.metric {
self.blade_width
}
else {
self.blade_width / 25.4
})
format!(
"{:.3}",
if self.metric {
self.blade_width
} else {
self.blade_width / 25.4
}
)
}
fn set_blade_width(&mut self, w: f64) {
@ -116,7 +121,11 @@ impl Model {
}
fn blade_width_units(&self) -> &'static str {
if self.metric { "mm" } else { "\"" }
if self.metric {
"mm"
} else {
"\""
}
}
}
@ -150,6 +159,7 @@ impl Component for Model {
Msg::ChangeAvailableCost(id, cost) => {
self.available[id].cost = cost;
change = false;
}
Msg::DeleteAvailable(id) => {
@ -186,12 +196,18 @@ impl Component for Model {
change = false;
}
Msg::ForceUpdate => {}
Msg::ChangeMetric => {
self.metric = true;
}
Msg::ChangeImperial => {
self.metric = false;
}
Msg::Calculate => {
// convert available/required wood to the expected types
let available_names: Vec<String> =
self.available.iter().map(|x| x.name()).collect();
self.available.iter().map(|x| x.name(self.metric)).collect();
let available: Vec<Wood1D> = self
.available
.iter()
@ -200,7 +216,8 @@ impl Component for Model {
.map(|(i, x)| Wood1D::new(&available_names[i], x.length, x.cost))
.collect();
let required_names: Vec<String> = self.required.iter().map(|x| x.name()).collect();
let required_names: Vec<String> =
self.required.iter().map(|x| x.name(self.metric)).collect();
let mut required = Vec::new();
for (i, req) in self.required.iter().enumerate().filter(|(_, x)| !x.deleted) {
for _ in 0..req.amount {
@ -227,9 +244,6 @@ impl Component for Model {
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
// Should only return "true" if new properties are different to
// previously received properties.
// This component has no properties so we will always return "false".
false
}
@ -243,6 +257,54 @@ impl Component for Model {
<div class="container">
<div class="row">
<div class="col-sm">
<div>
<h4>{ "General Settings" }</h4>
//Metric radio
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="unitRadio"
id="metricRadio"
onclick=self.link.callback(|_| Msg::ChangeMetric)
checked=self.metric
/>
<label class="form-check-label" for="metricRadio"> { "Metric" } </label>
</div>
// Imperial radio
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="unitRadio"
id="imperialRadio"
onclick=self.link.callback(|_| Msg::ChangeImperial)
checked=!self.metric
/>
<label class="form-check-label" for="imperialRadio"> { "Imperial" } </label>
</div>
<table class="bottom-margin">
<tr>
<td>
{ "Sawblade Width" }
</td>
<td>
<form novalidate=true>
<input type="number" min="0" step="0.125" value={self.get_blade_width_string()}
oninput=self.link.callback(|e: InputData| Msg::ChangeBladeWidth(e.value))
/>
</form>
{ self.blade_width_units() }
</td>
</tr>
</table>
</div>
</div>
<div class="col-sm">
<h2>
<button
class="btn btn-info right-margin"
@ -258,7 +320,8 @@ impl Component for Model {
<div class="wood">
<table>
<AvailableComponent
length=wood.length metric=false
metric=self.metric
length=wood.length
length_callback=self.link.callback(move |v| Msg::ChangeAvailableLength(idx, v))
cost=wood.cost
cost_callback=self.link.callback(move |v| Msg::ChangeAvailableCost(idx, v))
@ -278,9 +341,8 @@ impl Component for Model {
}
})
}
</div>
<div class="col-sm">
<h2>
<h2 class="top-margin">
<button
class="btn btn-info right-margin"
onclick=self.link.callback(|_| Msg::AddRequired)>{ "+" }</button>
@ -295,6 +357,7 @@ impl Component for Model {
<div class="wood">
<table>
<WoodComponent
metric=self.metric
length=wood.length
length_callback=self.link.callback(move |v| Msg::ChangeRequiredLength(idx, v))
name=wood.name.clone()
@ -322,22 +385,6 @@ impl Component for Model {
}
</div>
<div class="col-sm">
<div>
<h4>{ "General Settings" }</h4>
<table>
<tr>
<td>
{ "Sawblade Width" }
</td>
<td>
<input type="number" min="0" step="0.125" value={self.get_blade_width_string()}
oninput=self.link.callback(|e: InputData| Msg::ChangeBladeWidth(e.value))
/>
{ self.blade_width_units() }
</td>
</tr>
</table>
</div>
<button
class="btn btn-primary"
onclick=self.link.callback(|_| Msg::Calculate)>{ "Calculate" }</button>
@ -351,6 +398,7 @@ impl Component for Model {
for self.output.iter().map(|row| {
html! {
<CutlistComponent
metric=self.metric
name=&row.name
cost=row.cost
cuts=&row.pieces


+ 27
- 0
src/util.rs View File

@ -0,0 +1,27 @@
pub fn format_dimension(mm: f64, metric: bool) -> String {
if metric {
format_metric(mm)
} else {
format_imperial(mm)
}
}
fn format_imperial(mm: f64) -> String {
let ft = (mm / 304.8).floor() as u32;
let inches = ((mm % 304.8) / 25.4).round() as u32;
match (ft, inches) {
(0, _) => {
format!("{}\"", inches)
}
(_, 0) => {
format!("{}'", ft)
}
_ => {
format!("{}'{}\"", ft, inches)
}
}
}
fn format_metric(mm: f64) -> String {
format!("{:.2} mm", mm)
}

Loading…
Cancel
Save