107 lines
4.8 KiB
JavaScript
107 lines
4.8 KiB
JavaScript
/* The address-list dialog of the analytics pages (markup: the "analytics_dialog"
|
|||
|
|
template) and the tooltip that goes with it. A page loads a list with
|
||
|
|
AnalyticsDialog.load(url, title), then shows it with AnalyticsDialog.fill(m);
|
||
|
|
copying and the CSV download work on the list shown last. */
|
||
|
|
(function () {
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
function $(id) { return document.getElementById(id); }
|
||
|
|
function fmt(n) { return Math.round(n).toLocaleString('ru-RU'); }
|
||
|
|
function esc(s) {
|
||
|
|
return String(s).replace(/[&<>"']/g, function (c) {
|
||
|
|
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function tipAttr(t) { return 'data-tip="' + esc(t) + '"'; }
|
||
|
|
|
||
|
|
var cur = null; // the list shown in the dialog
|
||
|
|
|
||
|
|
function csvText(cols, rows) {
|
||
|
|
function q(v) { return '"' + String(v).replace(/"/g, '""') + '"'; }
|
||
|
|
return [cols].concat(rows).map(function (r) { return r.map(q).join(','); }).join('\r\n') + '\r\n';
|
||
|
|
}
|
||
|
|
|
||
|
|
function open() {
|
||
|
|
var d = $('an-dlg');
|
||
|
|
if (d.open) return;
|
||
|
|
if (d.showModal) d.showModal(); else d.setAttribute('open', '');
|
||
|
|
}
|
||
|
|
|
||
|
|
/* m: title, note, scope ("Запуск" by default), runLabel, cols, rows, hints
|
||
|
|
(column index -> text), cellHints (column index -> function of the value),
|
||
|
|
dist (HTML above the table), csvURL (the file behind "Скачать CSV"). */
|
||
|
|
function fill(m) {
|
||
|
|
cur = m;
|
||
|
|
$('an-dlg-title').textContent = m.title + ' · ' + fmt(m.rows.length);
|
||
|
|
$('an-dlg-note').textContent = (m.scope || 'Запуск') + ': ' + m.runLabel + '. ' + m.note;
|
||
|
|
$('an-dlg-dist').hidden = !m.dist;
|
||
|
|
$('an-dlg-dist').innerHTML = m.dist || '';
|
||
|
|
var hint = m.hints || {};
|
||
|
|
$('an-dlg-tbl').innerHTML = '<thead><tr>' + m.cols.map(function (c, i) {
|
||
|
|
return hint[i] ? '<th ' + tipAttr(hint[i]) + ' tabindex="0">' + esc(c) + '<span class="an-hint" aria-hidden="true">ⓘ</span></th>' : '<th>' + esc(c) + '</th>';
|
||
|
|
}).join('') + '</tr></thead><tbody>' + m.rows.map(function (x) {
|
||
|
|
return '<tr>' + x.map(function (v, i) {
|
||
|
|
var h = m.cellHints && m.cellHints[i] ? m.cellHints[i](v) : '';
|
||
|
|
return '<td class="' + (i < 2 ? 'an-a' : '') + '"' + (h ? ' ' + tipAttr(h) : '') + '>' + esc(v) + '</td>';
|
||
|
|
}).join('') + '</tr>';
|
||
|
|
}).join('') + '</tbody>';
|
||
|
|
$('an-dlg-msg').textContent = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Opens the dialog with the title and "Загрузка…", fetches the list as JSON;
|
||
|
|
resolves with it, or with null (and a message in the dialog) on a failure. */
|
||
|
|
function load(url, title) {
|
||
|
|
$('an-dlg-title').textContent = title;
|
||
|
|
$('an-dlg-note').textContent = 'Загрузка…';
|
||
|
|
$('an-dlg-dist').hidden = true;
|
||
|
|
$('an-dlg-tbl').innerHTML = '';
|
||
|
|
$('an-dlg-msg').textContent = '';
|
||
|
|
cur = null;
|
||
|
|
open();
|
||
|
|
return fetch(url, { headers: { Accept: 'application/json' }, credentials: 'same-origin' })
|
||
|
|
.then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
|
||
|
|
.then(function (l) { return l; })
|
||
|
|
.catch(function (e) {
|
||
|
|
$('an-dlg-note').textContent = 'Не удалось загрузить список: ' + e.message;
|
||
|
|
return null;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function copyCsv() {
|
||
|
|
var msg = $('an-dlg-msg');
|
||
|
|
if (!cur) return;
|
||
|
|
var text = csvText(cur.cols, cur.rows);
|
||
|
|
var done = function () { msg.textContent = 'CSV скопирован в буфер обмена.'; };
|
||
|
|
var fail = function () { msg.textContent = 'Буфер обмена недоступен в этом окне.'; };
|
||
|
|
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, fail); else fail();
|
||
|
|
}
|
||
|
|
|
||
|
|
function downloadCsv() {
|
||
|
|
if (!cur) return;
|
||
|
|
window.location.href = cur.csvURL;
|
||
|
|
}
|
||
|
|
|
||
|
|
$('an-dlg-csv').addEventListener('click', downloadCsv);
|
||
|
|
$('an-dlg-copy').addEventListener('click', copyCsv);
|
||
|
|
$('an-dlg-close').addEventListener('click', function () { $('an-dlg').close(); });
|
||
|
|
$('an-dlg').addEventListener('click', function (e) { if (e.target === $('an-dlg')) $('an-dlg').close(); });
|
||
|
|
|
||
|
|
/* tooltip: moved into the open dialog, otherwise the modal's top layer covers it */
|
||
|
|
var tip = $('an-tip');
|
||
|
|
function tipHost() { var d = $('an-dlg'); return d && d.open ? d : document.body; }
|
||
|
|
document.addEventListener('mouseover', function (e) {
|
||
|
|
var t = e.target.closest('[data-tip]');
|
||
|
|
if (!t) return;
|
||
|
|
var h = tipHost();
|
||
|
|
if (tip.parentNode !== h) h.appendChild(tip);
|
||
|
|
tip.textContent = t.dataset.tip;
|
||
|
|
tip.style.opacity = 1;
|
||
|
|
});
|
||
|
|
document.addEventListener('mousemove', function (e) {
|
||
|
|
tip.style.left = Math.max(8, Math.min(e.clientX + 14, window.innerWidth - 376)) + 'px';
|
||
|
|
tip.style.top = (e.clientY + 16) + 'px';
|
||
|
|
});
|
||
|
|
document.addEventListener('mouseout', function (e) { if (e.target.closest('[data-tip]')) tip.style.opacity = 0; });
|
||
|
|
|
||
|
|
window.AnalyticsDialog = { $: $, fmt: fmt, esc: esc, tipAttr: tipAttr, load: load, fill: fill };
|
||
|
|
})();
|