consolidation & ux improvements

This commit is contained in:
overcuriousity
2025-11-12 21:46:24 +01:00
parent 02e3700828
commit 2261f735d7
6 changed files with 538 additions and 49 deletions

View File

@@ -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; }
}

View File

@@ -7,6 +7,7 @@
<title>Dateisystem-Offset-Rechner</title>
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/components.css">
<link rel="stylesheet" href="css/ux-enhancements.css">
</head>
<body>

View File

@@ -63,25 +63,9 @@ 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);
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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 = `
<span class="hint-toggle" title="Tastenkombinationen anzeigen">⌨️</span>
<div class="hint-content" style="display: none;">
<h4>Tastenkombinationen</h4>
<ul>
<li><kbd>Ctrl+1-9</kbd> Tabs wechseln</li>
<li><kbd>Ctrl+K</kbd> Rechner fokussieren</li>
<li><kbd>Ctrl+L</kbd> Erste Eingabe fokussieren</li>
<li><kbd>Alt+C</kbd> Eingaben löschen</li>
<li><kbd>Ctrl+Shift+C</kbd> Alle Ergebnisse kopieren</li>
<li><kbd>Doppelklick</kbd> Wert kopieren</li>
</ul>
</div>
`;
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 = `
<div class="context-menu-header">Kopieren als:</div>
<div class="context-menu-item" data-value="${hexValue}">
<span class="format-label">Hex (0x)</span>
<span class="format-value">${hexValue}</span>
</div>
<div class="context-menu-item" data-value="${hexNoPrefix}">
<span class="format-label">Hex (roh)</span>
<span class="format-value">${hexNoPrefix}</span>
</div>
<div class="context-menu-item" data-value="${decValue}">
<span class="format-label">Dezimal</span>
<span class="format-value">${decValue}</span>
</div>
<div class="context-menu-item" data-value="${binary}">
<span class="format-label">Binär</span>
<span class="format-value">${binary}</span>
</div>
<div class="context-menu-item" data-value="${littleEndian}">
<span class="format-label">Little-Endian</span>
<span class="format-value">${littleEndian}</span>
</div>
`;
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!');
}