mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-09-13 21:32:08 +02:00
feat(transactions): update cuelume; bring back improved volume user setting
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from django import forms
|
||||
|
||||
|
||||
class RangeInput(forms.NumberInput):
|
||||
input_type = "range"
|
||||
+18
-1
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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 %}
|
||||
Generated
+4
-4
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user