@saluzi/saluzi-edu 0.1.74 → 0.1.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1996 -1975
- package/dist/parade/web/cube.js +79 -15
- package/dist/parade/web/index.html +54 -4
- package/package.json +1 -1
package/dist/parade/web/cube.js
CHANGED
|
@@ -31,7 +31,8 @@ const waitingPillEl = document.getElementById('waiting-pill')
|
|
|
31
31
|
const sessionsEl = document.getElementById('sessions')
|
|
32
32
|
const moreLineEl = document.getElementById('more-line')
|
|
33
33
|
const ctxMenu = document.getElementById('ctx-menu')
|
|
34
|
-
const
|
|
34
|
+
const ctxMode = document.getElementById('ctx-mode')
|
|
35
|
+
const closeBtn = document.getElementById('close-btn')
|
|
35
36
|
|
|
36
37
|
// ---------------------------------------------------------------------------
|
|
37
38
|
// WebSocket connection
|
|
@@ -65,9 +66,18 @@ function connectWebSocket() {
|
|
|
65
66
|
|
|
66
67
|
ws.onmessage = function (event) {
|
|
67
68
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const data = JSON.parse(event.data)
|
|
70
|
+
if (Array.isArray(data)) {
|
|
71
|
+
sessions = data
|
|
72
|
+
updateCounts()
|
|
73
|
+
updateSessionRows()
|
|
74
|
+
} else if (data && data.type === 'focus') {
|
|
75
|
+
// Server relayed a focus request — tell electron main process to
|
|
76
|
+
// activate the terminal window for this PID.
|
|
77
|
+
if (window.parade && typeof window.parade.focusSession === 'function') {
|
|
78
|
+
window.parade.focusSession(data.pid)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
71
81
|
} catch (_) {
|
|
72
82
|
// Ignore malformed messages
|
|
73
83
|
}
|
|
@@ -182,13 +192,30 @@ function buildSessionRow(s, kind) {
|
|
|
182
192
|
if (promptText.length > MAX_PROMPT_LENGTH) {
|
|
183
193
|
promptText = promptText.substring(0, MAX_PROMPT_LENGTH) + '...'
|
|
184
194
|
}
|
|
195
|
+
var text = document.createElement('span')
|
|
196
|
+
text.className = 'session-text'
|
|
185
197
|
if (kind === 'idle') {
|
|
186
|
-
|
|
198
|
+
text.textContent = promptText || '(idle)'
|
|
187
199
|
} else if (kind === 'blocked') {
|
|
188
|
-
|
|
200
|
+
text.textContent = promptText || '(needs approval)'
|
|
189
201
|
} else {
|
|
190
|
-
|
|
202
|
+
text.textContent = promptText || '(running)'
|
|
191
203
|
}
|
|
204
|
+
row.appendChild(text)
|
|
205
|
+
|
|
206
|
+
// Jump-to-terminal icon — appears on hover, sends focus request via WS
|
|
207
|
+
var jump = document.createElement('span')
|
|
208
|
+
jump.className = 'session-jump'
|
|
209
|
+
jump.textContent = '\u25B8' // ▸
|
|
210
|
+
jump.title = 'Jump to terminal'
|
|
211
|
+
jump.addEventListener('click', function (e) {
|
|
212
|
+
e.stopPropagation()
|
|
213
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
214
|
+
ws.send(JSON.stringify({ type: 'focus', pid: s.pid }))
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
row.appendChild(jump)
|
|
218
|
+
|
|
192
219
|
return row
|
|
193
220
|
}
|
|
194
221
|
|
|
@@ -198,6 +225,7 @@ function buildSessionRow(s, kind) {
|
|
|
198
225
|
|
|
199
226
|
document.addEventListener('contextmenu', function (e) {
|
|
200
227
|
e.preventDefault()
|
|
228
|
+
updateModeLabel()
|
|
201
229
|
ctxMenu.style.display = 'block'
|
|
202
230
|
ctxMenu.style.left = e.clientX + 'px'
|
|
203
231
|
ctxMenu.style.top = e.clientY + 'px'
|
|
@@ -215,6 +243,7 @@ if (window.parade && typeof window.parade.startDrag === 'function') {
|
|
|
215
243
|
document.addEventListener('mousedown', function (e) {
|
|
216
244
|
if (e.button !== 0) return // left button only
|
|
217
245
|
if (e.target.closest('#ctx-menu')) return // don't drag from menu
|
|
246
|
+
if (e.target.closest('#close-btn')) return // don't drag from close btn
|
|
218
247
|
var cornerEl = e.target.closest('[data-corner]')
|
|
219
248
|
if (cornerEl && typeof window.parade.startResize === 'function') {
|
|
220
249
|
window.parade.startResize(cornerEl.dataset.corner)
|
|
@@ -238,17 +267,19 @@ document.addEventListener('click', function () {
|
|
|
238
267
|
ctxMenu.style.display = 'none'
|
|
239
268
|
})
|
|
240
269
|
|
|
241
|
-
|
|
242
|
-
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Close button (top-right corner)
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
closeBtn.addEventListener('click', function () {
|
|
243
275
|
// POST /shutdown to stop the parade server — this releases the port and
|
|
244
276
|
// tears down all connections. The electron window process polls /health
|
|
245
|
-
// every 1s; after
|
|
277
|
+
// every 1s; after 10 failures it quits, closing the overlay.
|
|
246
278
|
fetch('http://localhost:' + port + '/shutdown', { method: 'POST' })
|
|
247
279
|
.catch(function () {
|
|
248
280
|
/* server may already be down */
|
|
249
281
|
})
|
|
250
282
|
.finally(function () {
|
|
251
|
-
// Also try window.close() — harmless if it no-ops (electron ignores it).
|
|
252
283
|
try {
|
|
253
284
|
window.close()
|
|
254
285
|
} catch (_) {
|
|
@@ -257,6 +288,37 @@ ctxClose.addEventListener('click', function () {
|
|
|
257
288
|
})
|
|
258
289
|
})
|
|
259
290
|
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Dynamic / Static cube mode toggle
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Dynamic (default): cube rotates when sessions are running, static when idle.
|
|
295
|
+
// Static (inverse): cube static when running, rotates when idle.
|
|
296
|
+
// The context menu shows the *switchable* label (opposite of current mode).
|
|
297
|
+
|
|
298
|
+
var cubeMode = 'dynamic'
|
|
299
|
+
try {
|
|
300
|
+
cubeMode = localStorage.getItem('parade-cube-mode') || 'dynamic'
|
|
301
|
+
} catch (_) {
|
|
302
|
+
/* localStorage may be unavailable */
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function updateModeLabel() {
|
|
306
|
+
ctxMode.textContent = cubeMode === 'dynamic' ? 'Static' : 'Dynamic'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ctxMode.addEventListener('click', function () {
|
|
310
|
+
ctxMenu.style.display = 'none'
|
|
311
|
+
cubeMode = cubeMode === 'dynamic' ? 'static' : 'dynamic'
|
|
312
|
+
try {
|
|
313
|
+
localStorage.setItem('parade-cube-mode', cubeMode)
|
|
314
|
+
} catch (_) {
|
|
315
|
+
/* ignore */
|
|
316
|
+
}
|
|
317
|
+
updateModeLabel()
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
updateModeLabel()
|
|
321
|
+
|
|
260
322
|
// ---------------------------------------------------------------------------
|
|
261
323
|
// WebGL: Purple 3D Cube
|
|
262
324
|
// ---------------------------------------------------------------------------
|
|
@@ -265,7 +327,6 @@ var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
|
|
|
265
327
|
var cubeProgram = null
|
|
266
328
|
var cubeBuffer = null
|
|
267
329
|
var cubeIndexBuffer = null
|
|
268
|
-
var isRotating = false
|
|
269
330
|
|
|
270
331
|
// Sync the canvas drawing buffer to its CSS display size × devicePixelRatio.
|
|
271
332
|
// Without this, the canvas keeps its initial 240×320 buffer regardless of the
|
|
@@ -533,11 +594,14 @@ function mat4Multiply(a, b) {
|
|
|
533
594
|
|
|
534
595
|
/**
|
|
535
596
|
* Animation loop. Rotates the cube on X and Y axes (gyroscope-style)
|
|
536
|
-
*
|
|
537
|
-
*
|
|
597
|
+
* based on the current cube mode:
|
|
598
|
+
* Dynamic: rotate when runningCount > 0
|
|
599
|
+
* Static: rotate when runningCount === 0 (inverse)
|
|
538
600
|
*/
|
|
539
601
|
function animate() {
|
|
540
|
-
|
|
602
|
+
var shouldRotate =
|
|
603
|
+
cubeMode === 'dynamic' ? runningCount > 0 : runningCount === 0
|
|
604
|
+
if (shouldRotate) {
|
|
541
605
|
rotY += 0.025
|
|
542
606
|
rotX += 0.012
|
|
543
607
|
}
|
|
@@ -127,12 +127,18 @@
|
|
|
127
127
|
font-size: 10.5px;
|
|
128
128
|
line-height: 1.35;
|
|
129
129
|
color: var(--text-dim);
|
|
130
|
-
white-space: nowrap;
|
|
131
|
-
overflow: hidden;
|
|
132
|
-
text-overflow: ellipsis;
|
|
133
130
|
padding: 2px 0 2px 9px;
|
|
134
131
|
position: relative;
|
|
135
132
|
font-variant-numeric: tabular-nums;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
}
|
|
136
|
+
.session-text {
|
|
137
|
+
flex: 1;
|
|
138
|
+
overflow: hidden;
|
|
139
|
+
text-overflow: ellipsis;
|
|
140
|
+
white-space: nowrap;
|
|
141
|
+
min-width: 0;
|
|
136
142
|
}
|
|
137
143
|
.session-row::before {
|
|
138
144
|
content: '';
|
|
@@ -216,6 +222,49 @@
|
|
|
216
222
|
background: var(--purple);
|
|
217
223
|
color: #fff;
|
|
218
224
|
}
|
|
225
|
+
|
|
226
|
+
/* ---- Close button (top-right corner) ---- */
|
|
227
|
+
#close-btn {
|
|
228
|
+
position: fixed;
|
|
229
|
+
top: 4px;
|
|
230
|
+
right: 6px;
|
|
231
|
+
width: 16px;
|
|
232
|
+
height: 16px;
|
|
233
|
+
border-radius: 50%;
|
|
234
|
+
background: rgba(139, 92, 246, 0.15);
|
|
235
|
+
color: var(--purple-soft);
|
|
236
|
+
font-size: 13px;
|
|
237
|
+
line-height: 15px;
|
|
238
|
+
text-align: center;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
z-index: 300;
|
|
241
|
+
transition: all 0.15s ease;
|
|
242
|
+
opacity: 0;
|
|
243
|
+
border: 1px solid rgba(139, 92, 246, 0.2);
|
|
244
|
+
}
|
|
245
|
+
body:hover #close-btn { opacity: 0.5; }
|
|
246
|
+
body #close-btn:hover {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
background: rgba(139, 92, 246, 0.4);
|
|
249
|
+
color: #fff;
|
|
250
|
+
box-shadow: 0 0 8px var(--purple-glow);
|
|
251
|
+
transform: scale(1.15);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* ---- Session jump icon ---- */
|
|
255
|
+
.session-jump {
|
|
256
|
+
display: inline-block;
|
|
257
|
+
margin-left: auto;
|
|
258
|
+
padding-left: 4px;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
opacity: 0;
|
|
261
|
+
color: var(--purple-soft);
|
|
262
|
+
font-size: 9px;
|
|
263
|
+
transition: opacity 0.15s;
|
|
264
|
+
flex-shrink: 0;
|
|
265
|
+
}
|
|
266
|
+
.session-row:hover .session-jump { opacity: 0.7; }
|
|
267
|
+
.session-row:hover .session-jump:hover { opacity: 1; }
|
|
219
268
|
</style>
|
|
220
269
|
</head>
|
|
221
270
|
<body>
|
|
@@ -235,8 +284,9 @@
|
|
|
235
284
|
<div id="more-line"></div>
|
|
236
285
|
</div>
|
|
237
286
|
<div id="ctx-menu">
|
|
238
|
-
<div class="ctx-item" id="ctx-
|
|
287
|
+
<div class="ctx-item" id="ctx-mode">Static</div>
|
|
239
288
|
</div>
|
|
289
|
+
<div id="close-btn" title="Close parade">×</div>
|
|
240
290
|
<div id="ws-status" title="WS: connecting"></div>
|
|
241
291
|
<!-- Corner resize handles: invisible hit areas at each corner -->
|
|
242
292
|
<div class="corner-handle corner-tl" data-corner="tl"></div>
|