diff --git a/app/templates/admin/labels/tape.html b/app/templates/admin/labels/tape.html
index 8ee54d8..4cc8621 100644
--- a/app/templates/admin/labels/tape.html
+++ b/app/templates/admin/labels/tape.html
@@ -18,6 +18,9 @@
opened with no options at all. Without it, a default-on checkbox could
never be turned off. #}
+{# Which parts share a physical label: "1,2|3". Rewritten by the Combine
+ and Split buttons under the preview. Empty means "group by drawer". #}
+
@@ -67,9 +70,29 @@
value="{{ options.copies }}">
-
-
+
+
+
+
+
+
+ {% for preset, text in [(1.0, 'Normal'), (1.5, 'Large'), (2.0, 'Huge'), (3.0, 'Max')] %}
+
+ {% endfor %}
+
+
+ The part name is sized first and the image gives way to
+ it, so turning this up shrinks the picture rather than
+ clipping the text. The whole name always prints - it wraps
+ and settles at the largest size that fits, so a long name
+ stops growing before a short one does. Untick
+ Part image for the biggest type of all.
+
@@ -161,6 +184,13 @@
Download PDF
+
+ Jobs are sent one at a time: LIM waits for the printer to
+ finish each label before sending the next, because the
+ VC-500W answers "busy" to anything that arrives mid-print.
+ So a big run takes a while - leave the page alone until it
+ comes back rather than refreshing, or you'll print twice.
+
{% else %}
@@ -195,9 +225,32 @@
will be scaled down. Load 50mm tape, or pick a different size.
{% endif %}
+
+ Tick labels, then:
+
+
+
+
+
+ For customs and MOC drawers, where the parts have no drawer in
+ common. A combined label grows 5mm for each part past the second,
+ and hand-made groups are kept whatever the size setting says.
+
+
{% for item in plan[:12] %}
-
+
+
Printing…';
+ }
+});
+
document.getElementById('testBtn')?.addEventListener('click', function () {
const out = document.getElementById('testResult');
out.innerHTML = 'Probing…';
@@ -283,5 +347,102 @@ document.getElementById('testBtn')?.addEventListener('click', function () {
document.getElementById(id)?.addEventListener('change',
() => document.getElementById('tapeForm').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('tapeForm').submit());
+ document.querySelectorAll('.font-preset').forEach(btn => {
+ btn.addEventListener('click', () => {
+ num.value = range.value = btn.dataset.scale;
+ show(btn.dataset.scale);
+ document.getElementById('tapeForm').submit();
+ });
+ });
+})();
+
+// Combining parts onto one label by hand.
+//
+// The grouping lives in the hidden `groups` field as "1,2|3". These buttons
+// rewrite it from what is ticked and resubmit, so the preview always shows
+// the real physical labels rather than a guess.
+//
+// The starting point is the whole current plan (data-plan), not just the
+// cards on screen - only the first 12 labels are previewed, and a spec built
+// from those alone would quietly un-group everything after them.
+(function () {
+ const spec = document.getElementById('groupSpec');
+ const bar = document.getElementById('groupBar');
+ const form = document.getElementById('tapeForm');
+ if (!spec || !bar || !form) { return; }
+
+ const plan = () => (bar.dataset.plan || '')
+ .split('|').filter(Boolean).map(g => g.split(',').filter(Boolean));
+
+ function tickedIds() {
+ const ids = new Set();
+ document.querySelectorAll('.plan-card').forEach(card => {
+ if (card.querySelector('.plan-pick')?.checked) {
+ (card.dataset.ids || '').split(',').filter(Boolean)
+ .forEach(id => ids.add(id));
+ }
+ });
+ return ids;
+ }
+
+ function write(groups) {
+ spec.value = groups.filter(g => g.length).map(g => g.join(',')).join('|');
+ form.submit();
+ }
+
+ document.getElementById('combineBtn')?.addEventListener('click', () => {
+ const picked = tickedIds();
+ if (picked.size < 2) {
+ alert('Tick two or more labels to combine them onto one.');
+ return;
+ }
+ const groups = [];
+ const merged = [];
+ let placed = false;
+ plan().forEach(group => {
+ const mine = group.filter(id => picked.has(id));
+ const rest = group.filter(id => !picked.has(id));
+ if (mine.length) {
+ merged.push(...mine);
+ // Keep the combined label where the first ticked part was,
+ // so the preview does not jump to the end of a long list.
+ if (!placed) { groups.push(merged); placed = true; }
+ }
+ if (rest.length) { groups.push(rest); }
+ });
+ write(groups);
+ });
+
+ document.getElementById('splitBtn')?.addEventListener('click', () => {
+ const picked = tickedIds();
+ if (!picked.size) {
+ alert('Tick the labels you want split apart first.');
+ return;
+ }
+ const groups = [];
+ plan().forEach(group => {
+ const rest = group.filter(id => !picked.has(id));
+ group.filter(id => picked.has(id)).forEach(id => groups.push([id]));
+ if (rest.length) { groups.push(rest); }
+ });
+ write(groups);
+ });
+
+ document.getElementById('resetGroupsBtn')?.addEventListener('click', () => {
+ spec.value = '';
+ form.submit();
+ });
+})();
{% endblock %}