diff --git a/index.h b/index.h index 8b2d699..5ae6e0c 100644 --- a/index.h +++ b/index.h @@ -238,6 +238,7 @@ const char index_html[] PROGMEM = R"rawliteral( +
@@ -274,21 +275,40 @@ const char index_html[] PROGMEM = R"rawliteral( let canMessages = {}; let messageOrder = []; - // CAN 속도 설정 저장 및 복원 + // CAN 속도 이름 매핑 + const speedNames = ['125 Kbps', '250 Kbps', '500 Kbps', '1 Mbps']; + + // CAN 속도 설정 저장 function saveCanSpeed() { const speed = document.getElementById('can-speed').value; try { localStorage.setItem('canSpeed', speed); - } catch(e) {} + console.log('Saved CAN speed:', speedNames[speed]); + } catch(e) { + console.error('Failed to save CAN speed:', e); + } } + // CAN 속도 설정 복원 function loadCanSpeed() { try { const savedSpeed = localStorage.getItem('canSpeed'); if (savedSpeed !== null) { document.getElementById('can-speed').value = savedSpeed; + console.log('Restored CAN speed:', speedNames[savedSpeed]); + + // 복원되었음을 표시 + const statusSpan = document.getElementById('speed-status'); + if (statusSpan) { + statusSpan.textContent = '(Restored: ' + speedNames[savedSpeed] + ')'; + setTimeout(() => { + statusSpan.textContent = ''; + }, 3000); + } } - } catch(e) {} + } catch(e) { + console.error('Failed to load CAN speed:', e); + } } function initWebSocket() { @@ -461,7 +481,25 @@ const char index_html[] PROGMEM = R"rawliteral( function setCanSpeed() { const speed = document.getElementById('can-speed').value; + const speedName = speedNames[speed]; + ws.send(JSON.stringify({cmd: 'setSpeed', speed: parseInt(speed)})); + + // 설정 저장 + saveCanSpeed(); + + // 적용 완료 표시 + const statusSpan = document.getElementById('speed-status'); + if (statusSpan) { + statusSpan.textContent = '✓ Applied: ' + speedName; + statusSpan.style.color = '#11998e'; + + setTimeout(() => { + statusSpan.textContent = ''; + }, 3000); + } + + console.log('CAN speed set to:', speedName); } function refreshFiles() { @@ -480,6 +518,11 @@ const char index_html[] PROGMEM = R"rawliteral( window.location.href = '/download?file=' + encodeURIComponent(filename); } + // 페이지 로드 시 저장된 CAN speed 복원 + window.addEventListener('load', function() { + loadCanSpeed(); + }); + initWebSocket(); setTimeout(() => { refreshFiles(); }, 2000); diff --git a/transmit.h b/transmit.h index c8600bd..d765020 100644 --- a/transmit.h +++ b/transmit.h @@ -119,6 +119,10 @@ const char transmit_html[] PROGMEM = R"rawliteral( background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); color: white; } + .btn-warning { + background: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%); + color: white; + } .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.3); } .message-list { @@ -162,10 +166,6 @@ const char transmit_html[] PROGMEM = R"rawliteral( font-size: 0.85em; word-break: break-all; } - .message-interval { - color: #888; - font-size: 0.8em; - } .message-controls { display: flex; gap: 5px; @@ -197,6 +197,63 @@ const char transmit_html[] PROGMEM = R"rawliteral( } .status-bar span { font-weight: 600; } + .preset-manager { + background: #fff3cd; + padding: 15px; + border-radius: 10px; + margin-bottom: 15px; + border-left: 4px solid #f2994a; + } + .preset-controls { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; + margin-bottom: 10px; + } + .preset-controls input { + flex: 1; + min-width: 200px; + padding: 8px; + border: 2px solid #f2994a; + border-radius: 5px; + font-size: 0.9em; + } + .preset-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 10px; + margin-top: 10px; + } + .preset-item { + background: white; + padding: 10px; + border-radius: 5px; + border: 2px solid #f2c94c; + display: flex; + justify-content: space-between; + align-items: center; + transition: all 0.3s; + } + .preset-item:hover { + transform: translateX(5px); + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + } + .preset-name { + font-weight: 600; + color: #333; + font-size: 0.9em; + } + .preset-info { + font-size: 0.75em; + color: #666; + margin-top: 3px; + } + .preset-buttons { + display: flex; + gap: 5px; + } + @media (max-width: 768px) { body { padding: 5px; } .header h1 { font-size: 1.5em; } @@ -218,17 +275,6 @@ const char transmit_html[] PROGMEM = R"rawliteral( .message-controls { justify-content: flex-start; } - .control-row { - flex-direction: column; - align-items: stretch; - } - .control-row > * { - width: 100%; - } - .nav { - padding: 8px; - gap: 5px; - } .nav a { padding: 8px 10px; font-size: 0.85em; @@ -241,6 +287,9 @@ const char transmit_html[] PROGMEM = R"rawliteral( gap: 8px; text-align: center; } + .preset-list { + grid-template-columns: 1fr; + } } @@ -263,6 +312,17 @@ const char transmit_html[] PROGMEM = R"rawliteral( Sent: 0
+

Message List Presets

+
+
+ + +
+
+

No saved presets

+
+
+

Add CAN Message

@@ -358,6 +418,127 @@ const char transmit_html[] PROGMEM = R"rawliteral( }; } + // 프리셋 관리 + function savePreset() { + const presetName = document.getElementById('preset-name').value.trim(); + + if (!presetName) { + alert('Please enter a preset name!'); + return; + } + + if (messages.length === 0) { + alert('No messages to save!'); + return; + } + + try { + let presets = JSON.parse(localStorage.getItem('tx_presets') || '{}'); + + if (presets[presetName] && !confirm('Preset "' + presetName + '" already exists. Overwrite?')) { + return; + } + + presets[presetName] = { + messages: JSON.parse(JSON.stringify(messages)), + savedAt: new Date().toISOString(), + count: messages.length + }; + + localStorage.setItem('tx_presets', JSON.stringify(presets)); + + document.getElementById('preset-name').value = ''; + loadPresetList(); + + alert('Preset "' + presetName + '" saved successfully!'); + } catch(e) { + console.error('Failed to save preset:', e); + alert('Failed to save preset!'); + } + } + + function loadPreset(presetName) { + try { + const presets = JSON.parse(localStorage.getItem('tx_presets') || '{}'); + const preset = presets[presetName]; + + if (!preset) { + alert('Preset not found!'); + return; + } + + if (messages.length > 0 && !confirm('Current message list will be replaced. Continue?')) { + return; + } + + stopAll(); + messages = JSON.parse(JSON.stringify(preset.messages)); + messages.forEach(msg => msg.active = false); + + updateMessageList(); + alert('Loaded preset "' + presetName + '" with ' + preset.count + ' messages'); + } catch(e) { + console.error('Failed to load preset:', e); + alert('Failed to load preset!'); + } + } + + function deletePreset(presetName) { + if (!confirm('Delete preset "' + presetName + '"?')) { + return; + } + + try { + let presets = JSON.parse(localStorage.getItem('tx_presets') || '{}'); + delete presets[presetName]; + localStorage.setItem('tx_presets', JSON.stringify(presets)); + loadPresetList(); + } catch(e) { + console.error('Failed to delete preset:', e); + alert('Failed to delete preset!'); + } + } + + function loadPresetList() { + const presetListDiv = document.getElementById('preset-list'); + + try { + const presets = JSON.parse(localStorage.getItem('tx_presets') || '{}'); + const presetNames = Object.keys(presets); + + if (presetNames.length === 0) { + presetListDiv.innerHTML = '

No saved presets

'; + return; + } + + presetListDiv.innerHTML = ''; + + presetNames.sort().forEach(name => { + const preset = presets[name]; + const item = document.createElement('div'); + item.className = 'preset-item'; + + const savedDate = new Date(preset.savedAt); + const dateStr = savedDate.toLocaleDateString() + ' ' + savedDate.toLocaleTimeString(); + + item.innerHTML = + '
' + + '
' + name + '
' + + '
' + preset.count + ' messages | ' + dateStr + '
' + + '
' + + '
' + + '' + + '' + + '
'; + + presetListDiv.appendChild(item); + }); + } catch(e) { + console.error('Failed to load preset list:', e); + presetListDiv.innerHTML = '

Error loading presets

'; + } + } + function addMessage() { const id = document.getElementById('can-id').value.toUpperCase(); const type = document.getElementById('msg-type').value; @@ -501,6 +682,11 @@ const char transmit_html[] PROGMEM = R"rawliteral( this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, ''); }); + // 페이지 로드 시 프리셋 목록 불러오기 + window.addEventListener('load', function() { + loadPresetList(); + }); + initWebSocket();