diff --git a/webroot/css/ux-enhancements.css b/webroot/css/ux-enhancements.css new file mode 100644 index 0000000..c9bdd03 --- /dev/null +++ b/webroot/css/ux-enhancements.css @@ -0,0 +1,184 @@ +/* ============================================ + UX ENHANCEMENTS STYLES - Optimized + ============================================ */ + +/* Keyboard Shortcuts Hint */ +.keyboard-hint { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1000; + background: #2a2a2a; + border: 1px solid #4a4a4a; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); +} + +.hint-toggle { + display: block; + padding: 10px 15px; + cursor: pointer; + font-size: 24px; + background: none; + border: none; + transition: transform 0.2s; +} + +.hint-toggle:hover { transform: scale(1.1); } + +.hint-content { + padding: 15px; + max-width: 300px; +} + +.hint-content h4 { + margin: 0 0 10px 0; + color: #4CAF50; + font-size: 14px; +} + +.hint-content ul { + list-style: none; + padding: 0; + margin: 0; +} + +.hint-content li { + padding: 5px 0; + font-size: 12px; + color: #cccccc; +} + +.hint-content kbd { + background: #1a1a1a; + border: 1px solid #555; + border-radius: 3px; + padding: 2px 6px; + font-family: 'Consolas', monospace; + font-size: 11px; + color: #ffffff; + margin-right: 8px; +} + +/* Compact Mode Toggle Button */ +.compact-toggle { + background: #2a2a2a; + border: 1px solid #4a4a4a; + border-radius: 4px; + color: #cccccc; + padding: 8px 15px; + cursor: pointer; + font-size: 12px; + transition: all 0.2s; +} + +.compact-toggle:hover { + background: #3a3a3a; + border-color: #666666; + color: #ffffff; +} + +/* Compact Mode Styles - Condensed */ +body.compact-mode .section { padding: 10px; } +body.compact-mode .section h2 { font-size: 14px; margin-bottom: 8px; } +body.compact-mode .input-group { margin-bottom: 8px; } +body.compact-mode .input-group label { font-size: 11px; } +body.compact-mode .input-group input { padding: 4px 8px; font-size: 12px; } +body.compact-mode .result-item { padding: 6px 10px; } +body.compact-mode .result-label, +body.compact-mode .result-value { font-size: 11px; } +body.compact-mode .hex-calculator { display: none; } +body.compact-mode .tab { padding: 6px 12px; font-size: 12px; } +body.compact-mode .formula-item { padding: 4px 8px; font-size: 11px; } + +/* Context Menu - Condensed */ +.context-menu { + position: fixed; + background: #2a2a2a; + border: 1px solid #4a4a4a; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + z-index: 10000; + min-width: 250px; + padding: 4px 0; +} + +.context-menu-header { + padding: 8px 12px; + font-size: 11px; + color: #888888; + font-weight: bold; + border-bottom: 1px solid #3a3a3a; + text-transform: uppercase; +} + +.context-menu-item { + display: flex; + justify-content: space-between; + padding: 8px 12px; + cursor: pointer; + transition: background-color 0.2s; + font-size: 12px; +} + +.context-menu-item:hover { background: #3a3a3a; } + +.format-label { color: #888888; font-weight: bold; margin-right: 15px; } +.format-value { color: #4CAF50; font-family: 'Consolas', monospace; font-weight: bold; } + +/* Notification System - Condensed */ +.notification { + position: fixed; + top: 20px; + right: 20px; + padding: 12px 20px; + background: #2a2a2a; + border: 1px solid #4a4a4a; + border-radius: 6px; + color: #ffffff; + font-size: 13px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + z-index: 10001; + opacity: 0; + transform: translateX(400px); + transition: all 0.3s ease; +} + +.notification.show { opacity: 1; transform: translateX(0); } +.notification-success { border-left: 4px solid #4CAF50; } +.notification-info { border-left: 4px solid #2196F3; } +.notification-warning { border-left: 4px solid #FF9800; } +.notification-error { border-left: 4px solid #F44336; } + +/* Hover effects & Responsive - Condensed */ +.result-value, .conv-value { cursor: pointer; transition: background-color 0.3s; } +.result-value:hover, .conv-value:hover { background-color: rgba(76, 175, 80, 0.1); } + +button:focus-visible, +input:focus-visible, +.tab:focus-visible { outline: 2px solid #4CAF50; outline-offset: 2px; } + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* Responsive */ +@media (max-width: 768px) { + .keyboard-hint, .notification { bottom: 10px; right: 10px; } + .notification { top: 10px; max-width: calc(100% - 20px); } +} + +@media print { + .keyboard-hint, + .compact-toggle, + .context-menu, + .notification { display: none !important; } +} diff --git a/webroot/index.html b/webroot/index.html index aabaf29..a0d31fc 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -7,6 +7,7 @@ Dateisystem-Offset-Rechner + diff --git a/webroot/js/filesystems/exfat.js b/webroot/js/filesystems/exfat.js index 9a425e1..549e998 100644 --- a/webroot/js/filesystems/exfat.js +++ b/webroot/js/filesystems/exfat.js @@ -62,26 +62,10 @@ export class ExFATFilesystem extends BaseFilesystem { calculate(variantId) { if (variantId !== 'exfat') return; - - const values = {}; + + const values = this.getInputValues(variantId); const results = {}; - - // Gather all input values for this variant - const variant = this.variants.find(v => v.id === variantId); - if (!variant) return; - - [...variant.constants, ...variant.inputs].forEach(field => { - const el = document.getElementById(field.id); - if (el) { - let val = el.value; - if (val && val.startsWith('0x')) { - values[field.id] = parseInt(val, 16); - } else { - values[field.id] = Number(val) || 0; - } - } - }); - + this.calculateExFAT(values, results); } diff --git a/webroot/js/filesystems/fat.js b/webroot/js/filesystems/fat.js index d9ad53c..b0e6db8 100644 --- a/webroot/js/filesystems/fat.js +++ b/webroot/js/filesystems/fat.js @@ -8,22 +8,10 @@ import { checkDependencies, updateResultItem } from '../utils.js'; export class FAT12_16Filesystem extends BaseFilesystem { calculate(variantId) { if (variantId !== 'fat1216') return; - const values = {}; + + const values = this.getInputValues(variantId); const results = {}; - // Gather all input values for this variant - const variant = this.variants.find(v => v.id === variantId); - if (!variant) return; - [...variant.constants, ...variant.inputs].forEach(field => { - const el = document.getElementById(field.id); - if (el) { - let val = el.value; - if (val && val.startsWith('0x')) { - values[field.id] = parseInt(val, 16); - } else { - values[field.id] = Number(val) || 0; - } - } - }); + this.calculateFAT1216(values, results); } constructor() { @@ -211,22 +199,10 @@ export class FAT12_16Filesystem extends BaseFilesystem { export class FAT32Filesystem extends BaseFilesystem { calculate(variantId) { if (variantId !== 'fat32') return; - const values = {}; + + const values = this.getInputValues(variantId); const results = {}; - // Gather all input values for this variant - const variant = this.variants.find(v => v.id === variantId); - if (!variant) return; - [...variant.constants, ...variant.inputs].forEach(field => { - const el = document.getElementById(field.id); - if (el) { - let val = el.value; - if (val && val.startsWith('0x')) { - values[field.id] = parseInt(val, 16); - } else { - values[field.id] = Number(val) || 0; - } - } - }); + this.calculateFAT32(values, results); } constructor() { diff --git a/webroot/js/main.js b/webroot/js/main.js index 17ce81a..4fb4e98 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -5,6 +5,7 @@ import { Calculator } from './calculator.js'; import { FAT12_16Filesystem, FAT32Filesystem } from './filesystems/fat.js'; import { NTFSFilesystem } from './filesystems/ntfs.js'; import { ExFATFilesystem } from './filesystems/exfat.js'; +import { initializeUXEnhancements } from './ux-enhancements.js'; class FilesystemCalculator { constructor() { @@ -87,6 +88,9 @@ class FilesystemCalculator { setupTooltips(); setupCopyButtons(); + // Initialize UX enhancements + initializeUXEnhancements(); + window.copyToClipboard = (elementId) => { import('./utils.js').then(module => { module.copyToClipboard(elementId); diff --git a/webroot/js/ux-enhancements.js b/webroot/js/ux-enhancements.js new file mode 100644 index 0000000..fe7dc22 --- /dev/null +++ b/webroot/js/ux-enhancements.js @@ -0,0 +1,340 @@ +// Enhanced UX utilities for hex editor companion workflow + +import { copyToClipboard as originalCopyToClipboard, formatHex } from './utils.js'; + +// ============================================ +// 1. KEYBOARD SHORTCUTS +// ============================================ + +export function setupKeyboardShortcuts() { + document.addEventListener('keydown', (e) => { + // Ctrl+1-9: Switch tabs + if (e.ctrlKey && !e.shiftKey && !e.altKey) { + const num = parseInt(e.key); + if (num >= 1 && num <= 9) { + const tabs = document.querySelectorAll('.tab'); + if (tabs[num - 1]) { + e.preventDefault(); + tabs[num - 1].click(); + } + } + } + + // Ctrl+K: Focus calculator + if (e.ctrlKey && e.key === 'k') { + e.preventDefault(); + const calcInput = document.querySelector('.tab-content.active .calc-input-field'); + if (calcInput) calcInput.focus(); + } + + // Ctrl+L: Focus first input field + if (e.ctrlKey && e.key === 'l') { + e.preventDefault(); + const firstInput = document.querySelector('.tab-content.active .input-group input'); + if (firstInput) firstInput.focus(); + } + + // Alt+C: Clear all inputs in active tab + if (e.altKey && e.key === 'c') { + e.preventDefault(); + const inputs = document.querySelectorAll('.tab-content.active .input-group input'); + inputs.forEach(input => { + if (!input.id.toLowerCase().includes('size') && !input.id.toLowerCase().includes('offset')) { + input.value = ''; + } + }); + // Trigger recalculation + if (inputs.length > 0) { + inputs[0].dispatchEvent(new Event('input', { bubbles: true })); + } + } + + // Ctrl+Shift+C: Copy all visible results + if (e.ctrlKey && e.shiftKey && e.key === 'C') { + e.preventDefault(); + copyAllResults(); + } + }); + + // Show keyboard shortcuts hint + showShortcutsHint(); +} + +function showShortcutsHint() { + // Create hint element if it doesn't exist + if (document.getElementById('keyboard-hint')) return; + + const hint = document.createElement('div'); + hint.id = 'keyboard-hint'; + hint.className = 'keyboard-hint'; + hint.innerHTML = ` + ⌨️ + + `; + document.body.appendChild(hint); + + // Toggle hint visibility + hint.querySelector('.hint-toggle').addEventListener('click', () => { + const content = hint.querySelector('.hint-content'); + content.style.display = content.style.display === 'none' ? 'block' : 'none'; + }); +} + +function copyAllResults() { + const results = []; + const activeTab = document.querySelector('.tab-content.active'); + if (!activeTab) return; + + const resultItems = activeTab.querySelectorAll('.result-item.available'); + resultItems.forEach(item => { + const label = item.querySelector('.result-label')?.textContent.trim(); + const value = item.querySelector('.result-value')?.textContent.trim(); + if (label && value && value !== '-') { + results.push(`${label}: ${value}`); + } + }); + + if (results.length > 0) { + const text = results.join('\n'); + navigator.clipboard.writeText(text).then(() => { + showNotification('Alle Ergebnisse kopiert!', 'success'); + }); + } +} + +// ============================================ +// 2. DOUBLE-CLICK TO COPY +// ============================================ + +export function setupDoubleClickCopy() { + document.addEventListener('dblclick', (e) => { + // Check if double-clicked on a result value or any hex value + const target = e.target; + + if (target.classList.contains('result-value') || + target.classList.contains('conv-value') || + target.closest('.result-value')) { + + const element = target.classList.contains('result-value') ? target : target.closest('.result-value'); + const text = element.textContent.trim(); + + // Extract hex value + const hexMatch = text.match(/0x[0-9a-fA-F]+/); + if (hexMatch) { + navigator.clipboard.writeText(hexMatch[0]).then(() => { + highlightElement(element); + showNotification('Kopiert: ' + hexMatch[0], 'success'); + }); + } + } + }); +} + +function highlightElement(element) { + element.style.transition = 'background-color 0.3s'; + element.style.backgroundColor = '#4CAF50'; + setTimeout(() => { + element.style.backgroundColor = ''; + }, 300); +} + +// ============================================ +// 3. COMPACT MODE TOGGLE +// ============================================ + +export function setupCompactMode() { + const toggle = document.createElement('button'); + toggle.id = 'compact-mode-toggle'; + toggle.className = 'compact-toggle'; + toggle.innerHTML = '⚡ Kompakt'; + toggle.title = 'Kompaktansicht umschalten'; + + // Insert toggle button in header + const header = document.querySelector('.header-content .nav'); + if (header) { + const toggleContainer = document.createElement('div'); + toggleContainer.appendChild(toggle); + header.appendChild(toggleContainer); + } + + // Load saved preference + const isCompact = localStorage.getItem('compactMode') === 'true'; + if (isCompact) { + document.body.classList.add('compact-mode'); + toggle.innerHTML = '🔍 Normal'; + } + + toggle.addEventListener('click', () => { + document.body.classList.toggle('compact-mode'); + const isNowCompact = document.body.classList.contains('compact-mode'); + toggle.innerHTML = isNowCompact ? '🔍 Normal' : '⚡ Kompakt'; + localStorage.setItem('compactMode', isNowCompact); + showNotification(isNowCompact ? 'Kompaktmodus aktiviert' : 'Normalmodus aktiviert', 'info'); + }); +} + +// ============================================ +// 4. AUTO-CALCULATE ON PASTE +// ============================================ + +export function setupAutoCalculateOnPaste() { + document.addEventListener('paste', (e) => { + const target = e.target; + + // Only handle paste in input fields + if (target.tagName !== 'INPUT' || !target.closest('.input-group')) { + return; + } + + setTimeout(() => { + // Trigger calculation after paste + target.dispatchEvent(new Event('input', { bubbles: true })); + showNotification('Wert eingefügt und berechnet', 'info'); + }, 10); + }); +} + +// ============================================ +// 5. ENHANCED COPY WITH FORMAT OPTIONS +// ============================================ + +export function setupContextMenu() { + let contextMenu = null; + + document.addEventListener('contextmenu', (e) => { + const target = e.target; + + if (target.classList.contains('result-value') || target.classList.contains('conv-value')) { + e.preventDefault(); + + const text = target.textContent.trim(); + const hexMatch = text.match(/0x[0-9a-fA-F]+/); + + if (hexMatch) { + const hexValue = hexMatch[0]; + const decValue = parseInt(hexValue, 16); + + showContextMenu(e.pageX, e.pageY, hexValue, decValue); + } + } + }); + + // Close menu on click outside + document.addEventListener('click', () => { + if (contextMenu) { + contextMenu.remove(); + contextMenu = null; + } + }); + + function showContextMenu(x, y, hexValue, decValue) { + // Remove existing menu + if (contextMenu) contextMenu.remove(); + + contextMenu = document.createElement('div'); + contextMenu.className = 'context-menu'; + contextMenu.style.left = x + 'px'; + contextMenu.style.top = y + 'px'; + + const hexNoPrefix = hexValue.substring(2); + const binary = '0b' + decValue.toString(2); + const littleEndian = toLittleEndianBytes(decValue); + + contextMenu.innerHTML = ` +
Kopieren als:
+
+ Hex (0x) + ${hexValue} +
+
+ Hex (roh) + ${hexNoPrefix} +
+
+ Dezimal + ${decValue} +
+
+ Binär + ${binary} +
+
+ Little-Endian + ${littleEndian} +
+ `; + + document.body.appendChild(contextMenu); + + // Handle menu item clicks + contextMenu.querySelectorAll('.context-menu-item').forEach(item => { + item.addEventListener('click', (e) => { + e.stopPropagation(); + const value = item.getAttribute('data-value'); + navigator.clipboard.writeText(value).then(() => { + showNotification('Kopiert: ' + value, 'success'); + contextMenu.remove(); + contextMenu = null; + }); + }); + }); + } +} + +function toLittleEndianBytes(value) { + const bytes = []; + let num = value; + + // Convert to 4-byte little-endian + for (let i = 0; i < 4; i++) { + bytes.push((num & 0xFF).toString(16).padStart(2, '0').toUpperCase()); + num = num >> 8; + } + + return bytes.join(' '); +} + +// ============================================ +// 6. NOTIFICATION SYSTEM +// ============================================ + +function showNotification(message, type = 'info') { + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.textContent = message; + + document.body.appendChild(notification); + + // Trigger animation + setTimeout(() => notification.classList.add('show'), 10); + + // Remove after 2 seconds + setTimeout(() => { + notification.classList.remove('show'); + setTimeout(() => notification.remove(), 300); + }, 2000); +} + +// ============================================ +// INITIALIZATION +// ============================================ + +export function initializeUXEnhancements() { + setupKeyboardShortcuts(); + setupDoubleClickCopy(); + setupCompactMode(); + setupAutoCalculateOnPaste(); + setupContextMenu(); + + console.log('✨ UX Enhancements activated!'); +}