ui: editor-like QoL — selection highlight, drag feedback, keyboard nav

Selection / highlight:
- .vv-row-selected: blue tint + left accent bar (was barely-visible gray)
- .vv-sb-selected: same treatment, consistent across both panels
- .vv-df-active (containers): match new selection style
- .vv-job-row + .vv-sb-row: smooth transition on state changes
- .vv-orch-row: hover feedback + pointer cursor (was no visual response)

Drag and drop:
- .vv-drag-ghost: adds dashed ring so source item stays visible while dragging
- .vv-drop-line: blue glow to match selection accent (was flat green)
- All drop targets: unified blue theme (.vv-children, .vv-folder-children)
- .vv-library-zone drop target: improved red with inset glow
- .vv-drag-handle:active: grabbing cursor state
- document.body.vv-is-dragging: cursor:grabbing on entire page during drag
  (added to all three drag start handlers + failsafe document dragend)

Keyboard navigation (scheduler):
- ↑/↓: navigate between top-level orchestrator cards
- Home/End: jump to first/last card
- Escape: close right panel, return to suggestions
- /: focus log search when log panel is open
- All shortcuts suppressed when focus is in any input/textarea/select

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gmer4Lfe
2026-05-31 20:03:27 -04:00
co-authored by Claude Sonnet 4.6
parent a943a5c294
commit 753f99ac0c
2 changed files with 79 additions and 13 deletions
+31 -13
View File
@@ -75,7 +75,11 @@
.vv-sched-card { margin-bottom: 10px; } .vv-sched-card { margin-bottom: 10px; }
.vv-script { background: #161616; border: 1px solid #333; border-radius: 4px; .vv-script { background: #161616; border: 1px solid #333; border-radius: 4px;
padding: 6px 10px; margin: 4px 0; margin-left: 20px; } padding: 6px 10px; margin: 4px 0; margin-left: 20px; }
.vv-job-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .vv-job-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
transition: background 0.1s, box-shadow 0.1s; }
/* Orchestrator top row — clickable, show pointer + hover feedback */
.vv-orch-row { cursor: pointer; border-radius: 4px; }
.vv-orch-row:hover { background: rgba(255,255,255,0.04); }
.vv-job-label { flex: 1; font-size: 16px; font-weight: bold; .vv-job-label { flex: 1; font-size: 16px; font-weight: bold;
color: #6fcf97; min-width: 80px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } color: #6fcf97; min-width: 80px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vv-job-actions { display: flex; align-items: center; gap: 8px; padding-left: 44px; margin-top: 6px; } .vv-job-actions { display: flex; align-items: center; gap: 8px; padding-left: 44px; margin-top: 6px; }
@@ -116,9 +120,10 @@
#vv-log-dot { animation: vv-pulse-dot 1s ease-in-out infinite; } #vv-log-dot { animation: vv-pulse-dot 1s ease-in-out infinite; }
@keyframes vv-pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } } @keyframes vv-pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } }
/* Selected job row */ /* Selected job row — editor-style left accent + blue tint */
.vv-row-selected { background: rgba(255,255,255,0.05); border-radius: 4px; .vv-row-selected { background: rgba(100,149,237,0.1); border-radius: 4px;
outline: 1px solid #555; } outline: 1px solid rgba(100,149,237,0.35);
box-shadow: inset 3px 0 0 #6495ed; }
/* Two-panel layout */ /* Two-panel layout */
#vv-sched-layout { display: flex; gap: 16px; align-items: stretch; } #vv-sched-layout { display: flex; gap: 16px; align-items: stretch; }
@@ -533,9 +538,12 @@ mark { background: #5d4037; color: #ffcc80; border-radius: 2px; }
#vv-sb-tree { padding: 0; } #vv-sb-tree { padding: 0; }
.vv-sb-entry { } .vv-sb-entry { }
.vv-sb-row { display: flex; align-items: center; gap: 6px; padding: 5px 6px; .vv-sb-row { display: flex; align-items: center; gap: 6px; padding: 5px 6px;
cursor: pointer; border-radius: 3px; user-select: none; } cursor: pointer; border-radius: 3px; user-select: none;
transition: background 0.1s, box-shadow 0.1s; }
.vv-sb-row:hover { background: rgba(255,255,255,0.04); } .vv-sb-row:hover { background: rgba(255,255,255,0.04); }
.vv-sb-selected { background: rgba(100,149,237,0.12) !important; outline: 1px solid #3a5a8a; } .vv-sb-selected { background: rgba(100,149,237,0.14) !important;
outline: 1px solid rgba(100,149,237,0.4);
box-shadow: inset 3px 0 0 #6495ed; }
.vv-sb-orch-row { border-bottom: 1px solid #1c1c1c; } .vv-sb-orch-row { border-bottom: 1px solid #1c1c1c; }
.vv-sb-child-row { padding-left: 2px; } .vv-sb-child-row { padding-left: 2px; }
.vv-sb-expand { width: 14px; flex-shrink: 0; color: #555; font-size: 10px; text-align: center; } .vv-sb-expand { width: 14px; flex-shrink: 0; color: #555; font-size: 10px; text-align: center; }
@@ -671,12 +679,19 @@ code.vv-unknown-var { color: #ff9800; background: #1f130d; }
/* ── Arrange mode ─────────────────────────────────────────────────────────── */ /* ── Arrange mode ─────────────────────────────────────────────────────────── */
.vv-drag-handle { cursor: grab; color: #555; font-size: 14px; padding: 0 5px 0 0; user-select: none; flex-shrink: 0; } .vv-drag-handle { cursor: grab; color: #555; font-size: 14px; padding: 0 5px 0 0; user-select: none; flex-shrink: 0; }
.vv-drag-handle:hover { color: #888; } .vv-drag-handle:hover { color: #888; }
.vv-drag-ghost { opacity: 0.35; } .vv-drag-handle:active { cursor: grabbing; color: #aaa; }
.vv-drop-line { height: 2px; background: #4caf50; border-radius: 2px; margin: 2px 0; pointer-events: none; } /* Dragged item: semi-transparent with a dashed ring so you still see what you grabbed */
.vv-drag-ghost { opacity: 0.4; outline: 1px dashed #555; border-radius: 4px; }
/* Drop insertion line: blue glow to match selection accent */
.vv-drop-line { height: 2px; background: #6495ed; border-radius: 2px; margin: 2px 0;
pointer-events: none; box-shadow: 0 0 6px rgba(100,149,237,0.7); }
.vv-arrange-active .vv-children { min-height: 28px; border: 1px dashed #2a2a2a; border-radius: 4px; .vv-arrange-active .vv-children { min-height: 28px; border: 1px dashed #2a2a2a; border-radius: 4px;
padding: 4px 2px; transition: border-color 0.12s, background 0.12s; } padding: 4px 2px; transition: border-color 0.12s, background 0.12s; }
.vv-arrange-active .vv-children.vv-drop-target { border-color: #4caf50; background: rgba(76,175,80,0.07); } /* Drop targets use blue to match selection theme */
.vv-arrange-active .vv-children.vv-drop-target { border-color: #6495ed; background: rgba(100,149,237,0.07); }
.vv-arrange-btn-active { background: #1a3a1e !important; color: #4caf50 !important; border-color: #2d5c33 !important; } .vv-arrange-btn-active { background: #1a3a1e !important; color: #4caf50 !important; border-color: #2d5c33 !important; }
/* Grabbing cursor while dragging anything */
.vv-is-dragging, .vv-is-dragging * { cursor: grabbing !important; }
.vv-arrange-save-btn { background: #1a3a1e; border-color: #2d5c33; color: #4caf50; } .vv-arrange-save-btn { background: #1a3a1e; border-color: #2d5c33; color: #4caf50; }
.vv-arrange-save-btn:hover { background: #22502a; } .vv-arrange-save-btn:hover { background: #22502a; }
#vv-arrange-btn { background: #7b1fa2; border-color: #7b1fa2; } #vv-arrange-btn { background: #7b1fa2; border-color: #7b1fa2; }
@@ -695,7 +710,8 @@ code.vv-unknown-var { color: #ff9800; background: #1f130d; }
.vv-pending-arrow { color: #555; font-size: 10px; } .vv-pending-arrow { color: #555; font-size: 10px; }
.vv-library-zone { border: 1px dashed #2a2a2a; border-radius: 4px; padding: 6px; .vv-library-zone { border: 1px dashed #2a2a2a; border-radius: 4px; padding: 6px;
min-height: 60px; transition: border-color 0.12s, background 0.12s; } min-height: 60px; transition: border-color 0.12s, background 0.12s; }
.vv-library-zone.vv-drop-target { border-color: #c62828; background: rgba(198,40,40,0.07); } .vv-library-zone.vv-drop-target { border-color: #ef5350; background: rgba(239,83,80,0.09);
box-shadow: inset 0 0 8px rgba(239,83,80,0.08); }
.vv-arrange-drop-hint { font-size: 10px; color: #444; text-align: center; padding: 2px 0 7px; } .vv-arrange-drop-hint { font-size: 10px; color: #444; text-align: center; padding: 2px 0 7px; }
.vv-lib-card { background: #1c1c1c; border: 1px solid #2e2e2e; border-radius: 3px; .vv-lib-card { background: #1c1c1c; border: 1px solid #2e2e2e; border-radius: 3px;
padding: 4px 8px; margin-bottom: 4px; cursor: grab; display: flex; padding: 4px 8px; margin-bottom: 4px; cursor: grab; display: flex;
@@ -715,8 +731,8 @@ code.vv-unknown-var { color: #ff9800; background: #1f130d; }
.vv-folder-count { font-size: 10px; color: #555; background: #1c1c1c; .vv-folder-count { font-size: 10px; color: #555; background: #1c1c1c;
padding: 0 5px; border-radius: 8px; border: 1px solid #2a2a2a; } padding: 0 5px; border-radius: 8px; border: 1px solid #2a2a2a; }
.vv-folder-children { padding-left: 12px; min-height: 4px; } .vv-folder-children { padding-left: 12px; min-height: 4px; }
.vv-folder-children.vv-drop-target { background: rgba(76,175,80,0.07); border-radius: 4px; .vv-folder-children.vv-drop-target { background: rgba(100,149,237,0.07); border-radius: 4px;
outline: 1px dashed #3a6a3e; } outline: 1px dashed rgba(100,149,237,0.4); }
.vv-folder-new-row { display: flex; align-items: center; gap: 6px; padding: 4px 6px; } .vv-folder-new-row { display: flex; align-items: center; gap: 6px; padding: 4px 6px; }
.vv-new-folder-btn { background: #0277bd !important; border: none !important; color: #fff !important; } .vv-new-folder-btn { background: #0277bd !important; border: none !important; color: #fff !important; }
.vv-new-folder-btn:hover { background: #01579b !important; } .vv-new-folder-btn:hover { background: #01579b !important; }
@@ -760,7 +776,9 @@ code.vv-unknown-var { color: #ff9800; background: #1f130d; }
.vv-df-container { display: flex; align-items: center; gap: 7px; padding: 3px 6px; .vv-df-container { display: flex; align-items: center; gap: 7px; padding: 3px 6px;
cursor: pointer; border-radius: 3px; user-select: none; } cursor: pointer; border-radius: 3px; user-select: none; }
.vv-df-container:hover { background: rgba(255,255,255,0.04); } .vv-df-container:hover { background: rgba(255,255,255,0.04); }
.vv-df-active { background: rgba(255,255,255,0.05) !important; outline: 1px solid #444; } .vv-df-active { background: rgba(100,149,237,0.1) !important;
outline: 1px solid rgba(100,149,237,0.35);
box-shadow: inset 3px 0 0 #6495ed; }
.vv-df-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } .vv-df-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.vv-df-cname { flex: 1; font-size: 12px; color: #ccc; .vv-df-cname { flex: 1; font-size: 12px; color: #ccc;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+48
View File
@@ -3051,6 +3051,7 @@ function _vvOrchScriptDragStart(e) {
vvDragFromArray = this.dataset.confArray || null; vvDragFromArray = this.dataset.confArray || null;
e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', vvDragScript); e.dataTransfer.setData('text/plain', vvDragScript);
document.body.classList.add('vv-is-dragging');
setTimeout(() => this.classList.add('vv-drag-ghost'), 0); setTimeout(() => this.classList.add('vv-drag-ghost'), 0);
} }
@@ -3060,6 +3061,7 @@ function _vvCustomDragStart(e) {
vvDragFromArray = null; vvDragFromArray = null;
e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', vvDragScript); e.dataTransfer.setData('text/plain', vvDragScript);
document.body.classList.add('vv-is-dragging');
setTimeout(() => this.classList.add('vv-drag-ghost'), 0); setTimeout(() => this.classList.add('vv-drag-ghost'), 0);
} }
@@ -3069,13 +3071,17 @@ function vvLibDragStart(e, el) {
vvDragFromArray = null; vvDragFromArray = null;
e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', vvDragScript); e.dataTransfer.setData('text/plain', vvDragScript);
document.body.classList.add('vv-is-dragging');
setTimeout(() => el.classList.add('vv-drag-ghost'), 0); setTimeout(() => el.classList.add('vv-drag-ghost'), 0);
} }
function vvDragEnd(e) { function vvDragEnd(e) {
if (vvDragEl) vvDragEl.classList.remove('vv-drag-ghost'); if (vvDragEl) vvDragEl.classList.remove('vv-drag-ghost');
document.body.classList.remove('vv-is-dragging');
vvDragEl = null; vvDragEl = null;
} }
// Failsafe: clear dragging class if drop lands outside the window
document.addEventListener('dragend', () => document.body.classList.remove('vv-is-dragging'));
// Return the .vv-script element that the cursor is above the midpoint of, // Return the .vv-script element that the cursor is above the midpoint of,
// or null if cursor is below all items (meaning: append). // or null if cursor is below all items (meaning: append).
@@ -3470,4 +3476,46 @@ requestAnimationFrame(function() {
vvStartStatusPoll(); vvStartStatusPoll();
vvStartSnapPoll(); vvStartSnapPoll();
}); });
// ── Keyboard navigation ───────────────────────────────────────────────────────
// ↑/↓ — navigate top-level orchestrator cards
// Home/End — jump to first/last card
// Escape — close right panel
// / — focus log search when visible
document.addEventListener('keydown', function(e) {
// Don't intercept when typing in any input/textarea/select
const t = document.activeElement;
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' ||
t.tagName === 'SELECT' || t.isContentEditable)) return;
// Escape — back to suggestions
if (e.key === 'Escape' && vvActiveId) {
e.preventDefault();
vvBackToSuggestions();
return;
}
// / — focus log search when the log panel is open
if (e.key === '/' && document.getElementById('vv-log-search').style.display !== 'none') {
e.preventDefault();
document.getElementById('vv-log-search').focus();
return;
}
// ↑/↓/Home/End — navigate top-level orchestrator cards
if (!['ArrowUp','ArrowDown','Home','End'].includes(e.key)) return;
const cards = [...document.querySelectorAll('#vv-sched-cards > .vv-sched-card')];
if (!cards.length) return;
const cur = vvActiveId ? cards.findIndex(c => c.dataset.id === vvActiveId) : -1;
let next;
if (e.key === 'ArrowDown') next = cur < cards.length - 1 ? cur + 1 : cur;
else if (e.key === 'ArrowUp') next = cur > 0 ? cur - 1 : 0;
else if (e.key === 'Home') next = 0;
else next = cards.length - 1;
if (next === cur && cur !== -1) return;
if (next < 0) next = 0;
e.preventDefault();
cards[next].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
vvOpenRight(cards[next].dataset.id);
});
</script> </script>