Skip to content

Collections - Featured sounds finish#2075

Open
5aola wants to merge 23 commits into
MTG:masterfrom
5aola:featured-sounds
Open

Collections - Featured sounds finish#2075
5aola wants to merge 23 commits into
MTG:masterfrom
5aola:featured-sounds

Conversation

@5aola
Copy link
Copy Markdown
Contributor

@5aola 5aola commented Mar 23, 2026

1. display_sound_with_actions template

New template wrapping a sound card with featured-toggle and remove-toggle action buttons, driven entirely by JS.

Connected files: objectSelector.js (binds buttons to store), soundCard.js (addEditActions() clones it), selectableObject.scss (action bar styles, active/hover states, .marked-for-removal dimming), object_selector.html (show_actions branch).

2. Client-side collection grid

New modules:

File Role
soundStateStore.js In-memory bitmask map of sounds with added/remove/featured flags, toggleAction(), idsWithFlag(), and onChange listeners.
soundGridEditor.js Paginated grid: search filtering, column sorting, renders cards by cloning an HTML <template> via a pluggable renderCard hook. Inits players/ratings/modals per page.
soundCard.js populateSoundCard() fills a cloned template with player data, links, stats. addEditActions() wraps the card with toggle buttons for edit mode.
formatters.js Utilities: escapeAttr, truncate, formatDate, formatNumber, soundPlayerUrls.

Page entry points:

  • collection.js — Read-only grid with featured highlighting.
  • collectionEdit.js — Edit grid with added/remove/featured flags. Wires add-sounds modal to store.add(). On submit, populates hidden inputs from store state.

Updated: addSoundsModal.js — new prepareAddSoundsModalDynamic() that excludes present IDs and feeds selections into the store.

Templates: collection.html, edit_collection.html — replaced server-side loop with #sounds-grid container + json_script data blocks. _sound_card_template.html — HTML <template> for client-side rendering.

3. Backend: delta-based editing & featured sounds (fscollections/)

  • forms.pyCommaSeparatedIdField; CollectionEditForm uses added_sounds/removed_sounds/featured_sounds delta fields; atomic save() with bulk_create(ignore_conflicts).
  • views.py — Sounds serialized as JSON; edit view applies delta from form.
  • models.pyfeatured_sound_ids JSON field on Collection.
  • tests.py — Existing edit tests updated for delta fields. Four new tests: owner set/clear featured, maintainer changes ignored, featuring absent sound filtered, removing featured sound unfeatures it.

Brief description of the workflow

The Django view serializes sounds as JSON into the page. collectionEdit.js parses them into a SoundStateStore (bitmask flags per sound). SoundGridEditor renders paginated cards by cloning _sound_card_template.html, and addEditActions() wraps each with the display_sound_with_actions toggle buttons. initializeObjectSelectorActions() binds those buttons to store.toggleAction(), which flips the flag and fires onChange — re-rendering the grid. The add-sounds modal feeds new sounds via store.add(). On submit, hidden inputs are populated from store.idsWithFlag() and the backend applies the delta atomically in CollectionEditForm.save().

@5aola 5aola marked this pull request as ready for review March 31, 2026 18:42
@5aola 5aola changed the title Featured sounds Collections - Featured sounds finish Apr 20, 2026
Copy link
Copy Markdown
Member

@ffont ffont left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi!

I'm generally fine with it, but there's something that I dislike a lot and that we should try to improve. We discussed this but I understood something wrong. The problem basically has to do with that sound card rendering thing, because you need to replicate a lot of server-side code for displaying a sound, and this can be very problematic if we make changes in the "original" display sound in the future and don't update the "old" one. I would consider an alternative solution, which involves the server but in a different way as before. I was wondering if there could be an endpoint to which you pass a list of sound IDs, and it returns the rendered sound cards (well, the HTML bits of course!) so that you can then place them where needed. I mention that the endpoint can support passing a list of IDs because you'll typically want to display "a page" of sounds. You'd be calling this endpoint every time that you need to display a group of sounds that you have not displayed yet.

The advantage of such a solution is that the sound rendering code is in sync with the rest of Freesound. Also, in your frontend code you will not need to save that much data per sound, only what's needed to implement filtering and sorting. This method would mean some more DB queries and server requests, but not that many probably. Nevertheless it is still very fundamentally different from the previous solution based on htmx as the communication with the server is simply to get rendered sound IDs, but does not include any database logic, etc.

I know what I'm asking is not minor a minor change, but I think it would make the code a bit cleaner and more maintainable.

That view for "rendering sound cards" btw should be generic, not necessarily tied to the collections app. It will be used in the future in other parts of the web most likely.

I hope the idea is clear, let me know if not!!

Comment thread freesound/settings.py Outdated
ENABLE_COLLECTIONS = False
ENABLE_COLLECTIONS = True
MAX_SOUNDS_PER_COLLECTION = 250
MAX_FEATURED_SOUNDS_PER_COLLECTION = 250
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we set a lower limit here? Say 10?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the 6 because its 2 full rows on desktop and 3 on mobile but 12 is also good for this manner

Comment thread freesound/settings.py Outdated
# -------------------------------------------------------------------------------
# Collections
ENABLE_COLLECTIONS = False
ENABLE_COLLECTIONS = True
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still be False, you can overwrite in your local_settings.py fie

header_sounds = Sound.public.filter(id__in=collection.featured_sound_ids)
else:
header_sounds = Sound.public.filter(collections=collection)[:1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the rendering of display_collection will make many db queries. We have methods to optimize this. I should give it a closer look, but one first optimization will definitely be something like his here:

if collection.featured_sound_ids:
        header_sounds = Sound.objects.bulk_query_id(collection.featured_sound_ids)
    else:
        sound_ids = Sound.public.filter(collections=collection).values_list('id', flat=True)[:3]
        header_sounds = Sound.objects.bulk_query_id(sound_ids)

You can check with the djang debug toolbar the number of queries that a page load triggers. There are many queries here that are "similar" (this is shown in the panel), which means that there are probably optimizations to be made when retrieving objets from DB using Django ORM.

@5aola
Copy link
Copy Markdown
Contributor Author

5aola commented May 5, 2026

Refactored the collection detail page and collection edit page to use HTMX. I optimized the approach based on what we discussed, so now the HTMX roundtrips are only for rendering a few cards instead of hitting full database queries, making it faster.

What mainly changed

Added HTMX to the project

  • and a little bit of reorganising the pagination template so the htmx can also use it

Collection detail page:

  • Now fully HTMX-driven, removed all js

Collection edit page:

  • SoundStateStore kept to track temporary changes
  • SoundGridEditor orchestrates the sound cards — it handles search, sort, and pagination client-side (filtering and ordering against the store), then calls htmx.ajax() to fetch only the card HTML for the current page. A sort cache avoids redundant re-sorting when paginating or toggling flags.

Featured sound limit:

  • Added a hard limit on the edit page — once reached, the "make featured" buttons become disabled
  • Added a counter showing how many featured sounds are in the collection out of the allowed maximum

Tests added:

  • Featured sounds editing — adding, clearing, and reordering featured sounds as owner
  • Maintainer permissions — maintainers cannot modify featured sounds
  • Edge cases — featuring a sound not in the collection is ignored; removing a sound also removes it from featured
  • HTMX fragment rendering — detail page returns the correct fragment on HX-Request
  • render-cards endpoint — preserves requested ID order, includes edit-mode container attributes, returns 403 for non-members, and shows an empty-state message with a clear-search link when no sounds match
  • Serializationserialize_collection_sounds includes correct featured_order values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants