interactions.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. (function () {
  2. 'use strict';
  3. const petEl = document.getElementById('pet');
  4. const chest = document.getElementById('chest');
  5. const ctxMenu = document.getElementById('context-menu');
  6. const invoke = window.__TAURI__?.core?.invoke;
  7. let dragStartX = 0, dragStartY = 0;
  8. let pressTimer = null;
  9. let isDragging = false;
  10. const CHEST_HOLD_MS = 5000;
  11. let chestTimer = null;
  12. // ── 右键菜单 ──────────────────────────────
  13. petEl.addEventListener('contextmenu', function (e) {
  14. e.preventDefault();
  15. const x = Math.min(e.clientX, window.innerWidth - 130);
  16. const y = Math.min(e.clientY, window.innerHeight - 100);
  17. ctxMenu.style.left = x + 'px';
  18. ctxMenu.style.top = y + 'px';
  19. ctxMenu.style.display = 'block';
  20. });
  21. document.addEventListener('click', function (e) {
  22. if (!ctxMenu.contains(e.target)) ctxMenu.style.display = 'none';
  23. });
  24. document.addEventListener('keydown', function (e) {
  25. if (e.key === 'Escape') ctxMenu.style.display = 'none';
  26. });
  27. ctxMenu.addEventListener('click', function (e) {
  28. const item = e.target.closest('.item');
  29. if (!item) return;
  30. const action = item.dataset.action;
  31. ctxMenu.style.display = 'none';
  32. if (action === 'settings') invoke?.('open_settings');
  33. else if (action === 'quit') { try { invoke?.('quit_app'); } catch (_) { window.close(); } }
  34. });
  35. // ── 点击 / 长按 / 拖拽 ───────────────────
  36. petEl.addEventListener('mousedown', function (e) {
  37. if (window.__petPerched) return; // 站手跟随中:窗口由 Rust 驱动,禁用原生拖拽
  38. dragStartX = e.clientX;
  39. dragStartY = e.clientY;
  40. isDragging = false;
  41. pressTimer = setTimeout(() => {
  42. localReact('pet');
  43. if (backendEnabled()) notifyInteract('pet', {});
  44. }, 1500);
  45. });
  46. petEl.addEventListener('mousemove', function (e) {
  47. const dx = e.clientX - dragStartX;
  48. const dy = e.clientY - dragStartY;
  49. if (Math.abs(dx) > 4 || Math.abs(dy) > 4) {
  50. isDragging = true;
  51. clearTimeout(pressTimer);
  52. notifyDragStart();
  53. }
  54. });
  55. petEl.addEventListener('mouseup', function (e) {
  56. clearTimeout(pressTimer);
  57. if (!isDragging) {
  58. localReact('click');
  59. // 未登录 / 离线时不打后端,避免只剩「打卡失败」噪音,仅保留本地互动反馈。
  60. if (backendEnabled()) notifyInteract('click', {});
  61. } else {
  62. notifyDragEnd();
  63. }
  64. });
  65. // ── 胸部悬停 5s → 站手跟随 ────────────────
  66. if (chest) {
  67. chest.addEventListener('mouseenter', function () {
  68. if (window.__petPerched) return;
  69. clearChestTimer();
  70. chestTimer = setTimeout(function () {
  71. chestTimer = null;
  72. if (window.__petPerched) return;
  73. window.__petPerched = true; // 乐观置位;Rust 会回发 pet://perch 确认
  74. try { window.cfcHost.startPerch(); } catch (_) { /* noop */ }
  75. try { window.cfcPetAPI.showBubble('飞到你手上~', 2000); } catch (_) { /* noop */ }
  76. }, CHEST_HOLD_MS);
  77. });
  78. // 悬停未满 5s 就移开 → 取消;站手后窗口跟随光标会使指针移出胸部,此处仅为清理(无害)
  79. chest.addEventListener('mouseleave', clearChestTimer);
  80. }
  81. function clearChestTimer() {
  82. if (chestTimer) { clearTimeout(chestTimer); chestTimer = null; }
  83. }
  84. // 本地即时反馈:不依赖后端也能看到宠物对点击的响应。
  85. function localReact(kind) {
  86. try { if (window.cfcPetAPI && window.cfcPetAPI.react) window.cfcPetAPI.react(kind); } catch (_) { /* noop */ }
  87. }
  88. // 是否已连后端(有令牌)。默认允许,bridge 拉到设置后按 hasToken 修正。
  89. function backendEnabled() {
  90. return window.__petHasToken !== false;
  91. }
  92. function notifyInteract(action, data) {
  93. try {
  94. window.cfcHost.onInteract(action, JSON.stringify(data));
  95. } catch (err) { console.log('[interaction]', action, data); }
  96. }
  97. function notifyDragStart() {
  98. try { window.cfcHost.onDragStart(); } catch (e) { /* noop */ }
  99. }
  100. function notifyDragEnd() {
  101. try { window.cfcHost.onDragEnd(); } catch (e) { /* noop */ }
  102. }
  103. })();