// SQLite editor of the file manager: the agent opens the database and runs // the queries, so a database in use keeps its locks. Tables and views in // tabs, rows a page at a time; a click on a value edits it (Enter saves, // Esc cancels, Shift+Enter a new line, the ∅ button makes it NULL). Rows are // added and deleted, and a tab runs any SQL. (function () { 'use strict'; var PAGE = 200; var esc = function (s) { return String(s).replace(/[&<>"]/g, function (c) { return {'&': '&', '<': '<', '>': '>', '"': '"'}[c]; }); }; function cellHTML(c) { if (c.t === 'n') return 'NULL'; if (c.t === 'b') return 'BLOB ' + esc(c.v) + ' Б'; var v = c.v || ''; return esc(v.length > 300 ? v.slice(0, 300) + '…' : v); } // The type a typed value goes as: what the value was, or what the column // is declared as, when the text fits it. function typeFor(text, was, decl) { var d = (decl || '').toUpperCase(); var int = /^\s*-?\d+\s*$/.test(text), num = text.trim() !== '' && isFinite(Number(text)); if (was === 'i' || (was !== 's' && /INT/.test(d))) return int ? 'i' : num ? 'r' : 's'; if (was === 'r' || (was !== 's' && /REAL|FLOA|DOUB|NUM|DEC/.test(d))) return num ? 'r' : 's'; return 's'; } function open(box, path, call, toast) { var S = {tables: [], cur: null, offset: 0, data: null, sel: new Set()}; box.innerHTML = '
' + esc(r.error || 'ошибка') + '
'; return; } S.data = r.data; draw(); }); } function draw() { var d = S.data, t = d.table, editable = !!t.key; // An INTEGER PRIMARY KEY column already shows the rowid. var showRowid = t.key === 'rowid' && !t.alias; $('.dbx-info').textContent = d.total + ' ' + plural(d.total) + (t.type === 'view' ? ' · представление' : !editable ? ' · только чтение' : ''); $('.dbx-range').textContent = d.rows.length ? (d.offset + 1) + '–' + (d.offset + d.rows.length) : ''; $('.dbx-prev').disabled = d.offset <= 0; $('.dbx-next').disabled = d.offset + d.rows.length >= d.total; $('.dbx-pager').hidden = d.total <= PAGE; $('.dbx-add').hidden = t.type !== 'table'; $('.dbx-del').hidden = !editable; selInfo(); var h = '| ' : '') + (showRowid ? ' | rowid | ' : ''); t.columns.forEach(function (c) { h += '' + esc(c.name) + (c.type ? '' + esc(c.type.toLowerCase()) + '' : '') + ' | '; }); h += '
|---|---|---|
| ' : '') + (showRowid ? ' | ' + esc(row.k) + ' | ' : ''); row.v.forEach(function (c, j) { h += '' + cellHTML(c) + ' | '; }); h += '
строк нет
'; $('.dbx-grid').innerHTML = h; $('.dbx-grid').scrollTop = 0; fixWidths($('.dbx-grid table')); } // The columns keep the widths they first came out at, so a value being // edited does not move the table about. function fixWidths(table) { if (!table) return; var ths = table.querySelectorAll('thead th'), ws = [], sum = 0; ths.forEach(function (th) { var w = Math.ceil(th.getBoundingClientRect().width); ws.push(w); sum += w; }); ths.forEach(function (th, i) { th.style.width = ws[i] + 'px'; }); table.style.width = sum + 'px'; table.style.tableLayout = 'fixed'; } function plural(n) { var m10 = n % 10, m100 = n % 100; return m10 === 1 && m100 !== 11 ? 'строка' : m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14) ? 'строки' : 'строк'; } function selInfo() { var b = $('.dbx-del'); b.disabled = !S.sel.size; b.textContent = S.sel.size ? 'Удалить (' + S.sel.size + ')' : 'Удалить'; } $('.dbx-prev').onclick = function () { show(S.cur, Math.max(0, S.offset - PAGE)); }; $('.dbx-next').onclick = function () { show(S.cur, S.offset + PAGE); }; // ---- selecting rows ---- $('.dbx-grid').addEventListener('change', function (ev) { var el = ev.target; if (el.classList.contains('dbx-all')) { box.querySelectorAll('tbody .dbx-ck input').forEach(function (c, i) { c.checked = el.checked; el.checked ? S.sel.add(i) : S.sel.delete(i); }); } else if (el.closest('tbody .dbx-ck')) { var i = +el.closest('tr').dataset.i; el.checked ? S.sel.add(i) : S.sel.delete(i); } selInfo(); }); // ---- editing a value ---- var editing = null; $('.dbx-grid').addEventListener('click', function (ev) { var td = ev.target.closest('td.ed'); if (!td || td.querySelector('textarea') || ev.target.closest('.dbx-edit')) return; edit(td); }); function edit(td) { if (editing) finish(editing, true); var i = +td.parentNode.dataset.i, j = +td.dataset.j; var row = S.data.rows[i], cell = row.v[j], col = S.data.table.columns[j]; var hgt = td.offsetHeight; td.classList.add('editing'); td.innerHTML = ''; var ta = td.querySelector('textarea'); ta.value = cell.t === 'n' ? '' : cell.v; ta.placeholder = cell.t === 'n' ? 'NULL' : ''; ta.style.height = Math.max(hgt, 30) + 'px'; ta.focus(); ta.select(); editing = {td: td, i: i, j: j, ta: ta, cell: cell, col: col, nul: false}; ta.addEventListener('keydown', function (ev) { if (ev.key === 'Enter' && !ev.shiftKey) { ev.preventDefault(); finish(editing, true); } else if (ev.key === 'Escape') { ev.preventDefault(); ev.stopPropagation(); finish(editing, false); } else if (ev.key === 'Tab') { ev.preventDefault(); var cur = editing, next = ev.shiftKey ? cur.td.previousElementSibling : cur.td.nextElementSibling; finish(cur, true); if (next && next.classList.contains('ed')) edit(next); } }); ta.addEventListener('blur', function () { setTimeout(function () { if (editing && editing.ta === ta && document.activeElement !== td.querySelector('.dbx-nullb')) finish(editing, true); }, 120); }); td.querySelector('.dbx-nullb').onmousedown = function (ev) { ev.preventDefault(); editing.nul = true; finish(editing, true); }; } function finish(e, keep) { if (!e || editing !== e) return; editing = null; var text = e.ta.value, cell = e.cell; var value = e.nul ? {t: 'n'} : {t: typeFor(text, cell.t, e.col.type), v: text}; var same = e.nul ? cell.t === 'n' : (cell.t !== 'n' || text !== '') && text === (cell.v || '') && (cell.t === value.t || cell.t === 's' || value.t === 's'); if (cell.t === 'n' && !e.nul && text === '') same = true; e.td.classList.remove('editing'); if (!keep || same) { e.td.innerHTML = cellHTML(cell); return; } e.td.innerHTML = cellHTML(value); e.td.classList.add('saving'); req('db.set', {table: S.cur, key: S.data.rows[e.i].k, column: e.col.name, value: value}).then(function (r) { e.td.classList.remove('saving'); if (!r.ok) { e.td.innerHTML = cellHTML(cell); flash(e.td, 'bad'); toast(r.error || 'не сохранилось', true); return; } var got = r.data.value; S.data.rows[e.i].v[e.j] = got; // A changed key column moves the row's key too. if (r.data.key !== undefined) S.data.rows[e.i].k = r.data.key; e.td.innerHTML = cellHTML(got); e.td.className = (got.t === 'i' || got.t === 'r' ? 'num' : '') + ' ed'; flash(e.td, 'ok'); }); } function flash(td, kind) { td.classList.remove('flash-ok', 'flash-bad'); void td.offsetWidth; td.classList.add('flash-' + kind); } // ---- adding and deleting rows ---- $('.dbx-add').onclick = function () { if (box.querySelector('tr.dbx-new')) return; var t = S.data.table, tbody = box.querySelector('.dbx-t tbody'); var tr = document.createElement('tr'); tr.className = 'dbx-new'; var h = (t.key ? '| ' + esc(c) + ' | '; }).join('') + '
|---|
| ' + cellHTML(c) + ' | '; }).join('') + '