235 lines
7.9 KiB
JavaScript
235 lines
7.9 KiB
JavaScript
const inputPopup = document.getElementById('inputPopup');
|
||
const createPopup = document.getElementById('createPopup');
|
||
|
||
const actionTable = {
|
||
'Редактировать': () => console.log('Редактировать'),
|
||
'Удалить': () => console.log('Удалить'),
|
||
'Копировать': () => console.log('Скопировано')
|
||
};
|
||
|
||
let menuDiv = document.querySelector('.window-menu');
|
||
if (!menuDiv) {
|
||
menuDiv = document.createElement('div');
|
||
menuDiv.className = 'window-menu';
|
||
menuDiv.style.position = 'absolute';
|
||
menuDiv.style.display = 'none';
|
||
document.body.appendChild(menuDiv);
|
||
}
|
||
|
||
function parseJSONSafe(str) {
|
||
if (!str) return null;
|
||
try { return JSON.parse(str); } catch (e) { return null; }
|
||
}
|
||
|
||
function splitMenuText(text) {
|
||
if (!text) return [];
|
||
return String(text).split(',').map(s => s.trim()).filter(Boolean);
|
||
}
|
||
|
||
function buildMenu(items) {
|
||
menuDiv.innerHTML = '';
|
||
items.forEach(label => {
|
||
const it = document.createElement('div');
|
||
it.className = 'window-item';
|
||
it.textContent = label;
|
||
it.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
if (actionTable[label]) actionTable[label]();
|
||
hideMenu();
|
||
});
|
||
menuDiv.appendChild(it);
|
||
});
|
||
}
|
||
|
||
function showMenuAt(x, y, items) {
|
||
if (!items || !items.length) return;
|
||
buildMenu(items);
|
||
menuDiv.style.display = 'block';
|
||
menuDiv.style.left = x + 'px';
|
||
menuDiv.style.top = y + 'px';
|
||
const rect = menuDiv.getBoundingClientRect();
|
||
if (rect.right > window.innerWidth) menuDiv.style.left = (x - rect.width) + 'px';
|
||
if (rect.bottom > window.innerHeight) menuDiv.style.top = (y - rect.height) + 'px';
|
||
setTimeout(() => document.addEventListener('click', hideMenu, { once: true }), 0);
|
||
}
|
||
|
||
function hideMenu() {
|
||
menuDiv.style.display = 'none';
|
||
}
|
||
|
||
function showOverlayMessage(text) {
|
||
if (!text) return;
|
||
const overlay = document.createElement('div');
|
||
overlay.className = 'overlay';
|
||
const popup = document.createElement('div');
|
||
popup.className = 'window-popup';
|
||
const content = document.createElement('div');
|
||
content.textContent = String(text);
|
||
const closeBtn = document.createElement('button');
|
||
closeBtn.textContent = 'Закрыть';
|
||
closeBtn.addEventListener('click', () => overlay.remove());
|
||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||
popup.appendChild(content);
|
||
popup.appendChild(closeBtn);
|
||
overlay.appendChild(popup);
|
||
document.body.appendChild(overlay);
|
||
}
|
||
|
||
function popupFileFromSections(title, sections, afterClose) {
|
||
const popup = document.createElement('div');
|
||
popup.className = 'window-panel';
|
||
const header = document.createElement('div');
|
||
header.className = 'window-header';
|
||
header.textContent = 'Свойства: ' + (title || '');
|
||
const tabs = document.createElement('div');
|
||
tabs.className = 'window-tabs';
|
||
const tabButtons = [];
|
||
const tabContents = [];
|
||
const keys = Object.keys(sections || {});
|
||
|
||
keys.forEach((name, i) => {
|
||
const t = document.createElement('div');
|
||
t.className = 'window-tab' + (i === 0 ? ' active' : '');
|
||
t.textContent = name;
|
||
t.addEventListener('click', () => switchTab(i));
|
||
tabButtons.push(t);
|
||
tabs.appendChild(t);
|
||
|
||
const content = document.createElement('div');
|
||
content.className = 'window-content' + (i === 0 ? ' active' : '');
|
||
const val = sections[name];
|
||
|
||
if (Array.isArray(val)) {
|
||
val.forEach(row => {
|
||
const rowEl = document.createElement('div');
|
||
rowEl.className = 'window-row';
|
||
if (Array.isArray(row) && row.length >= 2) {
|
||
const k = document.createElement('span');
|
||
k.textContent = row[0];
|
||
const v = document.createElement('span');
|
||
v.textContent = row[1];
|
||
rowEl.append(k, v);
|
||
} else {
|
||
rowEl.textContent = String(row);
|
||
}
|
||
content.appendChild(rowEl);
|
||
});
|
||
} else if (val && typeof val === 'object') {
|
||
Object.keys(val).forEach(kname => {
|
||
const rowEl = document.createElement('div');
|
||
rowEl.className = 'window-row';
|
||
const k = document.createElement('span');
|
||
k.textContent = kname + ':';
|
||
const v = document.createElement('span');
|
||
v.textContent = String(val[kname]);
|
||
rowEl.append(k, v);
|
||
content.appendChild(rowEl);
|
||
});
|
||
} else {
|
||
content.innerHTML = String(val || '');
|
||
}
|
||
|
||
tabContents.push(content);
|
||
});
|
||
|
||
function switchTab(i) {
|
||
tabButtons.forEach((t, idx) => t.classList.toggle('active', idx === i));
|
||
tabContents.forEach((c, idx) => c.classList.toggle('active', idx === i));
|
||
}
|
||
|
||
const buttons = document.createElement('div');
|
||
buttons.className = 'window-buttons';
|
||
const okBtn = document.createElement('button');
|
||
okBtn.textContent = 'ОК';
|
||
const cancelBtn = document.createElement('button');
|
||
cancelBtn.textContent = 'Отмена';
|
||
okBtn.onclick = close;
|
||
cancelBtn.onclick = close;
|
||
buttons.append(okBtn, cancelBtn);
|
||
|
||
let offsetX, offsetY, dragging = false;
|
||
|
||
header.addEventListener('mousedown', (e) => {
|
||
dragging = true;
|
||
offsetX = e.clientX - popup.offsetLeft;
|
||
offsetY = e.clientY - popup.offsetTop;
|
||
header.style.userSelect = 'none';
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (dragging) {
|
||
popup.style.left = (e.clientX - offsetX) + 'px';
|
||
popup.style.top = (e.clientY - offsetY) + 'px';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
dragging = false;
|
||
});
|
||
|
||
function close() {
|
||
popup.remove();
|
||
if (typeof afterClose === 'function') afterClose();
|
||
}
|
||
|
||
popup.append(header, tabs, ...tabContents, buttons);
|
||
document.body.append(popup);
|
||
}
|
||
|
||
function handleCommand(raw, opts) {
|
||
if (!raw) return;
|
||
const parsed = typeof raw === 'string' ? parseJSONSafe(raw.trim()) : raw;
|
||
if (!parsed || !parsed.act) return;
|
||
|
||
if (parsed.act === 'message' && opts && opts.source === 'button') {
|
||
const text = parsed.text || '';
|
||
if (!text) return;
|
||
showOverlayMessage(text);
|
||
return;
|
||
}
|
||
|
||
if (parsed.act === 'menu' && opts && opts.source === 'context') {
|
||
const items = Array.isArray(parsed.text)
|
||
? parsed.text.map(s => String(s).trim()).filter(Boolean)
|
||
: splitMenuText(String(parsed.text || ''));
|
||
|
||
if (!items.length) return;
|
||
|
||
const x = opts.coords && typeof opts.coords.x === 'number' ? opts.coords.x : (parsed.x || 0);
|
||
const y = opts.coords && typeof opts.coords.y === 'number' ? opts.coords.y : (parsed.y || 0);
|
||
|
||
showMenuAt(x, y, items);
|
||
return;
|
||
}
|
||
|
||
if (parsed.act === 'file' && opts && opts.source === 'button') {
|
||
const sections = parsed.text && typeof parsed.text === 'object' ? parsed.text : null;
|
||
const title = sections && sections['Общие'] && sections['Общие']['Имя']
|
||
? sections['Общие']['Имя']
|
||
: parsed.title || '';
|
||
|
||
if (!sections) return;
|
||
|
||
popupFileFromSections(title, sections);
|
||
return;
|
||
}
|
||
}
|
||
|
||
createPopup.addEventListener('click', () => {
|
||
const raw = (inputPopup && inputPopup.value) ? inputPopup.value : '';
|
||
handleCommand(raw, { source: 'button' });
|
||
});
|
||
|
||
document.addEventListener('contextmenu', (e) => {
|
||
const raw = (inputPopup && inputPopup.value) ? inputPopup.value : '';
|
||
const parsed = parseJSONSafe(raw);
|
||
if (!parsed || parsed.act !== 'menu') return;
|
||
|
||
e.preventDefault();
|
||
handleCommand(raw, { source: 'context', coords: { x: e.pageX, y: e.pageY } });
|
||
});
|
||
|
||
window.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') hideMenu();
|
||
});
|