From 33263b685caa45dd4d2ecf4e92c6c144e7cc91b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Thu, 22 Jan 2026 22:44:21 +0100 Subject: [PATCH] Normalize settings on save --- .../imgfloat/service/SettingsService.java | 95 ++++++++++++++++++- src/main/resources/static/css/styles.css | 85 +++++++++++++++++ src/main/resources/templates/settings.html | 14 +-- 3 files changed, 185 insertions(+), 9 deletions(-) diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/SettingsService.java b/src/main/java/dev/kruhlmann/imgfloat/service/SettingsService.java index a76f040..f8682ae 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/SettingsService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/SettingsService.java @@ -2,9 +2,15 @@ package dev.kruhlmann.imgfloat.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import dev.kruhlmann.imgfloat.model.AudioAsset; import dev.kruhlmann.imgfloat.model.Settings; +import dev.kruhlmann.imgfloat.model.VisualAsset; +import dev.kruhlmann.imgfloat.repository.AudioAssetRepository; import dev.kruhlmann.imgfloat.repository.SettingsRepository; +import dev.kruhlmann.imgfloat.repository.VisualAssetRepository; import jakarta.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -15,10 +21,19 @@ public class SettingsService { private static final Logger logger = LoggerFactory.getLogger(SettingsService.class); private final SettingsRepository repo; + private final VisualAssetRepository visualAssetRepository; + private final AudioAssetRepository audioAssetRepository; private final ObjectMapper objectMapper; - public SettingsService(SettingsRepository repo, ObjectMapper objectMapper) { + public SettingsService( + SettingsRepository repo, + VisualAssetRepository visualAssetRepository, + AudioAssetRepository audioAssetRepository, + ObjectMapper objectMapper + ) { this.repo = repo; + this.visualAssetRepository = visualAssetRepository; + this.audioAssetRepository = audioAssetRepository; this.objectMapper = objectMapper; } @@ -39,7 +54,9 @@ public class SettingsService { public Settings save(Settings settings) { settings.setId(1); logSettings("Saving settings", settings); - return repo.save(settings); + Settings savedSettings = repo.save(settings); + clampAssetsToSettings(savedSettings); + return savedSettings; } public void logSettings(String msg, Settings settings) { @@ -49,4 +66,78 @@ public class SettingsService { logger.error("Failed to serialize settings", e); } } + + private void clampAssetsToSettings(Settings settings) { + double minSpeed = settings.getMinAssetPlaybackSpeedFraction(); + double maxSpeed = settings.getMaxAssetPlaybackSpeedFraction(); + double minPitch = settings.getMinAssetAudioPitchFraction(); + double maxPitch = settings.getMaxAssetAudioPitchFraction(); + double minVolume = settings.getMinAssetVolumeFraction(); + double maxVolume = settings.getMaxAssetVolumeFraction(); + + List visualsToUpdate = new ArrayList<>(); + for (VisualAsset visual : visualAssetRepository.findAll()) { + boolean changed = false; + double speed = visual.getSpeed(); + double clampedSpeed = clamp(speed, minSpeed, maxSpeed); + if (Double.compare(speed, clampedSpeed) != 0) { + visual.setSpeed(clampedSpeed); + changed = true; + } + double volume = visual.getAudioVolume(); + double clampedVolume = clamp(volume, minVolume, maxVolume); + if (Double.compare(volume, clampedVolume) != 0) { + visual.setAudioVolume(clampedVolume); + changed = true; + } + if (changed) { + visualsToUpdate.add(visual); + } + } + + List audioToUpdate = new ArrayList<>(); + for (AudioAsset audio : audioAssetRepository.findAll()) { + boolean changed = false; + double speed = audio.getAudioSpeed(); + double clampedSpeed = clamp(speed, minSpeed, maxSpeed); + if (Double.compare(speed, clampedSpeed) != 0) { + audio.setAudioSpeed(clampedSpeed); + changed = true; + } + double pitch = audio.getAudioPitch(); + double clampedPitch = clamp(pitch, minPitch, maxPitch); + if (Double.compare(pitch, clampedPitch) != 0) { + audio.setAudioPitch(clampedPitch); + changed = true; + } + double volume = audio.getAudioVolume(); + double clampedVolume = clamp(volume, minVolume, maxVolume); + if (Double.compare(volume, clampedVolume) != 0) { + audio.setAudioVolume(clampedVolume); + changed = true; + } + if (changed) { + audioToUpdate.add(audio); + } + } + + if (!visualsToUpdate.isEmpty()) { + visualAssetRepository.saveAll(visualsToUpdate); + } + if (!audioToUpdate.isEmpty()) { + audioAssetRepository.saveAll(audioToUpdate); + } + + if (!visualsToUpdate.isEmpty() || !audioToUpdate.isEmpty()) { + logger.info( + "Normalized {} visual assets and {} audio assets to new settings ranges", + visualsToUpdate.size(), + audioToUpdate.size() + ); + } + } + + private double clamp(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } } diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index dacbe0b..763a6be 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -318,6 +318,82 @@ body.has-staging-banner { color: #fef3c7; } +.toast-container { + position: fixed; + top: 24px; + right: 24px; + display: flex; + flex-direction: column; + gap: 12px; + z-index: 1200; +} + +.toast { + display: flex; + align-items: center; + gap: 12px; + min-width: 220px; + max-width: 360px; + padding: 12px 16px; + border-radius: 12px; + border: 1px solid transparent; + background: rgba(15, 23, 42, 0.96); + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.45); + color: #e2e8f0; + font-size: 14px; + animation: toast-enter 0.2s ease-out; +} + +.toast-exit { + opacity: 0; + transform: translateX(8px); + transition: + opacity 0.2s ease, + transform 0.2s ease; +} + +.toast-indicator { + width: 10px; + height: 10px; + border-radius: 999px; + background: currentColor; +} + +.toast-message { + line-height: 1.4; +} + +.toast-success { + border-color: rgba(34, 197, 94, 0.45); + color: #86efac; +} + +.toast-error { + border-color: rgba(248, 113, 113, 0.45); + color: #fecaca; +} + +.toast-warning { + border-color: rgba(251, 191, 36, 0.5); + color: #fde68a; +} + +.toast-info { + border-color: rgba(56, 189, 248, 0.45); + color: #bae6fd; +} + +@keyframes toast-enter { + from { + opacity: 0; + transform: translateX(8px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + .info-card { display: flex; flex-direction: column; @@ -624,6 +700,15 @@ button:disabled:hover { box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25); } +.text-input:invalid { + border-color: #f87171; +} + +.text-input:focus:invalid { + border-color: #f87171; + box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.25); +} + .text-input:disabled, .text-input[aria-disabled="true"] { background: #020617; diff --git a/src/main/resources/templates/settings.html b/src/main/resources/templates/settings.html index 229b63c..7221a76 100644 --- a/src/main/resources/templates/settings.html +++ b/src/main/resources/templates/settings.html @@ -121,7 +121,7 @@ class="text-input" type="text" inputmode="decimal" - pattern="^(0(\.\d+)?|1(\.0+)?)$" + pattern="^(0|[1-9]\d*)(\.\d+)?$" placeholder="0.5" /> @@ -134,7 +134,7 @@ class="text-input" type="text" inputmode="decimal" - pattern="^(0(\.\d+)?|1(\.0+)?)$" + pattern="^(0|[1-9]\d*)(\.\d+)?$" placeholder="1.0" /> @@ -154,7 +154,7 @@ class="text-input" type="text" inputmode="decimal" - pattern="^(0(\.\d+)?|1(\.0+)?)$" + pattern="^(0|[1-9]\d*)(\.\d+)?$" placeholder="0.8" /> @@ -167,7 +167,7 @@ class="text-input" type="text" inputmode="decimal" - pattern="^(0(\.\d+)?|1(\.0+)?)$" + pattern="^(0|[1-9]\d*)(\.\d+)?$" placeholder="1.0" /> @@ -181,7 +181,7 @@ class="text-input" type="text" inputmode="decimal" - pattern="^(0(\.\d+)?|1(\.0+)?)$" + pattern="^(0|[1-9]\d*)(\.\d+)?$" placeholder="0.2" /> @@ -194,7 +194,7 @@ class="text-input" type="text" inputmode="decimal" - pattern="^(0(\.\d+)?|1(\.0+)?)$" + pattern="^(0|[1-9]\d*)(\.\d+)?$" placeholder="1.0" /> @@ -214,7 +214,7 @@ class="text-input" type="text" inputmode="numeric" - pattern="^[1-9]\\d*$" + pattern="^[1-9]\d*$" placeholder="60" />