(function () {
'use strict';
var TYPE_LABELS = {
qr: 'QR',
ean13: 'EAN-13',
model: 'Модель',
type: 'Тип',
inventory: 'Инв. №',
serial: 'S/N',
custom: 'Текст',
};
var DEMO = {
model: 'Mikrotik RB952',
type: 'Роутер',
inventory: 'INV-DEMO-1',
serial: 'S/N ABC123',
custom: '',
qr: 'QR',
ean13: 'EAN-13',
};
var dataEl = document.getElementById('label-template-data');
var state = { width_mm: 70, height_mm: 42, layout: [] };
try {
state = JSON.parse(dataEl.textContent || '{}');
} catch (e) { /* keep defaults */ }
if (!Array.isArray(state.layout)) state.layout = [];
var canvas = document.getElementById('label-canvas');
var scaleInput = document.getElementById('tmpl-scale');
var widthInput = document.getElementById('tmpl-width');
var heightInput = document.getElementById('tmpl-height');
var statusEl = document.getElementById('status');
var selectedId = null;
var drag = null;
var uid = 1;
state.layout.forEach(function (b) {
if (b && b.id) {
var n = parseInt(String(b.id).replace(/\D/g, ''), 10);
if (n >= uid) uid = n + 1;
}
});
function scale() {
return parseFloat(scaleInput.value) || 4;
}
function setStatus(msg, isErr) {
statusEl.textContent = msg || '';
statusEl.className = isErr ? 'err' : '';
}
function selectedBlock() {
return state.layout.find(function (b) { return b.id === selectedId; }) || null;
}
function previewText(block) {
if (block.type === 'custom') return block.text || 'Текст';
return DEMO[block.type] || block.type;
}
function render() {
var s = scale();
canvas.style.width = (state.width_mm * s) + 'px';
canvas.style.height = (state.height_mm * s) + 'px';
canvas.innerHTML = '';
state.layout.forEach(function (block) {
var el = document.createElement('div');
el.className = 'lbl-block' + (block.id === selectedId ? ' selected' : '');
el.dataset.id = block.id;
el.style.left = (block.x_mm * s) + 'px';
el.style.top = (block.y_mm * s) + 'px';
el.style.width = (block.w_mm * s) + 'px';
el.style.height = (block.h_mm * s) + 'px';
el.style.fontSize = Math.max(8, block.font_pt * (s / 3.5)) + 'px';
el.style.fontWeight = block.bold ? '600' : '400';
el.style.textAlign = block.align || 'left';
el.innerHTML = '
' + (TYPE_LABELS[block.type] || block.type) + '
' +
'' + escapeHtml(previewText(block)) + '
' +
'';
canvas.appendChild(el);
});
syncProps();
}
function escapeHtml(str) {
return String(str || '')
.replace(/&/g, '&')
.replace(//g, '>');
}
function syncProps() {
var block = selectedBlock();
var empty = document.getElementById('props-empty');
var fields = document.getElementById('props-fields');
if (!block) {
empty.style.display = '';
fields.style.display = 'none';
return;
}
empty.style.display = 'none';
fields.style.display = '';
document.getElementById('prop-type-label').textContent = TYPE_LABELS[block.type] || block.type;
document.getElementById('prop-x').value = block.x_mm;
document.getElementById('prop-y').value = block.y_mm;
document.getElementById('prop-w').value = block.w_mm;
document.getElementById('prop-h').value = block.h_mm;
document.getElementById('prop-font').value = block.font_pt;
document.getElementById('prop-align').value = block.align || 'left';
document.getElementById('prop-bold').checked = !!block.bold;
var customWrap = document.getElementById('prop-custom-wrap');
customWrap.style.display = block.type === 'custom' ? '' : 'none';
document.getElementById('prop-text').value = block.text || '';
}
function clampBlock(block) {
block.w_mm = Math.max(2, Number(block.w_mm) || 10);
block.h_mm = Math.max(2, Number(block.h_mm) || 10);
block.x_mm = Math.max(0, Math.min(state.width_mm - 2, Number(block.x_mm) || 0));
block.y_mm = Math.max(0, Math.min(state.height_mm - 2, Number(block.y_mm) || 0));
if (block.x_mm + block.w_mm > state.width_mm) block.w_mm = Math.max(2, state.width_mm - block.x_mm);
if (block.y_mm + block.h_mm > state.height_mm) block.h_mm = Math.max(2, state.height_mm - block.y_mm);
}
function addBlock(type) {
var defaults = {
qr: { w_mm: 22, h_mm: 22 },
ean13: { w_mm: 50, h_mm: 14 },
model: { w_mm: 35, h_mm: 6, bold: true, font_pt: 10 },
type: { w_mm: 35, h_mm: 5, font_pt: 8 },
inventory: { w_mm: 35, h_mm: 5, font_pt: 10 },
serial: { w_mm: 35, h_mm: 4, font_pt: 8 },
custom: { w_mm: 30, h_mm: 5, text: 'Текст' },
};
var d = defaults[type] || { w_mm: 20, h_mm: 6 };
var block = {
id: type + '-' + (uid++),
type: type,
x_mm: 2,
y_mm: 2,
w_mm: d.w_mm,
h_mm: d.h_mm,
font_pt: d.font_pt || 9,
bold: !!d.bold,
align: 'left',
text: d.text || '',
};
clampBlock(block);
state.layout.push(block);
selectedId = block.id;
render();
}
canvas.addEventListener('pointerdown', function (ev) {
var target = ev.target.closest('.lbl-block');
if (!target) {
selectedId = null;
render();
return;
}
selectedId = target.dataset.id;
var block = selectedBlock();
if (!block) return;
var s = scale();
var isResize = !!ev.target.closest('[data-resize]');
drag = {
id: block.id,
resize: isResize,
startX: ev.clientX,
startY: ev.clientY,
origX: block.x_mm,
origY: block.y_mm,
origW: block.w_mm,
origH: block.h_mm,
scale: s,
pointerId: ev.pointerId,
};
canvas.setPointerCapture(ev.pointerId);
render();
ev.preventDefault();
});
canvas.addEventListener('pointermove', function (ev) {
if (!drag) return;
var block = state.layout.find(function (b) { return b.id === drag.id; });
if (!block) return;
var dx = (ev.clientX - drag.startX) / drag.scale;
var dy = (ev.clientY - drag.startY) / drag.scale;
if (drag.resize) {
block.w_mm = drag.origW + dx;
block.h_mm = drag.origH + dy;
} else {
block.x_mm = drag.origX + dx;
block.y_mm = drag.origY + dy;
}
clampBlock(block);
render();
});
function endDrag() {
drag = null;
syncProps();
}
canvas.addEventListener('pointerup', endDrag);
canvas.addEventListener('pointercancel', endDrag);
document.querySelectorAll('.palette [data-add]').forEach(function (btn) {
btn.addEventListener('click', function () {
addBlock(btn.getAttribute('data-add'));
});
});
function bindProp(id, apply) {
var el = document.getElementById(id);
el.addEventListener('change', function () {
var block = selectedBlock();
if (!block) return;
apply(block, el);
clampBlock(block);
render();
});
el.addEventListener('input', function () {
var block = selectedBlock();
if (!block) return;
apply(block, el);
clampBlock(block);
render();
});
}
bindProp('prop-x', function (b, el) { b.x_mm = parseFloat(el.value) || 0; });
bindProp('prop-y', function (b, el) { b.y_mm = parseFloat(el.value) || 0; });
bindProp('prop-w', function (b, el) { b.w_mm = parseFloat(el.value) || 2; });
bindProp('prop-h', function (b, el) { b.h_mm = parseFloat(el.value) || 2; });
bindProp('prop-font', function (b, el) { b.font_pt = parseFloat(el.value) || 9; });
bindProp('prop-align', function (b, el) { b.align = el.value; });
bindProp('prop-bold', function (b, el) { b.bold = el.checked; });
bindProp('prop-text', function (b, el) { b.text = el.value; });
document.getElementById('prop-delete').addEventListener('click', function () {
if (!selectedId) return;
state.layout = state.layout.filter(function (b) { return b.id !== selectedId; });
selectedId = null;
render();
});
function resizeFromInputs() {
state.width_mm = Math.max(20, Math.min(200, parseFloat(widthInput.value) || 70));
state.height_mm = Math.max(15, Math.min(200, parseFloat(heightInput.value) || 42));
widthInput.value = state.width_mm;
heightInput.value = state.height_mm;
state.layout.forEach(clampBlock);
render();
}
widthInput.addEventListener('change', resizeFromInputs);
heightInput.addEventListener('change', resizeFromInputs);
scaleInput.addEventListener('change', render);
document.getElementById('btn-save').addEventListener('click', function () {
var csrf = document.querySelector('meta[name="csrf-token"]');
var payload = {
width_mm: state.width_mm,
height_mm: state.height_mm,
layout: state.layout,
};
setStatus('Сохранение…');
fetch('/it-manager/equipment/labels/template/', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrf ? csrf.content : '',
},
body: JSON.stringify(payload),
}).then(function (r) {
return r.json().then(function (data) {
if (!r.ok || !data.ok) throw new Error((data && data.error) || 'Ошибка сохранения');
setStatus('Сохранено');
});
}).catch(function (err) {
setStatus(err.message || 'Ошибка', true);
});
});
render();
})();