Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
291 lines
14 KiB
HTML
291 lines
14 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Dymo Label Sheets - Admin{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/labels-bricklink.css') }}">
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="bl">
|
||
{% set section = 'labels' %}
|
||
{% set bar_sub = 'Dymo LabelWriter die-cut stock' %}
|
||
{% set crumbs = [{'name': 'My Labels', 'url': url_for('labels.index')}, {'name': 'Dymo sheets'}] %}
|
||
{% include 'admin/labels/_nav.html' %}
|
||
|
||
<form method="POST" action="{{ url_for('dymo.index') }}" id="dymoForm">
|
||
<input type="hidden" name="ids" value="{{ ids }}">
|
||
{# Same hidden marker as the tape page: it is how an unticked checkbox is
|
||
told apart from a page opened with no options at all. #}
|
||
<input type="hidden" name="_opts" value="1">
|
||
<input type="hidden" name="groups" value="{{ request.values.get('groups', '') }}">
|
||
|
||
<div class="row g-2 mt-0">
|
||
<div class="col-lg-4">
|
||
<div class="bl-panel">
|
||
<div class="bl-panel-head"><span>Label stock</span></div>
|
||
<div class="bl-panel-body">
|
||
<label class="fld">Loaded in the LabelWriter</label>
|
||
<select name="stock" class="w-100 mb-2" id="stockSelect">
|
||
{% for key, preset in stocks.items() %}
|
||
<option value="{{ key }}" {% if key == sheet.stock %}selected{% endif %}>{{ preset.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<div class="k mb-2" style="text-transform:none;">{{ stock.note }}</div>
|
||
|
||
<label class="fld">Labels per sticker</label>
|
||
<select name="layout" class="w-100 mb-2" id="layoutSelect">
|
||
{% for key, text in layouts %}
|
||
<option value="{{ key }}" {% if key == sheet.layout %}selected{% endif %}>{{ text }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<div class="k mb-2" style="text-transform:none;">
|
||
{{ stock.width_mm }} × {{ stock.height_mm }}mm fits
|
||
<strong>{{ per_sheet }}</strong> standard 40 × 15mm labels
|
||
({{ grid }}). A two-part drawer label is taller, so a mixed
|
||
run fits fewer.
|
||
</div>
|
||
|
||
<div class="row g-2">
|
||
<div class="col-6">
|
||
<label class="fld">Margin (mm)</label>
|
||
<input type="number" name="margin_mm" step="0.5" min="0" max="10"
|
||
class="w-100" value="{{ sheet.margin_mm }}">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="fld">Gap (mm)</label>
|
||
<input type="number" name="gap_mm" step="0.5" min="0" max="10"
|
||
class="w-100" value="{{ sheet.gap_mm }}">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="fld">Skip slots</label>
|
||
<input type="number" name="start_at" min="0" max="50"
|
||
class="w-100" value="{{ sheet.start_at }}">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="fld">Copies each</label>
|
||
<input type="number" name="copies" min="1" max="99"
|
||
class="w-100" value="{{ options.copies }}">
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-2">
|
||
<label class="d-flex align-items-center gap-2" style="font-size:12px;">
|
||
<input type="checkbox" name="cut_guides" value="1"
|
||
{% if sheet.cut_guides %}checked{% endif %}>
|
||
Dashed cut guides
|
||
</label>
|
||
<label class="d-flex align-items-center gap-2" style="font-size:12px;">
|
||
<input type="checkbox" name="centre" value="1"
|
||
{% if sheet.centre %}checked{% endif %}>
|
||
Centre on the sticker
|
||
</label>
|
||
<label class="fld mt-2">Ink</label>
|
||
<select name="ink" class="w-100" id="inkSelect">
|
||
{% for key, text in ink_modes %}
|
||
<option value="{{ key }}" {% if key == sheet.ink %}selected{% endif %}>{{ text }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<div class="k mt-1" style="text-transform:none;">
|
||
A LabelWriter is direct thermal: each dot is burnt or it
|
||
isn't, with no colour and no greys in between. LIM makes
|
||
that choice here rather than leaving it to the driver,
|
||
whose halftoning turns 2mm letters to mush. Keep
|
||
<em>Crisp</em> unless the part photo matters more than
|
||
the words.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bl-panel">
|
||
<div class="bl-panel-head"><span>The labels themselves</span></div>
|
||
<div class="bl-panel-body">
|
||
<label class="fld">Size</label>
|
||
<select name="size_mode" class="w-100 mb-2" id="sizeSelect">
|
||
{% for key, text in size_modes %}
|
||
<option value="{{ key }}" {% if key == options.size_mode %}selected{% endif %}>{{ text }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
|
||
<label class="d-flex align-items-center gap-2 mb-2" style="font-size:12px;">
|
||
<input type="checkbox" name="group_drawers" value="1"
|
||
{% if options.group_drawers %}checked{% endif %}>
|
||
Combine parts that share a drawer
|
||
</label>
|
||
|
||
<div class="row g-2">
|
||
<div class="col-6">
|
||
<label class="fld">Text size
|
||
<span class="num" id="fontOut">{{ '%.2f'|format(options.font_scale) }}×</span>
|
||
</label>
|
||
<input type="number" name="font_scale" step="0.05" min="0.5" max="2"
|
||
class="w-100" value="{{ options.font_scale }}" id="fontNum">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="fld">Image width %</label>
|
||
<input type="number" name="image_percent" min="0" max="60"
|
||
class="w-100" value="{{ options.image_percent }}">
|
||
</div>
|
||
<div class="col-12">
|
||
<input type="range" min="0.5" max="2" step="0.05" class="w-100"
|
||
value="{{ options.font_scale }}" id="fontRange">
|
||
<div class="d-flex gap-1 flex-wrap">
|
||
{% for preset, text in [(1.0, 'Normal'), (1.4, 'Large'), (2.0, 'Largest')] %}
|
||
<button type="button" class="bl-btn bl-btn-sm font-preset"
|
||
data-scale="{{ preset }}">{{ text }}</button>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="k mt-1" style="text-transform:none;">
|
||
The name is sized first and the image gives way to it. The
|
||
whole name always prints, so once it spans the 40mm label
|
||
turning this up changes nothing — most names hit that by
|
||
about 1.4×. Untick <em>Part image</em> to give the text
|
||
the whole width.
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="fld">Padding (mm)</label>
|
||
<input type="number" name="padding_mm" step="0.1" min="0" max="10"
|
||
class="w-100" value="{{ options.padding_mm }}">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="fld">Align</label>
|
||
<select name="align" class="w-100">
|
||
<option value="left" {% if options.align == 'left' %}selected{% endif %}>Left</option>
|
||
<option value="center" {% if options.align == 'center' %}selected{% endif %}>Centre</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bl-panel">
|
||
<div class="bl-panel-head"><span>Show on label</span></div>
|
||
<div class="bl-panel-body">
|
||
{% for value, text in field_options %}
|
||
<label class="d-flex align-items-center gap-2" style="font-size:12px;">
|
||
<input type="checkbox" name="fields" value="{{ value }}"
|
||
{% if value in options.fields %}checked{% endif %}> {{ text }}
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-lg-8">
|
||
{% if font_info.kind != 'system' %}
|
||
<div class="bl-panel">
|
||
<div class="bl-panel-body">
|
||
<span class="bl-tag {{ 'err' if not font_info.scalable else 'warn' }}">Font</span>
|
||
{% if font_info.scalable %}
|
||
Labels are being drawn with {{ font_info.name }}. It works, but
|
||
installing a proper font on the server gives better letterforms:
|
||
<code>apt install fonts-dejavu-core</code>, or set
|
||
<code>LABEL_FONT</code> to a .ttf file.
|
||
{% else %}
|
||
No scalable font on this server, so labels will print about a
|
||
millimetre tall whatever <em>Text size</em> says. Install
|
||
<code>fonts-dejavu-core</code> (or set <code>LABEL_FONT</code> to a
|
||
.ttf) and print again.
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="bl-panel">
|
||
<div class="bl-panel-head"><span>Print</span></div>
|
||
<div class="bl-panel-body">
|
||
<div class="d-flex gap-2 flex-wrap align-items-center">
|
||
<button type="submit" formaction="{{ url_for('dymo.download_pdf') }}"
|
||
class="bl-btn bl-btn-go">
|
||
<i class="bi bi-file-pdf"></i>
|
||
Download PDF — {{ pages|length }} sticker{{ '' if pages|length == 1 else 's' }}
|
||
</button>
|
||
<button type="submit" class="bl-btn">Refresh preview</button>
|
||
<a href="{{ url_for('tape.index', ids=ids) }}" class="bl-btn">
|
||
<i class="bi bi-upc"></i> Tape instead
|
||
</a>
|
||
</div>
|
||
<div class="k mt-2" style="text-transform:none;">
|
||
Each PDF page is exactly {{ stock.width_mm }} × {{ stock.height_mm }}mm,
|
||
so print it with the Dymo driver set to this label and
|
||
<strong>scale 100% / Actual size</strong> — not "Fit to page",
|
||
which shrinks everything by a few percent and spoils the 40mm width.
|
||
The LabelWriter 4XL is USB, so LIM can't print to it over the
|
||
network the way it does the tape printer.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bl-panel">
|
||
<div class="bl-panel-head">
|
||
<span>Preview — {{ pages|length }} sticker{{ '' if pages|length == 1 else 's' }}
|
||
from {{ plan|length }} label{{ '' if plan|length == 1 else 's' }}</span>
|
||
<span class="num">{{ stock.width_mm }} × {{ stock.height_mm }}mm</span>
|
||
</div>
|
||
<div class="bl-panel-body">
|
||
{% if not options.fields %}
|
||
<div class="mb-2"><span class="bl-tag warn">Nothing selected</span>
|
||
Every field is unticked, so the labels will print blank.</div>
|
||
{% endif %}
|
||
|
||
{% for page in pages[:6] %}
|
||
<div class="mb-3">
|
||
<div class="k mb-1" style="text-transform:none;">
|
||
Sticker {{ loop.index }} of {{ pages|length }} ·
|
||
{{ page | rejectattr('blank') | list | length }} label{{ '' if page|length == 1 else 's' }}
|
||
</div>
|
||
<img style="border:1px solid #c9d3dd;background:#fff;max-width:100%;"
|
||
alt="Dymo sticker {{ loop.index }}"
|
||
src="{{ url_for('dymo.preview', page=loop.index, **query) }}">
|
||
</div>
|
||
{% endfor %}
|
||
|
||
{% if pages|length > 6 %}
|
||
<div class="k" style="text-transform:none;">
|
||
Showing the first 6 of {{ pages|length }}. All of them are in the PDF.
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="k mt-2" style="text-transform:none;">
|
||
300 dpi, the LabelWriter's native resolution. Drawn by the same
|
||
code as the tape labels, so a drawer label is the same
|
||
{{ '%.0f'|format(40) }} × 15mm sticker whichever printer makes it.
|
||
Cut along the dashed lines.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
// Changing the stock, the grid or the label size re-renders the sheet.
|
||
['stockSelect', 'layoutSelect', 'sizeSelect'].forEach(id => {
|
||
document.getElementById(id)?.addEventListener('change',
|
||
() => document.getElementById('dymoForm').submit());
|
||
});
|
||
|
||
// Text size: slider, box and presets all drive the one value.
|
||
(function () {
|
||
const num = document.getElementById('fontNum');
|
||
const range = document.getElementById('fontRange');
|
||
const out = document.getElementById('fontOut');
|
||
if (!num || !range) { return; }
|
||
const show = v => { if (out) { out.textContent = Number(v).toFixed(2) + '×'; } };
|
||
num.addEventListener('input', () => { range.value = num.value; show(num.value); });
|
||
range.addEventListener('input', () => { num.value = range.value; show(range.value); });
|
||
range.addEventListener('change', () => document.getElementById('dymoForm').submit());
|
||
document.querySelectorAll('.font-preset').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
num.value = range.value = btn.dataset.scale;
|
||
show(btn.dataset.scale);
|
||
document.getElementById('dymoForm').submit();
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
{% endblock %}
|