<svelte:options runes={true} /> <script lang="ts"> import * as Card from "$lib/components/ui/card"; import {Button} from "$lib/components/ui/button"; import { Grids, inventorySizes, InventoryType, localization, metricModifiers, type Thruster, ThrusterSize, ThrusterType, thrustValues, weightPerVolume } from "$lib/constants"; import {Input} from "$lib/components/ui/input"; import {Label} from "$lib/components/ui/label"; import {Separator} from "$lib/components/ui/separator"; import {onMount} from "svelte"; let locale = $state("en-GB"); let mounted = $state(false); let thrusters: Array<Thruster> = $state([]) $effect(() => { if (mounted) { localStorage.setItem("thrusters", JSON.stringify(thrusters)); } }) let inventories: Array<InventoryType> = $state([]) $effect(() => { if (mounted) { localStorage.setItem("inventories", JSON.stringify(inventories)); } }) let inventoryMultiplier = $state(1); $effect(() => { if (mounted) { localStorage.setItem("inventoryMultiplier", inventoryMultiplier); } }) let gridSize = $state(Grids.Small); $effect(() => { if (mounted) { localStorage.setItem("gridSize", gridSize); } }) let gravity = $state(1); $effect(() => { if (mounted) { localStorage.setItem("gravity", gravity); } }) let newThruster = $state({ type: ThrusterType.Atmospheric, size: ThrusterSize.Small, }) $effect(() => { if (mounted) { localStorage.setItem("newThruster", JSON.stringify(newThruster)) } }) let newInventory = $state(InventoryType.CargoSmall) $effect(() => { if (mounted) { localStorage.setItem("newInventory", JSON.stringify(newInventory)) } }) let totalThrust = $derived.by(() => { let thrust = 0; thrusters.forEach((thruster) => { thrust += getThrusterPower(thruster) }) return thrust }); let totalVolume = $derived.by(() => { let volume = 0; inventories.forEach((inventory) => { volume += getInventoryVolume(inventory); }) return volume; }) let maxWeight = $derived(totalThrust / (gravity * 9.81)) let maxVehicleWeight = $derived(maxWeight - totalVolume * weightPerVolume) function weightConversion(weight: number): string { if (weight > 1000) { return applyUnits(weight/1000, "t") } else { return `${weight.toFixed(2)}kg` } } function getInventoryVolume(inventory: InventoryType): number { return inventorySizes.get(gridSize).get(inventory) * inventoryMultiplier } function getThrusterPower(thruster: Thruster): number { return thrustValues.get(gridSize).get(thruster.type).get(thruster.size) } function addThruster() { thrusters.push({ type: newThruster.type, size: newThruster.size}); } function addInventory() { inventories.push(newInventory); } function localized(key: string): string { let localizationObject = localization.get(key) if (typeof localizationObject === "undefined") { return `missing text: ${key}` } else { let localizedText = localizationObject.get(locale) if (typeof localizedText === "undefined") { return localizationObject.get("en-GB") } else { return localizedText } } } function applyUnits(value: number, unit: string) { for (const [power, modifier] of [...metricModifiers.entries()]) { if (value > power) { if (value / power >= 10000) { let base = value / power; let rest = base % 1000; base = (base - rest) / 1000; rest = Math.round((rest + Number.EPSILON) * 100) / 100 return `${base}${localized("separator")}${rest}${modifier}${unit}` } else { let rounded = Math.round(((value / power) + Number.EPSILON) * 100) / 100 return `${rounded}${modifier}${unit}` } } } let rounded = Math.round(((value + Number.EPSILON) * 100) / 100) return `${rounded}${unit}` } onMount(() => { if (navigator) { locale = navigator.language; } if (localStorage.getItem("thrusters") !== null) { thrusters = JSON.parse(localStorage.getItem("thrusters")) } if (localStorage.getItem("inventories") !== null) { inventories = JSON.parse(localStorage.getItem("inventories")) } if (localStorage.getItem("gridSize") !== null) { gridSize = localStorage.getItem("gridSize") } if (localStorage.getItem("gravity") !== null) { gravity = localStorage.getItem("gravity") } if (localStorage.getItem("inventoryMultiplier") !== null) { inventoryMultiplier = localStorage.getItem("inventoryMultiplier") } if (localStorage.getItem("newThruster") !== null) { newThruster = JSON.parse(localStorage.getItem("newThruster")) } if (localStorage.getItem("newInventory") !== null) { newInventory = JSON.parse(localStorage.getItem("newInventory")) } mounted = true; }) </script> <svelte:head> <title>{localized("title")}</title> <meta name="robots" content="noindex nofollow" /> </svelte:head> <div class="gap-4 grid grid-cols-3 h-full"> <Card.Root class="col-span-1"> <Card.Header> <Card.Title>{localized("title")}</Card.Title> </Card.Header> <Card.Content class="flex flex-col gap-3"> <Separator /> <Label for="gravity">{localized("gravity")}</Label> <Input type="number" step="0.01" id="gravity" bind:value={gravity}/> <Separator /> <Label for="gridSize">{localized("gridSize")}</Label> <div id="gridSize" class="flex"> {#each Object.values(Grids) as size} {#if gridSize === size} <Button variant="primary">{localized(size)}</Button> {:else} <Button variant="secondary" onclick={() => gridSize = size}>{localized(size)}</Button> {/if} {/each} </div> <Separator /> <p>{localized("liftableVehcileWeight")}: {weightConversion(maxVehicleWeight)}</p> <p>{localized("liftableWeight")}: {weightConversion(maxWeight)}</p> </Card.Content> </Card.Root> <Card.Root class="col-span-1 flex flex-col flex-grow"> <Card.Header> <Card.Title>{localized("thrusterSettings")}</Card.Title> </Card.Header> <Card.Content class="flex flex-col flex-grow gap-3"> <Separator /> <Card.Root> <Card.Content class="flex flex-col gap-3 pt-6"> <div class="flex gap-2"> {#each Object.values(ThrusterType) as thruster} {#if newThruster.type === thruster} <Button variant="primary">{localized(thruster)}</Button> {:else} <Button variant="secondary" onclick={() => newThruster.type = thruster}>{localized(thruster)}</Button> {/if} {/each} </div> <div class="flex gap-2"> {#each Object.values(ThrusterSize) as size} {#if newThruster.size === size} <Button variant="primary">{localized(size)}</Button> {:else} <Button variant="secondary" onclick={() => newThruster.size = size}>{localized(size)}</Button> {/if} {/each} </div> <Button variant="secondary" onclick={() => addThruster()}>{localized("addThruster")}</Button> </Card.Content> </Card.Root> <Card.Root class="flex flex-col h-0 flex-grow"> <Card.Header> <Card.Title>{localized("thrusters")} ({applyUnits(totalThrust, "N")})</Card.Title> </Card.Header> <Card.Content class="overflow-hidden"> <Separator /> <div class="max-h-full overflow-y-auto"> {#each thrusters as thruster, index} <p class="p-1"> - {localized(thruster.size)} {localized(thruster.type)} {localized("thruster")}: {applyUnits(getThrusterPower(thruster), "N")} <Button variant="destructive" onclick={() => thrusters.splice(index, 1)}>X</Button> </p> {/each} </div> </Card.Content> </Card.Root> </Card.Content> </Card.Root> <Card.Root class="col-span-1 flex flex-col flex-grow"> <Card.Header> <Card.Title>{localized("inventorySettings")}</Card.Title> </Card.Header> <Card.Content class="flex flex-col flex-grow gap-3"> <Separator /> <Label for="inventorySize">{localized("inventory")} {localized("multiplier")}</Label> <Input id="inventorySize" type="number" bind:value={inventoryMultiplier}/> <Card.Root> <Card.Content class="pt-6 flex flex-col gap-3"> <div class="flex gap-2 flex-wrap"> {#each Object.values(InventoryType) as inventory} {#if newInventory === inventory} <Button variant="primary">{localized(inventory)}</Button> {:else} <Button variant="secondary" onclick={() => newInventory = inventory}>{localized(inventory)}</Button> {/if} {/each} </div> <Button variant="secondary" onclick={() => addInventory()}>{localized("addInventory")}</Button> </Card.Content> </Card.Root> <Card.Root class="flex flex-col h-0 flex-grow"> <Card.Header> <Card.Title>{localized("inventories")} ({applyUnits(totalVolume, "l")})</Card.Title> </Card.Header> <Card.Content class="overflow-hidden"> <Separator /> <div class="max-h-full overflow-y-auto"> {#each inventories as inventory, index} <p class="p-1"> - {localized(inventory)} {localized("volume")}: {applyUnits(getInventoryVolume(inventory), "l")} <Button variant="destructive" onclick={() => inventories.splice(index, 1)}>X</Button> </p> {/each} </div> </Card.Content> </Card.Root> </Card.Content> </Card.Root> </div>