Normalize settings on save

This commit is contained in:
2026-01-22 22:44:21 +01:00
parent 54bb2e6c49
commit 33263b685c
3 changed files with 185 additions and 9 deletions

View File

@@ -2,9 +2,15 @@ package dev.kruhlmann.imgfloat.service;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import dev.kruhlmann.imgfloat.model.AudioAsset;
import dev.kruhlmann.imgfloat.model.Settings; 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.SettingsRepository;
import dev.kruhlmann.imgfloat.repository.VisualAssetRepository;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -15,10 +21,19 @@ public class SettingsService {
private static final Logger logger = LoggerFactory.getLogger(SettingsService.class); private static final Logger logger = LoggerFactory.getLogger(SettingsService.class);
private final SettingsRepository repo; private final SettingsRepository repo;
private final VisualAssetRepository visualAssetRepository;
private final AudioAssetRepository audioAssetRepository;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public SettingsService(SettingsRepository repo, ObjectMapper objectMapper) { public SettingsService(
SettingsRepository repo,
VisualAssetRepository visualAssetRepository,
AudioAssetRepository audioAssetRepository,
ObjectMapper objectMapper
) {
this.repo = repo; this.repo = repo;
this.visualAssetRepository = visualAssetRepository;
this.audioAssetRepository = audioAssetRepository;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }
@@ -39,7 +54,9 @@ public class SettingsService {
public Settings save(Settings settings) { public Settings save(Settings settings) {
settings.setId(1); settings.setId(1);
logSettings("Saving settings", settings); logSettings("Saving settings", settings);
return repo.save(settings); Settings savedSettings = repo.save(settings);
clampAssetsToSettings(savedSettings);
return savedSettings;
} }
public void logSettings(String msg, Settings settings) { public void logSettings(String msg, Settings settings) {
@@ -49,4 +66,78 @@ public class SettingsService {
logger.error("Failed to serialize settings", e); 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<VisualAsset> 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<AudioAsset> 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));
}
} }

View File

@@ -318,6 +318,82 @@ body.has-staging-banner {
color: #fef3c7; 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 { .info-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -624,6 +700,15 @@ button:disabled:hover {
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25); 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:disabled,
.text-input[aria-disabled="true"] { .text-input[aria-disabled="true"] {
background: #020617; background: #020617;

View File

@@ -121,7 +121,7 @@
class="text-input" class="text-input"
type="text" type="text"
inputmode="decimal" inputmode="decimal"
pattern="^(0(\.\d+)?|1(\.0+)?)$" pattern="^(0|[1-9]\d*)(\.\d+)?$"
placeholder="0.5" placeholder="0.5"
/> />
</label> </label>
@@ -134,7 +134,7 @@
class="text-input" class="text-input"
type="text" type="text"
inputmode="decimal" inputmode="decimal"
pattern="^(0(\.\d+)?|1(\.0+)?)$" pattern="^(0|[1-9]\d*)(\.\d+)?$"
placeholder="1.0" placeholder="1.0"
/> />
</label> </label>
@@ -154,7 +154,7 @@
class="text-input" class="text-input"
type="text" type="text"
inputmode="decimal" inputmode="decimal"
pattern="^(0(\.\d+)?|1(\.0+)?)$" pattern="^(0|[1-9]\d*)(\.\d+)?$"
placeholder="0.8" placeholder="0.8"
/> />
</label> </label>
@@ -167,7 +167,7 @@
class="text-input" class="text-input"
type="text" type="text"
inputmode="decimal" inputmode="decimal"
pattern="^(0(\.\d+)?|1(\.0+)?)$" pattern="^(0|[1-9]\d*)(\.\d+)?$"
placeholder="1.0" placeholder="1.0"
/> />
</label> </label>
@@ -181,7 +181,7 @@
class="text-input" class="text-input"
type="text" type="text"
inputmode="decimal" inputmode="decimal"
pattern="^(0(\.\d+)?|1(\.0+)?)$" pattern="^(0|[1-9]\d*)(\.\d+)?$"
placeholder="0.2" placeholder="0.2"
/> />
</label> </label>
@@ -194,7 +194,7 @@
class="text-input" class="text-input"
type="text" type="text"
inputmode="decimal" inputmode="decimal"
pattern="^(0(\.\d+)?|1(\.0+)?)$" pattern="^(0|[1-9]\d*)(\.\d+)?$"
placeholder="1.0" placeholder="1.0"
/> />
</label> </label>
@@ -214,7 +214,7 @@
class="text-input" class="text-input"
type="text" type="text"
inputmode="numeric" inputmode="numeric"
pattern="^[1-9]\\d*$" pattern="^[1-9]\d*$"
placeholder="60" placeholder="60"
/> />
</label> </label>