| |
| class KeyboardInputHandler { |
| constructor() { |
| this.onCommand = null; |
| this.commandHistory = []; |
| this.maxHistorySize = 20; |
| this.isPanelCollapsed = false; |
| this.isActive = false; |
| |
| |
| this.isDragging = false; |
| this.dragOffset = { x: 0, y: 0 }; |
| this.currentPosition = { x: null, y: null }; |
| |
| this.init(); |
| } |
| |
| init() { |
| this.setupEventListeners(); |
| this.updatePanel(); |
| this.setActiveState(false); |
| this.setPanelCollapsed(true); |
| console.log('键盘输入处理器已初始化'); |
| } |
| |
| setupEventListeners() { |
| |
| const commandInput = document.getElementById('commandInput'); |
| const sendBtn = document.getElementById('sendCommandBtn'); |
| const toggleBtn = document.getElementById('toggleKeyboardPanel'); |
| |
| if (commandInput) { |
| |
| commandInput.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter') { |
| e.preventDefault(); |
| this.sendCommand(); |
| } |
| }); |
| |
| |
| commandInput.addEventListener('keydown', (e) => { |
| if (e.key === 'ArrowUp') { |
| e.preventDefault(); |
| this.navigateHistory(-1); |
| } else if (e.key === 'ArrowDown') { |
| e.preventDefault(); |
| this.navigateHistory(1); |
| } |
| }); |
| } |
| |
| |
| if (sendBtn) { |
| sendBtn.addEventListener('click', () => { |
| this.sendCommand(); |
| }); |
| } |
| |
| |
| if (toggleBtn) { |
| toggleBtn.addEventListener('click', () => { |
| this.togglePanel(); |
| }); |
| } |
| |
| |
| const quickCmdBtns = document.querySelectorAll('.quick-cmd'); |
| quickCmdBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| const command = btn.getAttribute('data-command'); |
| if (command) { |
| this.executeCommand(command); |
| } |
| }); |
| }); |
| |
| |
| document.addEventListener('keydown', (e) => { |
| this.handleGlobalShortcuts(e); |
| }); |
| |
| |
| this.setupHistoryListener(); |
| |
| |
| this.setupDragFunctionality(); |
| } |
| |
| setupHistoryListener() { |
| const historyList = document.getElementById('commandHistory'); |
| if (historyList) { |
| historyList.addEventListener('click', (e) => { |
| if (e.target.classList.contains('history-item') || |
| e.target.parentElement.classList.contains('history-item')) { |
| |
| const historyItem = e.target.classList.contains('history-item') |
| ? e.target |
| : e.target.parentElement; |
| |
| const command = historyItem.querySelector('.history-command').textContent; |
| if (command) { |
| this.fillInputWithCommand(command); |
| } |
| } |
| }); |
| } |
| } |
| |
| handleGlobalShortcuts(e) { |
| |
| |
| |
| |
| |
| |
| |
| |
| if (!this.isActive || document.activeElement.tagName === 'INPUT') { |
| return; |
| } |
| |
| |
| if ((e.ctrlKey || e.metaKey) && e.key === 'k') { |
| e.preventDefault(); |
| this.focusInput(); |
| } |
| |
| |
| if ((e.ctrlKey || e.metaKey) && e.key === '`') { |
| e.preventDefault(); |
| this.togglePanel(); |
| } |
| |
| |
| if (e.key === 'Escape') { |
| const input = document.getElementById('commandInput'); |
| if (input && input.value) { |
| input.value = ''; |
| input.blur(); |
| } else if (this.isActive) { |
| this.setActiveState(false); |
| } else { |
| this.togglePanel(); |
| } |
| } |
| |
| |
| if (this.isActive && e.key >= '1' && e.key <= '6') { |
| const quickCommands = [ |
| 'cook cook cook pot', |
| 'stop stop stop pot', |
| 'apple', |
| 'banana', |
| 'pizza', |
| 'cake' |
| ]; |
| |
| const commandIndex = parseInt(e.key) - 1; |
| if (commandIndex < quickCommands.length) { |
| e.preventDefault(); |
| this.executeCommand(quickCommands[commandIndex]); |
| } |
| } |
| } |
| |
| sendCommand() { |
| const input = document.getElementById('commandInput'); |
| if (!input || !this.isActive) return; |
| |
| const command = input.value.trim(); |
| if (!command) return; |
| |
| this.executeCommand(command); |
| input.value = ''; |
| } |
| |
| executeCommand(command) { |
| |
| this.addToHistory(command); |
| |
| |
| console.log(`[键盘输入] 执行命令: "${command}"`); |
| |
| |
| if (this.onCommand) { |
| this.onCommand(command); |
| } |
| |
| |
| this.updatePanel(); |
| this.showCommandFeedback(command); |
| } |
| |
| addToHistory(command) { |
| |
| if (this.commandHistory.length > 0 && |
| this.commandHistory[this.commandHistory.length - 1].command === command) { |
| return; |
| } |
| |
| const historyItem = { |
| command: command, |
| timestamp: new Date().toLocaleTimeString(), |
| fullTimestamp: Date.now() |
| }; |
| |
| this.commandHistory.push(historyItem); |
| |
| |
| if (this.commandHistory.length > this.maxHistorySize) { |
| this.commandHistory.shift(); |
| } |
| |
| this.updateHistoryDisplay(); |
| } |
| |
| navigateHistory(direction) { |
| |
| const input = document.getElementById('commandInput'); |
| if (!input || this.commandHistory.length === 0) return; |
| |
| if (!this.historyIndex) { |
| this.historyIndex = this.commandHistory.length; |
| } |
| |
| this.historyIndex += direction; |
| |
| if (this.historyIndex < 0) { |
| this.historyIndex = 0; |
| } else if (this.historyIndex >= this.commandHistory.length) { |
| this.historyIndex = this.commandHistory.length; |
| input.value = ''; |
| return; |
| } |
| |
| input.value = this.commandHistory[this.historyIndex].command; |
| } |
| |
| fillInputWithCommand(command) { |
| const input = document.getElementById('commandInput'); |
| if (input) { |
| input.value = command; |
| input.focus(); |
| } |
| } |
| |
| updateHistoryDisplay() { |
| const historyList = document.getElementById('commandHistory'); |
| if (!historyList) return; |
| |
| if (this.commandHistory.length === 0) { |
| historyList.innerHTML = '<div style="color: rgba(255,255,255,0.5); font-style: italic; text-align: center; padding: 10px;">暂无命令历史</div>'; |
| return; |
| } |
| |
| const historyHTML = this.commandHistory |
| .slice(-10) |
| .reverse() |
| .map(item => ` |
| <div class="history-item" title="点击重新使用此命令"> |
| <span class="history-command">${this.escapeHtml(item.command)}</span> |
| <span class="history-time">${item.timestamp}</span> |
| </div> |
| `).join(''); |
| |
| historyList.innerHTML = historyHTML; |
| } |
| |
| showCommandFeedback(command) { |
| |
| const panel = document.getElementById('keyboardInputPanel'); |
| if (!panel) return; |
| |
| |
| panel.style.borderColor = '#ff6b6b'; |
| setTimeout(() => { |
| panel.style.borderColor = '#4ecdc4'; |
| }, 200); |
| |
| |
| const header = panel.querySelector('.input-header h4'); |
| if (header) { |
| const originalText = header.textContent; |
| header.textContent = `🎮 执行: ${command}`; |
| header.style.color = '#ff6b6b'; |
| |
| setTimeout(() => { |
| header.textContent = originalText; |
| header.style.color = 'white'; |
| }, 1500); |
| } |
| } |
| |
| togglePanel() { |
| this.setPanelCollapsed(!this.isPanelCollapsed); |
| } |
| |
| setPanelCollapsed(collapsed) { |
| const panel = document.getElementById('keyboardInputPanel'); |
| const content = document.getElementById('keyboardInputContent'); |
| const toggleBtn = document.getElementById('toggleKeyboardPanel'); |
| |
| if (!panel || !content || !toggleBtn) return; |
| |
| this.isPanelCollapsed = collapsed; |
| |
| if (this.isPanelCollapsed) { |
| |
| panel.style.display = 'none'; |
| panel.classList.add('collapsed'); |
| } else { |
| |
| panel.style.display = 'block'; |
| panel.classList.remove('collapsed'); |
| content.style.display = 'block'; |
| toggleBtn.textContent = '⌨️'; |
| toggleBtn.title = '折叠键盘输入面板'; |
| } |
| |
| console.log(`键盘输入面板${this.isPanelCollapsed ? '已隐藏' : '已显示'}`); |
| } |
| |
| focusInput() { |
| const input = document.getElementById('commandInput'); |
| if (input) { |
| |
| if (!this.isActive) { |
| this.setActiveState(true); |
| } |
| |
| |
| if (this.isPanelCollapsed) { |
| this.setPanelCollapsed(false); |
| } |
| |
| setTimeout(() => { |
| input.focus(); |
| }, 100); |
| } |
| } |
| |
| updatePanel() { |
| this.updateHistoryDisplay(); |
| |
| |
| const quickCmdBtns = document.querySelectorAll('.quick-cmd'); |
| quickCmdBtns.forEach(btn => { |
| btn.addEventListener('mouseenter', () => { |
| const command = btn.getAttribute('data-command'); |
| btn.title = `点击执行: ${command}`; |
| }); |
| }); |
| } |
| |
| |
| escapeHtml(text) { |
| const div = document.createElement('div'); |
| div.textContent = text; |
| return div.innerHTML; |
| } |
| |
| |
| setActiveState(active) { |
| console.log(`setActiveState called with: ${active}`); |
| this.isActive = active; |
| const panel = document.getElementById('keyboardInputPanel'); |
| if (!panel) { |
| console.log('keyboardInputPanel not found!'); |
| return; |
| } |
| |
| panel.classList.remove('active', 'inactive'); |
| |
| if (active) { |
| panel.classList.add('active'); |
| console.log('Panel activated - adding active class'); |
| this.showActivationIndicator('面板已激活 - 可以输入命令'); |
| } else { |
| panel.classList.add('inactive'); |
| console.log('Panel deactivated - adding inactive class'); |
| this.showActivationIndicator('面板未激活 - 按F9激活'); |
| } |
| |
| console.log(`键盘输入面板${active ? '已激活' : '已取消激活'}`); |
| console.log('Panel classes:', panel.className); |
| } |
| |
| |
| toggleActiveState() { |
| console.log('toggleActiveState called, current state:', this.isActive); |
| this.setActiveState(!this.isActive); |
| console.log('toggleActiveState completed, new state:', this.isActive); |
| } |
| |
| |
| showActivationIndicator(message) { |
| |
| const existingIndicator = document.querySelector('.activation-indicator'); |
| if (existingIndicator) { |
| existingIndicator.remove(); |
| } |
| |
| |
| const indicator = document.createElement('div'); |
| indicator.className = 'activation-indicator'; |
| indicator.textContent = message; |
| indicator.style.cssText = ` |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: ${this.isActive ? '#4ecdc4' : '#6c757d'}; |
| color: white; |
| padding: 15px 25px; |
| border-radius: 25px; |
| font-family: 'Comic Neue', cursive; |
| font-weight: bold; |
| font-size: 16px; |
| z-index: 15000; |
| box-shadow: 0 8px 32px rgba(0,0,0,0.3); |
| animation: activationPulse 0.5s ease-out; |
| `; |
| |
| document.body.appendChild(indicator); |
| |
| |
| setTimeout(() => { |
| if (indicator.parentNode) { |
| indicator.style.animation = 'activationFadeOut 0.3s ease-out forwards'; |
| setTimeout(() => { |
| if (indicator.parentNode) { |
| indicator.parentNode.removeChild(indicator); |
| } |
| }, 300); |
| } |
| }, 2000); |
| } |
| |
| |
| getStats() { |
| return { |
| totalCommands: this.commandHistory.length, |
| isCollapsed: this.isPanelCollapsed, |
| isActive: this.isActive, |
| lastCommand: this.commandHistory.length > 0 |
| ? this.commandHistory[this.commandHistory.length - 1].command |
| : null |
| }; |
| } |
| |
| |
| clearHistory() { |
| this.commandHistory = []; |
| this.updateHistoryDisplay(); |
| console.log('命令历史已清空'); |
| } |
| |
| |
| exportHistory() { |
| const data = { |
| exported: new Date().toISOString(), |
| commands: this.commandHistory |
| }; |
| |
| const blob = new Blob([JSON.stringify(data, null, 2)], |
| { type: 'application/json' }); |
| const url = URL.createObjectURL(blob); |
| |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `magicpot-commands-${Date.now()}.json`; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| |
| console.log('命令历史已导出'); |
| } |
| |
| |
| getCommandSuggestions(input) { |
| const suggestions = [ |
| 'cook cook cook pot', |
| 'stop stop stop pot', |
| 'apple', 'banana', 'orange', 'strawberry', 'watermelon', 'grape', |
| 'pizza', 'burger', 'bread', 'rice', 'noodles', 'cake', 'cookie', |
| 'cheese', 'fish', 'chicken', 'carrot', 'tomato', 'corn', 'broccoli' |
| ]; |
| |
| if (!input) return suggestions.slice(0, 5); |
| |
| const filtered = suggestions.filter(cmd => |
| cmd.toLowerCase().includes(input.toLowerCase()) |
| ); |
| |
| return filtered.slice(0, 8); |
| } |
| |
| |
| setupDragFunctionality() { |
| const panel = document.getElementById('keyboardInputPanel'); |
| const header = panel?.querySelector('.input-header'); |
| |
| if (!panel || !header) return; |
| |
| |
| header.addEventListener('mousedown', (e) => { |
| if (!this.isActive) return; |
| |
| this.isDragging = true; |
| panel.classList.add('dragging'); |
| |
| const rect = panel.getBoundingClientRect(); |
| this.dragOffset.x = e.clientX - rect.left; |
| this.dragOffset.y = e.clientY - rect.top; |
| |
| |
| this.currentPosition.x = rect.left; |
| this.currentPosition.y = rect.top; |
| |
| e.preventDefault(); |
| }); |
| |
| |
| document.addEventListener('mousemove', (e) => { |
| if (!this.isDragging) return; |
| |
| e.preventDefault(); |
| |
| |
| let newX = e.clientX - this.dragOffset.x; |
| let newY = e.clientY - this.dragOffset.y; |
| |
| |
| const rect = panel.getBoundingClientRect(); |
| const maxX = window.innerWidth - rect.width; |
| const maxY = window.innerHeight - rect.height; |
| |
| newX = Math.max(0, Math.min(newX, maxX)); |
| newY = Math.max(0, Math.min(newY, maxY)); |
| |
| |
| panel.style.left = newX + 'px'; |
| panel.style.top = newY + 'px'; |
| panel.style.right = 'auto'; |
| panel.style.bottom = 'auto'; |
| |
| this.currentPosition.x = newX; |
| this.currentPosition.y = newY; |
| }); |
| |
| |
| document.addEventListener('mouseup', (e) => { |
| if (!this.isDragging) return; |
| |
| this.isDragging = false; |
| panel.classList.remove('dragging'); |
| |
| |
| this.savePosition(); |
| }); |
| |
| |
| this.loadPosition(); |
| } |
| |
| |
| savePosition() { |
| if (this.currentPosition.x !== null && this.currentPosition.y !== null) { |
| const position = { |
| x: this.currentPosition.x, |
| y: this.currentPosition.y |
| }; |
| localStorage.setItem('keyboardPanelPosition', JSON.stringify(position)); |
| } |
| } |
| |
| |
| loadPosition() { |
| const panel = document.getElementById('keyboardInputPanel'); |
| if (!panel) return; |
| |
| try { |
| const savedPosition = localStorage.getItem('keyboardPanelPosition'); |
| if (savedPosition) { |
| const position = JSON.parse(savedPosition); |
| |
| |
| const rect = panel.getBoundingClientRect(); |
| const maxX = window.innerWidth - rect.width; |
| const maxY = window.innerHeight - rect.height; |
| |
| if (position.x >= 0 && position.x <= maxX && |
| position.y >= 0 && position.y <= maxY) { |
| |
| panel.style.left = position.x + 'px'; |
| panel.style.top = position.y + 'px'; |
| panel.style.right = 'auto'; |
| panel.style.bottom = 'auto'; |
| |
| this.currentPosition.x = position.x; |
| this.currentPosition.y = position.y; |
| } |
| } |
| } catch (error) { |
| console.warn('加载面板位置失败:', error); |
| } |
| } |
| |
| |
| resetPosition() { |
| const panel = document.getElementById('keyboardInputPanel'); |
| if (!panel) return; |
| |
| panel.style.left = 'auto'; |
| panel.style.top = 'auto'; |
| panel.style.right = '20px'; |
| panel.style.bottom = '20px'; |
| |
| this.currentPosition.x = null; |
| this.currentPosition.y = null; |
| |
| |
| localStorage.removeItem('keyboardPanelPosition'); |
| |
| console.log('面板位置已重置到右下角'); |
| } |
| } |
|
|
| |
| const keyboardStyle = document.createElement('style'); |
| keyboardStyle.textContent = ` |
| .keyboard-shortcuts-hint { |
| position: absolute; |
| bottom: 5px; |
| left: 50%; |
| transform: translateX(-50%); |
| background: rgba(0, 0, 0, 0.8); |
| color: white; |
| padding: 5px 10px; |
| border-radius: 10px; |
| font-size: 0.7em; |
| white-space: nowrap; |
| opacity: 0; |
| transition: opacity 0.3s ease; |
| pointer-events: none; |
| font-family: 'Comic Neue', cursive; |
| } |
| |
| .keyboard-input-panel:hover .keyboard-shortcuts-hint { |
| opacity: 1; |
| } |
| |
| .command-input:focus + .keyboard-shortcuts-hint { |
| opacity: 1; |
| } |
| |
| @keyframes activationPulse { |
| 0% { |
| opacity: 0; |
| transform: translate(-50%, -50%) scale(0.8); |
| } |
| 50% { |
| transform: translate(-50%, -50%) scale(1.1); |
| } |
| 100% { |
| opacity: 1; |
| transform: translate(-50%, -50%) scale(1); |
| } |
| } |
| |
| @keyframes activationFadeOut { |
| from { |
| opacity: 1; |
| transform: translate(-50%, -50%) scale(1); |
| } |
| to { |
| opacity: 0; |
| transform: translate(-50%, -50%) scale(0.9); |
| } |
| } |
| `; |
| document.head.appendChild(keyboardStyle); |
|
|