claude-opencode-viewer 2.3.0 → 2.3.1
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/index.html +392 -131
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -116,6 +116,10 @@
|
|
|
116
116
|
align-items: center;
|
|
117
117
|
justify-content: center;
|
|
118
118
|
border: none;
|
|
119
|
+
outline: none;
|
|
120
|
+
-webkit-user-select: none;
|
|
121
|
+
-moz-user-select: none;
|
|
122
|
+
-ms-user-select: none;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
.virtual-key:active {
|
|
@@ -143,14 +147,16 @@
|
|
|
143
147
|
<div id="terminal-container">
|
|
144
148
|
<div id="terminal"></div>
|
|
145
149
|
<div id="virtual-keybar">
|
|
146
|
-
<
|
|
147
|
-
<
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
<
|
|
153
|
-
<
|
|
150
|
+
<div class="virtual-key" data-key="up">↑</div>
|
|
151
|
+
<div class="virtual-key" data-key="down">↓</div>
|
|
152
|
+
<div class="virtual-key" data-key="left">←</div>
|
|
153
|
+
<div class="virtual-key" data-key="right">→</div>
|
|
154
|
+
<div class="virtual-key" data-key="enter">Enter</div>
|
|
155
|
+
<div class="virtual-key" data-key="tab">Tab</div>
|
|
156
|
+
<div class="virtual-key" data-key="esc">Esc</div>
|
|
157
|
+
<div class="virtual-key" data-key="ctrlc">Ctrl+C</div>
|
|
158
|
+
<div class="virtual-key scroll-key" data-scroll="-5">⇡ 滚动</div>
|
|
159
|
+
<div class="virtual-key scroll-key" data-scroll="5">⇣ 滚动</div>
|
|
154
160
|
</div>
|
|
155
161
|
</div>
|
|
156
162
|
</div>
|
|
@@ -197,6 +203,53 @@
|
|
|
197
203
|
var momentumRaf = null;
|
|
198
204
|
var velocitySamples = [];
|
|
199
205
|
|
|
206
|
+
// 未发送消息缓存
|
|
207
|
+
var currentInputBuffer = '';
|
|
208
|
+
var CACHE_KEY = 'claude_opencode_input_cache';
|
|
209
|
+
var cacheRestored = false; // 防止重复恢复
|
|
210
|
+
|
|
211
|
+
function saveInputCache() {
|
|
212
|
+
if (currentInputBuffer) {
|
|
213
|
+
localStorage.setItem(CACHE_KEY, currentInputBuffer);
|
|
214
|
+
console.log('[cache] saved:', currentInputBuffer);
|
|
215
|
+
} else {
|
|
216
|
+
localStorage.removeItem(CACHE_KEY);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function loadInputCache() {
|
|
221
|
+
// 确保只恢复一次
|
|
222
|
+
if (cacheRestored) {
|
|
223
|
+
console.log('[cache] already restored, skipping');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var cached = localStorage.getItem(CACHE_KEY);
|
|
228
|
+
if (cached && ws && ws.readyState === 1) {
|
|
229
|
+
console.log('[cache] restoring:', cached);
|
|
230
|
+
// 立即清除缓存,防止重复调用
|
|
231
|
+
localStorage.removeItem(CACHE_KEY);
|
|
232
|
+
cacheRestored = true;
|
|
233
|
+
|
|
234
|
+
// 将缓存的输入发送到终端
|
|
235
|
+
ws.send(JSON.stringify({ type: 'input', data: cached }));
|
|
236
|
+
// 重要:恢复后清空 currentInputBuffer,防止再次保存
|
|
237
|
+
currentInputBuffer = '';
|
|
238
|
+
} else {
|
|
239
|
+
console.log('[cache] no cache to restore');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function clearInputCache() {
|
|
244
|
+
currentInputBuffer = '';
|
|
245
|
+
localStorage.removeItem(CACHE_KEY);
|
|
246
|
+
console.log('[cache] cleared');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getCellDims() {
|
|
250
|
+
return term._core && term._core._renderService && term._core._renderService.dimensions && term._core._renderService.dimensions.css && term._core._renderService.dimensions.css.cell;
|
|
251
|
+
}
|
|
252
|
+
|
|
200
253
|
// 参考 cc-viewer 的 App.jsx 行 76-94: iOS visualViewport 处理
|
|
201
254
|
// iOS 虚拟键盘弹出时,Safari 会滚动整个文档将页面上推,
|
|
202
255
|
// 导致导航栏消失在视口之外。通过 visualViewport 的 resize + scroll
|
|
@@ -218,10 +271,6 @@
|
|
|
218
271
|
onVVChange();
|
|
219
272
|
}
|
|
220
273
|
|
|
221
|
-
function getCellDims() {
|
|
222
|
-
return term._core && term._core._renderService && term._core._renderService.dimensions && term._core._renderService.dimensions.css && term._core._renderService.dimensions.css.cell;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
274
|
// 参考 cc-viewer 的 TerminalPanel.jsx 行 410-447: 移动端固定尺寸计算
|
|
226
275
|
function mobileFixedResize() {
|
|
227
276
|
if (!term) return;
|
|
@@ -302,109 +351,187 @@
|
|
|
302
351
|
pixelAccum = 0;
|
|
303
352
|
}
|
|
304
353
|
|
|
305
|
-
//
|
|
354
|
+
// OpenCode 模式触摸滚动实现 - 使用 xterm.js scrollLines API
|
|
355
|
+
var touchScreen = null;
|
|
356
|
+
var touchEventsBound = false;
|
|
357
|
+
|
|
358
|
+
function getLineHeight() {
|
|
359
|
+
var cellDims = getCellDims();
|
|
360
|
+
var height = (cellDims && cellDims.height) || 15;
|
|
361
|
+
console.log('[scroll] lineHeight:', height);
|
|
362
|
+
return height;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function stopMomentum() {
|
|
366
|
+
if (momentumRaf) {
|
|
367
|
+
cancelAnimationFrame(momentumRaf);
|
|
368
|
+
momentumRaf = null;
|
|
369
|
+
}
|
|
370
|
+
if (scrollRaf) {
|
|
371
|
+
cancelAnimationFrame(scrollRaf);
|
|
372
|
+
scrollRaf = null;
|
|
373
|
+
}
|
|
374
|
+
pendingDy = 0;
|
|
375
|
+
pixelAccum = 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
306
378
|
function flushScroll() {
|
|
307
379
|
scrollRaf = null;
|
|
308
380
|
if (pendingDy === 0) return;
|
|
381
|
+
|
|
309
382
|
pixelAccum += pendingDy;
|
|
310
383
|
pendingDy = 0;
|
|
311
|
-
|
|
312
|
-
var lh = (
|
|
384
|
+
|
|
385
|
+
var lh = getLineHeight();
|
|
313
386
|
var lines = Math.trunc(pixelAccum / lh);
|
|
387
|
+
|
|
314
388
|
if (lines !== 0) {
|
|
389
|
+
console.log('[scroll] scrollLines:', lines, 'pixelAccum:', pixelAccum, 'lineHeight:', lh);
|
|
315
390
|
term.scrollLines(lines);
|
|
316
391
|
pixelAccum -= lines * lh;
|
|
317
392
|
}
|
|
318
393
|
}
|
|
319
394
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
lastY = e.touches[0].clientY;
|
|
329
|
-
lastTime = performance.now();
|
|
330
|
-
velocitySamples = [];
|
|
331
|
-
}, { passive: true });
|
|
395
|
+
function handleTouchStart(e) {
|
|
396
|
+
console.log('[scroll] touchstart');
|
|
397
|
+
stopMomentum();
|
|
398
|
+
if (e.touches.length !== 1) return;
|
|
399
|
+
lastY = e.touches[0].clientY;
|
|
400
|
+
lastTime = performance.now();
|
|
401
|
+
velocitySamples = [];
|
|
402
|
+
}
|
|
332
403
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (!scrollRaf) scrollRaf = requestAnimationFrame(flushScroll);
|
|
348
|
-
lastY = y;
|
|
349
|
-
lastTime = now;
|
|
350
|
-
}, { passive: true });
|
|
404
|
+
function handleTouchMove(e) {
|
|
405
|
+
if (e.touches.length !== 1) return;
|
|
406
|
+
var y = e.touches[0].clientY;
|
|
407
|
+
var now = performance.now();
|
|
408
|
+
var dt = now - lastTime;
|
|
409
|
+
var dy = lastY - y;
|
|
410
|
+
|
|
411
|
+
if (dt > 0) {
|
|
412
|
+
var v = dy / dt * 16;
|
|
413
|
+
velocitySamples.push({ v: v, t: now });
|
|
414
|
+
while (velocitySamples.length > 0 && now - velocitySamples[0].t > 100) {
|
|
415
|
+
velocitySamples.shift();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
351
418
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
cancelAnimationFrame(scrollRaf);
|
|
355
|
-
scrollRaf = null;
|
|
356
|
-
}
|
|
357
|
-
if (pendingDy !== 0) {
|
|
358
|
-
pixelAccum += pendingDy;
|
|
359
|
-
pendingDy = 0;
|
|
360
|
-
var cellDims = getCellDims();
|
|
361
|
-
var lh = (cellDims && cellDims.height) || 15;
|
|
362
|
-
var lines = Math.trunc(pixelAccum / lh);
|
|
363
|
-
if (lines !== 0) term.scrollLines(lines);
|
|
364
|
-
pixelAccum = 0;
|
|
365
|
-
}
|
|
419
|
+
pendingDy += dy;
|
|
420
|
+
console.log('[scroll] touchmove dy:', dy, 'pendingDy:', pendingDy);
|
|
366
421
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
422
|
+
if (!scrollRaf) scrollRaf = requestAnimationFrame(flushScroll);
|
|
423
|
+
lastY = y;
|
|
424
|
+
lastTime = now;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function handleTouchEnd() {
|
|
428
|
+
console.log('[scroll] touchend');
|
|
429
|
+
|
|
430
|
+
if (scrollRaf) {
|
|
431
|
+
cancelAnimationFrame(scrollRaf);
|
|
432
|
+
scrollRaf = null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (pendingDy !== 0) {
|
|
436
|
+
pixelAccum += pendingDy;
|
|
437
|
+
pendingDy = 0;
|
|
438
|
+
var lh = getLineHeight();
|
|
439
|
+
var lines = Math.trunc(pixelAccum / lh);
|
|
440
|
+
if (lines !== 0) {
|
|
441
|
+
console.log('[scroll] final scrollLines:', lines);
|
|
442
|
+
term.scrollLines(lines);
|
|
443
|
+
}
|
|
444
|
+
pixelAccum = 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
var velocity = 0;
|
|
448
|
+
if (velocitySamples.length >= 2) {
|
|
449
|
+
var totalWeight = 0;
|
|
450
|
+
var weightedV = 0;
|
|
451
|
+
var latest = velocitySamples[velocitySamples.length - 1].t;
|
|
452
|
+
for (var i = 0; i < velocitySamples.length; i++) {
|
|
453
|
+
var s = velocitySamples[i];
|
|
454
|
+
var w = Math.max(0, 1 - (latest - s.t) / 100);
|
|
455
|
+
weightedV += s.v * w;
|
|
456
|
+
totalWeight += w;
|
|
457
|
+
}
|
|
458
|
+
velocity = totalWeight > 0 ? weightedV / totalWeight : 0;
|
|
459
|
+
}
|
|
460
|
+
velocitySamples = [];
|
|
461
|
+
|
|
462
|
+
console.log('[scroll] velocity:', velocity);
|
|
463
|
+
if (Math.abs(velocity) < 0.5) return;
|
|
464
|
+
|
|
465
|
+
var friction = 0.95;
|
|
466
|
+
var mAccum = 0;
|
|
467
|
+
var tick = function() {
|
|
468
|
+
if (Math.abs(velocity) < 0.3) {
|
|
469
|
+
var lh = getLineHeight();
|
|
470
|
+
var rest = Math.round(mAccum / lh);
|
|
471
|
+
if (rest !== 0) {
|
|
472
|
+
console.log('[scroll] momentum final:', rest);
|
|
473
|
+
term.scrollLines(rest);
|
|
379
474
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
475
|
+
momentumRaf = null;
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
mAccum += velocity;
|
|
479
|
+
var lh = getLineHeight();
|
|
480
|
+
var lines = Math.trunc(mAccum / lh);
|
|
481
|
+
if (lines !== 0) {
|
|
482
|
+
term.scrollLines(lines);
|
|
483
|
+
mAccum -= lines * lh;
|
|
484
|
+
}
|
|
485
|
+
velocity *= friction;
|
|
486
|
+
momentumRaf = requestAnimationFrame(tick);
|
|
487
|
+
};
|
|
488
|
+
momentumRaf = requestAnimationFrame(tick);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function unbindTouchScroll() {
|
|
492
|
+
if (touchScreen && touchEventsBound) {
|
|
493
|
+
console.log('[scroll] unbinding touch events');
|
|
494
|
+
touchScreen.removeEventListener('touchstart', handleTouchStart);
|
|
495
|
+
touchScreen.removeEventListener('touchmove', handleTouchMove);
|
|
496
|
+
touchScreen.removeEventListener('touchend', handleTouchEnd);
|
|
497
|
+
touchEventsBound = false;
|
|
498
|
+
touchScreen = null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function setupMobileTouchScroll() {
|
|
503
|
+
// 先解绑旧的
|
|
504
|
+
unbindTouchScroll();
|
|
505
|
+
|
|
506
|
+
// 只在 opencode 模式下绑定
|
|
507
|
+
if (currentMode !== 'opencode') {
|
|
508
|
+
console.log('[scroll] Not opencode mode, skip binding');
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
var screen = terminalEl.querySelector('.xterm-screen');
|
|
513
|
+
if (!screen) {
|
|
514
|
+
console.log('[scroll] .xterm-screen not found, retrying...');
|
|
515
|
+
setTimeout(setupMobileTouchScroll, 50);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
touchScreen = screen;
|
|
520
|
+
screen.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
521
|
+
screen.addEventListener('touchmove', handleTouchMove, { passive: true });
|
|
522
|
+
screen.addEventListener('touchend', handleTouchEnd, { passive: true });
|
|
523
|
+
touchEventsBound = true;
|
|
524
|
+
console.log('[scroll] Touch events bound to .xterm-screen (opencode mode)');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function rebindTouchScroll() {
|
|
528
|
+
if (isMobile) {
|
|
529
|
+
setTimeout(setupMobileTouchScroll, 100);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (isMobile) {
|
|
534
|
+
setupMobileTouchScroll();
|
|
408
535
|
}
|
|
409
536
|
|
|
410
537
|
function startTransition() {
|
|
@@ -452,6 +579,8 @@
|
|
|
452
579
|
else if (msg.type === 'exit') throttledWrite('\r\n[进程已退出: ' + msg.exitCode + ']\r\n');
|
|
453
580
|
else if (msg.type === 'mode') {
|
|
454
581
|
endTransition(msg.mode);
|
|
582
|
+
// 模式切换完成后,重新绑定触摸事件
|
|
583
|
+
rebindTouchScroll();
|
|
455
584
|
}
|
|
456
585
|
else if (msg.type === 'switching') {
|
|
457
586
|
// 服务端开始切换,前端清屏
|
|
@@ -469,12 +598,49 @@
|
|
|
469
598
|
};
|
|
470
599
|
|
|
471
600
|
term.onData(function(d) {
|
|
472
|
-
if (ws && ws.readyState === 1)
|
|
601
|
+
if (ws && ws.readyState === 1) {
|
|
602
|
+
ws.send(JSON.stringify({ type: 'input', data: d }));
|
|
603
|
+
|
|
604
|
+
// 缓存管理:跟踪当前行的输入(仅在 opencode 模式且未在恢复中)
|
|
605
|
+
if (currentMode === 'opencode' && cacheRestored) {
|
|
606
|
+
if (d === '\r' || d === '\n' || d === '\r\n') {
|
|
607
|
+
// 回车:命令已发送,清除缓存
|
|
608
|
+
clearInputCache();
|
|
609
|
+
} else if (d === '\x7f' || d === '\b') {
|
|
610
|
+
// 退格:删除最后一个字符
|
|
611
|
+
if (currentInputBuffer.length > 0) {
|
|
612
|
+
currentInputBuffer = currentInputBuffer.slice(0, -1);
|
|
613
|
+
saveInputCache();
|
|
614
|
+
}
|
|
615
|
+
} else if (d === '\x03') {
|
|
616
|
+
// Ctrl+C:中断,清除缓存
|
|
617
|
+
clearInputCache();
|
|
618
|
+
} else if (d === '\x15') {
|
|
619
|
+
// Ctrl+U:清空整行
|
|
620
|
+
clearInputCache();
|
|
621
|
+
} else if (d.length === 1 && d.charCodeAt(0) >= 32 && d.charCodeAt(0) < 127) {
|
|
622
|
+
// 可打印 ASCII 字符:添加到缓冲区
|
|
623
|
+
currentInputBuffer += d;
|
|
624
|
+
saveInputCache();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
473
629
|
// 点击终端输入时,滚动到底部
|
|
474
630
|
setTimeout(function() {
|
|
475
631
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
|
476
632
|
}, 50);
|
|
477
633
|
});
|
|
634
|
+
|
|
635
|
+
// 页面加载后尝试恢复缓存的输入(仅在 opencode 模式)
|
|
636
|
+
setTimeout(function() {
|
|
637
|
+
if (currentMode === 'opencode') {
|
|
638
|
+
loadInputCache();
|
|
639
|
+
} else {
|
|
640
|
+
// 非 opencode 模式,标记为已恢复,避免后续缓存逻辑干扰
|
|
641
|
+
cacheRestored = true;
|
|
642
|
+
}
|
|
643
|
+
}, 800);
|
|
478
644
|
}
|
|
479
645
|
|
|
480
646
|
window.addEventListener('resize', resize);
|
|
@@ -484,6 +650,20 @@
|
|
|
484
650
|
});
|
|
485
651
|
}
|
|
486
652
|
|
|
653
|
+
// 页面卸载前保存输入缓存
|
|
654
|
+
window.addEventListener('beforeunload', function() {
|
|
655
|
+
if (currentInputBuffer) {
|
|
656
|
+
saveInputCache();
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// 页面可见性变化时保存缓存
|
|
661
|
+
document.addEventListener('visibilitychange', function() {
|
|
662
|
+
if (document.hidden && currentInputBuffer) {
|
|
663
|
+
saveInputCache();
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
487
667
|
// 虚拟按键映射表
|
|
488
668
|
var KEY_MAP = {
|
|
489
669
|
'up': '\x1b[A',
|
|
@@ -503,42 +683,123 @@
|
|
|
503
683
|
}
|
|
504
684
|
}
|
|
505
685
|
|
|
686
|
+
function scrollTerminal(lines) {
|
|
687
|
+
if (term) {
|
|
688
|
+
term.scrollLines(lines);
|
|
689
|
+
console.log('[scroll] scrolled by:', lines);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
506
693
|
// 参考 cc-viewer 的 TerminalPanel.jsx 行 519-546: 虚拟按键触摸处理
|
|
507
|
-
|
|
694
|
+
// 每个按键独立绑定事件,不在容器上绑定
|
|
508
695
|
var vkStartX = 0, vkStartY = 0, vkMoved = false, vkTarget = null;
|
|
696
|
+
var scrollInterval = null; // 长按滚动定时器
|
|
697
|
+
|
|
698
|
+
function setupVirtualKeyEvents() {
|
|
699
|
+
var keys = document.querySelectorAll('.virtual-key');
|
|
700
|
+
keys.forEach(function(key) {
|
|
701
|
+
// 防止元素获得焦点
|
|
702
|
+
key.setAttribute('tabindex', '-1');
|
|
703
|
+
|
|
704
|
+
// 移动端触摸事件
|
|
705
|
+
key.addEventListener('touchstart', function(e) {
|
|
706
|
+
var touch = e.touches[0];
|
|
707
|
+
vkStartX = touch.clientX;
|
|
708
|
+
vkStartY = touch.clientY;
|
|
709
|
+
vkMoved = false;
|
|
710
|
+
vkTarget = e.currentTarget;
|
|
711
|
+
vkTarget.style.background = '#333';
|
|
712
|
+
|
|
713
|
+
// 如果是滚动按钮,阻止默认行为(防止键盘弹出)并启动持续滚动
|
|
714
|
+
var scrollLines = e.currentTarget.getAttribute('data-scroll');
|
|
715
|
+
if (scrollLines) {
|
|
716
|
+
e.preventDefault(); // 关键:阻止默认行为,防止键盘弹出
|
|
717
|
+
scrollTerminal(parseInt(scrollLines, 10));
|
|
718
|
+
scrollInterval = setInterval(function() {
|
|
719
|
+
scrollTerminal(parseInt(scrollLines, 10));
|
|
720
|
+
}, 100);
|
|
721
|
+
}
|
|
722
|
+
}, { passive: false }); // 必须是 false 才能调用 preventDefault()
|
|
723
|
+
|
|
724
|
+
key.addEventListener('touchmove', function(e) {
|
|
725
|
+
if (vkMoved) return;
|
|
726
|
+
var touch = e.touches[0];
|
|
727
|
+
var dx = touch.clientX - vkStartX;
|
|
728
|
+
var dy = touch.clientY - vkStartY;
|
|
729
|
+
if (dx * dx + dy * dy > 64) { // 8px 阈值
|
|
730
|
+
vkMoved = true;
|
|
731
|
+
if (vkTarget) {
|
|
732
|
+
vkTarget.style.background = '';
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}, { passive: true });
|
|
509
736
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
vkMoved = false;
|
|
517
|
-
vkTarget = e.target;
|
|
518
|
-
vkTarget.style.background = '#333';
|
|
519
|
-
});
|
|
737
|
+
key.addEventListener('touchend', function(e) {
|
|
738
|
+
// 清除滚动定时器
|
|
739
|
+
if (scrollInterval) {
|
|
740
|
+
clearInterval(scrollInterval);
|
|
741
|
+
scrollInterval = null;
|
|
742
|
+
}
|
|
520
743
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
var dy = touch.clientY - vkStartY;
|
|
526
|
-
if (dx * dx + dy * dy > 64) {
|
|
527
|
-
vkMoved = true;
|
|
528
|
-
}
|
|
529
|
-
});
|
|
744
|
+
if (vkTarget) {
|
|
745
|
+
vkTarget.style.background = '';
|
|
746
|
+
vkTarget = null;
|
|
747
|
+
}
|
|
530
748
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
749
|
+
// 如果没有移动,触发按键功能并阻止默认行为
|
|
750
|
+
if (!vkMoved) {
|
|
751
|
+
e.preventDefault(); // 阻止默认行为
|
|
752
|
+
var scrollLines = e.currentTarget.getAttribute('data-scroll');
|
|
753
|
+
if (!scrollLines) {
|
|
754
|
+
// 非滚动按钮才触发按键
|
|
755
|
+
var keyName = e.currentTarget.getAttribute('data-key');
|
|
756
|
+
sendKey(keyName);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}, { passive: false });
|
|
760
|
+
|
|
761
|
+
// PC端点击支持
|
|
762
|
+
key.addEventListener('click', function(e) {
|
|
763
|
+
e.preventDefault();
|
|
764
|
+
var scrollLines = e.currentTarget.getAttribute('data-scroll');
|
|
765
|
+
if (scrollLines) {
|
|
766
|
+
scrollTerminal(parseInt(scrollLines, 10));
|
|
767
|
+
} else {
|
|
768
|
+
var keyName = e.currentTarget.getAttribute('data-key');
|
|
769
|
+
sendKey(keyName);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// PC端鼠标按下支持(长按滚动)
|
|
774
|
+
key.addEventListener('mousedown', function(e) {
|
|
775
|
+
e.preventDefault();
|
|
776
|
+
var scrollLines = e.currentTarget.getAttribute('data-scroll');
|
|
777
|
+
if (scrollLines) {
|
|
778
|
+
scrollTerminal(parseInt(scrollLines, 10));
|
|
779
|
+
scrollInterval = setInterval(function() {
|
|
780
|
+
scrollTerminal(parseInt(scrollLines, 10));
|
|
781
|
+
}, 100);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
key.addEventListener('mouseup', function(e) {
|
|
786
|
+
if (scrollInterval) {
|
|
787
|
+
clearInterval(scrollInterval);
|
|
788
|
+
scrollInterval = null;
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
key.addEventListener('mouseleave', function(e) {
|
|
793
|
+
if (scrollInterval) {
|
|
794
|
+
clearInterval(scrollInterval);
|
|
795
|
+
scrollInterval = null;
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// 初始化虚拟按键事件
|
|
802
|
+
setupVirtualKeyEvents();
|
|
542
803
|
|
|
543
804
|
connect();
|
|
544
805
|
setTimeout(resize, 100);
|