/** @file manager.js @version 0.3.1 */ const managerNoticeTimers = new WeakMap(); function managerIcon(name) { const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.classList.add('icon'); svg.setAttribute('aria-hidden', 'true'); const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttribute('href', '#icon-' + name); svg.append(use); return svg; } async function managerRequest(url, options = {}) { const response = await fetch(url, options); let data; try { data = await response.json(); } catch { throw new Error('Сервер вернул неожиданный ответ. Попробуйте обновить страницу.'); } if (!response.ok) { throw new Error(typeof data.detail === 'string' ? data.detail : 'Проверьте введённые значения.'); } return data; } function managerNotice(element, text, type = 'info', timeout = type === 'info' ? 0 : 5000) { clearTimeout(managerNoticeTimers.get(element)); const hide = () => { clearTimeout(managerNoticeTimers.get(element)); managerNoticeTimers.delete(element); element.classList.add('is-hidden'); }; if (timeout > 0) managerNoticeTimers.set(element, setTimeout(hide, timeout)); if (timeout === 0 && element.dataset.message === text && element.dataset.type === type && !element.classList.contains('is-hidden')) return; element.dataset.message = text; element.dataset.type = type; element.classList.remove('is-hidden', 'notice-info', 'notice-success', 'notice-warning', 'notice-danger'); element.classList.add('notice', 'notice-' + type); const indicator = type === 'info' ? document.createElement('span') : managerIcon(type === 'success' ? 'check' : 'terminal'); if (type === 'info') { indicator.className = 'spinner'; indicator.setAttribute('aria-hidden', 'true'); } const copy = document.createElement('span'); copy.className = 'notice-copy'; copy.textContent = text; const close = document.createElement('button'); close.type = 'button'; close.className = 'icon-button'; close.setAttribute('aria-label', 'Скрыть сообщение'); close.append(managerIcon('close')); if (timeout > 0) { close.classList.add('notice-timed-close'); close.title = 'Скрыть сейчас; автоматически через ' + timeout / 1000 + ' с'; const ring = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); ring.classList.add('notice-countdown'); ring.setAttribute('viewBox', '0 0 32 32'); ring.setAttribute('aria-hidden', 'true'); ring.style.setProperty('--notice-timeout', timeout + 'ms'); for (const name of ['track', 'progress']) { const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.classList.add('notice-countdown-' + name); circle.setAttribute('cx', '16'); circle.setAttribute('cy', '16'); circle.setAttribute('r', '13'); circle.setAttribute('pathLength', '100'); ring.append(circle); } close.append(ring); } close.addEventListener('click', hide); element.replaceChildren(indicator, copy, close); } function managerMessage(text, type = 'info', timeout = type === 'info' ? 0 : 5000) { managerNotice(document.getElementById('operation-message'), text, type, timeout); } function managerFormError(form, text = '') { const element = form.querySelector('.form-feedback'); if (!element) return; element.textContent = text; element.classList.toggle('is-hidden', !text); } async function managerJob(job, report = null) { while (true) { const warnings = (job.warnings || []).join(' '); if (job.status === 'error') { if (report) report(job); throw new Error(job.error); } if (job.status === 'done') { if (report) report(job); else managerMessage(job.folder + ': готово.' + (warnings ? ' ' + warnings : ''), warnings ? 'warning' : 'success'); return job; } if (report) report(job); else managerMessage(job.folder + ': ' + job.stage + (warnings ? '. ' + warnings : ''), warnings ? 'warning' : 'info', 0); await new Promise(resolve => setTimeout(resolve, 1000)); job = await managerRequest('/api/jobs/' + encodeURIComponent(job.id)); } } function managerJson(method, data) { return {method, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)}; } document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('[data-dialog-open]').forEach(button => { button.addEventListener('click', () => { const dialog = document.getElementById(button.dataset.dialogOpen); managerFormError(dialog); dialog.showModal(); }); }); document.querySelectorAll('[data-dialog-close]').forEach(button => { button.addEventListener('click', () => button.closest('dialog').close()); }); document.querySelectorAll('dialog').forEach(dialog => { dialog.addEventListener('click', event => { if (event.target === dialog) { const rect = dialog.getBoundingClientRect(); if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close(); } }); dialog.addEventListener('close', () => dialog.querySelectorAll('input[type=password]').forEach(input => { input.value = ''; })); }); });