checkpoint
This commit is contained in:
@@ -26,6 +26,8 @@
|
||||
code { color: var(--accent); }
|
||||
.mono { font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.pill { display:inline-block; padding:2px 8px; border-radius: 999px; background:#0b1220; border:1px solid #334155; color: var(--muted); font-size: 12px; }
|
||||
.completed td:not(.done-col) { text-decoration: line-through; color: var(--muted); }
|
||||
.done-col { width: 1%; white-space: nowrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -33,19 +35,39 @@
|
||||
<h1>Satisfactory Production Calculator</h1>
|
||||
<p>Compute buildings and raw inputs for a target output rate (items per minute).</p>
|
||||
|
||||
<form class="card" method="get">
|
||||
<div class="row">
|
||||
<div>
|
||||
<label for="item">Product</label>
|
||||
<select id="item" name="item" required>
|
||||
{% for it in items %}
|
||||
<option value="{{ it }}" {% if selected_item and it==selected_item %}selected{% endif %}>{{ it }}</option>
|
||||
<form class="card" method="get" id="targets-form">
|
||||
<div>
|
||||
<label>Products</label>
|
||||
<table style="width:100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:60%">Item</th>
|
||||
<th style="width:30%" class="mono">Rate (items/min)</th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="targets-rows">
|
||||
{% for row in targets_ui %}
|
||||
<tr class="target-row">
|
||||
<td>
|
||||
<select name="item_{{ loop.index }}" required>
|
||||
{% for it in items %}
|
||||
<option value="{{ it }}" {% if row.item == it %}selected{% endif %}>{{ it }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input name="rate_{{ loop.index }}" type="number" step="0.01" min="0" value="{{ row.rate }}" required>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="pill" onclick="removeRow(this)" title="Remove">✕</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rate">Target rate (items/min)</label>
|
||||
<input id="rate" name="rate" type="number" step="0.01" min="0" value="{{ selected_rate }}" required>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="mt">
|
||||
<button type="button" onclick="addRow()">Add product</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,11 +86,12 @@
|
||||
{% if result.raw %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Item</th><th class="mono">Rate (items/min)</th></tr>
|
||||
<tr><th class="done-col">Done</th><th>Item</th><th class="mono">Rate (items/min)</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item,rate in result.raw.items() %}
|
||||
<tr>
|
||||
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
|
||||
<td>{{ item }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(rate) }}</td>
|
||||
</tr>
|
||||
@@ -82,8 +105,11 @@
|
||||
{% if overrides_ui and overrides_ui|length > 0 %}
|
||||
<h3 class="mt">Recipe overrides</h3>
|
||||
<form method="get" class="card" style="background: transparent; border:0; padding:0;">
|
||||
<input type="hidden" name="item" value="{{ selected_item }}">
|
||||
<input type="hidden" name="rate" value="{{ selected_rate }}">
|
||||
{# Preserve all target rows in the recalculation #}
|
||||
{% for row in targets_ui %}
|
||||
<input type="hidden" name="item_{{ loop.index }}" value="{{ row.item }}">
|
||||
<input type="hidden" name="rate_{{ loop.index }}" value="{{ row.rate }}">
|
||||
{% endfor %}
|
||||
{% if selected_recipe %}<input type="hidden" name="recipe" value="{{ selected_recipe }}">{% endif %}
|
||||
<table>
|
||||
<thead>
|
||||
@@ -120,9 +146,11 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="done-col">Done</th>
|
||||
<th>Output Item</th>
|
||||
<th>Recipe</th>
|
||||
<th>Building</th>
|
||||
<th>Inputs</th>
|
||||
<th class="mono">Target rate</th>
|
||||
<th class="mono">Per-building output</th>
|
||||
<th class="mono">Buildings</th>
|
||||
@@ -132,9 +160,21 @@
|
||||
<tbody>
|
||||
{% for s in result.steps %}
|
||||
<tr>
|
||||
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
|
||||
<td>{{ s.item }}</td>
|
||||
<td>{{ s.recipe }}</td>
|
||||
<td>{{ s.building }}</td>
|
||||
<td>
|
||||
{% if s.inputs and s.inputs|length > 0 %}
|
||||
<div>
|
||||
{% for inp in s.inputs %}
|
||||
<div>{{ inp.item }} — <span class="mono">{{ '%.2f'|format(inp.rate) }}</span></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="pill">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="mono">{{ '%.2f'|format(s.target_rate) }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(s.per_building_output) }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(s.buildings_float) }} (~ {{ s.buildings }})</td>
|
||||
@@ -147,15 +187,36 @@
|
||||
<p class="pill">No production steps (target is a raw resource).</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 class="mt">Excess products</h3>
|
||||
{% if result.excess and result.excess|length > 0 %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th class="done-col">Done</th><th>Item</th><th class="mono">Excess rate (items/min)</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item,rate in result.excess.items() %}
|
||||
<tr>
|
||||
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
|
||||
<td>{{ item }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(rate) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="pill">No excess products for this target.</p>
|
||||
{% endif %}
|
||||
|
||||
<h3 class="mt">Unused byproducts</h3>
|
||||
{% if result.unused and result.unused|length > 0 %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Item</th><th class="mono">Unused rate (items/min)</th></tr>
|
||||
<tr><th class="done-col">Done</th><th>Item</th><th class="mono">Unused rate (items/min)</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item,rate in result.unused.items() %}
|
||||
<tr>
|
||||
<td class="done-col"><input type="checkbox" onchange="toggleDone(this)"></td>
|
||||
<td>{{ item }}</td>
|
||||
<td class="mono">{{ '%.2f'|format(rate) }}</td>
|
||||
</tr>
|
||||
@@ -170,8 +231,45 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Tiny helper to maintain selected item after submit if not handled by server
|
||||
// (we already set it via Jinja, this is just progressive enhancement)
|
||||
// Dynamic add/remove for multiple product targets
|
||||
const itemsList = {{ items | tojson }};
|
||||
function addRow() {
|
||||
const tbody = document.getElementById('targets-rows');
|
||||
const idx = tbody.querySelectorAll('tr').length + 1;
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'target-row';
|
||||
const options = itemsList.map(it => `<option value="${it}">${it}</option>`).join('');
|
||||
tr.innerHTML = `
|
||||
<td>
|
||||
<select name="item_${idx}" required>${options}</select>
|
||||
</td>
|
||||
<td>
|
||||
<input name="rate_${idx}" type="number" step="0.01" min="0" value="60.0" required>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="pill" onclick="removeRow(this)" title="Remove">✕</button>
|
||||
</td>`;
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
function removeRow(btn) {
|
||||
const tr = btn.closest('tr');
|
||||
const tbody = tr && tr.parentElement;
|
||||
if (!tbody) return;
|
||||
tbody.removeChild(tr);
|
||||
// Optionally renumber names to keep indices compact
|
||||
Array.from(tbody.querySelectorAll('tr')).forEach((row, i) => {
|
||||
const idx = i + 1;
|
||||
const sel = row.querySelector('select[name^="item_"]');
|
||||
const rate = row.querySelector('input[name^="rate_"]');
|
||||
if (sel) sel.name = `item_${idx}`;
|
||||
if (rate) rate.name = `rate_${idx}`;
|
||||
});
|
||||
}
|
||||
function toggleDone(cb) {
|
||||
const tr = cb.closest('tr');
|
||||
if (!tr) return;
|
||||
tr.classList.toggle('completed', cb.checked);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user