Open Source Adventures: Episode 55: Sliders for BATTLETECH Weapon Ranking App

Time to make our app a little more interactive. I added the following sliders:

  • how much ammo to carry
  • how much heat to compensate for
  • how many double heat sinks you have

I also removed Flamers and Infernos as they don't really fit in this model, and removed minimum range, as not very relevant.

Slider.svelte

It's just copied from Russian losses app:

<script>
export let label, min, max, value, format
let id = Math.random().toString(36).slice(2)
</script>

<label for={id}>{label}:</label>
<input type="range" {min} {max} bind:value id={id} />
<span>{format(value)}</span>

Form.svelte

I might need to add some other control types like checkboxes or radios here, for now it's just all sliders.

<script>
import Slider from "./Slider.svelte"

export let ammoRounds
export let heatPercentage
export let doubleHeatSinksPercentage
</script>

<form>
  <Slider label="Ammo for how many rounds" bind:value={ammoRounds} min={1} max={30} format={(v) => `${v}`}/>
  <Slider label="Heat to compensate for" bind:value={heatPercentage} min={0} max={100} format={(v) => `${v}%`}/>
  <Slider label="How many double heat sinks" bind:value={doubleHeatSinksPercentage} min={0} max={100} format={(v) => `${v}%`}/>
</form>

<style>
form {
  display: grid;
  grid-template-columns: auto auto auto;
  margin-bottom: 1em;
}
</style>

Headers.svelte

Just to keep App small, I moved some static content to Headers component:

<tr>
  <th>Name</th>
  <th>Damage</th>
  <th>Heat</th>
  <th>Weight</th>
  <th>Ammo Weight</th>
  <th>Range</th>
  <th>Value</th>
  <th>Cost</th>
  <th>Ratio</th>
</tr>

App.svelte

I need to do some trickery with row.id to make reactivity work the way I want. What I'm doing here isn't a particularly elegant solution.

<script>
import {sortBy} from "lodash"
import data from "./data.json"
import Form from "./Form.svelte"
import Headers from "./Headers.svelte"
import Row from "./Row.svelte"

let ammoRounds = 10
let heatPercentage = 80
let doubleHeatSinksPercentage = 0

let round100 = (v) => Math.round(v * 100) / 100

$: heatSinkingPerTon = 3.0 + 3.0 * doubleHeatSinksPercentage / 100
$: costPerHeat = (heatPercentage / 100) / heatSinkingPerTon

let sortedData
$: {
  for(let row of data) {
    row.value = row.shots * row.baseDamage
    row.ammoWeight = round100(ammoRounds * row.ammoTonnagePerShot)
    row.cost = round100(row.tonnage + row.ammoWeight + row.heat * costPerHeat)
    row.ratio = round100(row.value / row.cost)
    row.id = Math.random().toString(36).slice(2)
  }
  sortedData = sortBy(data, [(x) => -x.ratio, (x) => x.name])
}
</script>

<h1>BATTLETECH Weapons Data</h1>

<Form bind:ammoRounds bind:heatPercentage bind:doubleHeatSinksPercentage />

<table>
  <Headers />
  {#each sortedData as row (row.id)}
    <Row data={row} />
  {/each}
</table>

<style>
:global(body) {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
table :global(tr):nth-child(even) {
  background-color: #f2f2f2;
}
table :global(tr):nth-child(odd) {
  background-color: #e0e0e0;
}
</style>

Story so far

All the code is on GitHub.

I deployed this on GitHub Pages, you can see it here.

Coming next

Over the next few episodes I'll continue to improve the app.