diff --git a/.claude/settings.local.json b/.claude/settings.local.json index afe5407..96a600c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,9 @@ "Bash(mkdir:*)", "Bash(npm install)", "Bash(npm run build-win:*)", - "Bash(chmod:*)" + "Bash(chmod:*)", + "Bash(ls:*)", + "Bash(cp:*)" ], "deny": [] } diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index 63265a8..743435d 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -7,7 +7,7 @@ - **Path**: `C:/Users/hendr/Desktop/IntelSight/Projektablage/Toolbox-Metadaten-Crawler` - **Files**: 88 files - **Size**: 275.2 MB -- **Last Modified**: 2025-07-10 19:19 +- **Last Modified**: 2025-07-10 19:36 ## Technology Stack @@ -87,3 +87,4 @@ This project is managed with Claude Project Manager. To work with this project: - README updated on 2025-07-08 20:45:50 - README updated on 2025-07-10 16:11:59 - README updated on 2025-07-10 19:34:36 +- README updated on 2025-07-10 23:16:36 diff --git a/app.js b/app.js index 3dfdbb0..37f7f55 100644 --- a/app.js +++ b/app.js @@ -311,9 +311,14 @@ function createMetadataSection(title, data, icon = '📋', cssClass = '') { for (const [key, value] of Object.entries(data)) { const formattedKey = formatMetadataKey(key); const formattedValue = formatMetadataValue(key, value); + const tooltip = getMetadataTooltip(key, formattedKey); + html += ` - ${formattedKey} + + ${formattedKey} + ${tooltip ? `Info` : ''} + ${formattedValue} `; @@ -518,29 +523,48 @@ function formatMetadataKey(key) { // Remove common prefixes and format key = key.replace(/^(EXIF|GPS|IPTC|XMP)/, ''); - // Add spaces before capital letters - key = key.replace(/([A-Z])/g, ' $1').trim(); - - // Special formatting for known keys + // Special formatting for known keys - do first before adding spaces const keyMap = { 'Make': 'Kamera-Hersteller', 'Model': 'Kamera-Modell', 'DateTime Original': 'Aufnahmedatum', + 'DateTimeOriginal': 'Aufnahmedatum', 'Exposure Time': 'Belichtungszeit', + 'ExposureTime': 'Belichtungszeit', 'F Number': 'Blende', + 'FNumber': 'Blende', 'ISO Speed Ratings': 'ISO-Wert', + 'ISOSpeedRatings': 'ISO-Wert', 'Focal Length': 'Brennweite', + 'FocalLength': 'Brennweite', 'Flash': 'Blitz', 'GPS Latitude': 'Breitengrad', + 'GPSLatitude': 'Breitengrad', 'GPS Longitude': 'Längengrad', + 'GPSLongitude': 'Längengrad', 'GPS Altitude': 'Höhe', + 'GPSAltitude': 'Höhe', 'Lens Model': 'Objektiv', + 'LensModel': 'Objektiv', 'White Balance': 'Weißabgleich', + 'WhiteBalance': 'Weißabgleich', 'Exposure Mode': 'Belichtungsmodus', - 'Color Space': 'Farbraum' + 'ExposureMode': 'Belichtungsmodus', + 'Color Space': 'Farbraum', + 'ColorSpace': 'Farbraum' }; - return keyMap[key.trim()] || key; + // Check if we have a known key first + if (keyMap[key]) { + return keyMap[key]; + } + + // Add spaces before capital letters but preserve acronyms + key = key.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2'); // For cases like "GPSLatitude" -> "GPS Latitude" + key = key.replace(/([a-z\d])([A-Z])/g, '$1 $2'); // For cases like "dateTime" -> "date Time" + key = key.trim(); + + return keyMap[key] || key; } // Format metadata values for display @@ -632,6 +656,65 @@ let currentFileData = null; let currentMetadata = null; let currentPreviewUrl = null; +// Get tooltips for metadata fields +function getMetadataTooltip(key, formattedKey) { + const tooltips = { + 'ISO-Wert': 'Die Lichtempfindlichkeit des Kamerasensors. Höhere Werte (z.B. 1600) bedeuten mehr Empfindlichkeit, aber auch mehr Bildrauschen.', + 'Blende': 'Die Öffnung des Objektivs (f-Zahl). Kleinere Zahlen (z.B. f/1.8) bedeuten eine größere Öffnung und mehr Licht.', + 'Belichtungszeit': 'Wie lange der Kamerasensor dem Licht ausgesetzt ist. Z.B. 1/100 bedeutet eine Hundertstelsekunde.', + 'Brennweite': 'Der Abstand zwischen Objektiv und Sensor in Millimetern. Bestimmt den Bildausschnitt.', + 'Weißabgleich': 'Einstellung zur korrekten Farbwiedergabe unter verschiedenen Lichtbedingungen.', + 'Belichtungsmodus': 'Automatik, Manuell, Blendenpriorität oder Zeitpriorität - bestimmt wie die Kamera die Belichtung steuert.', + 'Farbraum': 'Definiert den Bereich der darstellbaren Farben (z.B. sRGB für Web, Adobe RGB für Druck).', + 'Breitengrad': 'GPS-Koordinate für die Nord-Süd-Position des Aufnahmeortes.', + 'Längengrad': 'GPS-Koordinate für die Ost-West-Position des Aufnahmeortes.', + 'Höhe': 'Höhe über dem Meeresspiegel am Aufnahmeort.', + 'Objektiv': 'Das verwendete Kameraobjektiv mit seinen technischen Eigenschaften.', + 'Blitz': 'Informationen darüber, ob und wie der Blitz verwendet wurde.', + 'ExposureCompensation': 'Belichtungskorrektur - manuelle Anpassung der automatischen Belichtung.', + 'MeteringMode': 'Messmethode für die Belichtung (Spot, Matrix, Mittenbetont).', + 'FocalLengthIn35mmFilm': 'Brennweite umgerechnet auf Kleinbildformat für bessere Vergleichbarkeit.', + 'DigitalZoomRatio': 'Digitaler Zoom-Faktor - elektronische Vergrößerung mit Qualitätsverlust.', + 'SceneCaptureType': 'Art der Aufnahme (Landschaft, Portrait, Nachtaufnahme).', + 'SubjectDistanceRange': 'Entfernungsbereich zum Motiv (Makro, Nah, Fern).', + 'Sharpness': 'Nachschärfung - digitale Kantenbetonung des Bildes.', + 'Saturation': 'Farbsättigung - Intensität der Farben im Bild.', + 'Contrast': 'Kontrast - Unterschied zwischen hellen und dunklen Bereichen.', + 'GainControl': 'Verstärkungsregelung - elektronische Signalverstärkung bei wenig Licht.', + // Neue Erklärungen + 'ApertureValue': 'Blendenwert in APEX-Einheiten - technische Darstellung der Blendenöffnung für Berechnungen.', + 'Aperture Value': 'Blendenwert in APEX-Einheiten - technische Darstellung der Blendenöffnung für Berechnungen.', + 'ExposureBiasValue': 'Belichtungskorrektur in EV-Stufen. +1 bedeutet doppelt so hell, -1 halb so hell.', + 'Exposure Bias Value': 'Belichtungskorrektur in EV-Stufen. +1 bedeutet doppelt so hell, -1 halb so hell.', + 'MaxApertureValue': 'Größte Blendenöffnung des Objektivs (kleinste f-Zahl) - bestimmt die maximale Lichtmenge.', + 'Max Aperture Value': 'Größte Blendenöffnung des Objektivs (kleinste f-Zahl) - bestimmt die maximale Lichtmenge.', + 'ExposureProgram': 'Kameraeinstellung: 0=Unbekannt, 1=Manuell, 2=Normal, 3=Blendenpriorität, 4=Zeitpriorität, 5=Kreativ, 6=Action, 7=Portrait, 8=Landschaft.', + 'Exposure Program': 'Kameraeinstellung: 0=Unbekannt, 1=Manuell, 2=Normal, 3=Blendenpriorität, 4=Zeitpriorität, 5=Kreativ, 6=Action, 7=Portrait, 8=Landschaft.', + 'SubSampling': 'Farbunterabtastung bei JPEG - bestimmt wie Farbinformationen gespeichert werden (4:2:2 oder 4:2:0).', + 'YCbCrSubSampling': 'Farbunterabtastung bei JPEG - bestimmt wie Farbinformationen gespeichert werden (4:2:2 oder 4:2:0).', + 'Orientation': 'Bildausrichtung: 1=Normal, 3=180° gedreht, 6=90° rechts gedreht, 8=90° links gedreht.', + 'YCbCrPositioning': 'Position der Farbinformationen: 1=Zentriert (üblich), 2=Co-sited (professionell).', + 'Y Cb Cr Positioning': 'Position der Farbinformationen: 1=Zentriert (üblich), 2=Co-sited (professionell).', + 'ExifIFDPointer': 'Technischer Verweis auf den Speicherort der EXIF-Daten in der Datei.', + 'EXIF IFD Pointer': 'Technischer Verweis auf den Speicherort der EXIF-Daten in der Datei.', + 'ExifVersion': 'Version des EXIF-Standards (z.B. 0232 = Version 2.32) - bestimmt verfügbare Metadatenfelder.', + 'EXIF Version': 'Version des EXIF-Standards (z.B. 0232 = Version 2.32) - bestimmt verfügbare Metadatenfelder.', + 'ShutterSpeedValue': 'Verschlusszeit in APEX-Einheiten - technische Darstellung für Berechnungen.', + 'Shutter Speed Value': 'Verschlusszeit in APEX-Einheiten - technische Darstellung für Berechnungen.', + 'BrightnessValue': 'Objekthelligkeit in APEX-Einheiten - Messwert der Motivhelligkeit.', + 'Brightness Value': 'Objekthelligkeit in APEX-Einheiten - Messwert der Motivhelligkeit.', + 'OffsetTime': 'Zeitzonenversatz zur UTC-Zeit bei der Aufnahme (z.B. +02:00 für MESZ).', + 'Offset Time': 'Zeitzonenversatz zur UTC-Zeit bei der Aufnahme (z.B. +02:00 für MESZ).', + 'OffsetTimeOriginal': 'Ursprünglicher Zeitzonenversatz bei der Aufnahme - bleibt bei Bearbeitung erhalten.', + 'Offset Time Original': 'Ursprünglicher Zeitzonenversatz bei der Aufnahme - bleibt bei Bearbeitung erhalten.', + 'ImageUniqueID': 'Einzigartige Bild-ID aus Datum, Kameraseriennummer und Bildnummer - identifiziert das Bild eindeutig.', + 'Image Unique ID': 'Einzigartige Bild-ID aus Datum, Kameraseriennummer und Bildnummer - identifiziert das Bild eindeutig.' + }; + + // Check both the original key and formatted key + return tooltips[formattedKey] || tooltips[key] || null; +} + // Generate PDF report async function generateReport() { if (!currentFileData) { diff --git a/gitea_push_debug.txt b/gitea_push_debug.txt index f493a96..5ffdf9b 100644 --- a/gitea_push_debug.txt +++ b/gitea_push_debug.txt @@ -1,11 +1,11 @@ -Push Debug Info - 2025-07-10 19:11:49.841754 -Repository: Metadaten-Crawler +Push Debug Info - 2025-07-10 19:36:09.172689 +Repository: Toolbox-Metadaten-Crawler Owner: IntelSight -Path: C:\Users\hendr\Desktop\IntelSight\Projektablage\Metadaten-Crawler +Path: C:\Users\hendr\Desktop\IntelSight\Projektablage\Toolbox-Metadaten-Crawler Current branch: master Git remotes: -origin https://IntelSight_Admin:3b4a6ba1ade3f34640f3c85d2333b4a3a0627471@gitea-undso.intelsight.de/IntelSight/Metadaten-Crawler.git (fetch) -origin https://IntelSight_Admin:3b4a6ba1ade3f34640f3c85d2333b4a3a0627471@gitea-undso.intelsight.de/IntelSight/Metadaten-Crawler.git (push) +origin https://IntelSight_Admin:3b4a6ba1ade3f34640f3c85d2333b4a3a0627471@gitea-undso.intelsight.de/IntelSight/Toolbox-Metadaten-Crawler.git (fetch) +origin https://IntelSight_Admin:3b4a6ba1ade3f34640f3c85d2333b4a3a0627471@gitea-undso.intelsight.de/IntelSight/Toolbox-Metadaten-Crawler.git (push) Git status before push: Clean Push command: git push --set-upstream origin master:main -v @@ -13,10 +13,10 @@ Push result: Success Push stdout: branch 'master' set up to track 'origin/main'. Push stderr: -POST git-receive-pack (52750 bytes) +POST git-receive-pack (55591 bytes) remote: . Processing 1 references remote: Processed 1 references in total -Pushing to https://gitea-undso.intelsight.de/IntelSight/Metadaten-Crawler.git -To https://gitea-undso.intelsight.de/IntelSight/Metadaten-Crawler.git +Pushing to https://gitea-undso.intelsight.de/IntelSight/Toolbox-Metadaten-Crawler.git +To https://gitea-undso.intelsight.de/IntelSight/Toolbox-Metadaten-Crawler.git * [new branch] master -> main updating local tracking ref 'refs/remotes/origin/main' \ No newline at end of file diff --git a/icons/info.svg b/icons/info.svg new file mode 100644 index 0000000..a5be759 --- /dev/null +++ b/icons/info.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/icons/moon.svg b/icons/moon.svg new file mode 100644 index 0000000..1479a6d --- /dev/null +++ b/icons/moon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/icons/settings.svg b/icons/settings.svg new file mode 100644 index 0000000..b28a895 --- /dev/null +++ b/icons/settings.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/icons/sun.svg b/icons/sun.svg new file mode 100644 index 0000000..bf14738 --- /dev/null +++ b/icons/sun.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/index.html b/index.html index e2143cf..205ea15 100644 --- a/index.html +++ b/index.html @@ -20,45 +20,13 @@ - -
-
-
-

Einstellungen

- -
-
-
-

Erscheinungsbild

-
- - -
-
-
-
-

Metadaten-Crawler

-
diff --git a/renderer.js b/renderer.js index 8ba0965..155a388 100644 --- a/renderer.js +++ b/renderer.js @@ -14,10 +14,12 @@ function applyTheme(darkMode) { isDarkMode = darkMode; if (darkMode) { document.body.classList.remove('light-mode'); - document.getElementById('darkTheme').checked = true; } else { document.body.classList.add('light-mode'); - document.getElementById('lightTheme').checked = true; + } + // Save theme preference + if (window.electronAPI) { + window.electronAPI.setTheme(darkMode); } } @@ -34,32 +36,11 @@ document.getElementById('closeBtn').addEventListener('click', () => { if (window.electronAPI) window.electronAPI.closeWindow(); }); -// Settings dialog -const settingsDialog = document.getElementById('settingsDialog'); -const settingsBtn = document.getElementById('settingsBtn'); -const settingsCloseBtn = document.getElementById('settingsCloseBtn'); +// Theme toggle button +const themeToggleBtn = document.getElementById('themeToggleBtn'); -settingsBtn.addEventListener('click', () => { - settingsDialog.classList.add('show'); -}); - -settingsCloseBtn.addEventListener('click', () => { - settingsDialog.classList.remove('show'); -}); - -settingsDialog.addEventListener('click', (e) => { - if (e.target === settingsDialog) { - settingsDialog.classList.remove('show'); - } -}); - -// Theme radio buttons -document.getElementById('darkTheme').addEventListener('change', () => { - applyTheme(true); -}); - -document.getElementById('lightTheme').addEventListener('change', () => { - applyTheme(false); +themeToggleBtn.addEventListener('click', () => { + applyTheme(!isDarkMode); }); // Listen for theme changes from menu @@ -76,4 +57,20 @@ if (window.electronAPI) { // Initialize on load window.addEventListener('DOMContentLoaded', () => { initializeTheme(); -}); \ No newline at end of file +}); + +// Store theme preference +function setTheme(darkMode) { + localStorage.setItem('theme', darkMode ? 'dark' : 'light'); +} + +// Get theme preference +function getStoredTheme() { + const storedTheme = localStorage.getItem('theme'); + return storedTheme !== 'light'; +} + +// Initialize theme from stored preference +if (!window.electronAPI) { + applyTheme(getStoredTheme()); +} \ No newline at end of file diff --git a/styles.css b/styles.css index 3bc558c..991bad7 100644 --- a/styles.css +++ b/styles.css @@ -274,6 +274,7 @@ body.light-mode .metadata-content { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; + position: relative; } body.light-mode .metadata-table td:first-child { @@ -334,9 +335,12 @@ body.light-mode .metadata-table td:first-child { font-size: 16px; font-family: 'Poppins', sans-serif; font-weight: 600; - margin-top: 24px; transition: all 0.3s ease; letter-spacing: 0.5px; + height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; } .clear-btn:hover { @@ -417,7 +421,7 @@ body.light-mode .title-bar { -webkit-app-region: no-drag; } -.settings-btn { +.theme-toggle-btn { background-color: var(--secondary-bg); border: 1px solid var(--border-color); color: var(--accent); @@ -428,15 +432,43 @@ body.light-mode .title-bar { display: flex; align-items: center; justify-content: center; + width: 44px; + height: 44px; + position: relative; } -.settings-btn:hover { +.theme-toggle-btn:hover { background-color: var(--secondary-bg-hover); border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 212, 255, 0.2); } +.theme-icon { + width: 24px; + height: 24px; + position: absolute; + transition: opacity 0.3s ease; +} + +.theme-icon.dark-icon { + opacity: 1; + filter: brightness(0) saturate(100%) invert(88%) sepia(61%) saturate(3812%) hue-rotate(154deg) brightness(101%) contrast(101%); +} + +.theme-icon.light-icon { + opacity: 0; + filter: brightness(0) saturate(100%) invert(74%) sepia(91%) saturate(5214%) hue-rotate(355deg) brightness(103%) contrast(104%); +} + +body.light-mode .theme-icon.dark-icon { + opacity: 0; +} + +body.light-mode .theme-icon.light-icon { + opacity: 1; +} + .window-control { background: transparent; border: none; @@ -459,139 +491,108 @@ body.light-mode .title-bar { color: white; } -/* Settings Dialog */ -.settings-dialog { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.8); - z-index: 2000; - align-items: center; - justify-content: center; -} - -.settings-dialog.show { - display: flex; -} - -.settings-content { - background-color: var(--secondary-bg); - border-radius: 16px; - width: 500px; - max-width: 90%; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); - overflow: hidden; - animation: slideIn 0.3s ease; -} - -@keyframes slideIn { - from { - transform: translateY(-20px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } -} - -.settings-header { - background-color: var(--primary); - padding: 20px 24px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.settings-header h2 { - margin: 0; - font-family: 'Poppins', sans-serif; - font-weight: 600; - font-size: 20px; - color: var(--text-primary); -} - -.settings-close { - background: transparent; - border: none; - color: var(--text-primary); - font-size: 24px; - cursor: pointer; - padding: 0; - width: 32px; - height: 32px; - border-radius: 8px; - transition: all 0.3s ease; -} - -.settings-close:hover { - background-color: var(--error); -} - -.settings-body { - padding: 24px; -} - -.settings-section { - margin-bottom: 24px; -} - -.settings-section h3 { - margin: 0 0 16px 0; - font-family: 'Poppins', sans-serif; - font-weight: 600; - font-size: 16px; - color: var(--text-primary); -} - -.theme-selector { - display: flex; - gap: 16px; -} - -.theme-option { - flex: 1; - cursor: pointer; -} - -.theme-option input[type="radio"] { - display: none; -} - -.theme-label { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 16px; - background-color: var(--background); - border: 2px solid var(--border-color); - border-radius: 12px; - transition: all 0.3s ease; - font-weight: 500; -} - -.theme-option input[type="radio"]:checked + .theme-label { - border-color: var(--accent); - background-color: var(--secondary-bg-hover); -} - -.theme-label:hover { - border-color: var(--accent); -} - -.theme-icon { - font-size: 24px; -} /* Adjust container for spacing */ .container { padding-top: 20px; } +/* Info icon styling */ +.info-icon { + display: inline-block; + margin-left: 8px; + cursor: help; + vertical-align: middle; + position: relative; +} + +.info-icon img { + width: 16px; + height: 16px; + opacity: 0.5; + transition: opacity 0.3s ease; + filter: brightness(0) saturate(100%) invert(73%) sepia(11%) saturate(374%) hue-rotate(185deg) brightness(90%) contrast(87%); +} + +body.light-mode .info-icon img { + filter: brightness(0) saturate(100%) invert(39%) sepia(9%) saturate(505%) hue-rotate(175deg) brightness(92%) contrast(87%); +} + +.info-icon:hover img { + opacity: 0.8; +} + +/* Tooltip styling */ +.info-icon[title] { + position: relative; +} + +.info-icon[title]:hover::after { + content: attr(title); + position: absolute; + top: 50%; + left: calc(100% + 10px); + transform: translateY(-50%); + background-color: var(--primary); + color: var(--text-primary); + padding: 8px 12px; + border-radius: 8px; + font-size: 12px; + font-weight: normal; + text-transform: none; + letter-spacing: normal; + white-space: normal; + width: 300px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 1000; + pointer-events: none; + line-height: 1.4; + animation: tooltipFadeIn 0.3s ease; +} + +.info-icon[title]:hover::before { + content: ''; + position: absolute; + top: 50%; + left: 100%; + transform: translateY(-50%); + border: 6px solid transparent; + border-right-color: var(--primary); + margin-left: -2px; + z-index: 1001; + pointer-events: none; + animation: tooltipFadeIn 0.3s ease; +} + +@keyframes tooltipFadeIn { + from { + opacity: 0; + transform: translateY(-50%) translateX(5px); + } + to { + opacity: 1; + transform: translateY(-50%) translateX(0); + } +} + +/* Alternative tooltip position for items near the right edge */ +.metadata-table tr:nth-last-child(-n+3) .info-icon[title]:hover::after { + top: auto; + bottom: 50%; + left: auto; + right: calc(100% + 10px); + transform: translateY(50%); +} + +.metadata-table tr:nth-last-child(-n+3) .info-icon[title]:hover::before { + left: auto; + right: 100%; + border-right-color: transparent; + border-left-color: var(--primary); + margin-left: 0; + margin-right: -2px; +} + /* Action Buttons */ .action-buttons { display: flex; @@ -611,6 +612,10 @@ body.light-mode .title-bar { font-weight: 600; transition: all 0.3s ease; letter-spacing: 0.5px; + height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; } .report-btn:hover {