feat(transactions): update cuelume; bring back improved volume user setting

This commit is contained in:
Herculino Trotta
2026-09-13 01:24:37 -03:00
parent 82211567d0
commit a2ee0b4ae6
9 changed files with 102 additions and 8 deletions
+17
View File
@@ -3,3 +3,20 @@ from crispy_forms.layout import Field
class Switch(Field):
template = "crispy-daisyui/layout/switch.html"
class Range(Field):
template = "crispy-daisyui/layout/range.html"
def render(self, form, context, extra_context=None, **kwargs):
extra_context = extra_context or {}
# Build the measure (ticks + labels) from the field's min/max/step
attrs = form.fields[self.fields[0]].widget.attrs
if "min" in attrs and "max" in attrs:
step = attrs.get("step") or 1
extra_context["range_steps"] = list(
range(int(attrs["min"]), int(attrs["max"]) + 1, int(step))
)
return super().render(form, context, extra_context=extra_context, **kwargs)
+5
View File
@@ -0,0 +1,5 @@
from django import forms
class RangeInput(forms.NumberInput):
input_type = "range"
+18 -1
View File
@@ -2,7 +2,9 @@ from datetime import timedelta
from apps.common.middleware.thread_local import get_current_user
from apps.users.models import APIToken
from apps.common.widgets.crispy.daisyui import Range
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.range import RangeInput
from apps.common.widgets.tom_select import TomSelect
from apps.users.models import UserSettings
from apps.accounts.models import Account
@@ -131,6 +133,20 @@ class UserSettingsForm(forms.ModelForm):
required=False,
)
volume = forms.IntegerField(
min_value=0,
max_value=10,
label=_("Volume"),
help_text=_("Set to 0 to mute all sounds"),
widget=RangeInput(
attrs={
"step": 1,
# Preview the selected volume
"_": "on change call playSound('sparkle', my valueAsNumber)",
}
),
)
class Meta:
model = UserSettings
fields = [
@@ -141,6 +157,7 @@ class UserSettingsForm(forms.ModelForm):
"datetime_format",
"number_format",
"default_account",
"volume",
]
widgets = {
"default_account": TomSelect(clear_button=False, group_by="group"),
@@ -167,7 +184,7 @@ class UserSettingsForm(forms.ModelForm):
"start_page",
"default_account",
HTML('<hr class="hr my-3" />'),
"volume",
Range("volume"),
FormActions(
NoClassSubmit("submit", _("Save"), css_class="btn btn-primary"),
),
@@ -0,0 +1,19 @@
# Generated by Django 5.2.16 on 2026-09-12 21:53
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0027_alter_usersettings_timezone'),
]
operations = [
migrations.AlterField(
model_name='usersettings',
name='volume',
field=models.PositiveIntegerField(default=10, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)], verbose_name='Volume'),
),
]
+1 -1
View File
@@ -481,7 +481,7 @@ class UserSettings(models.Model):
mute_sounds = models.BooleanField(default=False)
volume = models.PositiveIntegerField(
default=10,
validators=[MinValueValidator(1), MaxValueValidator(10)],
validators=[MinValueValidator(0), MaxValueValidator(10)],
verbose_name=_("Volume"),
)
@@ -0,0 +1,29 @@
{% load crispy_forms_field %}
{% if field.is_hidden %}
{{ field }}
{% else %}
<{% if tag %}{{ tag }}{% else %}fieldset{% endif %} id="div_{{ field.auto_id }}" class="fieldset{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
{% if field.label and form_show_labels %}
<label {% if field.id_for_label %}for="{{ field.id_for_label }}"{% endif %} class="fieldset-legend{% if label_class %} {{ label_class }}{% endif %}{% if field.field.required %} requiredField{% endif %}">
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
<div class="w-full">
{% if field.errors %}
{% crispy_field field 'class' 'range range-error w-full' %}
{% else %}
{% crispy_field field 'class' 'range range-primary w-full' %}
{% endif %}
{% if range_steps %}
<div class="flex justify-between px-2.5 mt-2 text-xs">
{% for step in range_steps %}<span>|</span>{% endfor %}
</div>
<div class="flex justify-between px-2.5 mt-2 text-xs">
{% for step in range_steps %}<span>{{ step|stringformat:"02d" }}</span>{% endfor %}
</div>
{% endif %}
</div>
{% include 'crispy-daisyui/layout/help_text_and_errors.html' %}
</{% if tag %}{{ tag }}{% else %}fieldset{% endif %}>
{% endif %}
+4 -4
View File
@@ -25,7 +25,7 @@
"chart.js": "^4.5.1",
"chartjs-chart-sankey": "^0.14.3",
"chartjs-plugin-zoom": "^2.2.0",
"cuelume": "^0.1.2",
"cuelume": "^0.2.2",
"daisyui": "5.5.20",
"htmx.org": "^2.0.10",
"hyperscript.org": "^0.9.91",
@@ -1319,9 +1319,9 @@
}
},
"node_modules/cuelume": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/cuelume/-/cuelume-0.1.2.tgz",
"integrity": "sha512-VxL0k9l1fyKGot3VKM+wm5Xd2K75fVeBHdNTrR1EHiqKRtD6S5Dxy/vY7hwl9gXp3QT/TIywFXUos77n39YZVQ==",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cuelume/-/cuelume-0.2.2.tgz",
"integrity": "sha512-TiNzJRjhddjC4jy4M7sv63V/86VaFhdIs6iHUCrc4CKhqypaxRBv1aedCBGebrz/cO7nroPkEfqHe/P7fOE2GQ==",
"license": "MIT"
},
"node_modules/daisyui": {
+1 -1
View File
@@ -32,7 +32,7 @@
"chart.js": "^4.5.1",
"chartjs-chart-sankey": "^0.14.3",
"chartjs-plugin-zoom": "^2.2.0",
"cuelume": "^0.1.2",
"cuelume": "^0.2.2",
"daisyui": "5.5.20",
"htmx.org": "^2.0.10",
"hyperscript.org": "^0.9.91",
+8 -1
View File
@@ -1,4 +1,11 @@
import { play } from "cuelume";
window.playSound = play;
// User volume is stored as 0-10 (0 = muted); cuelume expects a 0-1 gain.
// Loudness is perceived logarithmically, so a linear mapping makes adjacent
// steps sound nearly identical. Squaring spreads them out (10 → 0dB, 5 → -12dB, 1 → -40dB).
// Pass `volume` to override the user's saved volume (e.g. to preview a new value).
window.playSound = (name, volume) => {
volume ??= JSON.parse(document.getElementById("volume")?.textContent ?? "10");
play(name, { volume: (volume / 10) ** 2 });
};