diff options
Diffstat (limited to 'internal/admin/static')
| -rw-r--r-- | internal/admin/static/admin.css | 7 | ||||
| -rw-r--r-- | internal/admin/static/admin.js | 4 | ||||
| -rw-r--r-- | internal/admin/static/map.js | 119 |
3 files changed, 127 insertions, 3 deletions
diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index 8c3ee8e..f068207 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -41,10 +41,15 @@ body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100 .search-bar input{width:100%;padding:6px 8px;font-size:13px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px} .login{max-width:360px;margin:80px auto;background:var(--panel);padding:30px;border:1px solid var(--border);border-radius:6px} .login h1{font-size:18px;margin-bottom:20px;text-align:center} -.z-controls{position:absolute;top:12px;left:12px;display:flex;gap:6px;align-items:center;background:var(--panel);padding:6px 10px;border-radius:4px;border:1px solid var(--border);z-index:10} +.z-controls-wrapper{position:absolute;top:12px;left:12px;z-index:10} +.z-controls{position:relative;display:flex;gap:6px;align-items:center;background:var(--panel);padding:6px 10px;border-radius:4px;border:1px solid var(--border)} .z-controls button{width:28px;height:28px;font-size:16px;line-height:1;background:var(--input-bg);color:var(--text);border:1px solid var(--border);border-radius:3px;cursor:pointer} .z-controls button:hover{background:var(--accent)} .z-controls .z-label{font-size:13px;min-width:40px;text-align:center;font-weight:bold} +.z-controls .z-text-btn{width:auto} +.z-controls .z-text-btn.active{background:var(--accent);border-color:#1a5a8e} +.z-controls .z-sep{width:1px;height:20px;background:var(--border);flex-shrink:0} +.z-dir-form{display:none;position:absolute;left:0;top:calc(100% + 4px);background:var(--panel);border:1px solid var(--border);border-radius:4px;padding:8px 10px;z-index:11;gap:6px;align-items:center;white-space:nowrap} .room-tooltip{position:absolute;background:var(--panel);border:1px solid var(--border);padding:6px 10px;border-radius:3px;font-size:11px;pointer-events:none;z-index:20;white-space:nowrap} .notification{position:fixed;bottom:16px;right:16px;background:var(--panel);border:1px solid var(--border);padding:10px 16px;border-radius:4px;font-size:12px;z-index:100;animation:fadeIn .2s} .notification.error{border-color:var(--danger)} diff --git a/internal/admin/static/admin.js b/internal/admin/static/admin.js index cce3bc9..f8e9d13 100644 --- a/internal/admin/static/admin.js +++ b/internal/admin/static/admin.js @@ -31,11 +31,11 @@ window.updateUndoBar = function() { }; window.doUndo = function() { - API.post('/api/undo/undo').then(function(r) { notify(r.message || 'Undo complete', 'success'); updateUndoBar(); if(window.refreshPage)refreshPage(); }).catch(function(e) { notify('Undo failed: '+e.message, 'error'); }); + API.post('/api/undo/undo').then(function(r) { notify(r.message || 'Undo complete', 'success'); updateUndoBar(); if(r.has_duplicates) { window.location = '/'; return; } if(window.refreshPage)refreshPage(); }).catch(function(e) { notify('Undo failed: '+e.message, 'error'); }); }; window.doRedo = function() { - API.post('/api/undo/redo').then(function(r) { notify(r.message || 'Redo complete', 'success'); updateUndoBar(); if(window.refreshPage)refreshPage(); }).catch(function(e) { notify('Redo failed: '+e.message, 'error'); }); + API.post('/api/undo/redo').then(function(r) { notify(r.message || 'Redo complete', 'success'); updateUndoBar(); if(r.has_duplicates) { window.location = '/'; return; } if(window.refreshPage)refreshPage(); }).catch(function(e) { notify('Redo failed: '+e.message, 'error'); }); }; window.addEventListener('keydown', function(e) { diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js index 6acec47..740edf3 100644 --- a/internal/admin/static/map.js +++ b/internal/admin/static/map.js @@ -135,6 +135,13 @@ function loadMap() { } renderMap(data); renderDisconnectedList(data); + var newRoomBtn = $('#newRoomBtn'); + var newRoomSep = $('#newRoomSep'); + if (newRoomBtn && newRoomSep) { + var empty = !data.rooms || data.rooms.length === 0; + newRoomBtn.style.display = empty ? '' : 'none'; + newRoomSep.style.display = empty ? '' : 'none'; + } if (wasDirChange && data.seed) { selectRoom(data.seed); } @@ -1716,4 +1723,116 @@ function updateDirSelect() { } } +function showRenameDir() { + if (currentDir === '') { notify('Cannot rename root', 'error'); return; } + hideNewDir(); + $('#renameDirBtn').classList.add('active'); + var form = $('#renameDirForm'); + form.style.display = 'flex'; + var base = currentDir.includes('/') ? currentDir.split('/').pop() : currentDir; + var input = $('#renameDirInput'); + input.placeholder = base; + input.value = ''; + input.focus(); +} + +function cancelRenameDir() { + $('#renameDirForm').style.display = 'none'; + $('#renameDirBtn').classList.remove('active'); +} + +function confirmRenameDir() { + var newName = $('#renameDirInput').value.trim(); + if (!newName) { notify('Enter a directory name', 'error'); return; } + if (newName.indexOf('/') !== -1 || newName.indexOf('\\') !== -1 || newName === '.' || newName === '..') { notify('Invalid directory name', 'error'); return; } + API.post('/api/room-dirs/rename', {dir: currentDir, new_name: newName}).then(function(r) { + if (r.error) { notify(r.error, 'error'); return; } + notify('Renamed to ' + newName, 'success'); + cancelRenameDir(); + currentDir = r.new_dir; + reloadRoomDirs(function() { + changeDir(r.new_dir); + }); + }).catch(function(e) { notify('Rename failed: ' + e.message, 'error'); }); +} + +function showNewDir() { + hideRenameDir(); + $('#newDirBtn').classList.add('active'); + var form = $('#newDirForm'); + form.style.display = 'flex'; + var input = $('#newDirInput'); + input.value = ''; + input.focus(); +} + +function cancelNewDir() { + $('#newDirForm').style.display = 'none'; + $('#newDirBtn').classList.remove('active'); +} + +function confirmNewDir() { + var name = $('#newDirInput').value.trim(); + if (!name) { notify('Enter a directory name', 'error'); return; } + if (name.indexOf('/') !== -1 || name.indexOf('\\') !== -1 || name === '.' || name === '..') { notify('Invalid directory name', 'error'); return; } + API.post('/api/room-dirs/create', {name: name}).then(function(r) { + if (r.error) { notify(r.error, 'error'); return; } + notify('Created ' + name, 'success'); + cancelNewDir(); + currentDir = name; + reloadRoomDirs(function() { + changeDir(name); + }); + }).catch(function(e) { notify('Create failed: ' + e.message, 'error'); }); +} + +function onDirInputKey(event, type) { + if (event.key === 'Enter') { + event.preventDefault(); + if (type === 'rename') confirmRenameDir(); + else if (type === 'new') confirmNewDir(); + } else if (event.key === 'Escape') { + event.preventDefault(); + if (type === 'rename') cancelRenameDir(); + else if (type === 'new') cancelNewDir(); + } +} + +function createEmptyRoom() { + API.post('/api/rooms/new-empty', {dir: currentDir}).then(function(r) { + if (r.error) { notify(r.error, 'error'); return; } + notify('Created room ' + r.room.id, 'success'); + loadMap().then(function() { + if (r.room && r.room.id) selectRoom(r.room.id); + }); + }).catch(function(e) { notify('Create room failed: ' + e.message, 'error'); }); +} + +function hideRenameDir() { + $('#renameDirForm').style.display = 'none'; + $('#renameDirBtn').classList.remove('active'); +} + +function hideNewDir() { + $('#newDirForm').style.display = 'none'; + $('#newDirBtn').classList.remove('active'); +} + +function reloadRoomDirs(cb) { + API.get('/api/room-dirs').then(function(dirs) { + var sel = $('#dirSelect'); + if (sel) { + sel.innerHTML = ''; + dirs.forEach(function(d) { + var opt = document.createElement('option'); + opt.value = d; + opt.textContent = d || '(root)'; + sel.appendChild(opt); + }); + } + updateDirSelect(); + if (cb) cb(dirs); + }); +} + document.addEventListener('DOMContentLoaded', function() { loadRoomDirs(function() { loadMap(); }); initPanelResize(); }); |
